odml.core.Value.java Source code

Java tutorial

Introduction

Here is the source code for odml.core.Value.java

Source

package odml.core;

/************************************************************************
 *   odML - open metadata Markup Language - 
 * Copyright (C) 2009, 2010 Jan Grewe, Jan Benda 
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the  GNU Lesser General Public License (LGPL) as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * odML is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */
import org.apache.commons.codec.binary.Base64;

import javax.swing.tree.TreeNode;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.CRC32;

/**
 * {@link Value} entities contain the values associated with a {@link Property}.
 * Values have the following fields:
 * <ol>
 * <li>value - mandatory, its the value = content itself.</li>
 * <li>uncertainty - optional, an estimation of the value's uncertainty.</li>
 * <li>unit - optional, the vlaue's unit.</li>
 * <li>type - optional, the data type of the value.</li>
 * <li>filename - optional, the default file name which should be used when saving the object.</li>
 * <li>definition - optional, here additional comments on the value of the property can be given.</li>
 * <li>reference - optional, here additional comments on the value of the property can be given.</li>
 * <li>encoder - optional. If binary content is included in the {@link Value}, indicate the encoder used in the form.</li>
 * <li>checksum - optional. The checksum of the file included in the {@link Value}. State the checksum in the form algorithm$checksum (e.g. crc32$...).</li>
 * </ol> 
 *   
 * @since 06.2010
 * @author Jan Grewe, Christine Seitz
 *
 */
public class Value implements Serializable, Cloneable, TreeNode {

    private static final long serialVersionUID = 147L;
    private String unit = null, type = null, reference = null;
    private Object content, uncertainty;
    private String definition, filename, checksum, encoder;
    private Property parent;
    private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    private final static SimpleDateFormat datetimeFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    private final static SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm:ss");
    private final static String regExNTuple;

    static {
        regExNTuple = "(?i)[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?;[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?";
    }

    /**
     * Creates a new Value instance.
     *
     * @param content The actual value of the Value entity.
     * @param unit The unit of this value.
     * @throws Exception
     */
    protected Value(Object content, String unit) throws Exception {
        this(content, unit, null, null);
    }

    /**
     * Creates a new Value instance.
     *
     * @param content The actual value of the Value entity.
     * @param unit The value's unit.
     * @param uncertainty The value's unvertainty
     * @throws Exception
     */
    protected Value(Object content, String unit, Object uncertainty) throws Exception {
        this(content, unit, uncertainty, null);
    }

    /**
     * Creates a new Value instance.
     *
     * @param content The actual value of the Value entity.
     * @param unit The value's unit.
     * @param uncertainty The value's uncertainty.
     * @param type The data type of the value.
     * @throws Exception
     */
    protected Value(Object content, String unit, Object uncertainty, String type) throws Exception {
        this(content, unit, uncertainty, type, null, null, null);
    }

    /**
     * Creates a Value from a Vector containing the value data in the following sequence:
     * "content","unit","uncertainty","type","fileName","definition","reference"
     *
     * @param data {@link Vector} of Objects that contains the data in the sequence as the {@link Value}
     * @throws Exception 
     */
    protected Value(Vector<Object> data) throws Exception {
        this(data.get(0), (String) data.get(1), data.get(2), (String) data.get(3), (String) data.get(4),
                (String) data.get(5), (String) data.get(6));
    }

    /**
     * Constructor for a Value containing all possible information. Any of the arguments
     * may be null except for Object value.
     *
     * @param content The actual value of the Value entity.
     * @param unit The value's unit, if applicable.
     * @param uncertainty The value's uncertainty, if any
     * @param type The values data type.
     * @param filename In case of binary content the original file name.
     * @param definition A textual definition of the value.
     * @param reference If the value references an external entity, its url goes here.
     * @throws Exception
     */
    protected Value(Object content, String unit, Object uncertainty, String type, String filename,
            String definition, String reference) throws Exception {
        this(content, unit, uncertainty, type, filename, definition, reference, "", "");
    }

