gsn.beans.StreamElement.java Source code

Java tutorial

Introduction

Here is the source code for gsn.beans.StreamElement.java

Source

/**
* Global Sensor Networks (GSN) Source Code
* Copyright (c) 2006-2014, Ecole Polytechnique Federale de Lausanne (EPFL)
* 
* This file is part of GSN.
* 
* GSN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 
* GSN 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 General Public License
* along with GSN.  If not, see <http://www.gnu.org/licenses/>.
* 
* File: src/gsn/beans/StreamElement.java
*
* @author rhietala
* @author Timotee Maret
* @author Sofiane Sarni
* @author Ali Salehi
* @author Mehdi Riahi
* @author Julien Eberle
*
*/

package gsn.beans;

import gsn.http.rest.StreamElement4Rest;
import gsn.utils.CaseInsensitiveComparator;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.TreeMap;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

public final class StreamElement implements Serializable {

    private static final long serialVersionUID = 2000261462783698617L;

    private static final transient Logger logger = LoggerFactory.getLogger(StreamElement.class);

    private transient TreeMap<String, Integer> indexedFieldNames = null;

    private long timeStamp = -1;

    private String[] fieldNames;

    private Serializable[] fieldValues;

    private transient Byte[] fieldTypes;

    private transient long internalPrimayKey = -1;

    private static final String NULL_ENCODING = "NULL"; // null encoding for transmission over xml-rpc

    private boolean timestampProvided = false;

    public StreamElement(StreamElement other) {
        this.fieldNames = new String[other.fieldNames.length];
        this.fieldValues = new Serializable[other.fieldValues.length];
        this.fieldTypes = new Byte[other.fieldTypes.length];
        for (int i = 0; i < other.fieldNames.length; i++) {
            fieldNames[i] = other.fieldNames[i];
            fieldValues[i] = other.fieldValues[i];
            fieldTypes[i] = other.fieldTypes[i];
        }
        this.timeStamp = other.timeStamp;
        this.internalPrimayKey = other.internalPrimayKey;
    }

    public StreamElement() { //constructor for serialization

    }

    public StreamElement(DataField[] outputStructure, final Serializable[] data) {
        this(outputStructure, data, System.currentTimeMillis());
    }

    public StreamElement(DataField[] outputStructure, final Serializable[] data, final long timeStamp) {
        this.fieldNames = new String[outputStructure.length];
        this.fieldTypes = new Byte[outputStructure.length];
        this.timeStamp = timeStamp;
        for (int i = 0; i < this.fieldNames.length; i++) {
            this.fieldNames[i] = outputStructure[i].getName().toLowerCase();
            this.fieldTypes[i] = outputStructure[i].getDataTypeID();
        }
        if (this.fieldNames.length != data.length)
            throw new IllegalArgumentException(
                    "The length of dataFileNames and the actual data provided in the constructor of StreamElement doesn't match.");
        this.verifyTypesCompatibility(this.fieldTypes, data);
        this.fieldValues = data;
    }

    public StreamElement(final String[] dataFieldNames, final Byte[] dataFieldTypes, final Serializable[] data) {
        this(dataFieldNames, dataFieldTypes, data, System.currentTimeMillis());
    }

    public StreamElement(final String[] dataFieldNames, final Byte[] dataFieldTypes, final Serializable[] data,
            final long timeStamp) {
        if (dataFieldNames.length != dataFieldTypes.length)
            throw new IllegalArgumentException(
                    "The length of dataFileNames and dataFileTypes provided in the constructor of StreamElement doesn't match.");
        if (dataFieldNames.length != data.length)
            throw new IllegalArgumentException(
                    "The length of dataFileNames and the actual data provided in the constructor of StreamElement doesn't match.");
        this.timeStamp = timeStamp;
        this.timestampProvided = true;
        this.fieldTypes = dataFieldTypes;
        this.fieldNames = dataFieldNames;
        this.fieldValues = data;
        this.verifyTypesCompatibility(dataFieldTypes, data);
    }

