package uk.ac.imperial.lsds.seep.api.data;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.ac.imperial.lsds.seep.errors.SchemaException;
import uk.ac.imperial.lsds.seep.util.Utils;
public class Schema {
private final int schemaId;
private final Type[] fields;
private final String[] names;
private final boolean variableSize;
// Maps fieldName to fieldPosition (fields are ordered in a certain way)
private Map<String, Integer> mapFieldNameToFieldPosition = new HashMap<>();
private SchemaParser parser = DefaultParser.getInstance();
private Schema(int schemaId, Type[] fields, String[] names){
this.schemaId = schemaId;
this.fields = fields;
this.names = names;
boolean variableSizeSchema = false;
for(int i = 0; i < names.length; i++){
mapFieldNameToFieldPosition.put(names[i], i);
if(fields[i].isVariableSize()) variableSizeSchema = true;
}
this.variableSize = variableSizeSchema;
}
public boolean isVariableSize(){
return this.variableSize;
}
public boolean hasField(String fieldName){
return mapFieldNameToFieldPosition.containsKey(fieldName);
}
public boolean typeCheck(String fieldName, Type type){
//Compare them as objects will be always false - compare their string representations instead ?
return fields[mapFieldNameToFieldPosition.get(fieldName)].toString().compareTo(type.toString()) == 0 ;
}
public boolean typeCheck(String fieldName, Object o){
Type t = fields[mapFieldNameToFieldPosition.get(fieldName)];
if (t.equals(Type.BYTE)){
if(o instanceof Byte){
return true;
}
}
else if(t.equals(Type.SHORT)){
if(o instanceof Short){
return true;
}
}
else if(t.equals(Type.INT)){
if(o instanceof Integer){
return true;
}
}
else if(t.equals(Type.LONG)){
if(o instanceof Long){
return true;
}
}
else if(t.equals(Type.STRING)){
if(o instanceof String){
return true;
}
}
else if(t.equals(Type.FLOAT)){
if(o instanceof Float){
return true;
}
}
else if(t.equals(Type.DOUBLE)){
if(o instanceof Double){
return true;
}
}
else if(t.equals(Type.BYTES)) {
if (o instanceof byte[]) {
return true;
}
}
return false;
}
public int getFieldPosition(String fieldName){
if(mapFieldNameToFieldPosition.containsKey(fieldName)){
return mapFieldNameToFieldPosition.get(fieldName);
}
return -1;
}
public int schemaId(){
return schemaId;
}
public Type[] fields(){
return fields;
}
public String[] names(){
return names;
}
public Type getField(String name){
if(!mapFieldNameToFieldPosition.containsKey(name)){
System.out.println("ERROR");
System.exit(0);
}
return fields[mapFieldNameToFieldPosition.get(name)];
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("SchemaID: "+schemaId);
sb.append(Utils.NL);
for(int i = 0; i < names.length; i++){
sb.append("Field: "+names[i]+" : "+fields[i].toString());
sb.append(Utils.NL);
}
return sb.toString();
}
public static class SchemaBuilder {
// Only one instance is safe as schemaId is handled internally automatically
private static SchemaBuilder instance = null;
private byte schemaId;
private List<Type> fields = new ArrayList<Type>();
private List<String> names = new ArrayList<String>();
private SchemaBuilder(){}
public static SchemaBuilder getInstance(){
if(instance == null){
instance = new SchemaBuilder();
}
return instance;
}
public SchemaBuilder newField(Type type, String name){
// safety checks
if(names.contains(name)){
throw new SchemaException("Schema already contains a field with name: "+ name);
}
this.fields.add(type);
this.names.add(name);
return this;
}
public Schema build(){
// Sanity check
if(! (fields.size() == names.size())){
throw new SchemaException("Name-Field Missmatch - Each Type should be mapped to exactly one Name");
}
Type[] f = new Type[fields.size()];
f = fields.toArray(f);
String[] s = new String[names.size()];
s = names.toArray(s);
Schema toReturn = new Schema(schemaId, f, s);
schemaId++; // always increasing schemaId to ensure unique id
this.fields.clear();
this.names.clear();
return toReturn;
}
}
/**
* Empty constructor for Kryo serialization
*/
public Schema() {
this.schemaId = 0;
this.fields = null;
this.names = null;
this.variableSize = false;
}
/**
* Will return an array of default values that follow this schema
* @return
*/
public Object[] defaultValues() {
Object[] values = new Object[fields.length];
for(int i = 0; i < values.length; i++) {
values[i] = fields[i].defaultValue();
}
return values;
}
public Object[] randomValues() {
Object[] values = new Object[fields.length];
for(int i = 0; i < values.length; i++) {
values[i] = fields[i].randomValue();
}
return values;
}
public SchemaParser getSchemaParser() {
return parser;
}
public void SchemaParser(SchemaParser newparser) {
parser = newparser;
}
private static class DefaultParser implements SchemaParser {
private String encoding = Charset.defaultCharset().name();
private static DefaultParser instance = null;
private DefaultParser(){}
public static DefaultParser getInstance(){
if(instance == null){
instance = new DefaultParser();
}
return instance;
}
public byte[] bytesFromString(String textRecord) {
byte[] byteline = textRecord.getBytes(Charset.forName(encoding));
ByteBuffer b = ByteBuffer.allocate((Integer.SIZE/Byte.SIZE) + byteline.length);
b.putInt(byteline.length);
b.put(byteline);
return b.array();
}
public String stringFromBytes(byte[] binaryRecord) {
ByteBuffer wrapper = ByteBuffer.allocate(binaryRecord.length - (Integer.SIZE/Byte.SIZE));
wrapper.put(binaryRecord, (Integer.SIZE/Byte.SIZE), binaryRecord.length - (Integer.SIZE/Byte.SIZE));
return new String(wrapper.array());
}
public String getCharsetName() {
return encoding;
}
public void setCharset(String newencoding) {
encoding = newencoding;
}
}
public int sizeOfTuple() {
int totalSize = 0;
for(int i = 0; i < fields.length; i++) {
totalSize = totalSize + fields[i].sizeOf(new Object());
}
return totalSize;
}
}