--- /dev/null
+/*******************************************************************************\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;
+
+import java.io.IOException;\r
+\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.BindingException;\r
+import org.simantics.databoard.binding.error.RuntimeBindingException;\r
+import org.simantics.databoard.binding.mutable.MutableVariant;\r
+import org.simantics.databoard.file.RuntimeIOException;\r
+import org.simantics.databoard.parser.repository.DataTypeRepository;\r
+import org.simantics.databoard.parser.repository.DataValueRepository;\r
+import org.simantics.databoard.type.Component;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.type.RecordType;\r
+import org.simantics.databoard.type.UnionType;\r
+
+/**
+ * A class that converts values to their text presentation.
+ *
+ * Refereable records are printed after their name. The name is checked
+ * from a data value repository. If the record doesn't exist, a name is
+ * made up and an entry is added.
+ *
+ * Names of referable record objects are acquired from a data values repository.
+ * If object is not in the repository, it is added.
+ *
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ */
+public class DataValuePrinter implements Binding.Visitor1 {
+
+ Appendable out;
+ int indentLevel = 0;
+ PrintFormat format = PrintFormat.SINGLE_LINE;
+ int nameCounter = 1;
+ DataValueRepository repo;
+ Object root;
+
+ /**
+ * Serialize value to a single line text string
+ *
+ * @param type
+ * @param value
+ * @return the print
+ * @throws IOException
+ * @throws BindingException
+ */
+ public static String writeValueSingleLine(Binding type, Object value)
+ throws IOException, BindingException {
+ StringBuffer sb = new StringBuffer();
+ DataValuePrinter writable = new DataValuePrinter(sb, new DataValueRepository());
+ writable.setFormat(PrintFormat.SINGLE_LINE);
+ writable.print(type, value);
+ return sb.toString();
+ }
+
+ /**
+ * Write value to one or more lines
+ *
+ * @param type
+ * @param value
+ * @return the print
+ * @throws IOException
+ * @throws BindingException
+ */
+ public static String writeValueMultiLine(Binding type, Object value)
+ throws IOException, BindingException {
+ StringBuffer sb = new StringBuffer();
+ DataValuePrinter writable = new DataValuePrinter(sb, new DataValueRepository());
+ writable.setFormat(PrintFormat.MULTI_LINE);
+ writable.print(type, value);
+ return sb.toString();
+ }
+
+ public DataValuePrinter(Appendable out, DataValueRepository valueRepository) {
+ setOutput(out);
+ this.repo = valueRepository;
+ }
+
+ public DataValueRepository getValueRepository() {
+ return repo;
+ }
+
+ public DataTypeRepository getTypeRepository() {
+ return repo.getTypeRepository();
+ }
+
+ public void setOutput(Appendable out) {
+ this.out = out;
+ }
+
+ public void setFormat(PrintFormat format) {
+ if (format == null)
+ throw new IllegalArgumentException("null arg");
+ this.format = format;
+ }
+
+ public void print(MutableVariant variant) throws IOException,
+ BindingException {
+ print(variant.getBinding(), variant.getValue());
+ }
+
+ public void print(Binding binding, Object instance) throws IOException,
+ BindingException {
+ try {
+ root = instance;
+ binding.accept(this, instance);
+ } catch (RuntimeIOException e) {
+ throw e.getCause();
+ } catch (RuntimeBindingException e) {
+ throw e.getCause();
+ } finally {
+ root = null;
+ }
+ }
+
+ @Override
+ public void visit(ArrayBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ Binding cb = b.getComponentBinding();
+ if (instance == null) {
+ out.append(format.openArray);
+ out.append(format.closeArray);
+ return;
+ }
+ int len = b.size(instance);
+ out.append(format.openArray);
+ for (int i = 0; i < len; i++) {
+ Object component = b.get(instance, i);\r
+ if (component == null)\r
+ out.append("null");\r
+ else
+ cb.accept(this, component);
+
+ if (i < len - 1) {\r
+ out.append(format.arraySeparator);\r
+ out.append(' ');\r
+ }\r
+ }
+ out.append(format.closeArray);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ @Override
+ public void visit(BooleanBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ boolean value = b.getValue_(instance);
+ out.append(value ? format.True : format.False);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public void visit(DoubleBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ out.append(b.getValue(instance).toString());
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public void visit(FloatBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ out.append(b.getValue(instance).toString());
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public void visit(IntegerBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ out.append(b.getValue(instance).toString());
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public void visit(ByteBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ out.append(b.getValue(instance).toString());
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ @Override
+ public void visit(LongBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ out.append(b.getValue(instance).toString());
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public void visit(OptionalBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ if (!b.hasValueUnchecked(instance))
+ try {
+ out.append(format.Null);
+ return;
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+
+ try {
+ instance = b.getValue(instance);
+ b.getComponentBinding().accept(this, instance);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ /**
+ * Create a new unique name that doesn't exist in the value repository.
+ *
+ * @return new name
+ */
+ String createNewName() {
+ String name;
+ do {
+ name = "obj" + (nameCounter++);
+ } while (repo.get(name)!=null);
+ return name;
+ }
+
+ @Override
+ public void visit(RecordBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ boolean singleLine = format.newLine == null;
+ boolean tuple = b.type().isTupleType();
+ try {
+ RecordType type = b.type();
+ Binding[] bindings = b.getComponentBindings();
+ Component[] components = b.type().getComponents();
+ int len = bindings.length;
+
+ // Has Name
+ if (type.isReferable() && instance!=root) {
+ String name = repo.getName(instance);
+ if (name == null) {
+ name = createNewName();
+ repo.put(name, b, instance);
+ }
+ out.append(name);
+ return;
+ }
+
+ if (tuple) {
+
+ out.append(format.openTuple);
+ for (int i = 0; i < len; i++) {
+ Binding componentBinding = bindings[i];
+ Object value = b.getComponent(instance, i);
+
+ if (i > 0) {
+ out.append(format.arraySeparator);
+ }
+
+ // Write object name
+ String name = repo.getName(value);
+ if (name != null) {
+ out.append(name);
+ continue;
+ }
+
+ // Write value
+ componentBinding.accept(this, value);
+
+ }
+ out.append(format.closeTuple);
+
+ } else if (len == 0) {
+ out.append(format.openRecord);
+ out.append(format.closeRecord);
+ } else if (singleLine) {
+
+ out.append(format.openRecord);
+ for (int i = 0; i < len; i++) {
+ Binding componentBinding = bindings[i];
+ Object value = b.getComponent(instance, i);
+
+ // Omit null OptionalType (Syntactic Sugar)
+ if (componentBinding instanceof OptionalBinding) {
+ OptionalBinding ob = (OptionalBinding) componentBinding;
+ if (!ob.hasValue(value))
+ continue;
+ }
+
+ if (i > 0) {
+ out.append(format.arraySeparator);
+ out.append(' ');
+ }
+
+ String fieldName = components[i].name;
+ putFieldName(fieldName);
+ out.append(" = ");
+
+ // Write object name
+ String name = repo.getName(value);
+ if (name != null) {
+ out.append(name);
+ } else {
+ // Write value
+ componentBinding.accept(this, value);
+ }
+ }
+ out.append(format.closeRecord);
+
+ } else {
+
+ out.append(format.openRecord);
+ putLineFeed();
+ addIndent();
+ try {\r
+ int fieldsLeft = 0;\r
+ for (int i = 0; i < len; i++) {\r
+ Binding componentBinding = bindings[i];\r
+ Object value = b.getComponent(instance, i);\r
+ if (componentBinding instanceof OptionalBinding) {\r
+ OptionalBinding ob = (OptionalBinding) componentBinding;\r
+ if (!ob.hasValue(value)) continue;\r
+ }\r
+ fieldsLeft++;\r
+ }\r
+
+ for (int i = 0; i < len; i++) {
+ Binding componentBinding = bindings[i];
+ Object value = b.getComponent(instance, i);
+
+ // Omit null OptionalType (Syntactic Sugar)
+ if (componentBinding instanceof OptionalBinding) {
+ OptionalBinding ob = (OptionalBinding) componentBinding;
+ if (!ob.hasValue(value))
+ continue;
+ }
+
+ putIndent();
+
+ String fieldName = components[i].name;
+ putFieldName(fieldName);
+ out.append(" = ");
+
+ // Write object name
+ String name = repo.getName(value);
+ if (name != null) {
+ out.append(name);
+ } else {
+ // Write value
+ componentBinding.accept(this, value);
+ }
+\r
+ // Add "," if there are more fields
+ fieldsLeft--;\r
+ if (fieldsLeft>0)\r
+ out.append(format.arraySeparator);\r
+ putLineFeed();
+ }
+
+ } finally {
+ decIndent();
+ }
+ putIndent();
+ out.append(format.closeRecord);
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ @Override
+ public void visit(StringBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ boolean singleLineFormat = format.newLine == null;
+ String unescapedString = b.getValue(instance);
+
+ // Analyse the string
+ boolean canUseLongString = true;
+ boolean hasCharsToBeEscaped = false;
+// boolean hasCharsToBeEscaped = true;
+ boolean hasLineFeeds = false;
+
+ char c = 0x00;
+ char pc = 0x00;
+ char ppc = 0x00;
+ for (int i = 0; i < unescapedString.length(); i++) {
+ ppc = pc;
+ pc = c;
+ c = unescapedString.charAt(i);
+
+ canUseLongString &= c != '\"' && pc != '\"' && ppc != '\"';
+
+ switch (c) {
+ // Backspace \b or \u0008
+ case '\b': {
+ hasCharsToBeEscaped = true;
+ }
+ // Form feed \f or
+ case '\f': {
+ hasCharsToBeEscaped = true;
+ }
+ // Backslash \\ or \u005c
+ case '\\': {
+ hasCharsToBeEscaped = true;
+ }
+ // Double Quote \" or \u0022
+ case '\"': {
+ hasCharsToBeEscaped = true;
+ }
+ // Single Quote \" or \u0027
+ case '\'': {
+ hasCharsToBeEscaped = true;
+ }
+ // New Line \n or\u000a
+ case '\n': {
+ hasCharsToBeEscaped = true;
+ hasLineFeeds = true;
+ }
+ // Carriage Return \r or\u000d
+ case '\r': {
+ hasCharsToBeEscaped = true;
+ hasLineFeeds = true;
+ }
+ // Tabulator \t or
+ case '\t': {
+ hasCharsToBeEscaped = true;
+ }
+ default:
+ }
+ }
+
+ // Make a selection between short and long string
+ // Short string prints everything in a single line and can escape
+ // anything
+ // Long string is more readable as it doesn't have escape characters
+ // Prefer Long string over short if there are characters to escape
+
+ if (canUseLongString && hasCharsToBeEscaped) {
+ if (singleLineFormat && hasLineFeeds)
+ putShortString(unescapedString);
+ else
+ putLongString(unescapedString);
+ } else {
+ putShortString(unescapedString);
+ }
+
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public void visit(UnionBinding b, Object instance)
+ throws RuntimeIOException, RuntimeBindingException {
+ try {
+ UnionType datatype = (UnionType) b.type();
+ int ordinal = b.getTag(instance);
+ Object component = b.getValue(instance);
+ Binding cb = b.getComponentBindings()[ordinal];
+ // boolean isTuple = (cb instanceof RecordBinding) &&
+ // ((RecordBinding)cb).getDataType().isTupleType();
+
+ String tagName = datatype.components[ordinal].name;
+ putFieldName(tagName);
+ out.append(' ');
+ // if (!isTuple) out.append( format.openUnion );
+ cb.accept(this, component);
+ // out.append( format.closeUnion );
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ @Override
+ public void visit(MapBinding b, Object entity) {
+ boolean singleLine = format.newLine == null;
+ try {
+ Binding keyBinding = b.getKeyBinding();
+ Binding valueBinding = b.getValueBinding();
+ int len = b.size(entity);
+
+ out.append("map ");
+
+ if (len==0) {
+ out.append(format.openRecord);
+ out.append(format.closeRecord);
+ }
+ else if (singleLine) {
+
+ out.append(format.openRecord);
+ Object keys[] = b.getKeys(entity);
+ for (int i=0; i<len; i++) {
+ Object key = keys[i];
+ Object value = b.get(entity, key);
+
+ if (i>0) {
+ out.append(format.arraySeparator);
+ out.append(' ');
+ }
+
+ keyBinding.accept(this, key);
+ out.append( " = ");
+
+ // Write object name
+ String name = repo.getName(value);
+ if (name != null) {
+ out.append(name);
+ } else {
+ // Write value
+ valueBinding.accept(this, value);
+ }
+ }
+ out.append(format.closeRecord);
+
+ } else {
+
+ out.append(format.openRecord);
+ putLineFeed();
+ addIndent();
+ try {
+ Object keys[] = b.getKeys(entity);
+ for (int i=0; i<len; i++) {
+ Object key = keys[i];
+ Object value = b.get(entity, key);
+
+ putIndent();
+
+ keyBinding.accept(this, key);
+ out.append( " = ");
+
+ // Write object name
+ String name = repo.getName(value);
+ if (name != null) {
+ out.append(name);
+ } else {
+ // Write value
+ valueBinding.accept(this, value);
+ }
+
+ if (i<len-1)
+ out.append(format.arraySeparator);
+ putLineFeed();
+ }
+
+ } finally {
+ decIndent();
+ }
+ putIndent();
+ out.append(format.closeRecord);
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ @Override
+ public void visit(VariantBinding b, Object variant) {
+ try {
+ Binding valueBinding = b.getContentBinding(variant);
+ Object value = b.getContent(variant, valueBinding);
+ Datatype type = b.getContentType(variant);
+
+ valueBinding.accept(this, value);
+
+ out.append(" : ");
+
+ //Binding typeBinding = Bindings.getBindingUnchecked(DataType.class);
+ // TODO Use type name if available
+ //typeBinding.printValue(type, out, true);
+ out.append(type.toSingleLineString());
+
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ private void putLongString(String unescapedString) throws IOException {
+ out.append(format.openLongString);\r
+ // Escape the following characters \\ " \ \t\r
+ for (int i = 0; i < unescapedString.length(); i++) {\r
+ char c = unescapedString.charAt(i);\r
+ switch (c) {\r
+ // Backspace \b or \u0008\r
+ case '\b':\r
+ out.append("\\b");\r
+ break;\r
+ // Form feed \f or\r
+ case '\f':\r
+ out.append("\\f");\r
+ break;\r
+ // Backslash \\ or \u005c\r
+ case '\\':\r
+ out.append("\\\\");\r
+ break;\r
+ // Single Quote \' or \u0027\r
+ case '\'':\r
+ out.append("\\\'");\r
+ break;\r
+ // Double Quote \" or \u0022\r
+ case '\"':\r
+ out.append("\\\"");\r
+ break;\r
+ // Tabulator \t or\r
+ case '\t':\r
+ out.append("\\t");\r
+ break;\r
+ default:\r
+ out.append(c);\r
+ break;\r
+ }\r
+ }\r
+ out.append(format.closeLongString);
+ }
+
+ private void putShortString(String unescapedString) throws IOException {
+ out.append(format.openString);
+ // Escape the following characters \\ " \ \n \r \t
+ for (int i = 0; i < unescapedString.length(); i++) {
+ char c = unescapedString.charAt(i);
+ switch (c) {
+ // Backspace \b or \u0008
+ case '\b':
+ out.append("\\b");
+ break;
+ // Form feed \f or
+ case '\f':
+ out.append("\\f");
+ break;
+ // Backslash \\ or \u005c
+ case '\\':
+ out.append("\\\\");
+ break;
+ // Single Quote \' or \u0027
+ case '\'':
+ out.append("\\\'");
+ break;
+ // Double Quote \" or \u0022
+ case '\"':
+ out.append("\\\"");
+ break;
+ // New Line \n or\u000a
+ case '\n':
+ out.append("\\n");
+ break;
+ // Carriage Return \r or\u000d
+ case '\r':
+ out.append("\\r");
+ break;
+ // Tabulator \t or
+ case '\t':
+ out.append("\\t");
+ break;
+ default:
+ out.append(c);
+ break;
+ }
+ }
+ out.append(format.closeString);
+ }
+
+ /**
+ * Put a field name of a record type. The name is writted as a long string,
+ * if it contains " " or escapeable characters.
+ *
+ * @param fieldName
+ * @throws IOException
+ */
+ private void putFieldName(String fieldName) throws IOException {
+ boolean hasCharsToBeEscaped = false;
+ for (int i = 0; i < fieldName.length(); i++) {
+ char c = fieldName.charAt(i);
+ switch (c) {
+ case '\b':
+ hasCharsToBeEscaped = true;
+ break;
+ case '\f':
+ hasCharsToBeEscaped = true;
+ break;
+ case '\\':
+ hasCharsToBeEscaped = true;
+ break;
+ case '\"':
+ hasCharsToBeEscaped = true;
+ break;
+ case '\'':
+ hasCharsToBeEscaped = true;
+ break;
+ case '\n':
+ hasCharsToBeEscaped = true;
+ break;
+ case '\r':
+ hasCharsToBeEscaped = true;
+ break;
+ case '\t':
+ hasCharsToBeEscaped = true;
+ break;
+ default:
+ }
+ }
+
+ if (hasCharsToBeEscaped) {
+ out.append('\'');
+ for (int i = 0; i < fieldName.length(); i++) {
+ char c = fieldName.charAt(i);
+ switch (c) {
+ // Backspace \b or \u0008
+ case '\b':
+ out.append("\\b");
+ break;
+ // Form feed \f or
+ case '\f':
+ out.append("\\f");
+ break;
+ // Backslash \\ or \u005c
+ case '\\':
+ out.append("\\\\");
+ break;
+ // Double Quote \" or \u0022
+ case '\"':
+ out.append("\\\"");
+ break;
+ // Single Quote \' or \u0027
+ case '\'':
+ out.append("\\\'");
+ break;
+ // New Line \n or\u000a
+ case '\n':
+ out.append("\\n");
+ break;
+ // Carriage Return \r or\u000d
+ case '\r':
+ out.append("\\r");
+ break;
+ // Tabulator \t or
+ case '\t':
+ out.append("\\t");
+ break;
+ default:
+ out.append(c);
+ break;
+ }
+ }
+ out.append('\'');
+ } else {
+ out.append(fieldName);
+ }
+ }
+
+ private void putIndent() throws IOException {
+ if (format.indent == null)
+ return;
+ for (int j = 0; j < indentLevel; j++)
+ out.append(format.indent);
+ }
+
+ private void putLineFeed() throws IOException {
+ if (format.newLine == null)
+ return;
+ out.append(format.newLine);
+ }
+
+ private void addIndent() {
+ if (format.indent == null)
+ return;
+ indentLevel++;
+ }
+
+ private void decIndent() {
+ if (format.indent == null)
+ return;
+ indentLevel--;
+ }
+
+}