    public StreamElement(TreeMap<String, Serializable> output, DataField[] fields) {
        int nbFields = output.keySet().size();
        if (output.containsKey("timed"))
            nbFields--;
        String fieldNames[] = new String[nbFields];
        Byte fieldTypes[] = new Byte[nbFields];
        Serializable fieldValues[] = new Serializable[nbFields];
        TreeMap<String, Integer> indexedFieldNames = new TreeMap<String, Integer>(new CaseInsensitiveComparator());
        int idx = 0;

        long timestamp = System.currentTimeMillis();
        for (String key : output.keySet()) {
            Serializable value = output.get(key);

            if (key.equalsIgnoreCase("timed")) {
                timestamp = (Long) value;
                timestampProvided = true;
            } else {
                fieldNames[idx] = key;
                fieldValues[idx] = value;
                for (int i = 0; i < fields.length; i++) {
                    if (fields[i].getName().equalsIgnoreCase(key))
                        fieldTypes[idx] = fields[i].getDataTypeID();
                }
                indexedFieldNames.put(key, idx);
                idx++;
            }
        }
        this.fieldNames = fieldNames;
        this.fieldTypes = fieldTypes;
        this.fieldValues = fieldValues;
        this.indexedFieldNames = indexedFieldNames;
        this.timeStamp = timestamp;
    }

    /**
     * Verify if the data corresponds to the fieldType
     * @param fieldType
     * @param data
     * @throws IllegalArgumentException
     */
    private void verifyTypeCompatibility(Byte fieldType, Serializable data) throws IllegalArgumentException {
        if (data == null)
            return;
        switch (fieldType) {
        case DataTypes.TINYINT:
            if (!(data instanceof Byte))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        case DataTypes.SMALLINT:
            if (!(data instanceof Short))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        case DataTypes.BIGINT:
            if (!(data instanceof Long))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        case DataTypes.CHAR:
        case DataTypes.VARCHAR:
            if (!(data instanceof String))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        case DataTypes.INTEGER:
            if (!(data instanceof Integer))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        case DataTypes.DOUBLE:
            if (!(data instanceof Double || data instanceof Float))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        case DataTypes.FLOAT:
            if (!(data instanceof Float))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        case DataTypes.BINARY:
            // if ( data[ i ] instanceof String ) data[ i ] = ( ( String )
            // data[ i ] ).getBytes( );
            if (!(data instanceof byte[] || data instanceof String))
                throw new IllegalArgumentException("The field is defined as " + DataTypes.TYPE_NAMES[fieldType]
                        + " while the actual data in the field is of type : *" + data.getClass().getCanonicalName()
                        + "*");
            break;
        }
    }

    /**
     * Checks the type compatibility of all fields of the StreamElement
     * @param fieldTypes the array of all fields' type
     * @param data the array of data to check
     * @throws IllegalArgumentException if a data field doesn't match the given type
     */
    private void verifyTypesCompatibility(final Byte[] fieldTypes, final Serializable[] data)
            throws IllegalArgumentException {
        for (int i = 0; i < data.length; i++) {
            try {
                verifyTypeCompatibility(fieldTypes[i], data[i]);
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException(
                        "The newly constructed Stream Element is not consistent for the " + (i + 1) + "th field.",
                        e);
            }
        }
    }

    public String toString() {
        final StringBuffer output = new StringBuffer("timed = ");
        output.append(this.getTimeStamp()).append("\t");
        for (int i = 0; i < this.fieldNames.length; i++)
            output.append(",").append(this.fieldNames[i]).append("/").append(this.fieldTypes[i]).append(" = ")
                    .append(this.fieldValues[i]);
        return output.toString();
    }

    public final String[] getFieldNames() {
        return this.fieldNames;
    }

    /*
     * Returns the field types in GSN format. Checkout gsn.beans.DataTypes
     */
    public final Byte[] getFieldTypes() {
        return this.fieldTypes;
    }

    public final Serializable[] getData() {
        return this.fieldValues;
    }

    public void setData(int index, Serializable data) {
        this.fieldValues[index] = data;
    }

    public long getTimeStamp() {
        return this.timeStamp;
    }

    public StringBuilder getFieldTypesInString() {
        final StringBuilder stringBuilder = new StringBuilder();
        for (final byte i : this.getFieldTypes())
            stringBuilder.append(DataTypes.TYPE_NAMES[i]).append(" , ");
        return stringBuilder;
    }

