/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.schematic.internal.document; import static org.modeshape.schematic.document.Json.ReservedField.BASE_64; import static org.modeshape.schematic.document.Json.ReservedField.BINARY_TYPE; import static org.modeshape.schematic.document.Json.ReservedField.CODE; import static org.modeshape.schematic.document.Json.ReservedField.DATE; import static org.modeshape.schematic.document.Json.ReservedField.INCREMENT; import static org.modeshape.schematic.document.Json.ReservedField.OBJECT_ID; import static org.modeshape.schematic.document.Json.ReservedField.REGEX_OPTIONS; import static org.modeshape.schematic.document.Json.ReservedField.REGEX_PATTERN; import static org.modeshape.schematic.document.Json.ReservedField.SCOPE; import static org.modeshape.schematic.document.Json.ReservedField.TIMESTAMP; import static org.modeshape.schematic.document.Json.ReservedField.UUID; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Array; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.Date; import java.util.Iterator; import java.util.Map; import java.util.UUID; import java.util.regex.Pattern; import org.modeshape.schematic.document.Binary; import org.modeshape.schematic.document.Bson; import org.modeshape.schematic.document.Code; import org.modeshape.schematic.document.CodeWithScope; import org.modeshape.schematic.document.Document; import org.modeshape.schematic.document.Document.Field; import org.modeshape.schematic.document.Json; import org.modeshape.schematic.document.MaxKey; import org.modeshape.schematic.document.MinKey; import org.modeshape.schematic.document.ObjectId; import org.modeshape.schematic.document.Symbol; import org.modeshape.schematic.document.Timestamp; public class CompactJsonWriter implements JsonWriter { @Override public void write( Object object, OutputStream stream ) throws IOException { Writer writer = new BufferedWriter(new OutputStreamWriter(stream, Json.UTF8)); write(object, writer); writer.flush(); } @Override public void write( Object object, Writer writer ) throws IOException { if (object == null) { writeNull(writer); } else if (object instanceof String) { write((String)object, writer); } else if (object instanceof Boolean) { write(((Boolean)object).booleanValue(), writer); } else if (object instanceof Integer) { write(((Integer)object).intValue(), writer); } else if (object instanceof Long) { write(((Long)object).longValue(), writer); } else if (object instanceof Float) { write(((Float)object).floatValue(), writer); } else if (object instanceof Double) { write(((Double)object).doubleValue(), writer); } else if (object.getClass().isArray()) { writeArray(object, writer); } else if (object instanceof ArrayEditor) { write((Iterable<?>)((ArrayEditor)object).unwrap(), writer); } else if (object instanceof DocumentEditor) { write(((DocumentEditor)object).unwrap(), writer); } else if (object instanceof Iterable) { // must check before 'BsonObject' because of inheritance write((Iterable<?>)object, writer); } else if (object instanceof Map) { write((Document)object, writer); } else if (object instanceof Binary) { write((Binary)object, writer); } else if (object instanceof Symbol) { write((Symbol)object, writer); } else if (object instanceof Pattern) { write((Pattern)object, writer); } else if (object instanceof Date) { write((Date)object, writer); } else if (object instanceof UUID) { write((UUID)object, writer); } else if (object instanceof CodeWithScope) { // must check before 'Code' because of inheritance write((CodeWithScope)object, writer); } else if (object instanceof Code) { write((Code)object, writer); } else if (object instanceof Timestamp) { write((Timestamp)object, writer); } else if (object instanceof Field) { write((Field)object, writer); } else if (object instanceof ObjectId) { write((ObjectId)object, writer); } else if (object instanceof MaxKey) { write((MaxKey)object, writer); } else if (object instanceof MinKey) { write((MinKey)object, writer); } } @Override public void write( Object object, StringBuilder writer ) { try { write(object, new StringBuilderWriter(writer)); } catch (IOException e) { throw new AssertionError(e); } } @Override public String write( Object object ) { StringBuilder sb = new StringBuilder(); write(object, sb); return sb.toString(); } protected void write( boolean value, Writer writer ) throws IOException { writer.append(Boolean.toString(value)); } protected void write( int value, Writer writer ) throws IOException { writer.append(Integer.toString(value)); } protected void write( long value, Writer writer ) throws IOException { writer.append(Long.toString(value)); } protected void write( float value, Writer writer ) throws IOException { writer.append(Float.toString(value)); } protected void write( double value, Writer writer ) throws IOException { writer.append(Double.toString(value)); } protected void writeNull( Writer writer ) throws IOException { writer.append("null"); } protected void write( String value, Writer writer ) throws IOException { // We need to escape certain characters found within the string ... writer.append('"'); CharacterIterator iter = new StringCharacterIterator(value); for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { switch (c) { case '"': case '\'': // Escape the single- or double-quote characters .. writer.append('\\').append(c); break; case '/': // JSON allows you to escape forward slashes (more for JavaScript), but we won't ... // writer.append('\\'); writer.append(c); break; case '\\': // These characters need to be escaped. writer.append('\\').append(c); break; case '\b': // These characters need to be escaped. writer.append("\\b"); break; case '\f': // These characters need to be escaped. writer.append("\\f"); break; case '\n': // These characters need to be escaped. writer.append("\\n"); break; case '\r': // These characters need to be escaped. writer.append("\\r"); break; case '\t': // These characters need to be escaped. writer.append("\\t"); break; default: if (c < ' ') { // Characters less than the first printable character ' ' are non-printable // and must be escaped as unicode ... String t = "000" + Integer.toHexString(c); writer.append("\\u").append(t.substring(t.length() - 4)); } else { // Otherwise it's a normal character writer.append(c); } break; } } writer.append('"'); } protected void write( Symbol value, Writer writer ) throws IOException { writer.append('"').append(value.getSymbol()).append('"'); } protected void write( ObjectId value, Writer writer ) throws IOException { write(new BasicDocument(OBJECT_ID, value.getBytesInBase16()), writer); } protected void write( Date value, Writer writer ) throws IOException { String isoDate = Bson.getDateFormatter().format(value); write(new BasicDocument(DATE, isoDate), writer); } protected void write( Timestamp value, Writer writer ) throws IOException { write(new BasicDocument(TIMESTAMP, value.getTime(), INCREMENT, value.getInc()), writer); } protected void write( MinKey value, Writer writer ) throws IOException { write("MinKey", writer); } protected void write( MaxKey value, Writer writer ) throws IOException { write("MaxKey", writer); } protected void write( Pattern value, Writer writer ) throws IOException { BasicDocument obj = new BasicDocument(REGEX_PATTERN, value.pattern()); String options = BsonUtils.regexFlagsFor(value); if (options.length() != 0) { obj.put(REGEX_OPTIONS, options); } write(obj, writer); } protected void write( Binary value, Writer writer ) throws IOException { int type = value.getType() - 0; String base64 = value.getBytesInBase64(); write(new BasicDocument(BINARY_TYPE, type, BASE_64, base64), writer); } protected void write( UUID value, Writer writer ) throws IOException { write(new BasicDocument(UUID, value.toString()), writer); } protected void write( Code value, Writer writer ) throws IOException { write(new BasicDocument(CODE, value.getCode()), writer); } protected void write( CodeWithScope value, Writer writer ) throws IOException { write(new BasicDocument(CODE, value.getCode(), SCOPE, value.getScope()), writer); } protected void write( Field field, Writer writer ) throws IOException { writer.append('"').append(field.getName()).append('"').append(' ').append(':').append(' '); write(field.getValue(), writer); } protected void write( Document bson, Writer writer ) throws IOException { writer.append('{').append(' '); Iterator<Field> iter = bson.fields().iterator(); if (iter.hasNext()) { write(iter.next(), writer); writer.append(' '); while (iter.hasNext()) { writer.append(',').append(' '); write(iter.next(), writer); writer.append(' '); } } writer.append('}'); } protected void write( Iterable<?> arrayValue, Writer writer ) throws IOException { writer.append('['); Iterator<?> iter = arrayValue.iterator(); if (iter.hasNext()) { writer.append(' '); write(iter.next(), writer); while (iter.hasNext()) { writer.append(' ').append(',').append(' '); write(iter.next(), writer); } } writer.append(' ').append(']'); } protected void writeArray( Object array, Writer writer ) throws IOException { // Could transform this into a List, but this is more efficient ... writer.append('['); for (int i = 0, len = Array.getLength(array); i < len; i++) { if (i > 0) { writer.append(' ').append(',').append(' '); } write(Array.get(array, i), writer); } writer.append(' ').append(']'); } protected static final class StringBuilderWriter extends Writer { private final StringBuilder builder; public StringBuilderWriter( final StringBuilder builder ) { this.builder = builder; } @Override public void write( final char[] cbuf, final int off, final int len ) { builder.append(cbuf, off, len); } @Override public Writer append( char c ) { builder.append(c); return this; } @Override public Writer append( CharSequence csq ) { builder.append(csq); return this; } @Override public Writer append( CharSequence csq, int start, int end ) { builder.append(csq, start, end); return this; } @Override public void write( final int c ) { builder.append(c); } @Override public void write( final char[] cbuf ) { builder.append(cbuf); } @Override public void write( final String str ) { builder.append(str); } @Override public void write( final String str, final int off, final int len ) { builder.append(str, off, len); } @Override public void flush() { } @Override public void close() { } } }