package uk.ac.imperial.lsds.seep.api.data; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import uk.ac.imperial.lsds.seep.errors.SchemaException; import uk.ac.imperial.lsds.seep.util.Utils; public class ITuple { protected final Schema schema; private int streamId; // attribute name - offset protected Map<String, Integer> mapFieldToOffset; // attribute index - offset protected int[] mapIdxToOffset; // attribute name - index in mapIdxToOffset protected Map<String, Integer> mapFieldToIdx; private ByteBuffer wrapper; private byte[] data; public ITuple(Schema schema){ this.schema = schema; mapFieldToOffset = new HashMap<>(); mapIdxToOffset = new int[schema.names().length]; mapFieldToIdx = new HashMap<>(); if(! schema.isVariableSize()) { // This only happens once this.populateOffsets(); } } private ITuple() { this.schema = null; // TODO: EmptySchema? } public static ITuple makeEmptyITuple() { return new ITuple(); } /** FIXME: temporal solution to allow on-demand ITuple creation **/ public ITuple(Schema schema, byte[] data) { this.schema = schema; mapFieldToOffset = new HashMap<>(); mapIdxToOffset = new int[schema.names().length]; mapFieldToIdx = new HashMap<>(); if( ! schema.isVariableSize()) { this.populateOffsets(); } this.data = data; // greedily populate offsets for lazy deserialisation if(schema.isVariableSize()){ this.populateOffsets(); } wrapper = ByteBuffer.wrap(data); } /** * This method is meant to be used once, and cache the index for future accesses. * It is only safe to use (in general only safe to use access by index) when schema is fixed size * @param fieldName * @return */ public int getIndexFor(String fieldName) { return mapFieldToIdx.get(fieldName); } public Schema getSchema() { return schema; } public void setStreamId(int streamId){ this.streamId = streamId; } public int getStreamId(){ return streamId; } public void setData(byte[] data){ this.data = data; // greedily populate offsets for lazy deserialisation if(schema.isVariableSize()){ this.populateOffsets(); } wrapper = ByteBuffer.wrap(data); } public byte[] getData(){ return data; } /** Consider moving these fields to a different interface to not expose the rest to users? **/ public byte getByte(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } if(! schema.typeCheck(fieldName, Type.BYTE)) { throw new SchemaException("Current Schema cannot typeCheck a field type '"+ Type.BYTE +"' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); return wrapper.get(); } public byte getByte(int idx) { int offset = mapIdxToOffset[idx]; wrapper.position(offset); return wrapper.get(); } public short getShort(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } if(! schema.typeCheck(fieldName, Type.SHORT)) { throw new SchemaException("Current Schema cannot typeCheck a field type '"+ Type.SHORT +"' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); return wrapper.getShort(); } public short getShort(int idx) { int offset = mapIdxToOffset[idx]; wrapper.position(offset); return wrapper.getShort(); } public int getInt(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } if(! schema.typeCheck(fieldName, Type.INT)) { throw new SchemaException("Current Schema cannot typeCheck a field type '"+ Type.INT+"' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); return wrapper.getInt(); } public int getInt(int idx) { int offset = mapIdxToOffset[idx]; wrapper.position(offset); return wrapper.getInt(); } public long getLong(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } if(! schema.typeCheck(fieldName, Type.LONG)) { throw new SchemaException("Current Schema cannot typeCheck a field type '"+ Type.LONG +"' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); return wrapper.getLong(); } public long getLong(int idx) { int offset = mapIdxToOffset[idx]; wrapper.position(offset); return wrapper.getLong(); } public String getString(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } else if(! schema.typeCheck(fieldName, Type.STRING)) { throw new SchemaException("Current Schema cannot typeCheck a field type '"+ Type.STRING +"' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); String str = (String) Type.STRING.read(wrapper); return str; } public float getFloat(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } if(! schema.typeCheck(fieldName, Type.FLOAT)) { throw new SchemaException("Current Schema cannot typeCheck a field type '" + Type.FLOAT + "' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); return wrapper.getFloat(); } public float getFloat(int idx) { int offset = mapIdxToOffset[idx]; wrapper.position(offset); return wrapper.getFloat(); } public double getDouble(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } if(! schema.typeCheck(fieldName, Type.DOUBLE)) { throw new SchemaException("Current Schema cannot typeCheck a field type '" + Type.DOUBLE + "' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); return wrapper.getDouble(); } public double getDouble(int idx) { int offset = mapIdxToOffset[idx]; wrapper.position(offset); return wrapper.getDouble(); } public byte[] getBytes(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } if(! schema.typeCheck(fieldName, Type.BYTES)) { throw new SchemaException("Current Schema cannot typeCheck a field type '" + Type.BYTES + "' with name '"+fieldName+"'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); byte[] bytes = (byte[]) Type.BYTES.read(wrapper); return bytes; } public Object get(String fieldName){ if(! schema.hasField(fieldName)){ throw new SchemaException("Current Schema does not have a field with name '"+fieldName+ "'"); } int offset = mapFieldToOffset.get(fieldName); wrapper.position(offset); Object o = null; Type t = schema.getField(fieldName); if(t.equals(Type.BYTE)){ o = wrapper.get(); } else if(t.equals(Type.INT)){ o = wrapper.getInt(); } else if(t.equals(Type.SHORT)){ o = wrapper.getShort(); } else if(t.equals(Type.LONG)){ o = wrapper.getLong(); } else if(t.equals(Type.STRING)){ o = Type.STRING.read(wrapper); } else if(t.equals(Type.FLOAT)){ o = wrapper.getFloat(); } else if(t.equals(Type.DOUBLE)){ o = wrapper.getDouble(); } return o; } @Override public String toString(){ StringBuffer sb = new StringBuffer(); for(String fieldName : schema.names()){ Object o = this.get(fieldName); sb.append(fieldName+": "+o.toString()); sb.append(Utils.NL); } return sb.toString(); } private void populateOffsets(){ Type[] fields = schema.fields(); String[] names = schema.names(); int offset = 0; for(int i = 0; i < fields.length; i++){ Type t = fields[i]; mapFieldToOffset.put(names[i], offset); mapFieldToIdx.put(names[i], i); // assign idx in order mapIdxToOffset[i] = offset; // store offset in idx if(! t.isVariableSize()){ // if not variable we just get the size of the Type offset = offset + t.sizeOf(null); } else { // if variable we need to read the size from the current offset ByteBuffer temp = ByteBuffer.wrap(data); temp.position(offset); int size = temp.getInt(); offset = offset + size + Type.SIZE_OVERHEAD; } } } public String printOffsets(){ StringBuffer sb = new StringBuffer(); for(Entry<String, Integer> entry : mapFieldToOffset.entrySet()){ sb.append("Field: "+entry.getKey()+" Offset: "+entry.getValue()); sb.append(Utils.NL); } return sb.toString(); } }