    /**
     * Returns true if the timestamp is set. A timestamp is valid if it is
     * set.
     * 
     * @return Whether the timestamp is set or not. If it is >0 it is assumed to be set
     */
    public boolean isTimestampSet() {
        return this.timeStamp > 0 || timestampProvided;
    }

    /**
     * Sets the time stamp of this stream element.
     * 
     * @param timeStamp The time stamp value. If the timestamp is zero or
     * negative, it is considered non valid and zero will be placed.
     */
    public void setTimeStamp(long timeStamp) {
        if (timeStamp <= 0)
            timeStamp = 0;
        else
            this.timeStamp = timeStamp;
    }

    /**
     * This method gets the attribute name as the input and returns the value
     * corresponding to that tuple.
     * 
     * @param fieldName The name of the tuple.
     * @return The value corresponding to the named tuple.
     */
    public final Serializable getData(final String fieldName) {
        generateIndex();
        Integer index = indexedFieldNames.get(fieldName);
        if (index == null) {
            logger.warn("There is a request for field " + fieldName + " for StreamElement: " + this.toString()
                    + ". As the requested field doesn't exist, GSN returns Null to the callee.");
            return null;
        }
        return this.fieldValues[index];
    }

    /**
     * This method gets the attribute name as the input and returns the type of the value
     * corresponding to that tuple.
     * 
     * @param fieldName The name of the tuple.
     * @return The type of the value corresponding to the named tuple.
     */
    public final Byte getType(final String fieldName) {
        generateIndex();
        Integer index = indexedFieldNames.get(fieldName);
        if (index == null) {
            logger.warn("There is a request for type of field " + fieldName + " for StreamElement: "
                    + this.toString() + ". As the requested field doesn't exist, GSN returns Null to the callee.");
            return null;
        }
        return this.fieldTypes[index];
    }

    public long getInternalPrimayKey() {
        return internalPrimayKey;
    }

    public void setInternalPrimayKey(long internalPrimayKey) {
        this.internalPrimayKey = internalPrimayKey;
    }

    /**
     * @return
     */
    public Object[] getDataInRPCFriendly() {
        Object[] toReturn = new Object[fieldValues.length];
        for (int i = 0; i < toReturn.length; i++) {
            //process null values
            if (fieldValues[i] == null) {
                toReturn[i] = NULL_ENCODING;
                continue;
            }
            switch (fieldTypes[i]) {
            case DataTypes.DOUBLE:
            case DataTypes.FLOAT:
                toReturn[i] = fieldValues[i];
                break;
            case DataTypes.BIGINT:
                toReturn[i] = Long.toString((Long) fieldValues[i]);
                break;
            //        case DataTypes.TIME :
            //        toReturn[ i ] = Long.toString( ( Long ) fieldValues[ i ] );
            //        break;
            case DataTypes.TINYINT:
            case DataTypes.SMALLINT:
            case DataTypes.INTEGER:
                toReturn[i] = new Integer((Integer) fieldValues[i]);
                break;
            case DataTypes.CHAR:
            case DataTypes.VARCHAR:
            case DataTypes.BINARY:
                toReturn[i] = fieldValues[i];
                break;
            default:
                logger.error("Type can't be converted : TypeID : " + fieldTypes[i]);
            }
        }
        return toReturn;

    }

    /**
     * Returns the type of the field in the output format or -1 if the field doesn't exit.
     * @param outputFormat
     * @param fieldName
     * @return
     */
    private static byte findIndexInDataField(DataField[] outputFormat, String fieldName) {
        for (int i = 0; i < outputFormat.length; i++)
            if (outputFormat[i].getName().equalsIgnoreCase(fieldName))
                return outputFormat[i].getDataTypeID();

        return -1;
    }

    /***
     * Used with the new JRuby/Mongrel/Rest interface
     */

