-/*******************************************************************************\r
- * Copyright (c) 2010 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.databoard.parser.repository;\r
-\r
-import java.io.IOException;\r
-import java.io.StringReader;\r
-import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.IdentityHashMap;\r
-import java.util.Map;\r
-import java.util.Map.Entry;\r
-import java.util.Set;\r
-\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.Datatypes;\r
-import org.simantics.databoard.binding.ArrayBinding;\r
-import org.simantics.databoard.binding.Binding;\r
-import org.simantics.databoard.binding.BooleanBinding;\r
-import org.simantics.databoard.binding.ByteBinding;\r
-import org.simantics.databoard.binding.DoubleBinding;\r
-import org.simantics.databoard.binding.FloatBinding;\r
-import org.simantics.databoard.binding.IntegerBinding;\r
-import org.simantics.databoard.binding.LongBinding;\r
-import org.simantics.databoard.binding.MapBinding;\r
-import org.simantics.databoard.binding.OptionalBinding;\r
-import org.simantics.databoard.binding.RecordBinding;\r
-import org.simantics.databoard.binding.StringBinding;\r
-import org.simantics.databoard.binding.UnionBinding;\r
-import org.simantics.databoard.binding.VariantBinding;\r
-import org.simantics.databoard.binding.error.BindingConstructionException;\r
-import org.simantics.databoard.binding.error.BindingException;\r
-import org.simantics.databoard.binding.error.RuntimeBindingException;\r
-import org.simantics.databoard.binding.factory.BindingScheme;\r
-import org.simantics.databoard.binding.mutable.MutableVariant;\r
-import org.simantics.databoard.file.RuntimeIOException;\r
-import org.simantics.databoard.parser.DataParser;\r
-import org.simantics.databoard.parser.DataValuePrinter;\r
-import org.simantics.databoard.parser.ParseException;\r
-import org.simantics.databoard.parser.PrintFormat;\r
-import org.simantics.databoard.parser.TokenMgrError;\r
-import org.simantics.databoard.parser.ast.value.AstArray;\r
-import org.simantics.databoard.parser.ast.value.AstBoolean;\r
-import org.simantics.databoard.parser.ast.value.AstComponentAssignment;\r
-import org.simantics.databoard.parser.ast.value.AstFloat;\r
-import org.simantics.databoard.parser.ast.value.AstInteger;\r
-import org.simantics.databoard.parser.ast.value.AstMap;\r
-import org.simantics.databoard.parser.ast.value.AstMapAssignment;\r
-import org.simantics.databoard.parser.ast.value.AstNull;\r
-import org.simantics.databoard.parser.ast.value.AstRecord;\r
-import org.simantics.databoard.parser.ast.value.AstReference;\r
-import org.simantics.databoard.parser.ast.value.AstString;\r
-import org.simantics.databoard.parser.ast.value.AstTaggedValue;\r
-import org.simantics.databoard.parser.ast.value.AstTuple;\r
-import org.simantics.databoard.parser.ast.value.AstValue;\r
-import org.simantics.databoard.parser.ast.value.AstValueDefinition;\r
-import org.simantics.databoard.parser.ast.value.AstVariant;\r
-import org.simantics.databoard.parser.ast.value.visitor.AstValueVisitor;\r
-import org.simantics.databoard.parser.unparsing.DataTypePrinter;\r
-import org.simantics.databoard.type.Component;\r
-import org.simantics.databoard.type.Datatype;\r
-import org.simantics.databoard.type.MapType;\r
-import org.simantics.databoard.type.RecordType;\r
-import org.simantics.databoard.type.UnionType;\r
-\r
-/**\r
- * Data value repository is a collection of name data values.\r
- * Each value is associated with type. \r
- * \r
- * <p>\r
- * It can also translate data lines and value texts to objects and \r
- * print values as \r
- * \r
- * \r
- * \r
- * @author Hannu Niemistö\r
- */\r
-public class DataValueRepository {\r
- \r
- // Type respository to convert \r
- DataTypeRepository typeRepository = Datatypes.datatypeRepository;\r
- \r
- // Scheme to convert values \r
- BindingScheme bindingScheme = Bindings.mutableBindingFactory;\r
- \r
- /** Stored values */\r
- Map<String, MutableVariant> values = new HashMap<String, MutableVariant>();\r
- Map<Object, String> nameMap = new IdentityHashMap<Object, String>();\r
- \r
- public MutableVariant get(String name) {\r
- return values.get(name);\r
- }\r
- \r
- public String getName(Object value) {\r
- return nameMap.get(value);\r
- }\r
-\r
- public void put(String name, Binding binding, Object value) {\r
- put(name, new MutableVariant(binding, value));\r
- }\r
- \r
- public void put(String name, MutableVariant value) {\r
- values.put(name, value);\r
- nameMap.put(value.getValue(), name);\r
- }\r
- \r
- public MutableVariant remove(String name) {\r
- MutableVariant value = values.remove(name);\r
- if (value==null) return null;\r
- nameMap.remove(value.getValue());\r
- return value;\r
- }\r
- \r
- public void clear() {\r
- values.clear();\r
- nameMap.clear();\r
- }\r
- \r
- /**\r
- * Get a view of the value names in this repository\r
- * \r
- * @return names\r
- */\r
- public Set<String> getValueNames() {\r
- return values.keySet();\r
- }\r
- \r
- /**\r
- * Translates a data value from an abstract syntax tree to an object by the binding.\r
- * \r
- * @param value\r
- * @param binding\r
- * @return value\r
- * @throws DataTypeSyntaxError\r
- */\r
- public Object translate(AstValue value, Binding binding) throws DataTypeSyntaxError { \r
- try {\r
- if(value instanceof AstReference) {\r
- String name = ((AstReference)value).name;\r
- MutableVariant v = get(name);\r
- if(v == null) {\r
- if(binding instanceof UnionBinding) {\r
- UnionBinding b = (UnionBinding)binding;\r
- UnionType type = b.type();\r
- Integer index = type.getComponentIndex(name);\r
- if(index != null)\r
- try {\r
- return b.create(index, \r
- b.getComponentBinding(index).createDefault());\r
- } catch(BindingException e) {\r
- throw new DataTypeSyntaxError(e);\r
- }\r
- }\r
- throw new DataTypeSyntaxError("Undefined reference to " + name + ".");\r
- }\r
- return Bindings.adaptUnchecked(v.getValue(), v.getBinding(), binding);\r
- }\r
- return binding.accept(new ValueTranslator(value));\r
- } catch(ValueTranslationRuntimeException e) {\r
- throw new DataTypeSyntaxError(e);\r
- }\r
- }\r
- \r
- /**\r
- * Translates a data value from a string to an object by the binding.\r
- * @param value\r
- * @param binding\r
- * @return value\r
- * @throws DataTypeSyntaxError\r
- */\r
- public Object translate(String value, Binding binding) throws DataTypeSyntaxError {\r
- try {\r
- return translate(new DataParser(new StringReader(value)).value(), binding);\r
- } catch (TokenMgrError e) {\r
- throw new DataTypeSyntaxError(e);\r
- } catch (ParseException e) {\r
- throw new DataTypeSyntaxError(e);\r
- }\r
- }\r
- \r
- /**\r
- * Adds a value definition to the repository\r
- * @param def\r
- * @throws DataTypeSyntaxError\r
- */\r
- public void addValueDefinition(AstValueDefinition def) throws DataTypeSyntaxError {\r
- Datatype type = typeRepository.translate(def.type);\r
- Binding binding = Bindings.getMutableBinding(type);\r
- MutableVariant variant = new MutableVariant(binding, translate(def.value, binding));\r
- values.put(def.name, variant);\r
- nameMap.put(variant.getValue(), def.name);\r
- }\r
- \r
- /**\r
- * Adds a value definition to the repository\r
- * @param def\r
- * @return name\r
- * @throws DataTypeSyntaxError\r
- */\r
- public String addValueDefinition(String def) throws DataTypeSyntaxError {\r
- try {\r
- StringReader reader = new StringReader(def);\r
- DataParser parser = new DataParser( reader );\r
- AstValueDefinition valueAstDef = parser.valueDefinition(); \r
- addValueDefinition( valueAstDef );\r
- return valueAstDef.name;\r
- } catch (TokenMgrError e) {\r
- throw new DataTypeSyntaxError(e);\r
- } catch (ParseException e) {\r
- throw new DataTypeSyntaxError(e);\r
- }\r
- }\r
- \r
- /**\r
- * Adds multiple value definitions to the repository\r
- * \r
- * @param defs\r
- * @throws DataTypeSyntaxError\r
- */\r
- public void addValueDefinitions(Collection<AstValueDefinition> defs) throws DataTypeSyntaxError {\r
- // TODO recursive definitions\r
- for(AstValueDefinition def : defs)\r
- addValueDefinition(def);\r
- }\r
- \r
- /**\r
- * Adds multiple value definitions to the repository\r
- * @param def\r
- * @throws DataTypeSyntaxError\r
- */\r
- public void addValueDefinitions(String def) throws DataTypeSyntaxError {\r
- try {\r
- addValueDefinitions(new DataParser(new StringReader(def)).valueDefinitions());\r
- } catch (TokenMgrError e) {\r
- throw new DataTypeSyntaxError(e);\r
- } catch (ParseException e) {\r
- throw new DataTypeSyntaxError(e);\r
- }\r
- }\r
- \r
- public DataTypeRepository getTypeRepository() {\r
- return typeRepository;\r
- }\r
-\r
- public void setTypeRepository(DataTypeRepository typeRepository) {\r
- this.typeRepository = typeRepository;\r
- }\r
-\r
- public BindingScheme getBindingScheme() {\r
- return bindingScheme;\r
- }\r
-\r
- public void setBindingScheme(BindingScheme bindingScheme) {\r
- this.bindingScheme = bindingScheme;\r
- }\r
-\r
- /**\r
- * Print the content part of a data value. This excludes the name and type of the value.\r
- * \r
- * @param valueName\r
- * @return value or <code>null</code> if value doesn't exist\r
- * @throws BindingException \r
- * @throws IOException \r
- */\r
- public String printValue(String valueName) throws IOException, BindingException {\r
- MutableVariant value = get(valueName);\r
- if (value==null) return null;\r
- StringBuilder sb = new StringBuilder();\r
- DataValuePrinter vp = new DataValuePrinter(sb, this);\r
- vp.print(value);\r
- return sb.toString();\r
- }\r
- \r
- /**\r
- * Print the whole value repository\r
- * \r
- * @param sb\r
- * @throws IOException\r
- * @throws BindingException\r
- */\r
- public void print(StringBuilder sb)\r
- throws IOException, BindingException\r
- {\r
- DataValuePrinter vp = new DataValuePrinter(sb, this);\r
- vp.setFormat( PrintFormat.SINGLE_LINE );\r
- DataTypePrinter tp = new DataTypePrinter( sb );\r
- tp.setLinefeed( false );\r
- \r
- for (Entry<String, MutableVariant> e : values.entrySet()) {\r
- String name = e.getKey();\r
- MutableVariant value = e.getValue();\r
- Datatype type = value.type();\r
- sb.append( name+" : " );\r
- tp.print(type);\r
- sb.append( " = " );\r
- vp.print(value); \r
- sb.append("\n");\r
- }\r
- } \r
-\r
- /**\r
- * Print the whole data value repository as a single multiline string\r
- * \r
- * @throws RuntimeBindingException\r
- * @throws {@link RuntimeIOException}\r
- */\r
- @Override\r
- public String toString() {\r
- try {\r
- StringBuilder sb = new StringBuilder();\r
- print(sb);\r
- return sb.toString();\r
- } catch (BindingException e) {\r
- throw new RuntimeBindingException(e);\r
- } catch (IOException e) {\r
- throw new RuntimeIOException(e);\r
- }\r
- }\r
- \r
- /**\r
- * Gives a data type to a value heuristically.\r
- */\r
- public Datatype guessDataType(AstValue value) throws DataTypeSyntaxError {\r
- return value.accept(guessDataType);\r
- }\r
- \r
- /**\r
- * Gives a data type to a value heuristically.\r
- */\r
- public Datatype guessDataType(String value) throws DataTypeSyntaxError {\r
- try {\r
- return guessDataType(new DataParser(new StringReader(value)).value());\r
- } catch (TokenMgrError e) {\r
- throw new DataTypeSyntaxError(e);\r
- } catch (ParseException e) {\r
- throw new DataTypeSyntaxError(e);\r
- }\r
- }\r
-\r
- class ValueTranslator implements Binding.Visitor<Object> {\r
-\r
- AstValue value;\r
- \r
- public ValueTranslator(AstValue value) {\r
- this.value = value;\r
- }\r
- \r
- private ValueTranslationRuntimeException typeError(Binding expectedType, AstValue actualValue) {\r
- throw new ValueTranslationRuntimeException("Expected " + expectedType.type().toSingleLineString() + \r
- " but got " + actualValue.getClass().getSimpleName() + ".");\r
- }\r
-\r
- @Override\r
- public Object visit(ArrayBinding b) {\r
- if(value instanceof AstArray) {\r
- AstArray array = (AstArray)value;\r
- Object[] components = new Object[array.elements.size()];\r
- Binding componentBinding = b.getComponentBinding();\r
- int i=0;\r
- for(AstValue component : array.elements) {\r
- value = component;\r
- components[i++] = componentBinding.accept(this);\r
- }\r
- return b.createUnchecked(components);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(BooleanBinding b) {\r
- if(value instanceof AstBoolean) { \r
- return b.createUnchecked(((AstBoolean)value).value);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(DoubleBinding b) {\r
- if(value instanceof AstFloat) { \r
- return b.createUnchecked(((AstFloat)value).value);\r
- }\r
- if(value instanceof AstInteger) { \r
- return b.createUnchecked(((AstInteger)value).value);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(FloatBinding b) {\r
- if(value instanceof AstFloat) { \r
- return b.createUnchecked(((AstFloat)value).value);\r
- }\r
- if(value instanceof AstInteger) { \r
- return b.createUnchecked(((AstInteger)value).value);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(IntegerBinding b) {\r
- if(value instanceof AstInteger) { \r
- return b.createUnchecked(((AstInteger)value).value);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(ByteBinding b) {\r
- if(value instanceof AstInteger) { \r
- return b.createUnchecked(((AstInteger)value).value);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(LongBinding b) {\r
- if(value instanceof AstInteger) { \r
- return b.createUnchecked(((AstInteger)value).value);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(OptionalBinding b) {\r
- if(value == AstNull.NULL)\r
- return b.createNoValueUnchecked();\r
- else\r
- return b.createValueUnchecked(b.getComponentBinding().accept(this)); \r
- }\r
-\r
- @Override\r
- public Object visit(RecordBinding b) {\r
- if(value instanceof AstRecord) {\r
- AstRecord record = (AstRecord)value;\r
- Object[] components = new Object[b.getComponentCount()];\r
- boolean[] assigned = new boolean[b.getComponentCount()];\r
- for(AstComponentAssignment assignment : record.components) {\r
- value = assignment.value;\r
- Integer index = b.type().getComponentIndex(assignment.component);\r
- if(index == null)\r
- throw new ValueTranslationRuntimeException("Invalid record component " + assignment.component + ".");\r
- components[index] = b.getComponentBinding(index).accept(this);\r
- assigned[index] = true;\r
- }\r
- for(int i=0;i<assigned.length;++i)\r
- if(!assigned[i]) {\r
- Binding binding = b.getComponentBinding(i);\r
- if(binding instanceof OptionalBinding)\r
- components[i] = ((OptionalBinding)binding).createNoValueUnchecked();\r
- else\r
- throw new ValueTranslationRuntimeException("Non-optional field " + \r
- b.type().getComponent(i).name + " is not defined.");\r
- }\r
- return b.createUnchecked(components); \r
- }\r
- if(value instanceof AstTuple) {\r
- AstTuple tuple = (AstTuple)value;\r
- Object[] components = new Object[b.getComponentCount()];\r
- int i=0;\r
- for(AstValue element : tuple.elements) {\r
- value = element;\r
- components[i] = b.getComponentBinding(i).accept(this);\r
- ++i;\r
- }\r
- return b.createUnchecked(components);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(StringBinding b) {\r
- if(value instanceof AstString) { \r
- return b.createUnchecked(((AstString)value).value);\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(UnionBinding b) {\r
- if(value instanceof AstTaggedValue) {\r
- AstTaggedValue taggedValue = (AstTaggedValue)value;\r
- Integer tagIndex = b.type().getComponentIndex(taggedValue.tag);\r
- if(tagIndex == null)\r
- throw new ValueTranslationRuntimeException("Invalid union tag " + taggedValue.tag + ".");\r
- value = taggedValue.value;\r
- return b.createUnchecked(tagIndex, b.getComponentBinding(tagIndex).accept(this));\r
- }\r
- else if(value instanceof AstReference) {\r
- AstReference ref = (AstReference)value;\r
- Integer tagIndex = b.type().getComponentIndex(ref.name);\r
- if(tagIndex == null)\r
- throw new ValueTranslationRuntimeException("Invalid union tag " + ref.name + ".");\r
- try {\r
- return b.createUnchecked(tagIndex, b.getComponentBinding(tagIndex).createDefault());\r
- } catch(BindingException e) { \r
- }\r
- }\r
- throw typeError(b, value);\r
- }\r
-\r
- @Override\r
- public Object visit(VariantBinding b) {\r
- try {\r
- if(value instanceof AstVariant) {\r
- AstVariant variant = (AstVariant)value;\r
- Datatype dataType = typeRepository.translate(variant.type);\r
- Binding binding = bindingScheme.getBinding(dataType);\r
- value = variant.value;\r
- return b.createUnchecked(binding, binding.accept(this));\r
- }\r
- else {\r
- Datatype dataType = guessDataType(value);\r
- Binding binding = bindingScheme.getBinding(dataType);\r
- return b.createUnchecked(binding, binding.accept(this));\r
- }\r
- } catch(DataTypeSyntaxError e) {\r
- throw new ValueTranslationRuntimeException(e);\r
- } catch (BindingConstructionException e) {\r
- throw new ValueTranslationRuntimeException(e);\r
- }\r
- }\r
-\r
- @Override\r
- public Object visit(MapBinding b) {\r
- if(value instanceof AstMap) {\r
- AstMap map = (AstMap)value;\r
- Object[] keys = new Object[map.components.size()];\r
- Object[] values = new Object[map.components.size()];\r
- Binding keyBinding = b.getKeyBinding();\r
- Binding valueBinding = b.getValueBinding();\r
- int i = 0;\r
- for(AstMapAssignment assignment : map.components) {\r
- value = assignment.key;\r
- keys[i] = keyBinding.accept(this); \r
- value = assignment.value;\r
- values[i] = valueBinding.accept(this); \r
- ++i;\r
- }\r
- return b.createUnchecked(keys, values); \r
- }\r
- throw typeError(b, value);\r
- }\r
- \r
- }\r
- \r
- AstValueVisitor<Datatype> guessDataType = new AstValueVisitor<Datatype>() {\r
-\r
- @Override\r
- public Datatype visit(AstArray astArray) {\r
- if(astArray.elements.isEmpty())\r
- throw new ValueTranslationRuntimeException("Cannot guess the data type of empty array.");\r
- return astArray.elements.get(0).accept(this);\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstBoolean astBoolean) {\r
- return Datatypes.BOOLEAN;\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstFloat astFloat) {\r
- return Datatypes.DOUBLE;\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstInteger astInteger) {\r
- return Datatypes.INTEGER;\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstMap astMap) {\r
- if(astMap.components.isEmpty())\r
- throw new ValueTranslationRuntimeException("Cannot guess the data type of empty map.");\r
- AstMapAssignment assignment = astMap.components.get(0);\r
- return new MapType(assignment.key.accept(this), assignment.value.accept(this));\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstNull astNull) {\r
- throw new ValueTranslationRuntimeException("Cannot guess the data type");\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstRecord astRecord) {\r
- Component[] components = new Component[astRecord.components.size()];\r
- int i = 0;\r
- for(AstComponentAssignment assignment : astRecord.components) {\r
- components[i++] = new Component(\r
- assignment.component,\r
- assignment.value.accept(this)\r
- );\r
- }\r
- return new RecordType(false, components);\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstReference astReference) {\r
- MutableVariant v = get(astReference.name);\r
- if(v == null)\r
- throw new ValueTranslationRuntimeException("Undefined reference to " + astReference.name + ".");\r
- return v.type();\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstString astString) {\r
- return Datatypes.STRING;\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstTaggedValue astTaggedValue) {\r
- // Guessed datatype would be a union with just one component. Not very useful.\r
- throw new ValueTranslationRuntimeException("Cannot guess the data type of tagged value");\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstTuple astTuple) {\r
- Component[] components = new Component[astTuple.elements.size()];\r
- int i = 0;\r
- for(AstValue value : astTuple.elements) {\r
- components[i] = new Component(\r
- Integer.toString(i),\r
- value.accept(this)\r
- );\r
- ++i;\r
- }\r
- return new RecordType(false, components);\r
- }\r
-\r
- @Override\r
- public Datatype visit(AstVariant astVariant) {\r
- return Datatypes.VARIANT;\r
- }\r
- };\r
- \r
- \r
-}\r
+/*******************************************************************************
+ * 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.
+ *
+ * <p>
+ * 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<String, MutableVariant> values = new HashMap<String, MutableVariant>();
+ Map<Object, String> nameMap = new IdentityHashMap<Object, String>();
+
+ 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<String> 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<AstValueDefinition> 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 <code>null</code> 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<String, MutableVariant> 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<Object> {
+
+ 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<assigned.length;++i)
+ if(!assigned[i]) {
+ Binding binding = b.getComponentBinding(i);
+ if(binding instanceof OptionalBinding)
+ components[i] = ((OptionalBinding)binding).createNoValueUnchecked();
+ else
+ throw new ValueTranslationRuntimeException("Non-optional field " +
+ b.type().getComponent(i).name + " is not defined.");
+ }
+ return b.createUnchecked(components);
+ }
+ if(value instanceof AstTuple) {
+ AstTuple tuple = (AstTuple)value;
+ Object[] components = new Object[b.getComponentCount()];
+ int i=0;
+ for(AstValue element : tuple.elements) {
+ value = element;
+ components[i] = b.getComponentBinding(i).accept(this);
+ ++i;
+ }
+ return b.createUnchecked(components);
+ }
+ throw typeError(b, value);
+ }
+
+ @Override
+ public Object visit(StringBinding b) {
+ if(value instanceof AstString) {
+ return b.createUnchecked(((AstString)value).value);
+ }
+ throw typeError(b, value);
+ }
+
+ @Override
+ public Object visit(UnionBinding b) {
+ if(value instanceof AstTaggedValue) {
+ AstTaggedValue taggedValue = (AstTaggedValue)value;
+ Integer tagIndex = b.type().getComponentIndex(taggedValue.tag);
+ if(tagIndex == null)
+ throw new ValueTranslationRuntimeException("Invalid union tag " + taggedValue.tag + ".");
+ value = taggedValue.value;
+ return b.createUnchecked(tagIndex, b.getComponentBinding(tagIndex).accept(this));
+ }
+ else if(value instanceof AstReference) {
+ AstReference ref = (AstReference)value;
+ Integer tagIndex = b.type().getComponentIndex(ref.name);
+ if(tagIndex == null)
+ throw new ValueTranslationRuntimeException("Invalid union tag " + ref.name + ".");
+ try {
+ return b.createUnchecked(tagIndex, b.getComponentBinding(tagIndex).createDefault());
+ } catch(BindingException e) {
+ }
+ }
+ throw typeError(b, value);
+ }
+
+ @Override
+ public Object visit(VariantBinding b) {
+ try {
+ if(value instanceof AstVariant) {
+ AstVariant variant = (AstVariant)value;
+ Datatype dataType = typeRepository.translate(variant.type);
+ Binding binding = bindingScheme.getBinding(dataType);
+ value = variant.value;
+ return b.createUnchecked(binding, binding.accept(this));
+ }
+ else {
+ Datatype dataType = guessDataType(value);
+ Binding binding = bindingScheme.getBinding(dataType);
+ return b.createUnchecked(binding, binding.accept(this));
+ }
+ } catch(DataTypeSyntaxError e) {
+ throw new ValueTranslationRuntimeException(e);
+ } catch (BindingConstructionException e) {
+ throw new ValueTranslationRuntimeException(e);
+ }
+ }
+
+ @Override
+ public Object visit(MapBinding b) {
+ if(value instanceof AstMap) {
+ AstMap map = (AstMap)value;
+ Object[] keys = new Object[map.components.size()];
+ Object[] values = new Object[map.components.size()];
+ Binding keyBinding = b.getKeyBinding();
+ Binding valueBinding = b.getValueBinding();
+ int i = 0;
+ for(AstMapAssignment assignment : map.components) {
+ value = assignment.key;
+ keys[i] = keyBinding.accept(this);
+ value = assignment.value;
+ values[i] = valueBinding.accept(this);
+ ++i;
+ }
+ return b.createUnchecked(keys, values);
+ }
+ throw typeError(b, value);
+ }
+
+ }
+
+ AstValueVisitor<Datatype> guessDataType = new AstValueVisitor<Datatype>() {
+
+ @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;
+ }
+ };
+
+
+}