    protected Value(Object content, String unit, Object uncertainty, String type, String filename,
            String definition, String reference, String encoder) throws Exception {
        this(content, unit, uncertainty, type, filename, definition, reference, encoder, "");
    }

    protected Value(Object content, String unit, Object uncertainty, String type, String filename,
            String definition, String reference, String encoder, String checksum) throws Exception {
        if (type == null || type.isEmpty()) {
            type = inferOdmlType(content);
        }
        this.content = null;
        this.uncertainty = null;
        this.filename = "";
        this.definition = "";
        this.reference = "";
        this.checksum = "";
        this.encoder = "";
        this.type = type;
        if (type.equalsIgnoreCase("binary")) {
            this.content = encodeContent(content);
        } else {
            this.content = checkDatatype(content, type);
        }
        if (uncertainty == null) {
            this.uncertainty = "";
        } else {
            try {
                this.uncertainty = uncertainty;
            } catch (Exception e) {
                this.uncertainty = "";
                System.out.println(e.getMessage());
            }
        }
        if (filename != null && !filename.isEmpty()) {
            this.filename = filename;
        }
        if (definition == null) {
            this.definition = "";
        } else {
            this.definition = definition;
        }
        if (reference == null) {
            this.reference = "";
        } else {
            this.reference = reference;
        }
        if (unit == null) {
            this.unit = "";
        } else {
            this.unit = unit;
        }
    }

    /**
     * Returns whether or not a {@link Value} is empty.
     * @return {@link Boolean}: true if value is empty, false otherwise.
     */
    public boolean isEmpty() {
        return (content == null) || (content instanceof String && ((String) content).isEmpty());
    }

    /**
     * Checks the passed values class and returns the odML type.
     * @param value {@link Object} the value;
     * @return {@link String} the type under which odml refers to it.
     */
    public static String inferOdmlType(Object value) {
        if (value instanceof Integer) {
            return "int";
        } else if (value instanceof Boolean) {
            return "boolean";
        } else if (value instanceof Date) {
            return "datetime";
        } else if (value instanceof Float) {
            return "float";
        } else if (value instanceof Double) {
            return "float";
        } else if (value instanceof URL) {
            return "url";
        } else if (value instanceof File) {
            return "binary";
        } else if (value instanceof String) {
            return inferDatatypeFromString(value.toString());
        }
        return "string";
    }

