1 /*--------------------------------------------------------------------------
2 * Copyright 2009 Taro L. Saito
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *--------------------------------------------------------------------------*/
16 //--------------------------------------
20 // Since: May 28, 2009 5:04:34 PM
24 //--------------------------------------
25 package org.xerial.silk;
27 import java.io.OutputStream;
28 import java.io.OutputStreamWriter;
29 import java.io.PrintWriter;
30 import java.io.Writer;
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
36 import java.util.Map.Entry;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
40 import org.xerial.core.XerialError;
41 import org.xerial.core.XerialErrorCode;
42 import org.xerial.lens.ObjectLens;
43 import org.xerial.lens.impl.ParameterGetter;
44 import org.xerial.util.StringUtil;
45 import org.xerial.util.bean.TypeInfo;
48 * Supporting class for generating Silk data. This class is not thread-safe,
49 * i.e, concurrent access to this class may cause invalid Silk format.
55 public class SilkWriter {
57 private final SilkWriter parent;
58 private final PrintWriter out;
59 private Set<SilkWriter> childWriterSet = new HashSet<SilkWriter>();
61 private final String contextNodeName;
64 * Silk output format configurations
69 public static class FormatConfig {
72 public int indentWidth = 2;
73 public boolean indentBeforeDataLine = false;
74 public boolean indentCommentLine = false;
77 public boolean insertSpaceAfterCommentSymbol = true;
80 public boolean insertSpaceAfterComma = true;
83 public boolean insertSpaceBeforeColon = false;
84 public boolean insertSpaceAfterColon = true;
86 * insert tab after colon symbol (intermediate node only)
88 public boolean insertTagAfterColon = false;
90 // colon (inline-node)
91 public boolean insertSpaceBeforeAttributeColon = false;
92 public boolean insertSpaceAfterAttributeColon = false;
93 public boolean insertTagAfterAttributeColon = false;
96 public boolean insertSpaceAfterPreambleSymbol = true;
99 public boolean insertSpaceOutsideOfParen = false;
100 public boolean insertSpaceInsideOfParen = false;
103 private FormatConfig formatConfig = new FormatConfig();
104 private final int levelOffset;
105 private boolean isUsable = true;
107 private SyntaxType nodeValueSyntaxType = SyntaxType.DEFAULT;
109 private int numAttribute = 0;
111 public SilkWriter(Writer out) {
112 this.out = new PrintWriter(out);
114 this.levelOffset = 0;
115 this.contextNodeName = null;
118 public SilkWriter(OutputStream out) {
119 this(new PrintWriter(new OutputStreamWriter(out)));
122 private SilkWriter(String contextNodeName, SilkWriter parent) {
123 this.parent = parent;
124 this.out = parent.out;
125 this.levelOffset = parent.levelOffset + 1;
126 this.contextNodeName = contextNodeName;
127 this.formatConfig = parent.formatConfig;
130 public void setFormatConfig(FormatConfig config) {
132 throw new NullPointerException("config is null");
134 this.formatConfig = config;
138 * Get the parent writer
142 public SilkWriter getParent() {
146 public String getContextNodeName() {
147 return contextNodeName;
150 public void flush() {
154 public void endDocument() {
156 attributeParenCloseCheck(true);
161 * Close the writer (or output stream) inside the SilkWriter
163 public void close() {
168 public SilkWriter preamble() {
170 if (formatConfig.insertSpaceAfterPreambleSymbol)
175 keyAndValue("version", "1.0");
182 * output comment line
187 public SilkWriter commentLine(String comment) {
190 // before generating comment line, close the opened attribute parenthesis
191 attributeParenCloseCheck(true);
193 String[] comments = comment.split("(\\r\\n|\\r|\\n)");
196 for (String each : comments) {
197 if (formatConfig.indentCommentLine)
202 if (formatConfig.insertSpaceAfterCommentSymbol)
205 if (index < comments.length - 1)
214 private void printIndent() {
215 final int indentLevel = levelOffset;
216 for (int i = 0; i < indentLevel; ++i) {
217 for (int w = 0; w < formatConfig.indentWidth; ++w)
222 private void invalidateChildWriters() {
223 for (SilkWriter each : childWriterSet) {
227 childWriterSet.clear();
230 private void invalidate() {
231 attributeParenCloseCheck(false);
232 this.isUsable = false;
235 private void usabilityCheck() {
237 throw new XerialError(XerialErrorCode.INVALID_USAGE, "This writer is no longer usable");
239 invalidateChildWriters();
242 private void openParen() {
243 if (formatConfig.insertSpaceOutsideOfParen)
246 if (formatConfig.insertSpaceInsideOfParen)
250 private void closeParen() {
251 if (formatConfig.insertSpaceInsideOfParen)
254 if (formatConfig.insertSpaceOutsideOfParen)
258 private void attributeParenCloseCheck(boolean insertNewline) {
259 if (numAttribute > 0) {
262 if (nodeValueSyntaxType == SyntaxType.TAB)
266 switch (nodeValueSyntaxType) {
279 * Output a silk node, and gets a new SilkWriter for genarating attributes
285 public SilkWriter node(String nodeName) {
288 attributeParenCloseCheck(true);
290 printNodeName(nodeName);
291 SilkWriter child = new SilkWriter(nodeName, this);
292 registChildWriter(child);
296 private void printNodeName(String nodeName) {
299 if (nodeName != null)
303 public SilkWriter tabDataSchema(String nodeName) {
304 SilkWriter child = node(nodeName);
305 child.setNodeValueSyntax(SyntaxType.TAB);
309 public SilkWriter multilineData(String nodeName) {
310 SilkWriter child = node(nodeName);
311 child.setNodeValueSyntax(SyntaxType.SEQUENCE);
315 public static enum SyntaxType {
316 DEFAULT, TAB, SEQUENCE
319 public SilkWriter setNodeValueSyntax(SyntaxType type) {
320 this.nodeValueSyntaxType = type;
325 * Remember the child writer
329 private void registChildWriter(SilkWriter childWriter) {
330 childWriterSet.add(childWriter);
333 public SilkWriter attribute(String nodeName) {
334 return attribute(nodeName, null);
337 private void keyAndValue(String key, String value) {
339 colonAndNodeValueForInlineNode(value);
342 public SilkWriter attribute(String nodeName, String nodeValue) {
345 if (numAttribute == 0) {
351 keyAndValue(nodeName, nodeValue);
357 public SilkWriter nodeValue(String value) {
359 attributeParenCloseCheck(false);
361 colonAndNodeValue(value);
365 public SilkWriter leaf(String nodeName) {
366 return leaf(nodeName, null);
369 public SilkWriter leaf(String nodeName, String nodeValue) {
372 attributeParenCloseCheck(true);
378 colonAndNodeValue(nodeValue);
385 if (formatConfig.insertSpaceAfterComma)
389 void colonAndNodeValueForInlineNode(String nodeValue) {
390 if (nodeValue != null) {
391 if (formatConfig.insertSpaceBeforeAttributeColon)
394 if (formatConfig.insertSpaceAfterAttributeColon)
396 if (formatConfig.insertTagAfterAttributeColon)
398 out.print(sanitizeInLineNodeValue(nodeValue));
402 static String sanitizeInLineNodeValue(String nodeValue) {
404 String mustBeEscaped = "\"";
405 nodeValue = nodeValue.replaceAll("\"", "\\\"");
407 Pattern p = Pattern.compile(String.format("[%s]", "\\[\\](){},:>*|"));
408 Matcher m = p.matcher(nodeValue);
410 nodeValue = StringUtil.doubleQuote(nodeValue);
416 * Test whether this writer is usable or not
420 public boolean isUsable() {
424 void colonAndNodeValue(String nodeValue) {
425 if (nodeValue != null) {
426 if (formatConfig.insertSpaceBeforeColon)
429 if (formatConfig.insertSpaceAfterColon)
431 if (formatConfig.insertTagAfterColon)
433 out.print(nodeValue);
437 public SilkWriter dataLine(String dataLine) {
440 attributeParenCloseCheck(true);
442 if (formatConfig.indentBeforeDataLine)
445 out.print(escapeDataLine(dataLine));
450 public SilkWriter text(String text) {
453 attributeParenCloseCheck(true);
455 if (formatConfig.indentBeforeDataLine)
458 String line[] = text.split("\r?\n");
462 for (String each : line) {
463 out.print(escapeDataLine(each));
469 public static String escapeText(String text) {
470 String[] line = text.split("\r?\n");
472 return escapeDataLine(text);
474 List<String> buf = new ArrayList<String>();
475 for (String each : line) {
476 buf.add(escapeDataLine(each));
478 return StringUtil.join(buf, StringUtil.NEW_LINE);
481 private static Pattern leadingHyphen = Pattern.compile("\\s*-");
483 private static String escapeDataLine(String dataLine) {
485 if (dataLine == null)
488 Matcher m = leadingHyphen.matcher(dataLine);
490 int hyphenPos = m.end();
491 StringBuilder buf = new StringBuilder();
492 buf.append(dataLine.substring(0, hyphenPos - 1));
494 buf.append(dataLine.substring(hyphenPos - 1));
495 return buf.toString();
502 private <Value> SilkWriter leafObject(String leafNodeName, Value v) {
506 if (TypeInfo.isBasicType(v.getClass())) {
509 attribute(leafNodeName, v.toString());
511 leaf(leafNodeName, v.toString());
514 ObjectLens lens = ObjectLens.getObjectLens(v.getClass());
515 if (lens.hasAttributes()) {
516 SilkWriter c = node(leafNodeName);
521 attribute(leafNodeName, v.toString());
523 leaf(leafNodeName, v.toString());
529 public SilkWriter toSilk(Object obj) {
536 Class< ? > c = obj.getClass();
538 if (TypeInfo.isBasicType(c)) {
539 nodeValue(obj.toString());
543 ObjectLens lens = ObjectLens.getObjectLens(obj.getClass());
545 if (TypeInfo.isIterable(c)) {
546 Iterable< ? > collection = (Iterable< ? >) obj;
547 boolean hasAttributes = lens.hasAttributes();
549 outputParemters(lens, obj);
552 if (collection != null) {
553 for (Object elem : collection) {
554 SilkWriter w = node(null);
559 else if (TypeInfo.isMap(c)) {
560 Map< ? , ? > map = (Map< ? , ? >) obj;
561 boolean hasAttributes = lens.hasAttributes();
564 outputParemters(lens, obj);
567 if (!map.isEmpty()) {
569 String mapElemName = getContextNodeName();
570 if (mapElemName == null)
571 mapElemName = "entry";
573 for (Entry< ? , ? > each : map.entrySet()) {
574 Object key = each.getKey();
575 Object value = each.getValue();
577 if (TypeInfo.isBasicType(key.getClass())) {
578 leafObject(key.toString(), value);
581 SilkWriter w = node(mapElemName);
582 w.node("key").toSilk(key);
583 w.node("value").toSilk(value);
589 if (lens.hasAttributes())
590 outputParemters(lens, obj);
592 out.print(obj.toString());
595 attributeParenCloseCheck(false);
600 private void outputParemters(ObjectLens lens, Object obj) {
602 List<ParameterGetter> getterContainer = lens.getGetterContainer();
603 List<ParameterGetter> postponedParameters = new ArrayList<ParameterGetter>();
605 // output attribute-like parameters first
606 for (ParameterGetter getter : getterContainer) {
608 Class< ? > c = getter.getReturnType();
609 if (TypeInfo.isBasicType(c)) {
610 leafObject(getter.getParamName(), getter.get(obj));
613 if (TypeInfo.isIterable(c) || TypeInfo.isMap(c)) {
614 postponedParameters.add(getter);
617 ObjectLens paramLens = ObjectLens.getObjectLens(c);
618 if (paramLens.hasAttributes())
619 postponedParameters.add(getter);
621 leafObject(getter.getParamName(), getter.get(obj));
627 for (ParameterGetter getter : postponedParameters) {
629 Class< ? > c = getter.getReturnType();
630 if (TypeInfo.isIterable(c)) {
631 Iterable< ? > collection = (Iterable< ? >) getter.get(obj);
633 if (collection != null) {
634 for (Object elem : collection) {
635 SilkWriter w = node(getter.getParamName());
640 else if (TypeInfo.isMap(c)) {
641 Map< ? , ? > map = (Map< ? , ? >) getter.get(obj);
643 if (!map.isEmpty()) {
645 String mapElemName = getter.getParamName();
647 for (Entry< ? , ? > each : map.entrySet()) {
648 Object key = each.getKey();
649 Object value = each.getValue();
651 if (TypeInfo.isBasicType(key.getClass())) {
652 leafObject(key.toString(), value);
655 SilkWriter w = node(mapElemName);
656 w.node("key").toSilk(key);
657 w.node("value").toSilk(value);
663 leafObject(getter.getParamName(), getter.get(obj));