/******************************************************************************* * Copyright (c) 2010 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.databoard.parser.repository; import java.io.IOException; import java.io.StringReader; import java.util.Collection; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.simantics.databoard.Bindings; import org.simantics.databoard.Datatypes; import org.simantics.databoard.binding.ArrayBinding; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.BooleanBinding; import org.simantics.databoard.binding.ByteBinding; import org.simantics.databoard.binding.DoubleBinding; import org.simantics.databoard.binding.FloatBinding; import org.simantics.databoard.binding.IntegerBinding; import org.simantics.databoard.binding.LongBinding; import org.simantics.databoard.binding.MapBinding; import org.simantics.databoard.binding.OptionalBinding; import org.simantics.databoard.binding.RecordBinding; import org.simantics.databoard.binding.StringBinding; import org.simantics.databoard.binding.UnionBinding; import org.simantics.databoard.binding.VariantBinding; import org.simantics.databoard.binding.error.BindingConstructionException; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.error.RuntimeBindingException; import org.simantics.databoard.binding.factory.BindingScheme; import org.simantics.databoard.binding.mutable.MutableVariant; import org.simantics.databoard.file.RuntimeIOException; import org.simantics.databoard.parser.DataParser; import org.simantics.databoard.parser.DataValuePrinter; import org.simantics.databoard.parser.ParseException; import org.simantics.databoard.parser.PrintFormat; import org.simantics.databoard.parser.TokenMgrError; import org.simantics.databoard.parser.ast.value.AstArray; import org.simantics.databoard.parser.ast.value.AstBoolean; import org.simantics.databoard.parser.ast.value.AstComponentAssignment; import org.simantics.databoard.parser.ast.value.AstFloat; import org.simantics.databoard.parser.ast.value.AstInteger; import org.simantics.databoard.parser.ast.value.AstMap; import org.simantics.databoard.parser.ast.value.AstMapAssignment; import org.simantics.databoard.parser.ast.value.AstNull; import org.simantics.databoard.parser.ast.value.AstRecord; import org.simantics.databoard.parser.ast.value.AstReference; import org.simantics.databoard.parser.ast.value.AstString; import org.simantics.databoard.parser.ast.value.AstTaggedValue; import org.simantics.databoard.parser.ast.value.AstTuple; import org.simantics.databoard.parser.ast.value.AstValue; import org.simantics.databoard.parser.ast.value.AstValueDefinition; import org.simantics.databoard.parser.ast.value.AstVariant; import org.simantics.databoard.parser.ast.value.visitor.AstValueVisitor; import org.simantics.databoard.parser.unparsing.DataTypePrinter; import org.simantics.databoard.type.Component; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.type.MapType; import org.simantics.databoard.type.RecordType; import org.simantics.databoard.type.UnionType; /** * Data value repository is a collection of name data values. * Each value is associated with type. * *

* It can also translate data lines and value texts to objects and * print values as * * * * @author Hannu Niemistö */ public class DataValueRepository { // Type respository to convert DataTypeRepository typeRepository = Datatypes.datatypeRepository; // Scheme to convert values BindingScheme bindingScheme = Bindings.mutableBindingFactory; /** Stored values */ Map values = new HashMap(); Map nameMap = new IdentityHashMap(); public MutableVariant get(String name) { return values.get(name); } public String getName(Object value) { return nameMap.get(value); } public void put(String name, Binding binding, Object value) { put(name, new MutableVariant(binding, value)); } public void put(String name, MutableVariant value) { values.put(name, value); nameMap.put(value.getValue(), name); } public MutableVariant remove(String name) { MutableVariant value = values.remove(name); if (value==null) return null; nameMap.remove(value.getValue()); return value; } public void clear() { values.clear(); nameMap.clear(); } /** * Get a view of the value names in this repository * * @return names */ public Set getValueNames() { return values.keySet(); } /** * Translates a data value from an abstract syntax tree to an object by the binding. * * @param value * @param binding * @return value * @throws DataTypeSyntaxError */ public Object translate(AstValue value, Binding binding) throws DataTypeSyntaxError { try { if(value instanceof AstReference) { String name = ((AstReference)value).name; MutableVariant v = get(name); if(v == null) { if(binding instanceof UnionBinding) { UnionBinding b = (UnionBinding)binding; UnionType type = b.type(); Integer index = type.getComponentIndex(name); if(index != null) try { return b.create(index, b.getComponentBinding(index).createDefault()); } catch(BindingException e) { throw new DataTypeSyntaxError(e); } } throw new DataTypeSyntaxError("Undefined reference to " + name + "."); } return Bindings.adaptUnchecked(v.getValue(), v.getBinding(), binding); } return binding.accept(new ValueTranslator(value)); } catch(ValueTranslationRuntimeException e) { throw new DataTypeSyntaxError(e); } } /** * Translates a data value from a string to an object by the binding. * @param value * @param binding * @return value * @throws DataTypeSyntaxError */ public Object translate(String value, Binding binding) throws DataTypeSyntaxError { try { return translate(new DataParser(new StringReader(value)).value(), binding); } catch (TokenMgrError e) { throw new DataTypeSyntaxError(e); } catch (ParseException e) { throw new DataTypeSyntaxError(e); } } /** * Adds a value definition to the repository * @param def * @throws DataTypeSyntaxError */ public void addValueDefinition(AstValueDefinition def) throws DataTypeSyntaxError { Datatype type = typeRepository.translate(def.type); Binding binding = Bindings.getMutableBinding(type); MutableVariant variant = new MutableVariant(binding, translate(def.value, binding)); values.put(def.name, variant); nameMap.put(variant.getValue(), def.name); } /** * Adds a value definition to the repository * @param def * @return name * @throws DataTypeSyntaxError */ public String addValueDefinition(String def) throws DataTypeSyntaxError { try { StringReader reader = new StringReader(def); DataParser parser = new DataParser( reader ); AstValueDefinition valueAstDef = parser.valueDefinition(); addValueDefinition( valueAstDef ); return valueAstDef.name; } catch (TokenMgrError e) { throw new DataTypeSyntaxError(e); } catch (ParseException e) { throw new DataTypeSyntaxError(e); } } /** * Adds multiple value definitions to the repository * * @param defs * @throws DataTypeSyntaxError */ public void addValueDefinitions(Collection defs) throws DataTypeSyntaxError { // TODO recursive definitions for(AstValueDefinition def : defs) addValueDefinition(def); } /** * Adds multiple value definitions to the repository * @param def * @throws DataTypeSyntaxError */ public void addValueDefinitions(String def) throws DataTypeSyntaxError { try { addValueDefinitions(new DataParser(new StringReader(def)).valueDefinitions()); } catch (TokenMgrError e) { throw new DataTypeSyntaxError(e); } catch (ParseException e) { throw new DataTypeSyntaxError(e); } } public DataTypeRepository getTypeRepository() { return typeRepository; } public void setTypeRepository(DataTypeRepository typeRepository) { this.typeRepository = typeRepository; } public BindingScheme getBindingScheme() { return bindingScheme; } public void setBindingScheme(BindingScheme bindingScheme) { this.bindingScheme = bindingScheme; } /** * Print the content part of a data value. This excludes the name and type of the value. * * @param valueName * @return value or null if value doesn't exist * @throws BindingException * @throws IOException */ public String printValue(String valueName) throws IOException, BindingException { MutableVariant value = get(valueName); if (value==null) return null; StringBuilder sb = new StringBuilder(); DataValuePrinter vp = new DataValuePrinter(sb, this); vp.print(value); return sb.toString(); } /** * Print the whole value repository * * @param sb * @throws IOException * @throws BindingException */ public void print(StringBuilder sb) throws IOException, BindingException { DataValuePrinter vp = new DataValuePrinter(sb, this); vp.setFormat( PrintFormat.SINGLE_LINE ); DataTypePrinter tp = new DataTypePrinter( sb ); tp.setLinefeed( false ); for (Entry e : values.entrySet()) { String name = e.getKey(); MutableVariant value = e.getValue(); Datatype type = value.type(); sb.append( name+" : " ); tp.print(type); sb.append( " = " ); vp.print(value); sb.append("\n"); } } /** * Print the whole data value repository as a single multiline string * * @throws RuntimeBindingException * @throws {@link RuntimeIOException} */ @Override public String toString() { try { StringBuilder sb = new StringBuilder(); print(sb); return sb.toString(); } catch (BindingException e) { throw new RuntimeBindingException(e); } catch (IOException e) { throw new RuntimeIOException(e); } } /** * Gives a data type to a value heuristically. */ public Datatype guessDataType(AstValue value) throws DataTypeSyntaxError { return value.accept(guessDataType); } /** * Gives a data type to a value heuristically. */ public Datatype guessDataType(String value) throws DataTypeSyntaxError { try { return guessDataType(new DataParser(new StringReader(value)).value()); } catch (TokenMgrError e) { throw new DataTypeSyntaxError(e); } catch (ParseException e) { throw new DataTypeSyntaxError(e); } } class ValueTranslator implements Binding.Visitor { AstValue value; public ValueTranslator(AstValue value) { this.value = value; } private ValueTranslationRuntimeException typeError(Binding expectedType, AstValue actualValue) { throw new ValueTranslationRuntimeException("Expected " + expectedType.type().toSingleLineString() + " but got " + actualValue.getClass().getSimpleName() + "."); } @Override public Object visit(ArrayBinding b) { if(value instanceof AstArray) { AstArray array = (AstArray)value; Object[] components = new Object[array.elements.size()]; Binding componentBinding = b.getComponentBinding(); int i=0; for(AstValue component : array.elements) { value = component; components[i++] = componentBinding.accept(this); } return b.createUnchecked(components); } throw typeError(b, value); } @Override public Object visit(BooleanBinding b) { if(value instanceof AstBoolean) { return b.createUnchecked(((AstBoolean)value).value); } throw typeError(b, value); } @Override public Object visit(DoubleBinding b) { if(value instanceof AstFloat) { return b.createUnchecked(((AstFloat)value).value); } if(value instanceof AstInteger) { return b.createUnchecked(((AstInteger)value).value); } throw typeError(b, value); } @Override public Object visit(FloatBinding b) { if(value instanceof AstFloat) { return b.createUnchecked(((AstFloat)value).value); } if(value instanceof AstInteger) { return b.createUnchecked(((AstInteger)value).value); } throw typeError(b, value); } @Override public Object visit(IntegerBinding b) { if(value instanceof AstInteger) { return b.createUnchecked(((AstInteger)value).value); } throw typeError(b, value); } @Override public Object visit(ByteBinding b) { if(value instanceof AstInteger) { return b.createUnchecked(((AstInteger)value).value); } throw typeError(b, value); } @Override public Object visit(LongBinding b) { if(value instanceof AstInteger) { return b.createUnchecked(((AstInteger)value).value); } throw typeError(b, value); } @Override public Object visit(OptionalBinding b) { if(value == AstNull.NULL) return b.createNoValueUnchecked(); else return b.createValueUnchecked(b.getComponentBinding().accept(this)); } @Override public Object visit(RecordBinding b) { if(value instanceof AstRecord) { AstRecord record = (AstRecord)value; Object[] components = new Object[b.getComponentCount()]; boolean[] assigned = new boolean[b.getComponentCount()]; for(AstComponentAssignment assignment : record.components) { value = assignment.value; Integer index = b.type().getComponentIndex(assignment.component); if(index == null) throw new ValueTranslationRuntimeException("Invalid record component " + assignment.component + "."); components[index] = b.getComponentBinding(index).accept(this); assigned[index] = true; } for(int i=0;i guessDataType = new AstValueVisitor() { @Override public Datatype visit(AstArray astArray) { if(astArray.elements.isEmpty()) throw new ValueTranslationRuntimeException("Cannot guess the data type of empty array."); return astArray.elements.get(0).accept(this); } @Override public Datatype visit(AstBoolean astBoolean) { return Datatypes.BOOLEAN; } @Override public Datatype visit(AstFloat astFloat) { return Datatypes.DOUBLE; } @Override public Datatype visit(AstInteger astInteger) { return Datatypes.INTEGER; } @Override public Datatype visit(AstMap astMap) { if(astMap.components.isEmpty()) throw new ValueTranslationRuntimeException("Cannot guess the data type of empty map."); AstMapAssignment assignment = astMap.components.get(0); return new MapType(assignment.key.accept(this), assignment.value.accept(this)); } @Override public Datatype visit(AstNull astNull) { throw new ValueTranslationRuntimeException("Cannot guess the data type"); } @Override public Datatype visit(AstRecord astRecord) { Component[] components = new Component[astRecord.components.size()]; int i = 0; for(AstComponentAssignment assignment : astRecord.components) { components[i++] = new Component( assignment.component, assignment.value.accept(this) ); } return new RecordType(false, components); } @Override public Datatype visit(AstReference astReference) { MutableVariant v = get(astReference.name); if(v == null) throw new ValueTranslationRuntimeException("Undefined reference to " + astReference.name + "."); return v.type(); } @Override public Datatype visit(AstString astString) { return Datatypes.STRING; } @Override public Datatype visit(AstTaggedValue astTaggedValue) { // Guessed datatype would be a union with just one component. Not very useful. throw new ValueTranslationRuntimeException("Cannot guess the data type of tagged value"); } @Override public Datatype visit(AstTuple astTuple) { Component[] components = new Component[astTuple.elements.size()]; int i = 0; for(AstValue value : astTuple.elements) { components[i] = new Component( Integer.toString(i), value.accept(this) ); ++i; } return new RecordType(false, components); } @Override public Datatype visit(AstVariant astVariant) { return Datatypes.VARIANT; } }; }