1 /*******************************************************************************
\r
2 * Copyright (c) 2010 Association for Decentralized Information Management in
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.databoard.parser.repository;
\r
14 import java.io.IOException;
\r
15 import java.io.StringReader;
\r
16 import java.util.Collection;
\r
17 import java.util.HashMap;
\r
18 import java.util.IdentityHashMap;
\r
19 import java.util.Map;
\r
20 import java.util.Map.Entry;
\r
21 import java.util.Set;
\r
23 import org.simantics.databoard.Bindings;
\r
24 import org.simantics.databoard.Datatypes;
\r
25 import org.simantics.databoard.binding.ArrayBinding;
\r
26 import org.simantics.databoard.binding.Binding;
\r
27 import org.simantics.databoard.binding.BooleanBinding;
\r
28 import org.simantics.databoard.binding.ByteBinding;
\r
29 import org.simantics.databoard.binding.DoubleBinding;
\r
30 import org.simantics.databoard.binding.FloatBinding;
\r
31 import org.simantics.databoard.binding.IntegerBinding;
\r
32 import org.simantics.databoard.binding.LongBinding;
\r
33 import org.simantics.databoard.binding.MapBinding;
\r
34 import org.simantics.databoard.binding.OptionalBinding;
\r
35 import org.simantics.databoard.binding.RecordBinding;
\r
36 import org.simantics.databoard.binding.StringBinding;
\r
37 import org.simantics.databoard.binding.UnionBinding;
\r
38 import org.simantics.databoard.binding.VariantBinding;
\r
39 import org.simantics.databoard.binding.error.BindingConstructionException;
\r
40 import org.simantics.databoard.binding.error.BindingException;
\r
41 import org.simantics.databoard.binding.error.RuntimeBindingException;
\r
42 import org.simantics.databoard.binding.factory.BindingScheme;
\r
43 import org.simantics.databoard.binding.mutable.MutableVariant;
\r
44 import org.simantics.databoard.file.RuntimeIOException;
\r
45 import org.simantics.databoard.parser.DataParser;
\r
46 import org.simantics.databoard.parser.DataValuePrinter;
\r
47 import org.simantics.databoard.parser.ParseException;
\r
48 import org.simantics.databoard.parser.PrintFormat;
\r
49 import org.simantics.databoard.parser.TokenMgrError;
\r
50 import org.simantics.databoard.parser.ast.value.AstArray;
\r
51 import org.simantics.databoard.parser.ast.value.AstBoolean;
\r
52 import org.simantics.databoard.parser.ast.value.AstComponentAssignment;
\r
53 import org.simantics.databoard.parser.ast.value.AstFloat;
\r
54 import org.simantics.databoard.parser.ast.value.AstInteger;
\r
55 import org.simantics.databoard.parser.ast.value.AstMap;
\r
56 import org.simantics.databoard.parser.ast.value.AstMapAssignment;
\r
57 import org.simantics.databoard.parser.ast.value.AstNull;
\r
58 import org.simantics.databoard.parser.ast.value.AstRecord;
\r
59 import org.simantics.databoard.parser.ast.value.AstReference;
\r
60 import org.simantics.databoard.parser.ast.value.AstString;
\r
61 import org.simantics.databoard.parser.ast.value.AstTaggedValue;
\r
62 import org.simantics.databoard.parser.ast.value.AstTuple;
\r
63 import org.simantics.databoard.parser.ast.value.AstValue;
\r
64 import org.simantics.databoard.parser.ast.value.AstValueDefinition;
\r
65 import org.simantics.databoard.parser.ast.value.AstVariant;
\r
66 import org.simantics.databoard.parser.ast.value.visitor.AstValueVisitor;
\r
67 import org.simantics.databoard.parser.unparsing.DataTypePrinter;
\r
68 import org.simantics.databoard.type.Component;
\r
69 import org.simantics.databoard.type.Datatype;
\r
70 import org.simantics.databoard.type.MapType;
\r
71 import org.simantics.databoard.type.RecordType;
\r
72 import org.simantics.databoard.type.UnionType;
\r
75 * Data value repository is a collection of name data values.
\r
76 * Each value is associated with type.
\r
79 * It can also translate data lines and value texts to objects and
\r
84 * @author Hannu Niemistö
\r
86 public class DataValueRepository {
\r
88 // Type respository to convert
\r
89 DataTypeRepository typeRepository = Datatypes.datatypeRepository;
\r
91 // Scheme to convert values
\r
92 BindingScheme bindingScheme = Bindings.mutableBindingFactory;
\r
94 /** Stored values */
\r
95 Map<String, MutableVariant> values = new HashMap<String, MutableVariant>();
\r
96 Map<Object, String> nameMap = new IdentityHashMap<Object, String>();
\r
98 public MutableVariant get(String name) {
\r
99 return values.get(name);
\r
102 public String getName(Object value) {
\r
103 return nameMap.get(value);
\r
106 public void put(String name, Binding binding, Object value) {
\r
107 put(name, new MutableVariant(binding, value));
\r
110 public void put(String name, MutableVariant value) {
\r
111 values.put(name, value);
\r
112 nameMap.put(value.getValue(), name);
\r
115 public MutableVariant remove(String name) {
\r
116 MutableVariant value = values.remove(name);
\r
117 if (value==null) return null;
\r
118 nameMap.remove(value.getValue());
\r
122 public void clear() {
\r
128 * Get a view of the value names in this repository
\r
132 public Set<String> getValueNames() {
\r
133 return values.keySet();
\r
137 * Translates a data value from an abstract syntax tree to an object by the binding.
\r
142 * @throws DataTypeSyntaxError
\r
144 public Object translate(AstValue value, Binding binding) throws DataTypeSyntaxError {
\r
146 if(value instanceof AstReference) {
\r
147 String name = ((AstReference)value).name;
\r
148 MutableVariant v = get(name);
\r
150 if(binding instanceof UnionBinding) {
\r
151 UnionBinding b = (UnionBinding)binding;
\r
152 UnionType type = b.type();
\r
153 Integer index = type.getComponentIndex(name);
\r
156 return b.create(index,
\r
157 b.getComponentBinding(index).createDefault());
\r
158 } catch(BindingException e) {
\r
159 throw new DataTypeSyntaxError(e);
\r
162 throw new DataTypeSyntaxError("Undefined reference to " + name + ".");
\r
164 return Bindings.adaptUnchecked(v.getValue(), v.getBinding(), binding);
\r
166 return binding.accept(new ValueTranslator(value));
\r
167 } catch(ValueTranslationRuntimeException e) {
\r
168 throw new DataTypeSyntaxError(e);
\r
173 * Translates a data value from a string to an object by the binding.
\r
177 * @throws DataTypeSyntaxError
\r
179 public Object translate(String value, Binding binding) throws DataTypeSyntaxError {
\r
181 return translate(new DataParser(new StringReader(value)).value(), binding);
\r
182 } catch (TokenMgrError e) {
\r
183 throw new DataTypeSyntaxError(e);
\r
184 } catch (ParseException e) {
\r
185 throw new DataTypeSyntaxError(e);
\r
190 * Adds a value definition to the repository
\r
192 * @throws DataTypeSyntaxError
\r
194 public void addValueDefinition(AstValueDefinition def) throws DataTypeSyntaxError {
\r
195 Datatype type = typeRepository.translate(def.type);
\r
196 Binding binding = Bindings.getMutableBinding(type);
\r
197 MutableVariant variant = new MutableVariant(binding, translate(def.value, binding));
\r
198 values.put(def.name, variant);
\r
199 nameMap.put(variant.getValue(), def.name);
\r
203 * Adds a value definition to the repository
\r
206 * @throws DataTypeSyntaxError
\r
208 public String addValueDefinition(String def) throws DataTypeSyntaxError {
\r
210 StringReader reader = new StringReader(def);
\r
211 DataParser parser = new DataParser( reader );
\r
212 AstValueDefinition valueAstDef = parser.valueDefinition();
\r
213 addValueDefinition( valueAstDef );
\r
214 return valueAstDef.name;
\r
215 } catch (TokenMgrError e) {
\r
216 throw new DataTypeSyntaxError(e);
\r
217 } catch (ParseException e) {
\r
218 throw new DataTypeSyntaxError(e);
\r
223 * Adds multiple value definitions to the repository
\r
226 * @throws DataTypeSyntaxError
\r
228 public void addValueDefinitions(Collection<AstValueDefinition> defs) throws DataTypeSyntaxError {
\r
229 // TODO recursive definitions
\r
230 for(AstValueDefinition def : defs)
\r
231 addValueDefinition(def);
\r
235 * Adds multiple value definitions to the repository
\r
237 * @throws DataTypeSyntaxError
\r
239 public void addValueDefinitions(String def) throws DataTypeSyntaxError {
\r
241 addValueDefinitions(new DataParser(new StringReader(def)).valueDefinitions());
\r
242 } catch (TokenMgrError e) {
\r
243 throw new DataTypeSyntaxError(e);
\r
244 } catch (ParseException e) {
\r
245 throw new DataTypeSyntaxError(e);
\r
249 public DataTypeRepository getTypeRepository() {
\r
250 return typeRepository;
\r
253 public void setTypeRepository(DataTypeRepository typeRepository) {
\r
254 this.typeRepository = typeRepository;
\r
257 public BindingScheme getBindingScheme() {
\r
258 return bindingScheme;
\r
261 public void setBindingScheme(BindingScheme bindingScheme) {
\r
262 this.bindingScheme = bindingScheme;
\r
266 * Print the content part of a data value. This excludes the name and type of the value.
\r
269 * @return value or <code>null</code> if value doesn't exist
\r
270 * @throws BindingException
\r
271 * @throws IOException
\r
273 public String printValue(String valueName) throws IOException, BindingException {
\r
274 MutableVariant value = get(valueName);
\r
275 if (value==null) return null;
\r
276 StringBuilder sb = new StringBuilder();
\r
277 DataValuePrinter vp = new DataValuePrinter(sb, this);
\r
279 return sb.toString();
\r
283 * Print the whole value repository
\r
286 * @throws IOException
\r
287 * @throws BindingException
\r
289 public void print(StringBuilder sb)
\r
290 throws IOException, BindingException
\r
292 DataValuePrinter vp = new DataValuePrinter(sb, this);
\r
293 vp.setFormat( PrintFormat.SINGLE_LINE );
\r
294 DataTypePrinter tp = new DataTypePrinter( sb );
\r
295 tp.setLinefeed( false );
\r
297 for (Entry<String, MutableVariant> e : values.entrySet()) {
\r
298 String name = e.getKey();
\r
299 MutableVariant value = e.getValue();
\r
300 Datatype type = value.type();
\r
301 sb.append( name+" : " );
\r
303 sb.append( " = " );
\r
310 * Print the whole data value repository as a single multiline string
\r
312 * @throws RuntimeBindingException
\r
313 * @throws {@link RuntimeIOException}
\r
316 public String toString() {
\r
318 StringBuilder sb = new StringBuilder();
\r
320 return sb.toString();
\r
321 } catch (BindingException e) {
\r
322 throw new RuntimeBindingException(e);
\r
323 } catch (IOException e) {
\r
324 throw new RuntimeIOException(e);
\r
329 * Gives a data type to a value heuristically.
\r
331 public Datatype guessDataType(AstValue value) throws DataTypeSyntaxError {
\r
332 return value.accept(guessDataType);
\r
336 * Gives a data type to a value heuristically.
\r
338 public Datatype guessDataType(String value) throws DataTypeSyntaxError {
\r
340 return guessDataType(new DataParser(new StringReader(value)).value());
\r
341 } catch (TokenMgrError e) {
\r
342 throw new DataTypeSyntaxError(e);
\r
343 } catch (ParseException e) {
\r
344 throw new DataTypeSyntaxError(e);
\r
348 class ValueTranslator implements Binding.Visitor<Object> {
\r
352 public ValueTranslator(AstValue value) {
\r
353 this.value = value;
\r
356 private ValueTranslationRuntimeException typeError(Binding expectedType, AstValue actualValue) {
\r
357 throw new ValueTranslationRuntimeException("Expected " + expectedType.type().toSingleLineString() +
\r
358 " but got " + actualValue.getClass().getSimpleName() + ".");
\r
362 public Object visit(ArrayBinding b) {
\r
363 if(value instanceof AstArray) {
\r
364 AstArray array = (AstArray)value;
\r
365 Object[] components = new Object[array.elements.size()];
\r
366 Binding componentBinding = b.getComponentBinding();
\r
368 for(AstValue component : array.elements) {
\r
370 components[i++] = componentBinding.accept(this);
\r
372 return b.createUnchecked(components);
\r
374 throw typeError(b, value);
\r
378 public Object visit(BooleanBinding b) {
\r
379 if(value instanceof AstBoolean) {
\r
380 return b.createUnchecked(((AstBoolean)value).value);
\r
382 throw typeError(b, value);
\r
386 public Object visit(DoubleBinding b) {
\r
387 if(value instanceof AstFloat) {
\r
388 return b.createUnchecked(((AstFloat)value).value);
\r
390 if(value instanceof AstInteger) {
\r
391 return b.createUnchecked(((AstInteger)value).value);
\r
393 throw typeError(b, value);
\r
397 public Object visit(FloatBinding b) {
\r
398 if(value instanceof AstFloat) {
\r
399 return b.createUnchecked(((AstFloat)value).value);
\r
401 if(value instanceof AstInteger) {
\r
402 return b.createUnchecked(((AstInteger)value).value);
\r
404 throw typeError(b, value);
\r
408 public Object visit(IntegerBinding b) {
\r
409 if(value instanceof AstInteger) {
\r
410 return b.createUnchecked(((AstInteger)value).value);
\r
412 throw typeError(b, value);
\r
416 public Object visit(ByteBinding b) {
\r
417 if(value instanceof AstInteger) {
\r
418 return b.createUnchecked(((AstInteger)value).value);
\r
420 throw typeError(b, value);
\r
424 public Object visit(LongBinding b) {
\r
425 if(value instanceof AstInteger) {
\r
426 return b.createUnchecked(((AstInteger)value).value);
\r
428 throw typeError(b, value);
\r
432 public Object visit(OptionalBinding b) {
\r
433 if(value == AstNull.NULL)
\r
434 return b.createNoValueUnchecked();
\r
436 return b.createValueUnchecked(b.getComponentBinding().accept(this));
\r
440 public Object visit(RecordBinding b) {
\r
441 if(value instanceof AstRecord) {
\r
442 AstRecord record = (AstRecord)value;
\r
443 Object[] components = new Object[b.getComponentCount()];
\r
444 boolean[] assigned = new boolean[b.getComponentCount()];
\r
445 for(AstComponentAssignment assignment : record.components) {
\r
446 value = assignment.value;
\r
447 Integer index = b.type().getComponentIndex(assignment.component);
\r
449 throw new ValueTranslationRuntimeException("Invalid record component " + assignment.component + ".");
\r
450 components[index] = b.getComponentBinding(index).accept(this);
\r
451 assigned[index] = true;
\r
453 for(int i=0;i<assigned.length;++i)
\r
455 Binding binding = b.getComponentBinding(i);
\r
456 if(binding instanceof OptionalBinding)
\r
457 components[i] = ((OptionalBinding)binding).createNoValueUnchecked();
\r
459 throw new ValueTranslationRuntimeException("Non-optional field " +
\r
460 b.type().getComponent(i).name + " is not defined.");
\r
462 return b.createUnchecked(components);
\r
464 if(value instanceof AstTuple) {
\r
465 AstTuple tuple = (AstTuple)value;
\r
466 Object[] components = new Object[b.getComponentCount()];
\r
468 for(AstValue element : tuple.elements) {
\r
470 components[i] = b.getComponentBinding(i).accept(this);
\r
473 return b.createUnchecked(components);
\r
475 throw typeError(b, value);
\r
479 public Object visit(StringBinding b) {
\r
480 if(value instanceof AstString) {
\r
481 return b.createUnchecked(((AstString)value).value);
\r
483 throw typeError(b, value);
\r
487 public Object visit(UnionBinding b) {
\r
488 if(value instanceof AstTaggedValue) {
\r
489 AstTaggedValue taggedValue = (AstTaggedValue)value;
\r
490 Integer tagIndex = b.type().getComponentIndex(taggedValue.tag);
\r
491 if(tagIndex == null)
\r
492 throw new ValueTranslationRuntimeException("Invalid union tag " + taggedValue.tag + ".");
\r
493 value = taggedValue.value;
\r
494 return b.createUnchecked(tagIndex, b.getComponentBinding(tagIndex).accept(this));
\r
496 else if(value instanceof AstReference) {
\r
497 AstReference ref = (AstReference)value;
\r
498 Integer tagIndex = b.type().getComponentIndex(ref.name);
\r
499 if(tagIndex == null)
\r
500 throw new ValueTranslationRuntimeException("Invalid union tag " + ref.name + ".");
\r
502 return b.createUnchecked(tagIndex, b.getComponentBinding(tagIndex).createDefault());
\r
503 } catch(BindingException e) {
\r
506 throw typeError(b, value);
\r
510 public Object visit(VariantBinding b) {
\r
512 if(value instanceof AstVariant) {
\r
513 AstVariant variant = (AstVariant)value;
\r
514 Datatype dataType = typeRepository.translate(variant.type);
\r
515 Binding binding = bindingScheme.getBinding(dataType);
\r
516 value = variant.value;
\r
517 return b.createUnchecked(binding, binding.accept(this));
\r
520 Datatype dataType = guessDataType(value);
\r
521 Binding binding = bindingScheme.getBinding(dataType);
\r
522 return b.createUnchecked(binding, binding.accept(this));
\r
524 } catch(DataTypeSyntaxError e) {
\r
525 throw new ValueTranslationRuntimeException(e);
\r
526 } catch (BindingConstructionException e) {
\r
527 throw new ValueTranslationRuntimeException(e);
\r
532 public Object visit(MapBinding b) {
\r
533 if(value instanceof AstMap) {
\r
534 AstMap map = (AstMap)value;
\r
535 Object[] keys = new Object[map.components.size()];
\r
536 Object[] values = new Object[map.components.size()];
\r
537 Binding keyBinding = b.getKeyBinding();
\r
538 Binding valueBinding = b.getValueBinding();
\r
540 for(AstMapAssignment assignment : map.components) {
\r
541 value = assignment.key;
\r
542 keys[i] = keyBinding.accept(this);
\r
543 value = assignment.value;
\r
544 values[i] = valueBinding.accept(this);
\r
547 return b.createUnchecked(keys, values);
\r
549 throw typeError(b, value);
\r
554 AstValueVisitor<Datatype> guessDataType = new AstValueVisitor<Datatype>() {
\r
557 public Datatype visit(AstArray astArray) {
\r
558 if(astArray.elements.isEmpty())
\r
559 throw new ValueTranslationRuntimeException("Cannot guess the data type of empty array.");
\r
560 return astArray.elements.get(0).accept(this);
\r
564 public Datatype visit(AstBoolean astBoolean) {
\r
565 return Datatypes.BOOLEAN;
\r
569 public Datatype visit(AstFloat astFloat) {
\r
570 return Datatypes.DOUBLE;
\r
574 public Datatype visit(AstInteger astInteger) {
\r
575 return Datatypes.INTEGER;
\r
579 public Datatype visit(AstMap astMap) {
\r
580 if(astMap.components.isEmpty())
\r
581 throw new ValueTranslationRuntimeException("Cannot guess the data type of empty map.");
\r
582 AstMapAssignment assignment = astMap.components.get(0);
\r
583 return new MapType(assignment.key.accept(this), assignment.value.accept(this));
\r
587 public Datatype visit(AstNull astNull) {
\r
588 throw new ValueTranslationRuntimeException("Cannot guess the data type");
\r
592 public Datatype visit(AstRecord astRecord) {
\r
593 Component[] components = new Component[astRecord.components.size()];
\r
595 for(AstComponentAssignment assignment : astRecord.components) {
\r
596 components[i++] = new Component(
\r
597 assignment.component,
\r
598 assignment.value.accept(this)
\r
601 return new RecordType(false, components);
\r
605 public Datatype visit(AstReference astReference) {
\r
606 MutableVariant v = get(astReference.name);
\r
608 throw new ValueTranslationRuntimeException("Undefined reference to " + astReference.name + ".");
\r
613 public Datatype visit(AstString astString) {
\r
614 return Datatypes.STRING;
\r
618 public Datatype visit(AstTaggedValue astTaggedValue) {
\r
619 // Guessed datatype would be a union with just one component. Not very useful.
\r
620 throw new ValueTranslationRuntimeException("Cannot guess the data type of tagged value");
\r
624 public Datatype visit(AstTuple astTuple) {
\r
625 Component[] components = new Component[astTuple.elements.size()];
\r
627 for(AstValue value : astTuple.elements) {
\r
628 components[i] = new Component(
\r
629 Integer.toString(i),
\r
634 return new RecordType(false, components);
\r
638 public Datatype visit(AstVariant astVariant) {
\r
639 return Datatypes.VARIANT;
\r