    public static StreamElement fromREST(DataField[] outputFormat, String[] fieldNames, String[] fieldValues,
            String timestamp) {
        Serializable[] values = new Serializable[outputFormat.length];
        for (int i = 0; i < fieldNames.length; i++) {
            switch (findIndexInDataField(outputFormat, (String) fieldNames[i])) {
            case DataTypes.DOUBLE:
                values[i] = Double.parseDouble(fieldValues[i]);
                break;
            case DataTypes.FLOAT:
                values[i] = Float.parseFloat((String) fieldValues[i]);
                break;
            case DataTypes.BIGINT:
                //        case DataTypes.TIME :
                values[i] = Long.parseLong((String) fieldValues[i]);
                break;
            case DataTypes.TINYINT:
                values[i] = Byte.parseByte((String) fieldValues[i]);
                break;
            case DataTypes.SMALLINT:
            case DataTypes.INTEGER:
                values[i] = Integer.parseInt(fieldValues[i]);
                break;
            case DataTypes.CHAR:
            case DataTypes.VARCHAR:
                values[i] = new String(Base64.decodeBase64(fieldValues[i].getBytes()));
                break;
            case DataTypes.BINARY:
                values[i] = (byte[]) Base64.decodeBase64(fieldValues[i].getBytes());
                break;
            case -1:
            default:
                logger.error("The field name doesn't exit in the output structure : FieldName : "
                        + (String) fieldNames[i]);
            }
        }
        return new StreamElement(outputFormat, values, Long.parseLong(timestamp));
    }

    public static StreamElement createElementFromREST(DataField[] outputFormat, String[] fieldNames,
            Object[] fieldValues) {
        ArrayList<Serializable> values = new ArrayList<Serializable>();
        // ArrayList<String> fields = new ArrayList<String>();

        long timestamp = -1;
        for (int i = 0; i < fieldNames.length; i++) {
            if (fieldNames[i].equalsIgnoreCase("TIMED")) {
                timestamp = Long.parseLong((String) fieldValues[i]);
                continue;
            }
            boolean found = false;
            for (DataField f : outputFormat) {
                if (f.getName().equalsIgnoreCase(fieldNames[i])) {
                    //     fields.add(fieldNames[i]);
                    found = true;
                    break;
                }
            }
            if (found == false)
                continue;

            switch (findIndexInDataField(outputFormat, fieldNames[i])) {
            case DataTypes.DOUBLE:
                values.add(Double.parseDouble((String) fieldValues[i]));
                break;
            case DataTypes.FLOAT:
                values.add(Float.parseFloat((String) fieldValues[i]));
                break;
            case DataTypes.BIGINT:
                values.add(Long.parseLong((String) fieldValues[i]));
                break;
            case DataTypes.TINYINT:
                values.add(Byte.parseByte((String) fieldValues[i]));
                break;
            case DataTypes.SMALLINT:
                values.add(Short.parseShort((String) fieldValues[i]));
                break;
            case DataTypes.INTEGER:
                values.add(Integer.parseInt((String) fieldValues[i]));
                break;
            case DataTypes.CHAR:
            case DataTypes.VARCHAR:
                values.add(new String((byte[]) fieldValues[i]));
                break;
            case DataTypes.BINARY:
                try {
                    //          StreamElementTest.md5Digest(fieldValues[ i ]);
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
                values.add((byte[]) fieldValues[i]);
                break;
            case -1:
            default:
                logger.error("The field name doesn't exit in the output structure : FieldName : "
                        + (String) fieldNames[i]);
            }

        }
        if (timestamp == -1)
            timestamp = System.currentTimeMillis();
        return new StreamElement(outputFormat, values.toArray(new Serializable[] {}), timestamp);
    }

    public StreamElement4Rest toRest() {
        StreamElement4Rest toReturn = new StreamElement4Rest(this);
        return toReturn;
    }

    /**
     * Build the index for mapping field name to their positions in the array if it is not yet built
     * This assumes that StreamElements cannot change their structure
     */
    private void generateIndex() {
        if (indexedFieldNames == null) {
            indexedFieldNames = new TreeMap<String, Integer>(new CaseInsensitiveComparator());
            for (int i = 0; i < this.fieldNames.length; i++)
                this.indexedFieldNames.put(fieldNames[i], i);
        }
    }

    /**
     * set the data in the coresponding field, throws an exception if the data type doesn't match
     * @param fieldName
     * @param data
     * @throws IllegalArgumentException
     */
    protected void setData(String fieldName, Serializable data) throws IllegalArgumentException {
        generateIndex();
        Integer index = indexedFieldNames.get(fieldName);
        if (index == null) {
            logger.warn("There is a request for setting field " + fieldName + " for StreamElement: "
                    + this.toString() + ". But the requested field doesn't exist.");
        }
        verifyTypeCompatibility(fieldTypes[index], data);
        setData(index, data);
    }
}