    /**
     * Checks and converts the content passed to the Value.
     * @param content Object: The content that needs to be checked.
     * @param type String: The type of the content
     * @return returns the content in the correct class or null if an error occurred.
     */
    public static Object checkDatatype(Object content, String type) {
        if (content == null || content.toString().isEmpty()) {
            System.out.println("Found empty content!!!");
            return null;
        }
        if (type.matches("(?i)int.*")) {
            if (content instanceof java.lang.Integer) {
                return content;
            } else if (content instanceof java.lang.String) {
                if (((java.lang.String) content).contains(".") || ((String) content).contains(",")) {
                    int index = ((String) content).indexOf(".");
                    if (index == -1)
                        index = ((String) content).indexOf(",");
                    content = ((String) content).substring(0, index);
                }
                return Integer.parseInt((String) content);
            } else if (content instanceof Number) {
                return ((Number) content).intValue();
            } else {
                System.out.println("Cannot convert value of class " + content.getClass().getSimpleName()
                        + " to requested type: " + type);
                return null;
            }
        } else if (type.matches("(?i)float.*")) {
            if (content instanceof Number) {
                return ((Number) content).floatValue();
            } else if (content instanceof java.lang.String) { // float could be masked as string
                return Float.parseFloat((String) content);
            } else {
                System.out.println("Cannot convert value of class " + content.getClass().getSimpleName()
                        + " to requested type " + type);
                return null;
            }
        } else if (type.matches("(?i)string") || type.matches("(?i)text")) {
            if (content instanceof String) {
                return content;
            } else if (content instanceof Character) {
                return content.toString();
            } else {
                System.out.println("Error converting content of class: " + content.getClass().getSimpleName()
                        + " to requested type: " + type);
                return null;
            }
        } else if (type.matches("(?i)n-tuple")) {
            if (content instanceof String && ((String) content).matches(regExNTuple)) {
                return content;
            } else {
                System.out.println("Value does not match the n-tuple definition (regExp: " + regExNTuple + ")!");
                return null;
            }
        } else {
            if (type.matches("(?i)date")) {
                if (content instanceof Date) {
                    try {
                        return dateFormat.parse(dateFormat.format(content));
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                } else if (content instanceof String) {
                    try {
                        return dateFormat.parse((String) content);
                    } catch (Exception e) {
                        System.out.println("Cannot convert passed String : " + content + " to a date value!");
                        return null;
                    }
                } else {
                    System.out.println("Cannot convert passed object of class: "
                            + content.getClass().getSimpleName() + " to a date value!");
                    return null;
                }
            } else if (type.matches("(?i)time")) {
                if (content instanceof Date) {
                    try {
                        return timeFormat.parse(timeFormat.format(content));
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                } else if (content instanceof String) {
                    try {
                        return timeFormat.parse((String) content);
                    } catch (Exception e) {
                        System.out.println(e.getLocalizedMessage());
                    }
                } else {
                    System.out.println("Cannot convert passed object of class: "
                            + content.getClass().getSimpleName() + " to a time value!");
                    return null;
                }
            } else if (type.matches("(?i)datetime")) {
                if (content instanceof Date) {
                    try {
                        return datetimeFormat.parse(datetimeFormat.format(content));
                    } catch (Exception e) {
                        System.out.println(e.getLocalizedMessage());
                    }
                } else if (content instanceof String) {
                    try {
                        return datetimeFormat.parse((String) content);
                    } catch (Exception e) {
                        System.out.println(e.getLocalizedMessage());
                    }
                } else {
                    System.out.println("Cannot convert passed object of class: "
                            + content.getClass().getSimpleName() + " to a datetime value!");
                    return null;
                }
            } else if (type.matches("(?i)bool.*")) {
                if (content instanceof Boolean) {
                    return content;
                } else if (content instanceof String) {
                    return Boolean.parseBoolean((String) content);
                } else {
                    System.out.println("Cannot convert object of class: " + content.getClass().getSimpleName()
                            + " to a " + type + ": value!");
                    return null;
                }
            } else if (type.matches("(?i)URL")) {
                if (content instanceof URL) {
                    return content;
                } else if (content instanceof String) {
                    try {
                        return new URL((String) content);
                    } catch (MalformedURLException e) {
                        System.out.println(e.getLocalizedMessage());
                    }
                } else {
                    System.out.println("Could not convert " + content.getClass().getSimpleName()
                            + " to required type: " + type);
                    return null;
                }
            } else {
                if (type.matches("(?i)binary")) {
                    if (content instanceof String || content instanceof File || content instanceof URL
                            || content instanceof URI) {
                        return content;
                    } else {
                        System.out.println("Binary (String), File, URL, or URI content expected, "
                                + content.getClass().getSimpleName() + " found!");
                        return null;
                    }
                } else {
                    if (type.matches("(?i)person")) {
                        if (!(content instanceof String)) {
                            System.out
                                    .println("Expect a person to be of class expected, not " + content.getClass());
                            return null;
                        } else {
                            return content;
                        }
                    } else {
                        return content;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Checks a {@link String} in more detail, and returns the odml data type.
     * 
     * @param content {@link String}
     * @return {@link String}: the odml type that matches best.
     */
    protected static String inferDatatypeFromString(String content) {
        content = content.trim();
        HashMap<String, String> regExpMap = new HashMap<String, String>();
        regExpMap.put("date",
                "[0-9]{4}-(((([0][13-9])|([1][0-2]))-(([0-2][0-9])|([3][01])))|(([0][2]-[0-2][0-9])))");
        regExpMap.put("datetime", "[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}");
        regExpMap.put("time", "(([01][0-9])|([2][0-4])):(([0-5][0-9])|([6][0])):(([0-5][0-9])|([6][0]))");
        regExpMap.put("int", "^[+-]?[0-9]+$");
        regExpMap.put("float", "^[+-]?[0-9]*\\.[0-9]+$");
        regExpMap.put("boolean", "(true)|(false)|1|0");
        regExpMap.put("n-tuple", regExNTuple);
        for (String key : regExpMap.keySet()) {
            if (content.toLowerCase().matches(regExpMap.get(key)))
                return key;
        }
        return "string";
    }

    //***************************************************************************************
    //*****               methods to handle binary content               **********
    //***************************************************************************************
    /**
     * Function to convert the content of the indicated file to an array of bytes.
     * Is primarily for internal use to Base64 encode binary data. 
     * @param file {@link File}: the file to convert.
     * @return byte[]: the array of bytes contained in the file.
     * @throws IOException
     */
    public static byte[] getBytesFromFile(File file) throws IOException {
        InputStream in = new FileInputStream(file);
        long length = file.length();
        if (length > Integer.MAX_VALUE) {
            throw new IOException("File exceeds max value: " + Integer.MAX_VALUE);
        }
        //Create the byte array to hold the data
        byte[] bytes = new byte[(int) length];
        //Read in the bytes
        int offset = 0;
        int numRead;
        while (offset < bytes.length && (numRead = in.read(bytes, offset, bytes.length - offset)) >= 0) {
            offset += numRead;
        }
        //Ensure all the bytes have been read
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file" + file.getName());
        }
        in.close();
        return bytes;
    }

    /**
     * Writes the value content which is Base64 encoded to disc.
     * @param content {@link String}: the value content.
     * @param outFile {@link File}: the File into which the decoded content should be written
     */
    public static void writeBinaryToDisc(String content, File outFile) throws Exception {
        if (outFile == null) {
            throw new Exception("Argument outFile not specified!");
        }
        FileOutputStream os;
        try {
            os = new FileOutputStream(outFile);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw e;
        }
        Base64 base = new Base64();
        byte[] bytes = base.decode(content.getBytes("UTF-8"));
        os.write(bytes);
        os.flush();
        os.close();
    }

    protected void setAssociatedProperty(Property property) {
        this.parent = property;
    }

    protected void setContent(Object content) {
        this.content = content;
    }

    protected Object getContent() {
        return this.content;
    }

    protected void setUnit(String unit) {
        this.unit = unit;
    }

    protected String getUnit() {
        return this.unit;
    }

    protected void setUncertainty(Object uncertainty) {
        this.uncertainty = uncertainty;
    }

    protected Object getUncertainty() {
        return this.uncertainty;
    }

    protected void setType(String type) {
        this.type = type;
    }

    protected String getType() {
        return this.type;
    }

    protected void setFilename(String filename) {
        this.filename = filename;
    }

    protected String getFilename() {
        return this.filename;
    }

    protected void setDefinition(String comment) {
        this.definition = comment;
    }

    protected String getDefinition() {
        return this.definition;
    }

    protected void setReference(String reference) {
        this.reference = reference;
    }

    protected String getReference() {
        return this.reference;
    }

    protected void setEncoder(String encoder) {
        if (encoder == null || encoder.isEmpty())
            this.encoder = "";
        else
            this.encoder = encoder;
    }

    protected String getEncoder() {
        return this.encoder;
    }

    protected void setChecksum(String checksum) {
        if (checksum == null || checksum.isEmpty())
            this.checksum = "";
        else
            this.checksum = checksum;
    }

    protected String getChecksum() {
        return this.checksum;
    }

    /**
     * Validates this {@link Value} against the terminology definition.
     * This function is marked @deprecated and will be removed in future versions!
     * 
     * Use validate instead!
     * 
     * @param termProp Property: The definition retrieved from a terminology.
     */
    @Deprecated
    public void compareToTerminology(Property termProp) {
        validate(termProp);
    }

    /**
     * Validates a {@link Value} against the value definition in a terminology. 
     * 
     * @param terminologyProperty {@link Property}: The respective {@link Property} 
     * that defines the kind of value.
     * 
     */
    public void validate(Property terminologyProperty) {
        if (this.type != null && !this.type.isEmpty()) {
            if (!this.type.equalsIgnoreCase(terminologyProperty.getType())) {
                System.out.println("Value type (" + this.type + ") does not match the one given in the terminology("
                        + terminologyProperty.getType()
                        + ")! To guarantee interoperability please ckeck. However, kept provided type.");
            }
        } else {
            try {
                checkDatatype(this.content, terminologyProperty.getType());
                this.setType(terminologyProperty.getType());
                System.out.println("Added type information to value.");
            } catch (Exception e) {
                System.out.println("Value is not compatible with the type information the terminology suggests ("
                        + terminologyProperty.getType() + "). Did not change anything, but please check");
            }
        }
        if (this.unit != null && !this.unit.isEmpty()) {
            if (!this.unit.equalsIgnoreCase(terminologyProperty.getUnit(0))) {
                System.out.println("Value unit (" + this.unit + ") does not match the one given in the terminology("
                        + terminologyProperty.getUnit()
                        + ")! To guarantee interoperability please ckeck. However, kept provided unit.");
            }
        } else {
            if (terminologyProperty.getUnit() != null && !terminologyProperty.getUnit(0).isEmpty()) {
                this.setUnit(terminologyProperty.getUnit(0));
                System.out.println("Added unit " + terminologyProperty.getUnit() + " information to value.");
            }
        }
    }

    /**
     *
     * Compares the content of two values and returns whether they are equal. So far this
     * concerns only the value content. Not type,definition etc.
     * @param other - The Value with which this should be compared.
     * @return {@link Boolean} <b>true</b> if the content of two values matches, <b>false</b> otherwise.
     */
    public boolean isEqual(Value other) {
        return this.content.toString().equals(other.content.toString());
    }

    /**
     * Base64 encodes the content if it represents either a File, URL, URI, or String that can be converted to a file.
     *
     * @param content - the content that should be encoded.
     * @return encoded content as {@link String}
     */
    private String encodeContent(Object content) {
        if (content == null) {
            return null;
        }
        System.out.println("Encoding content: " + content.toString());
        String encoded = null;
        File file;
        if (content instanceof String) {
            try {
                URI uri = new URI((String) content);
                file = new File(uri);
            } catch (Exception e) {
                return (String) content;
            }
        } else if (content instanceof URL) {
            try {
                file = new File(((URL) content).toURI());
            } catch (Exception e) {
                System.out.println("Could not create a file from the specified URL: " + content.toString());
                file = null;
            }
        } else if (content instanceof URI) {
            try {
                file = new File((URI) content);
            } catch (Exception e) {
                System.out.println("Could not create a file from the specified URI: " + content.toString());
                file = null;
            }
        } else if (content instanceof File) {
            file = (File) content;
        } else {
            System.out.println("Could not create a File from input! Class: " + content.getClass().getSimpleName()
                    + " Content: " + content.toString());
            file = null;
        }
        if (file == null) {
            return "";
        }
        Base64 enc = new Base64();
        //the value has to be converted to String; if it is already just take it, if it is not
        //try different things 
        try {
            byte[] bytes = enc.encode(getBytesFromFile(file));
            CRC32 crc = new CRC32();
            crc.update(bytes);
            this.setChecksum("CRC32$" + crc.getValue());
            this.setFilename(file.getName());
            this.setEncoder("Base64");
            encoded = new String(bytes, "UTF-8");
        } catch (Exception e) {
            System.out.println("An error occurred during encoding: " + e.getLocalizedMessage());
        }
        return encoded;
    }

    //****************************************************************
    //*****               Overrides for TreeNode         **********
    //****************************************************************
    @Override
    public Enumeration<TreeNode> children() {
        return null;
    }

    @Override
    public boolean getAllowsChildren() {
        return false;
    }

    @Override
    public TreeNode getChildAt(int childIndex) {
        return null;
    }

    @Override
    public int getChildCount() {
        return 0;
    }

    @Override
    public int getIndex(TreeNode node) {
        return 0;
    }

    @Override
    public Property getParent() {
        return this.parent;
    }

    @Override
    public boolean isLeaf() {
        return true;
    }

    @Override
    public String toString() {
        String s = "";
        if (this.getContent() != null)
            s = s.concat(this.getContent().toString());
        if (this.getUncertainty() != null && !this.getUncertainty().toString().isEmpty())
            s = s.concat(" +- " + this.getUncertainty().toString());
        if (this.getUnit() != null)
            s = s.concat(" " + this.getUnit());
        return s;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((checksum == null) ? 0 : checksum.hashCode());
        result = prime * result + ((content == null) ? 0 : content.hashCode());
        result = prime * result + ((definition == null) ? 0 : definition.hashCode());
        result = prime * result + ((encoder == null) ? 0 : encoder.hashCode());
        result = prime * result + ((filename == null) ? 0 : filename.hashCode());

        // cannot use parent - would cause infinite loop
        //result = prime * result + ((parent == null) ? 0 : parent.hashCode());

        result = prime * result + ((reference == null) ? 0 : reference.hashCode());
        result = prime * result + ((type == null) ? 0 : type.hashCode());
        result = prime * result + ((uncertainty == null) ? 0 : uncertainty.hashCode());
        result = prime * result + ((unit == null) ? 0 : unit.hashCode());
        return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Value other = (Value) obj;
        if (checksum == null) {
            if (other.checksum != null) {
                return false;
            }
        } else if (!checksum.equals(other.checksum)) {
            return false;
        }
        if (content == null) {
            if (other.content != null) {
                return false;
            }
        } else if (!content.equals(other.content)) {
            return false;
        }
        if (definition == null) {
            if (other.definition != null) {
                return false;
            }
        } else if (!definition.equals(other.definition)) {
            return false;
        }
        if (encoder == null) {
            if (other.encoder != null) {
                return false;
            }
        } else if (!encoder.equals(other.encoder)) {
            return false;
        }
        if (filename == null) {
            if (other.filename != null) {
                return false;
            }
        } else if (!filename.equals(other.filename)) {
            return false;
        }

        // cannot use parent - would cause infinite loop
        /*if (parent == null) {
        if (other.parent != null) { return false; }
        } else if (!parent.equals(other.parent)) { return false; }*/

        if (reference == null) {
            if (other.reference != null) {
                return false;
            }
        } else if (!reference.equals(other.reference)) {
            return false;
        }
        if (type == null) {
            if (other.type != null) {
                return false;
            }
        } else if (!type.equals(other.type)) {
            return false;
        }
        if (uncertainty == null) {
            if (other.uncertainty != null) {
                return false;
            }
        } else if (!uncertainty.equals(other.uncertainty)) {
            return false;
        }
        if (unit == null) {
            if (other.unit != null) {
                return false;
            }
        } else if (!unit.equals(other.unit)) {
            return false;
        }
        return true;
    }

    public Map<String, Object> getMap() {
        Map<String, Object> self = new HashMap<String, Object>();
        self.put("type", type);
        self.put("uncertainty", uncertainty);
        self.put("unit", unit);
        self.put("reference", reference);
        self.put("definition", definition);
        self.put("filename", filename);
        self.put("encoder", encoder);
        self.put("checksum", checksum);
        return self;
    }
}