package com.revolsys.record.io.format.json; import java.io.IOException; import java.io.Writer; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.revolsys.datatype.DataType; import com.revolsys.io.AbstractRecordWriter; import com.revolsys.io.FileUtil; import com.revolsys.io.IoConstants; import com.revolsys.record.Record; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.util.Exceptions; import com.revolsys.util.number.Numbers; public class JsonRecordWriter extends AbstractRecordWriter { private int depth = 0; private Writer out; private RecordDefinition recordDefinition; private boolean singleObject; private boolean startAttribute; private boolean written; private final JsonStringEncodingWriter encodingOut; public JsonRecordWriter(final RecordDefinition recordDefinition, final Writer out) { this.recordDefinition = recordDefinition; this.out = out; this.encodingOut = new JsonStringEncodingWriter(out); } @Override public void close() { try { final Writer out = this.out; if (out != null) { if (!this.singleObject) { out.write("\n]}\n"); } final String callback = getProperty(IoConstants.JSONP_PROPERTY); if (callback != null) { out.write(");\n"); } } } catch (final IOException e) { } finally { FileUtil.closeSilent(this.out); this.out = null; this.recordDefinition = null; } } private void endList() throws IOException { final Writer out = this.out; this.depth--; out.write('\n'); indent(); out.write(']'); } private void endObject() throws IOException { final Writer out = this.out; this.depth--; out.write('\n'); indent(); out.write('}'); } @Override public void flush() { try { final Writer out = this.out; if (out != null) { out.flush(); } } catch (final IOException e) { } } private void indent() throws IOException { final Writer out = this.out; if (isIndent()) { for (int i = 0; i < this.depth; i++) { out.write(' '); } } } private void label(final String key) throws IOException { final Writer out = this.out; indent(); out.write('"'); this.encodingOut.write(key); out.write('"'); out.write(':'); if (isIndent()) { out.write(' '); } this.startAttribute = true; } private void list(final List<? extends Object> values) throws IOException { startList(); int i = 0; final int size = values.size(); final Iterator<? extends Object> iterator = values.iterator(); while (i < size - 1) { final Object value = iterator.next(); value(null, value); this.out.write(",\n"); this.startAttribute = false; i++; } if (iterator.hasNext()) { final Object value = iterator.next(); value(null, value); } endList(); } private void startList() throws IOException { final Writer out = this.out; if (!this.startAttribute) { indent(); } out.write("[\n"); this.depth++; this.startAttribute = false; } private void startObject() throws IOException { final Writer out = this.out; if (!this.startAttribute) { indent(); } out.write("{\n"); this.depth++; this.startAttribute = false; } private void string(final String string) throws IOException { final Writer out = this.out; out.write('"'); this.encodingOut.write(string); out.write('"'); } @Override public String toString() { return this.recordDefinition.getPath().toString(); } @SuppressWarnings("unchecked") private void value(final DataType dataType, final Object value) throws IOException { final Writer out = this.out; if (value == null) { out.write("null"); } else if (value instanceof Boolean) { if ((Boolean)value) { out.write("true"); } else { out.write("false"); } } else if (value instanceof Number) { out.write(Numbers.toString((Number)value)); } else if (value instanceof List) { final List<? extends Object> list = (List<? extends Object>)value; list(list); } else if (value instanceof Map) { final Map<String, ? extends Object> map = (Map<String, ? extends Object>)value; write(map); } else if (value instanceof CharSequence) { final CharSequence string = (CharSequence)value; string(string.toString()); } else if (dataType == null) { string(value.toString()); } else { final String string = dataType.toString(value); string(string); } } @Override public void write(final Map<String, ? extends Object> values) { try { startObject(); boolean first = true; for (final Entry<String, ? extends Object> entry : values.entrySet()) { final String key = entry.getKey(); final Object value = entry.getValue(); if (value != null) { if (!first) { this.out.write(",\n"); } label(key); value(null, value); first = false; } } endObject(); } catch (final IOException e) { throw Exceptions.wrap(e); } } @Override public void write(final Record record) { try { final Writer out = this.out; if (this.written) { out.write(",\n"); } else { writeHeader(); } startObject(); boolean hasValue = false; for (final FieldDefinition field : this.recordDefinition.getFields()) { final int fieldIndex = field.getIndex(); final Object value; if (isWriteCodeValues()) { value = record.getCodeValue(fieldIndex); } else { value = record.getValue(fieldIndex); } if (isValueWritable(value)) { if (hasValue) { this.out.write(",\n"); } else { hasValue = true; } final String name = field.getName(); label(name); final DataType dataType = field.getDataType(); value(dataType, value); } } endObject(); } catch (final IOException e) { throw Exceptions.wrap(e); } } private void writeHeader() throws IOException { final Writer out = this.out; final String callback = getProperty(IoConstants.JSONP_PROPERTY); if (callback != null) { out.write(callback); out.write('('); } this.singleObject = Boolean.TRUE.equals(getProperty(IoConstants.SINGLE_OBJECT_PROPERTY)); if (!this.singleObject) { out.write("{\"items\": [\n"); } this.written = true; } }