/** * 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: gsn-tiny/src/tinygsn/beans/StreamElement.java * * @author Do Ngoc Hoan */ package tinygsn.beans; import android.util.Base64; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import tinygsn.utils.CaseInsensitiveComparator; import java.io.Serializable; import java.text.DecimalFormat; import java.util.Date; import java.util.TreeMap; public final class StreamElement implements Serializable { private static final long serialVersionUID = 2000261462783698617L; private long timeStamp = -1; private String[] fieldNames; private Serializable[] fieldValues; private transient TreeMap < String , Integer > indexedFieldNames = null; private transient Byte[] fieldTypes; private transient long internalPrimayKey = -1; private static final String NULL_ENCODING = "NULL"; // null encoding for public StreamElement(StreamElement other) { this.fieldNames = new String[other.fieldNames.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(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 dataFieldNames 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.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; // 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; // } private void verifyTypesCompatibility(final Byte[] fieldTypes, final Serializable[] data) throws IllegalArgumentException { for (int i = 0; i < data.length; i++) { if (data[i] == null) continue; switch (fieldTypes[i]) { case DataTypes.TINYINT: if (!(data[i] instanceof Byte)) throw new IllegalArgumentException( "The newly constructed Stream Element is not consistent. The " + (i + 1) + "th field is defined as " + DataTypes.TYPE_NAMES[fieldTypes[i]] + " while the actual data in the field is of type : *" + data[i].getClass().getCanonicalName() + "*"); break; case DataTypes.SMALLINT: if (!(data[i] instanceof Short)) throw new IllegalArgumentException( "The newly constructed Stream Element is not consistent. The " + (i + 1) + "th field is defined as " + DataTypes.TYPE_NAMES[fieldTypes[i]] + " while the actual data in the field is of type : *" + data[i].getClass().getCanonicalName() + "*"); break; case DataTypes.BIGINT: if (!(data[i] instanceof Long)) { throw new IllegalArgumentException( "The newly constructed Stream Element is not consistant. The " + (i + 1) + "th field is defined as " + DataTypes.TYPE_NAMES[fieldTypes[i]] + " while the actual data in the field is of type : *" + data[i].getClass().getCanonicalName() + "*"); } break; case DataTypes.CHAR: case DataTypes.VARCHAR: if (!(data[i] instanceof String)) { throw new IllegalArgumentException( "The newly constructed Stream Element is not consistant. The " + (i + 1) + "th field is defined as " + DataTypes.TYPE_NAMES[fieldTypes[i]] + " while the actual data in the field is of type : *" + data[i].getClass().getCanonicalName() + "*"); } break; case DataTypes.INTEGER: if (!(data[i] instanceof Integer)) { throw new IllegalArgumentException( "The newly constructed Stream Element is not consistant. The " + (i + 1) + "th field is defined as " + DataTypes.TYPE_NAMES[fieldTypes[i]] + " while the actual data in the field is of type : *" + data[i].getClass().getCanonicalName() + "*"); } break; case DataTypes.DOUBLE: if (!(data[i] instanceof Double || data[i] instanceof Float)) throw new IllegalArgumentException( "The newly constructed Stream Element is not consistant. The " + (i + 1) + "th field is defined as " + DataTypes.TYPE_NAMES[fieldTypes[i]] + " while the actual data in the field is of type : *" + data[i].getClass().getCanonicalName() + "*"); break; case DataTypes.BINARY: // if ( data[ i ] instanceof String ) data[ i ] = ( ( String ) // data[ i ] ).getBytes( ); if (!(data[i] instanceof byte[] || data[i] instanceof String)) throw new IllegalArgumentException( "The newly constructed Stream Element is not consistant. The " + (i + 1) + "th field is defined as " + DataTypes.TYPE_NAMES[fieldTypes[i]] + " while the actual data in the field is of type : *" + data[i].getClass().getCanonicalName() + "*"); break; } } } public static String toJSON(String vs_name, StreamElement[] s){ GeoJsonField[] fields = new GeoJsonField[s[0].getFieldNames().length+1]; fields[0] = new GeoJsonField(); fields[0].setName("timestamp"); fields[0].setType("time"); fields[0].setUnit("ms"); for(int i = 1; i < fields.length; i++){ fields[i] = new GeoJsonField(); fields[i].setName(s[0].getFieldNames()[i-1]); fields[i].setType(DataTypes.TYPE_NAMES[s[0].getFieldTypes()[i-1]]); } Serializable[][] values = new Serializable[s.length][fields.length]; for(int i = 0; i < s.length; i++){ values[i][0] = s[i].getTimeStamp(); for(int j = 1; j < fields.length; j++){ values[i][j] = s[i].getData()[j-1]; } } GeoJsonProperties prop = new GeoJsonProperties(); prop.setVs_name(vs_name); prop.setFields(fields); prop.setValues(values); GeoJsonFeature feature = new GeoJsonFeature(); feature.setPage_size(1); feature.setTotal_size(1); feature.setType("Feature"); feature.setProperties(prop); Gson gson = new Gson(); return gson.toJson(feature); } /** * Build stream elements from a JSON representation like this one: * {"type":"Feature","properties":{"vs_name":"geo_oso3m","values":[[1464094800000,21,455.364922]],"fields":[{"name":"timestamp","type":"time","unit":"ms"},{"name":"station","type":"smallint","unit":null},{"name":"altitude","type":"float","unit":null}],"stats":{"start-datetime":1381953249010,"end-datetime":1464096133100},"geographical":"Lausanne, Switzerland","description":"OZ47 Sensor"},"geometry":{"type":"Point","coordinates":[6.565356337141691,46.5608445136986,689.7967]},"total_size":0,"page_size":0} * Expecting the first value to be the timestamp * @param s * @return */ public static StreamElement[] fromJSON(String s){ JsonParser parser = new JsonParser(); JsonObject jn = parser.parse(s).getAsJsonObject().get("properties").getAsJsonObject(); DataField[] df = new DataField[jn.get("fields").getAsJsonArray().size()-1]; int i = 0; for(JsonElement f : jn.get("fields").getAsJsonArray()){ if (f.getAsJsonObject().get("name").getAsString().equals("timestamp")) continue; df[i] = new DataField(f.getAsJsonObject().get("name").getAsString(),f.getAsJsonObject().get("type").getAsString()); i++; } StreamElement[] ret = new StreamElement[jn.get("values").getAsJsonArray().size()]; int k = 0; for(JsonElement v : jn.get("values").getAsJsonArray()){ Serializable[] data = new Serializable[df.length]; for(int j=1;j < v.getAsJsonArray().size();j++){ switch(df[j].getDataTypeID()){ case DataTypes.DOUBLE: data[j] = v.getAsJsonArray().get(j).getAsDouble(); break; case DataTypes.FLOAT: data[j] = (float)v.getAsJsonArray().get(j).getAsDouble(); break; case DataTypes.BIGINT: data[j] = v.getAsJsonArray().get(j).getAsLong(); break; case DataTypes.TINYINT: data[j] = (byte)v.getAsJsonArray().get(j).getAsInt(); break; case DataTypes.SMALLINT: case DataTypes.INTEGER: data[j] = v.getAsJsonArray().get(j).getAsInt(); break; case DataTypes.CHAR: case DataTypes.VARCHAR: data[j] = v.getAsJsonArray().get(j).getAsString(); break; case DataTypes.BINARY: data[j] = Base64.decode(v.getAsJsonArray().get(j).getAsString(), Base64.DEFAULT); break; default: //logger.error("The data type of the field cannot be parsed: " + df[j].toString()); } } ret[k] = new StreamElement(df, data, v.getAsJsonArray().get(0).getAsLong()); } return ret; } public String toString() { final StringBuffer output = new StringBuffer("timed = "); output.append(new Date(this.getTimeStamp())).append("\n"); DecimalFormat df = new DecimalFormat("#.###"); for (int i = 0; i < this.fieldNames.length; i++) { if (this.fieldTypes[i] == DataTypes.VARCHAR || this.fieldTypes[i] == DataTypes.CHAR || this.fieldTypes[i] == DataTypes.BINARY) { output.append("\t").append(this.fieldNames[i]).append(" = ") .append(this.fieldValues[i].toString() + "\n"); } else { output.append("\t").append(this.fieldNames[i]).append(" = ") .append(df.format(this.fieldValues[i]) + "\n"); } } return output.toString(); } public final String[] getFieldNames() { return this.fieldNames; } public final Byte[] getFieldTypes() { return this.fieldTypes; } public final Serializable[] getData() { return this.fieldValues; } public final Serializable getData(final String fieldName) { // 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 ); // // for (String k : this.indexedFieldNames.keySet()) // // System.out.println("Key : "+k + // " VALUE = "+this.indexedFieldNames.get(k)); // } // // System.out.print(fieldName+" AT INDEX : "+ this.indexedFieldNames.get( // fieldName ) ); // // System.out.println(" HAS VALUE : "+this.fieldValues[ // this.indexedFieldNames.get( fieldName ) ]); // Integer index = indexedFieldNames.get( fieldName ); // if (index == null) { // logger.info("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; // } int index = -1; for (int i = 0; i < this.fieldNames.length; i++) { if (fieldNames[i].equalsIgnoreCase(fieldName)) { index = i; break; } } if (index == -1) return null; else return this.fieldValues[index]; } public void setData(int index, Serializable data) { this.fieldValues[index] = data; } /** * 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 ); } } public void setData(String fieldName, Serializable data) throws IllegalArgumentException { generateIndex(); Integer index = indexedFieldNames.get( fieldName ); if (index != null) { setData(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; } public boolean isTimestampSet() { return this.timeStamp > 0; } public void setTimeStamp(long timeStamp) { if (this.timeStamp <= 0) timeStamp = 0; else this.timeStamp = timeStamp; } public long getInternalPrimayKey() { return internalPrimayKey; } public void setInternalPrimayKey(long internalPrimayKey) { this.internalPrimayKey = internalPrimayKey; } }