View Javadoc
1   /*
2    * #%L
3    * WriteVisitor.java - mongodb-async-driver - Allanbank Consulting, Inc.
4    * %%
5    * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.allanbank.mongodb.bson.io;
21  
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.util.List;
25  
26  import com.allanbank.mongodb.bson.Document;
27  import com.allanbank.mongodb.bson.Element;
28  import com.allanbank.mongodb.bson.ElementType;
29  import com.allanbank.mongodb.bson.element.ObjectId;
30  import com.allanbank.mongodb.bson.element.SizeAwareVisitor;
31  
32  /**
33   * A visitor to myOutput.write the BSON document to a {@link OutputStream}. The
34   * BSON specification uses prefixed length integers in several locations. This
35   * visitor uses a {@link StringEncoder} and the {@link Element#size()} to
36   * compute the size item about to be written removing the requirements to buffer
37   * the data being written.
38   * 
39   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
40   *         mutated in incompatible ways between any two releases of the driver.
41   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
42   */
43  /* package */class WriteVisitor implements SizeAwareVisitor {
44  
45      /** Stream to myOutput.write to. */
46      protected final BsonOutputStream myOutput;
47  
48      /**
49       * Creates a new {@link WriteVisitor}.
50       * 
51       * @param output
52       *            The stream to myOutput.write to.
53       */
54      public WriteVisitor(final BsonOutputStream output) {
55          myOutput = output;
56      }
57  
58      /**
59       * Creates a new {@link WriteVisitor}.
60       * 
61       * @param output
62       *            The stream to myOutput.write to.
63       */
64      public WriteVisitor(final OutputStream output) {
65          this(new BsonOutputStream(output));
66      }
67  
68      /**
69       * Returns the I/O exception encountered by the visitor.
70       * 
71       * @return The I/O exception encountered by the visitor.
72       */
73      public IOException getError() {
74          return myOutput.getError();
75      }
76  
77      /**
78       * Returns true if the visitor had an I/O error.
79       * 
80       * @return True if the visitor had an I/O error, false otherwise.
81       */
82      public boolean hasError() {
83          return myOutput.hasError();
84      }
85  
86      /**
87       * Clears the internal buffer and prepares to myOutput.write another
88       * document.
89       */
90      public void reset() {
91          myOutput.reset();
92      }
93  
94      /**
95       * Determines the size of the document written in BSON format. The
96       * {@link Document}'s size is cached for subsequent write operations.
97       * 
98       * @param doc
99       *            The document to determine the size of.
100      * @return The number of bytes require to Write the document.
101      * @deprecated Replaced with {@link Document#size()}. This method will be
102      *             removed after the 2.2.0 release.
103      */
104     @Deprecated
105     public int sizeOf(final Document doc) {
106         return (int) doc.size();
107     }
108 
109     /**
110      * Computes the size of the encoded UTF8 String.
111      * 
112      * @param string
113      *            The string to determine the length of.
114      * @return The length of the string encoded as UTF8.
115      * @see StringEncoder#utf8Size(String)
116      */
117     public int utf8Size(final String string) {
118         return StringEncoder.utf8Size(string);
119     }
120 
121     /**
122      * {@inheritDoc}
123      */
124     @Override
125     public void visit(final List<Element> elements) {
126 
127         int size = 4 + 1; // Length (int,4) and null.
128         for (final Element element : elements) {
129             size += element.size();
130         }
131 
132         writeElements(elements, size);
133     }
134 
135     /**
136      * {@inheritDoc}
137      */
138     @Override
139     public void visitArray(final String name, final List<Element> elements) {
140         myOutput.writeByte(ElementType.ARRAY.getToken());
141         myOutput.writeCString(name);
142         visit(elements);
143     }
144 
145     /**
146      * {@inheritDoc}
147      */
148     @Override
149     public void visitArray(final String name, final List<Element> elements,
150             final long totalSize) {
151         myOutput.writeByte(ElementType.ARRAY.getToken());
152         myOutput.writeCString(name);
153         writeElements(elements,
154                 ((int) totalSize) - (myOutput.sizeOfCString(name) + 1));
155     }
156 
157     /**
158      * {@inheritDoc}
159      */
160     @Override
161     public void visitBinary(final String name, final byte subType,
162             final byte[] data) {
163         myOutput.writeByte(ElementType.BINARY.getToken());
164         myOutput.writeCString(name);
165         switch (subType) {
166         case 2: {
167             myOutput.writeInt(data.length + 4);
168             myOutput.writeByte(subType);
169             myOutput.writeInt(data.length);
170             myOutput.writeBytes(data);
171             break;
172 
173         }
174         case 0:
175         default:
176             myOutput.writeInt(data.length);
177             myOutput.writeByte(subType);
178             myOutput.writeBytes(data);
179             break;
180         }
181     }
182 
183     /**
184      * {@inheritDoc}
185      */
186     @Override
187     public void visitBoolean(final String name, final boolean value) {
188 
189         myOutput.writeByte(ElementType.BOOLEAN.getToken());
190         myOutput.writeCString(name);
191         myOutput.writeByte(value ? (byte) 0x01 : 0x00);
192     }
193 
194     /**
195      * {@inheritDoc}
196      */
197     @SuppressWarnings("deprecation")
198     @Override
199     public void visitDBPointer(final String name, final String databaseName,
200             final String collectionName, final ObjectId id) {
201         myOutput.writeByte(ElementType.DB_POINTER.getToken());
202         myOutput.writeCString(name);
203         myOutput.writeString(databaseName + "." + collectionName);
204         // Just to be complicated the Object ID is big endian.
205         myOutput.writeInt(EndianUtils.swap(id.getTimestamp()));
206         myOutput.writeLong(EndianUtils.swap(id.getMachineId()));
207     }
208 
209     /**
210      * {@inheritDoc}
211      */
212     @Override
213     public void visitDocument(final String name, final List<Element> elements) {
214         myOutput.writeByte(ElementType.DOCUMENT.getToken());
215         myOutput.writeCString(name);
216         visit(elements);
217     }
218 
219     /**
220      * {@inheritDoc}
221      */
222     @Override
223     public void visitDocument(final String name, final List<Element> elements,
224             final long totalSize) {
225         myOutput.writeByte(ElementType.DOCUMENT.getToken());
226         myOutput.writeCString(name);
227         writeElements(elements,
228                 ((int) totalSize) - (myOutput.sizeOfCString(name) + 1));
229     }
230 
231     /**
232      * {@inheritDoc}
233      */
234     @Override
235     public void visitDouble(final String name, final double value) {
236         myOutput.writeByte(ElementType.DOUBLE.getToken());
237         myOutput.writeCString(name);
238         myOutput.writeLong(Double.doubleToLongBits(value));
239     }
240 
241     /**
242      * {@inheritDoc}
243      */
244     @Override
245     public void visitInteger(final String name, final int value) {
246         myOutput.writeByte(ElementType.INTEGER.getToken());
247         myOutput.writeCString(name);
248         myOutput.writeInt(value);
249     }
250 
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     public void visitJavaScript(final String name, final String code) {
256         myOutput.writeByte(ElementType.JAVA_SCRIPT.getToken());
257         myOutput.writeCString(name);
258         myOutput.writeString(code);
259     }
260 
261     /**
262      * {@inheritDoc}
263      */
264     @Override
265     public void visitJavaScript(final String name, final String code,
266             final Document scope) {
267         myOutput.writeByte(ElementType.JAVA_SCRIPT_WITH_SCOPE.getToken());
268         myOutput.writeCString(name);
269 
270         myOutput.writeInt(4 + StringEncoder.computeStringSize(code)
271                 + (int) scope.size());
272         myOutput.writeString(code);
273 
274         scope.accept(this);
275     }
276 
277     /**
278      * {@inheritDoc}
279      */
280     @Override
281     public void visitLong(final String name, final long value) {
282         myOutput.writeByte(ElementType.LONG.getToken());
283         myOutput.writeCString(name);
284         myOutput.writeLong(value);
285     }
286 
287     /**
288      * {@inheritDoc}
289      */
290     @Override
291     public void visitMaxKey(final String name) {
292         myOutput.writeByte(ElementType.MAX_KEY.getToken());
293         myOutput.writeCString(name);
294     }
295 
296     /**
297      * {@inheritDoc}
298      */
299     @Override
300     public void visitMinKey(final String name) {
301         myOutput.writeByte(ElementType.MIN_KEY.getToken());
302         myOutput.writeCString(name);
303     }
304 
305     /**
306      * {@inheritDoc}
307      */
308     @Override
309     public void visitMongoTimestamp(final String name, final long value) {
310         myOutput.writeByte(ElementType.MONGO_TIMESTAMP.getToken());
311         myOutput.writeCString(name);
312         myOutput.writeLong(value);
313     }
314 
315     /**
316      * {@inheritDoc}
317      */
318     @Override
319     public void visitNull(final String name) {
320         myOutput.writeByte(ElementType.NULL.getToken());
321         myOutput.writeCString(name);
322     }
323 
324     /**
325      * {@inheritDoc}
326      */
327     @Override
328     public void visitObjectId(final String name, final ObjectId id) {
329         myOutput.writeByte(ElementType.OBJECT_ID.getToken());
330         myOutput.writeCString(name);
331         // Just to be complicated the Object ID is big endian.
332         myOutput.writeInt(EndianUtils.swap(id.getTimestamp()));
333         myOutput.writeLong(EndianUtils.swap(id.getMachineId()));
334     }
335 
336     /**
337      * {@inheritDoc}
338      */
339     @Override
340     public void visitRegularExpression(final String name, final String pattern,
341             final String options) {
342         myOutput.writeByte(ElementType.REGEX.getToken());
343         myOutput.writeCString(name);
344         myOutput.writeCString(pattern);
345         myOutput.writeCString(options);
346     }
347 
348     /**
349      * {@inheritDoc}
350      */
351     @Override
352     public void visitString(final String name, final String value) {
353         myOutput.writeByte(ElementType.STRING.getToken());
354         myOutput.writeCString(name);
355         myOutput.writeString(value);
356     }
357 
358     /**
359      * {@inheritDoc}
360      */
361     @Override
362     public void visitSymbol(final String name, final String symbol) {
363         myOutput.writeByte(ElementType.SYMBOL.getToken());
364         myOutput.writeCString(name);
365         myOutput.writeString(symbol);
366     }
367 
368     /**
369      * {@inheritDoc}
370      */
371     @Override
372     public void visitTimestamp(final String name, final long timestamp) {
373         myOutput.writeByte(ElementType.UTC_TIMESTAMP.getToken());
374         myOutput.writeCString(name);
375         myOutput.writeLong(timestamp);
376     }
377 
378     /**
379      * Writes a list of elements.
380      * 
381      * @param elements
382      *            The sub elements of the document.
383      * @param size
384      *            The size of the elements.
385      */
386     protected void writeElements(final List<Element> elements, final int size) {
387         myOutput.writeInt(size);
388         for (final Element element : elements) {
389             element.accept(this);
390         }
391         myOutput.writeByte((byte) 0);
392     }
393 
394 }