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.Bson.END_OF_DOCUMENT;
019import static org.modeshape.schematic.document.Bson.END_OF_STRING;
020import java.io.ByteArrayOutputStream;
021import java.io.DataOutput;
022import java.io.IOException;
023import java.io.OutputStream;
024import java.lang.reflect.Array;
025import java.util.Date;
026import java.util.Iterator;
027import java.util.UUID;
028import java.util.regex.Pattern;
029import org.modeshape.schematic.annotation.ThreadSafe;
030import org.modeshape.schematic.document.Binary;
031import org.modeshape.schematic.document.Bson.BinaryType;
032import org.modeshape.schematic.document.Bson.Type;
033import org.modeshape.schematic.document.Code;
034import org.modeshape.schematic.document.CodeWithScope;
035import org.modeshape.schematic.document.Document;
036import org.modeshape.schematic.document.Document.Field;
037import org.modeshape.schematic.document.MaxKey;
038import org.modeshape.schematic.document.MinKey;
039import org.modeshape.schematic.document.Null;
040import org.modeshape.schematic.document.ObjectId;
041import org.modeshape.schematic.document.Symbol;
042import org.modeshape.schematic.document.Timestamp;
043import org.modeshape.schematic.internal.io.BsonDataOutput;
044
045/**
046 * A component that writes BSON representations from the in-memory {@link Document} representation.
047 * 
048 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
049 */
050@ThreadSafe
051public class BsonWriter {
052
053    /**
054     * Write the supplied in-memory {@link Document} in standard BSON binary format to the supplied stream.
055     * 
056     * @param object the BSON object or BSON value; may not be null
057     * @param stream the output stream; may not be null
058     * @throws IOException if there was a problem reading from the stream
059     */
060    public void write( Object object,
061                       OutputStream stream ) throws IOException {
062        BsonDataOutput buffer = new BsonDataOutput();
063        write(null, object, buffer);
064        buffer.writeTo(stream);
065    }
066
067    /**
068     * Return the array of bytes containing the standard BSON binary form of the supplied in-memory {@link Document}.
069     * 
070     * @param object the BSON object or BSON value; may not be null
071     * @return the bytes
072     * @throws IOException if there was a problem reading from the stream
073     */
074    public byte[] write( Object object ) throws IOException {
075        BsonDataOutput buffer = new BsonDataOutput();
076        write(null, object, buffer);
077        ByteArrayOutputStream stream = new ByteArrayOutputStream(buffer.size());
078        buffer.writeTo(stream);
079        return stream.toByteArray();
080    }
081
082    /**
083     * Write to the supplied output the binary BSON representation of the supplied in-memory {@link Document}.
084     * 
085     * @param object the BSON object or BSON value; may not be null
086     * @param output the output; may not be null
087     * @throws IOException if there was a problem writing to the ObjectOutput
088     */
089    public void write( Object object,
090                       DataOutput output ) throws IOException {
091        ByteArrayOutputStream stream = new ByteArrayOutputStream();
092        write(object, stream);
093        stream.flush();
094        byte[] bytes = stream.toByteArray();
095        output.write(bytes);
096    }
097
098    protected void write( String name,
099                          Object object,
100                          BsonDataOutput output ) {
101        if (object == null) {
102            writeNull(name, output);
103        } else if (object instanceof String) {
104            write(name, (String)object, output);
105        } else if (object instanceof Boolean) {
106            write(name, ((Boolean)object).booleanValue(), output);
107        } else if (object instanceof Integer) {
108            write(name, ((Integer)object).intValue(), output);
109        } else if (object instanceof Long) {
110            write(name, ((Long)object).longValue(), output);
111        } else if (object instanceof Float) {
112            write(name, ((Float)object).floatValue(), output);
113        } else if (object instanceof Double) {
114            write(name, ((Double)object).doubleValue(), output);
115        } else if (object.getClass().isArray()) {
116            writeArray(name, object, output);
117        } else if (object instanceof ArrayEditor) {
118            write(name, (Iterable<?>)((ArrayEditor)object).unwrap(), output);
119        } else if (object instanceof DocumentEditor) {
120            write(name, ((DocumentEditor)object).unwrap(), output);
121        } else if (object instanceof Iterable) { // must check before 'BsonObject' because of inheritance
122            write(name, (Iterable<?>)object, output);
123        } else if (object instanceof Document) {
124            write(name, (Document)object, output);
125        } else if (object instanceof Binary) {
126            write(name, (Binary)object, output);
127        } else if (object instanceof Symbol) {
128            write(name, (Symbol)object, output);
129        } else if (object instanceof Pattern) {
130            write(name, (Pattern)object, output);
131        } else if (object instanceof Date) {
132            write(name, (Date)object, output);
133        } else if (object instanceof UUID) {
134            write(name, (UUID)object, output);
135        } else if (object instanceof CodeWithScope) { // must check before 'Code' because of inheritance
136            write(name, (CodeWithScope)object, output);
137        } else if (object instanceof Code) {
138            write(name, (Code)object, output);
139        } else if (object instanceof Timestamp) {
140            write(name, (Timestamp)object, output);
141        } else if (object instanceof ObjectId) {
142            write(name, (ObjectId)object, output);
143        } else if (object instanceof MaxKey) {
144            write(name, (MaxKey)object, output);
145        } else if (object instanceof MinKey) {
146            write(name, (MinKey)object, output);
147        } else if (object instanceof Null) { // do this last because it is rare ...
148            writeNull(name, output);
149        } else {
150            throw new RuntimeException("Unable to serialize type \'" + object.getClass() + "\" to BSON");
151        }
152    }
153
154    protected void writeCString( String value,
155                                 BsonDataOutput output ) {
156        output.writeUTFString(value);
157        output.writeByte(END_OF_STRING);
158    }
159
160    protected void writeString( String value,
161                                BsonDataOutput output ) {
162        // Write out the size of the string; use '0' now and rewrite it ...
163        int position = output.size();
164        output.writeInt(0);
165        int len = output.writeUTFString(value);
166        output.writeByte(END_OF_STRING);
167        // Now write the length ...
168        assert (len + 1) >= 0;
169        output.writeInt(position, len + 1);// +1 for the zero-byte
170    }
171
172    protected void writeNull( String name,
173                              BsonDataOutput output ) {
174        output.writeByte(Type.NULL);
175        writeCString(name, output);
176    }
177
178    protected void write( String name,
179                          String value,
180                          BsonDataOutput output ) {
181        output.writeByte(Type.STRING);
182        writeCString(name, output);
183        writeString(value, output);
184    }
185
186    protected void write( String name,
187                          boolean value,
188                          BsonDataOutput output ) {
189        output.writeByte(Type.BOOLEAN);
190        writeCString(name, output);
191        output.writeByte(value ? (byte)0x01 : (byte)0x00);
192    }
193
194    protected void write( String name,
195                          int value,
196                          BsonDataOutput output ) {
197        output.writeByte(Type.INT32);
198        writeCString(name, output);
199        output.writeInt(value);
200    }
201
202    protected void write( String name,
203                          long value,
204                          BsonDataOutput output ) {
205        output.writeByte(Type.INT64);
206        writeCString(name, output);
207        output.writeLong(value);
208    }
209
210    protected void write( String name,
211                          float value,
212                          BsonDataOutput output ) {
213        output.writeByte(Type.DOUBLE);
214        writeCString(name, output);
215        output.writeDouble(value);
216    }
217
218    protected void write( String name,
219                          double value,
220                          BsonDataOutput output ) {
221        output.writeByte(Type.DOUBLE);
222        writeCString(name, output);
223        output.writeDouble(value);
224    }
225
226    protected void writeArray( String name,
227                               Object arrayValue,
228                               BsonDataOutput output ) {
229        if (name != null) {
230            output.writeByte(Type.ARRAY);
231            writeCString(name, output);
232        }
233        // Write the size for the array; we'll come back to this after we write the array ...
234        int arraySizePosition = output.size();
235        output.writeInt(0);
236
237        // Now write out the array as a document ...
238        int length = Array.getLength(arrayValue);
239        Iterator<String> indexIter = IndexSequence.infiniteSequence();
240        for (int i = 0; i != length; ++i) {
241            String elementName = indexIter.next(); // the strings are shared/reused
242            Object value = Array.get(arrayValue, i);
243            write(elementName, value, output);
244        }
245        output.writeByte(END_OF_DOCUMENT);
246
247        // Determine the number of bytes written in the array, and overwrite the value we wrote earlier ..
248        int arraySize = output.size() - arraySizePosition;
249        output.writeInt(arraySizePosition, arraySize);
250    }
251
252    protected void write( String name,
253                          Iterable<?> arrayValue,
254                          BsonDataOutput output ) {
255        if (name != null) {
256            output.writeByte(Type.ARRAY);
257            writeCString(name, output);
258        }
259        // Write the size for the array; we'll come back to this after we write the array ...
260        int arraySizePosition = output.size();
261        output.writeInt(0);
262        // Now write out the array as a document ...
263        Iterator<String> indexIter = IndexSequence.infiniteSequence();
264        Iterator<?> valueIter = arrayValue.iterator();
265        while (valueIter.hasNext()) {
266            String elementName = indexIter.next(); // the strings are shared/reused
267            Object value = valueIter.next();
268            write(elementName, value, output);
269        }
270        output.writeByte(END_OF_DOCUMENT);
271
272        // Determine the number of bytes written in the array, and overwrite the value we wrote earlier ..
273        int arraySize = output.size() - arraySizePosition;
274        output.writeInt(arraySizePosition, arraySize);
275    }
276
277    protected void write( String name,
278                          Document document,
279                          BsonDataOutput output ) {
280        if (name != null) {
281            output.writeByte(Type.DOCUMENT);
282            writeCString(name, output);
283        }
284        // Write the size for the document; we'll come back to this after we write the array ...
285        int arraySizePosition = output.size();
286        output.writeInt(-1);
287
288        for (Field field : document.fields()) {
289            write(field.getName(), field.getValue(), output);
290        }
291        output.writeByte(END_OF_DOCUMENT);
292
293        // Determine the number of bytes written in the array, and overwrite the value we wrote earlier ..
294        int arraySize = output.size() - arraySizePosition;
295        output.writeInt(arraySizePosition, arraySize);
296    }
297
298    protected void write( String name,
299                          Binary value,
300                          BsonDataOutput output ) {
301        output.writeByte(Type.BINARY);
302        writeCString(name, output);
303        byte[] bytes = value.getBytes();
304        output.writeInt(bytes.length);
305        output.writeByte(value.getType());
306        output.write(bytes);
307    }
308
309    protected void write( String name,
310                          Symbol value,
311                          BsonDataOutput output ) {
312        output.writeByte(Type.SYMBOL);
313        writeCString(name, output);
314        writeString(value.getSymbol(), output);
315    }
316
317    protected void write( String name,
318                          Pattern value,
319                          BsonDataOutput output ) {
320        output.writeByte(Type.REGEX);
321        writeCString(name, output);
322        writeCString(value.pattern(), output);
323        writeCString(BsonUtils.regexFlagsFor(value), output);
324    }
325
326    protected void write( String name,
327                          Date value,
328                          BsonDataOutput output ) {
329        output.writeByte(Type.DATETIME);
330        writeCString(name, output);
331        output.writeLong(value.getTime());
332    }
333
334    protected void write( String name,
335                          UUID value,
336                          BsonDataOutput output ) {
337        output.writeByte(Type.BINARY);
338        writeCString(name, output);
339        output.writeInt(16);
340        output.writeByte(BinaryType.UUID);
341        output.writeLong(value.getMostSignificantBits());
342        output.writeLong(value.getLeastSignificantBits());
343    }
344
345    protected void write( String name,
346                          CodeWithScope value,
347                          BsonDataOutput output ) {
348        output.writeByte(Type.JAVASCRIPT_WITH_SCOPE);
349        writeCString(name, output);
350        // Write the size for the CodeWithScope; we'll come back to this after we write the object ...
351        int arraySizePosition = output.size();
352        output.writeInt(0);
353
354        // Write the code & scope ...
355        writeString(value.getCode(), output);
356        write(null, value.getScope(), output);
357
358        // Determine the number of bytes written in the array, and overwrite the value we wrote earlier ..
359        int arraySize = output.size() - arraySizePosition;
360        output.writeInt(arraySizePosition, arraySize);
361    }
362
363    protected void write( String name,
364                          Code value,
365                          BsonDataOutput output ) {
366        output.writeByte(Type.JAVASCRIPT);
367        writeCString(name, output);
368        writeString(value.getCode(), output);
369    }
370
371    protected void write( String name,
372                          Timestamp value,
373                          BsonDataOutput output ) {
374        output.writeByte(Type.TIMESTAMP);
375        writeCString(name, output);
376        output.writeInt(value.getInc());
377        output.writeInt(value.getTime());
378    }
379
380    protected void write( String name,
381                          ObjectId value,
382                          BsonDataOutput output ) {
383        output.writeByte(Type.OBJECTID);
384        writeCString(name, output);
385        output.write(value.getBytes());
386    }
387
388    protected void write( String name,
389                          MaxKey value,
390                          BsonDataOutput output ) {
391        output.writeByte(Type.MAXKEY);
392        writeCString(name, output);
393    }
394
395    protected void write( String name,
396                          MinKey value,
397                          BsonDataOutput output ) {
398        output.writeByte(Type.MINKEY);
399        writeCString(name, output);
400    }
401}