001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.schematic.internal.document;
017
018import static org.modeshape.schematic.document.Json.ReservedField.BASE_64;
019import static org.modeshape.schematic.document.Json.ReservedField.BINARY_TYPE;
020import static org.modeshape.schematic.document.Json.ReservedField.CODE;
021import static org.modeshape.schematic.document.Json.ReservedField.DATE;
022import static org.modeshape.schematic.document.Json.ReservedField.INCREMENT;
023import static org.modeshape.schematic.document.Json.ReservedField.OBJECT_ID;
024import static org.modeshape.schematic.document.Json.ReservedField.REGEX_OPTIONS;
025import static org.modeshape.schematic.document.Json.ReservedField.REGEX_PATTERN;
026import static org.modeshape.schematic.document.Json.ReservedField.SCOPE;
027import static org.modeshape.schematic.document.Json.ReservedField.TIMESTAMP;
028import static org.modeshape.schematic.document.Json.ReservedField.UUID;
029import java.io.BufferedWriter;
030import java.io.IOException;
031import java.io.OutputStream;
032import java.io.OutputStreamWriter;
033import java.io.Writer;
034import java.lang.reflect.Array;
035import java.text.CharacterIterator;
036import java.text.StringCharacterIterator;
037import java.util.Date;
038import java.util.Iterator;
039import java.util.Map;
040import java.util.UUID;
041import java.util.regex.Pattern;
042import org.modeshape.schematic.document.Binary;
043import org.modeshape.schematic.document.Bson;
044import org.modeshape.schematic.document.Code;
045import org.modeshape.schematic.document.CodeWithScope;
046import org.modeshape.schematic.document.Document;
047import org.modeshape.schematic.document.Document.Field;
048import org.modeshape.schematic.document.Json;
049import org.modeshape.schematic.document.MaxKey;
050import org.modeshape.schematic.document.MinKey;
051import org.modeshape.schematic.document.ObjectId;
052import org.modeshape.schematic.document.Symbol;
053import org.modeshape.schematic.document.Timestamp;
054
055public class CompactJsonWriter implements JsonWriter {
056
057    @Override
058    public void write( Object object,
059                       OutputStream stream ) throws IOException {
060        Writer writer = new BufferedWriter(new OutputStreamWriter(stream, Json.UTF8));
061        write(object, writer);
062        writer.flush();
063    }
064
065    @Override
066    public void write( Object object,
067                       Writer writer ) throws IOException {
068        if (object == null) {
069            writeNull(writer);
070        } else if (object instanceof String) {
071            write((String)object, writer);
072        } else if (object instanceof Boolean) {
073            write(((Boolean)object).booleanValue(), writer);
074        } else if (object instanceof Integer) {
075            write(((Integer)object).intValue(), writer);
076        } else if (object instanceof Long) {
077            write(((Long)object).longValue(), writer);
078        } else if (object instanceof Float) {
079            write(((Float)object).floatValue(), writer);
080        } else if (object instanceof Double) {
081            write(((Double)object).doubleValue(), writer);
082        } else if (object.getClass().isArray()) {
083            writeArray(object, writer);
084        } else if (object instanceof ArrayEditor) {
085            write((Iterable<?>)((ArrayEditor)object).unwrap(), writer);
086        } else if (object instanceof DocumentEditor) {
087            write(((DocumentEditor)object).unwrap(), writer);
088        } else if (object instanceof Iterable) { // must check before 'BsonObject' because of inheritance
089            write((Iterable<?>)object, writer);
090        } else if (object instanceof Map) {
091            write((Document)object, writer);
092        } else if (object instanceof Binary) {
093            write((Binary)object, writer);
094        } else if (object instanceof Symbol) {
095            write((Symbol)object, writer);
096        } else if (object instanceof Pattern) {
097            write((Pattern)object, writer);
098        } else if (object instanceof Date) {
099            write((Date)object, writer);
100        } else if (object instanceof UUID) {
101            write((UUID)object, writer);
102        } else if (object instanceof CodeWithScope) { // must check before 'Code' because of inheritance
103            write((CodeWithScope)object, writer);
104        } else if (object instanceof Code) {
105            write((Code)object, writer);
106        } else if (object instanceof Timestamp) {
107            write((Timestamp)object, writer);
108        } else if (object instanceof Field) {
109            write((Field)object, writer);
110        } else if (object instanceof ObjectId) {
111            write((ObjectId)object, writer);
112        } else if (object instanceof MaxKey) {
113            write((MaxKey)object, writer);
114        } else if (object instanceof MinKey) {
115            write((MinKey)object, writer);
116        }
117    }
118
119    @Override
120    public void write( Object object,
121                       StringBuilder writer ) {
122        try {
123            write(object, new StringBuilderWriter(writer));
124        } catch (IOException e) {
125            throw new AssertionError(e);
126        }
127    }
128
129    @Override
130    public String write( Object object ) {
131        StringBuilder sb = new StringBuilder();
132        write(object, sb);
133        return sb.toString();
134    }
135
136    protected void write( boolean value,
137                          Writer writer ) throws IOException {
138        writer.append(Boolean.toString(value));
139    }
140
141    protected void write( int value,
142                          Writer writer ) throws IOException {
143        writer.append(Integer.toString(value));
144    }
145
146    protected void write( long value,
147                          Writer writer ) throws IOException {
148        writer.append(Long.toString(value));
149    }
150
151    protected void write( float value,
152                          Writer writer ) throws IOException {
153        writer.append(Float.toString(value));
154    }
155
156    protected void write( double value,
157                          Writer writer ) throws IOException {
158        writer.append(Double.toString(value));
159    }
160
161    protected void writeNull( Writer writer ) throws IOException {
162        writer.append("null");
163    }
164
165    protected void write( String value,
166                          Writer writer ) throws IOException {
167        // We need to escape certain characters found within the string ...
168        writer.append('"');
169        CharacterIterator iter = new StringCharacterIterator(value);
170        for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
171            switch (c) {
172                case '"':
173                case '\'':
174                    // Escape the single- or double-quote characters ..
175                    writer.append('\\').append(c);
176                    break;
177                case '/':
178                    // JSON allows you to escape forward slashes (more for JavaScript), but we won't ...
179                    // writer.append('\\');
180                    writer.append(c);
181                    break;
182                case '\\':
183                    // These characters need to be escaped.
184                    writer.append('\\').append(c);
185                    break;
186                case '\b':
187                    // These characters need to be escaped.
188                    writer.append("\\b");
189                    break;
190                case '\f':
191                    // These characters need to be escaped.
192                    writer.append("\\f");
193                    break;
194                case '\n':
195                    // These characters need to be escaped.
196                    writer.append("\\n");
197                    break;
198                case '\r':
199                    // These characters need to be escaped.
200                    writer.append("\\r");
201                    break;
202                case '\t':
203                    // These characters need to be escaped.
204                    writer.append("\\t");
205                    break;
206                default:
207                    if (c < ' ') {
208                        // Characters less than the first printable character ' ' are non-printable
209                        // and must be escaped as unicode ...
210                        String t = "000" + Integer.toHexString(c);
211                        writer.append("\\u").append(t.substring(t.length() - 4));
212                    } else {
213                        // Otherwise it's a normal character
214                        writer.append(c);
215                    }
216                    break;
217            }
218        }
219        writer.append('"');
220    }
221
222    protected void write( Symbol value,
223                          Writer writer ) throws IOException {
224        writer.append('"').append(value.getSymbol()).append('"');
225    }
226
227    protected void write( ObjectId value,
228                          Writer writer ) throws IOException {
229        write(new BasicDocument(OBJECT_ID, value.getBytesInBase16()), writer);
230    }
231
232    protected void write( Date value,
233                          Writer writer ) throws IOException {
234        String isoDate = Bson.getDateFormatter().format(value);
235        write(new BasicDocument(DATE, isoDate), writer);
236    }
237
238    protected void write( Timestamp value,
239                          Writer writer ) throws IOException {
240        write(new BasicDocument(TIMESTAMP, value.getTime(), INCREMENT, value.getInc()), writer);
241    }
242
243    protected void write( MinKey value,
244                          Writer writer ) throws IOException {
245        write("MinKey", writer);
246    }
247
248    protected void write( MaxKey value,
249                          Writer writer ) throws IOException {
250        write("MaxKey", writer);
251    }
252
253    protected void write( Pattern value,
254                          Writer writer ) throws IOException {
255        BasicDocument obj = new BasicDocument(REGEX_PATTERN, value.pattern());
256        String options = BsonUtils.regexFlagsFor(value);
257        if (options.length() != 0) {
258            obj.put(REGEX_OPTIONS, options);
259        }
260        write(obj, writer);
261    }
262
263    protected void write( Binary value,
264                          Writer writer ) throws IOException {
265        int type = value.getType() - 0;
266        String base64 = value.getBytesInBase64();
267        write(new BasicDocument(BINARY_TYPE, type, BASE_64, base64), writer);
268    }
269
270    protected void write( UUID value,
271                          Writer writer ) throws IOException {
272        write(new BasicDocument(UUID, value.toString()), writer);
273    }
274
275    protected void write( Code value,
276                          Writer writer ) throws IOException {
277        write(new BasicDocument(CODE, value.getCode()), writer);
278    }
279
280    protected void write( CodeWithScope value,
281                          Writer writer ) throws IOException {
282        write(new BasicDocument(CODE, value.getCode(), SCOPE, value.getScope()), writer);
283    }
284
285    protected void write( Field field,
286                          Writer writer ) throws IOException {
287        writer.append('"').append(field.getName()).append('"').append(' ').append(':').append(' ');
288        write(field.getValue(), writer);
289    }
290
291    protected void write( Document bson,
292                          Writer writer ) throws IOException {
293        writer.append('{').append(' ');
294        Iterator<Field> iter = bson.fields().iterator();
295        if (iter.hasNext()) {
296            write(iter.next(), writer);
297            writer.append(' ');
298            while (iter.hasNext()) {
299                writer.append(',').append(' ');
300                write(iter.next(), writer);
301                writer.append(' ');
302            }
303        }
304        writer.append('}');
305    }
306
307    protected void write( Iterable<?> arrayValue,
308                          Writer writer ) throws IOException {
309        writer.append('[');
310        Iterator<?> iter = arrayValue.iterator();
311        if (iter.hasNext()) {
312            writer.append(' ');
313            write(iter.next(), writer);
314            while (iter.hasNext()) {
315                writer.append(' ').append(',').append(' ');
316                write(iter.next(), writer);
317            }
318        }
319        writer.append(' ').append(']');
320    }
321
322    protected void writeArray( Object array,
323                               Writer writer ) throws IOException {
324        // Could transform this into a List, but this is more efficient ...
325        writer.append('[');
326        for (int i = 0, len = Array.getLength(array); i < len; i++) {
327            if (i > 0) {
328                writer.append(' ').append(',').append(' ');
329            }
330            write(Array.get(array, i), writer);
331        }
332        writer.append(' ').append(']');
333    }
334
335    protected static final class StringBuilderWriter extends Writer {
336
337        private final StringBuilder builder;
338
339        public StringBuilderWriter( final StringBuilder builder ) {
340            this.builder = builder;
341        }
342
343        @Override
344        public void write( final char[] cbuf,
345                           final int off,
346                           final int len ) {
347            builder.append(cbuf, off, len);
348        }
349
350        @Override
351        public Writer append( char c ) {
352            builder.append(c);
353            return this;
354        }
355
356        @Override
357        public Writer append( CharSequence csq ) {
358            builder.append(csq);
359            return this;
360        }
361
362        @Override
363        public Writer append( CharSequence csq,
364                              int start,
365                              int end ) {
366            builder.append(csq, start, end);
367            return this;
368        }
369
370        @Override
371        public void write( final int c ) {
372            builder.append(c);
373        }
374
375        @Override
376        public void write( final char[] cbuf ) {
377            builder.append(cbuf);
378        }
379
380        @Override
381        public void write( final String str ) {
382            builder.append(str);
383        }
384
385        @Override
386        public void write( final String str,
387                           final int off,
388                           final int len ) {
389            builder.append(str, off, len);
390        }
391
392        @Override
393        public void flush() {
394        }
395
396        @Override
397        public void close() {
398        }
399    }
400
401}