/******************************************************************************* * 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; import java.io.IOException; 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.BindingException; import org.simantics.databoard.binding.error.RuntimeBindingException; import org.simantics.databoard.binding.mutable.MutableVariant; import org.simantics.databoard.file.RuntimeIOException; import org.simantics.databoard.parser.repository.DataTypeRepository; import org.simantics.databoard.parser.repository.DataValueRepository; import org.simantics.databoard.type.Component; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.type.RecordType; import org.simantics.databoard.type.UnionType; /** * 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 */ 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); if (component == null) out.append("null"); else cb.accept(this, component); if (i < len - 1) { out.append(format.arraySeparator); out.append(' '); } } 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 { int fieldsLeft = 0; for (int i = 0; i < len; i++) { Binding componentBinding = bindings[i]; Object value = b.getComponent(instance, i); if (componentBinding instanceof OptionalBinding) { OptionalBinding ob = (OptionalBinding) componentBinding; if (!ob.hasValue(value)) continue; } fieldsLeft++; } 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); } // Add "," if there are more fields fieldsLeft--; if (fieldsLeft>0) 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(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; i0) { 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