View Javadoc
1   /*
2    * #%L
3    * BufferingWriteVisitor.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.Visitor;
30  import com.allanbank.mongodb.bson.element.ObjectId;
31  
32  /**
33   * A visitor to write the BSON document to a {@link RandomAccessOutputStream}.
34   * The BSON specification uses prefixed length integers in several locations.
35   * The {@link RandomAccessOutputStream} allows those values to be re-written
36   * with a single serialization pass.
37   * 
38   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
39   *         mutated in incompatible ways between any two releases of the driver.
40   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
41   */
42  /* package */class BufferingWriteVisitor implements Visitor {
43  
44      /** Output buffer for spooling the written document. */
45      protected final RandomAccessOutputStream myOutputBuffer;
46  
47      /**
48       * Creates a new {@link BufferingWriteVisitor}.
49       */
50      public BufferingWriteVisitor() {
51          this(new StringEncoderCache());
52      }
53  
54      /**
55       * Creates a new {@link BufferingWriteVisitor}.
56       * 
57       * @param output
58       *            The output buffer to use.
59       */
60      public BufferingWriteVisitor(final RandomAccessOutputStream output) {
61          myOutputBuffer = output;
62      }
63  
64      /**
65       * Creates a new {@link BufferingWriteVisitor}.
66       * 
67       * @param cache
68       *            The cache for encoding strings
69       */
70      public BufferingWriteVisitor(final StringEncoderCache cache) {
71          this(new RandomAccessOutputStream(cache));
72      }
73  
74      /**
75       * Returns the maximum number of strings that may have their encoded form
76       * cached.
77       * 
78       * @return The maximum number of strings that may have their encoded form
79       *         cached.
80       * @deprecated The cache {@link StringEncoderCache} should be controlled
81       *             directory. This method will be removed after the 2.1.0
82       *             release.
83       */
84      @Deprecated
85      public int getMaxCachedStringEntries() {
86          return myOutputBuffer.getMaxCachedStringEntries();
87      }
88  
89      /**
90       * Returns the maximum length for a string that the stream is allowed to
91       * cache.
92       * 
93       * @return The maximum length for a string that the stream is allowed to
94       *         cache.
95       * @deprecated The cache {@link StringEncoderCache} should be controlled
96       *             directory. This method will be removed after the 2.1.0
97       *             release.
98       */
99      @Deprecated
100     public int getMaxCachedStringLength() {
101         return myOutputBuffer.getMaxCachedStringLength();
102     }
103 
104     /**
105      * Return the current Size of the written document.
106      * 
107      * @return The current size of the encoded document.
108      */
109     public long getSize() {
110         return myOutputBuffer.getPosition();
111     }
112 
113     /**
114      * Clears the internal buffer and prepares to write another document.
115      */
116     public void reset() {
117         myOutputBuffer.reset();
118     }
119 
120     /**
121      * Sets the value of maximum number of strings that may have their encoded
122      * form cached.
123      * 
124      * @param maxCacheEntries
125      *            The new value for the maximum number of strings that may have
126      *            their encoded form cached.
127      * @deprecated The cache {@link StringEncoderCache} should be controlled
128      *             directory. This method will be removed after the 2.1.0
129      *             release.
130      */
131     @Deprecated
132     public void setMaxCachedStringEntries(final int maxCacheEntries) {
133         myOutputBuffer.setMaxCachedStringEntries(maxCacheEntries);
134     }
135 
136     /**
137      * Sets the value of length for a string that the stream is allowed to cache
138      * to the new value. This can be used to stop a single long string from
139      * pushing useful values out of the cache.
140      * 
141      * @param maxlength
142      *            The new value for the length for a string that the encoder is
143      *            allowed to cache.
144      * @deprecated The cache {@link StringEncoderCache} should be controlled
145      *             directory. This method will be removed after the 2.1.0
146      *             release.
147      */
148     @Deprecated
149     public void setMaxCachedStringLength(final int maxlength) {
150         myOutputBuffer.setMaxCachedStringLength(maxlength);
151 
152     }
153 
154     /**
155      * {@inheritDoc}
156      */
157     @Override
158     public void visit(final List<Element> elements) {
159         final long position = myOutputBuffer.getPosition();
160 
161         myOutputBuffer.writeInt(0);
162         for (final Element element : elements) {
163             element.accept(this);
164         }
165         myOutputBuffer.writeByte((byte) 0);
166 
167         final int size = (int) (myOutputBuffer.getPosition() - position);
168         myOutputBuffer.writeIntAt(position, size);
169     }
170 
171     /**
172      * {@inheritDoc}
173      */
174     @Override
175     public void visitArray(final String name, final List<Element> elements) {
176         myOutputBuffer.writeByte(ElementType.ARRAY.getToken());
177         myOutputBuffer.writeCString(name);
178         visit(elements);
179     }
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
185     public void visitBinary(final String name, final byte subType,
186             final byte[] data) {
187         myOutputBuffer.writeByte(ElementType.BINARY.getToken());
188         myOutputBuffer.writeCString(name);
189         switch (subType) {
190         case 2: {
191             myOutputBuffer.writeInt(data.length + 4);
192             myOutputBuffer.writeByte(subType);
193             myOutputBuffer.writeInt(data.length);
194             myOutputBuffer.writeBytes(data);
195             break;
196 
197         }
198         case 0:
199         default:
200             myOutputBuffer.writeInt(data.length);
201             myOutputBuffer.writeByte(subType);
202             myOutputBuffer.writeBytes(data);
203             break;
204         }
205     }
206 
207     /**
208      * {@inheritDoc}
209      */
210     @Override
211     public void visitBoolean(final String name, final boolean value) {
212 
213         myOutputBuffer.writeByte(ElementType.BOOLEAN.getToken());
214         myOutputBuffer.writeCString(name);
215         myOutputBuffer.writeByte(value ? (byte) 0x01 : 0x00);
216     }
217 
218     /**
219      * {@inheritDoc}
220      */
221     @SuppressWarnings("deprecation")
222     @Override
223     public void visitDBPointer(final String name, final String databaseName,
224             final String collectionName, final ObjectId id) {
225         myOutputBuffer.writeByte(ElementType.DB_POINTER.getToken());
226         myOutputBuffer.writeCString(name);
227         myOutputBuffer.writeString(databaseName + "." + collectionName);
228         // Just to be complicated the Object ID is big endian.
229         myOutputBuffer.writeInt(EndianUtils.swap(id.getTimestamp()));
230         myOutputBuffer.writeLong(EndianUtils.swap(id.getMachineId()));
231     }
232 
233     /**
234      * {@inheritDoc}
235      */
236     @Override
237     public void visitDocument(final String name, final List<Element> elements) {
238         myOutputBuffer.writeByte(ElementType.DOCUMENT.getToken());
239         myOutputBuffer.writeCString(name);
240         visit(elements);
241     }
242 
243     /**
244      * {@inheritDoc}
245      */
246     @Override
247     public void visitDouble(final String name, final double value) {
248         myOutputBuffer.writeByte(ElementType.DOUBLE.getToken());
249         myOutputBuffer.writeCString(name);
250         myOutputBuffer.writeLong(Double.doubleToLongBits(value));
251     }
252 
253     /**
254      * {@inheritDoc}
255      */
256     @Override
257     public void visitInteger(final String name, final int value) {
258         myOutputBuffer.writeByte(ElementType.INTEGER.getToken());
259         myOutputBuffer.writeCString(name);
260         myOutputBuffer.writeInt(value);
261     }
262 
263     /**
264      * {@inheritDoc}
265      */
266     @Override
267     public void visitJavaScript(final String name, final String code) {
268         myOutputBuffer.writeByte(ElementType.JAVA_SCRIPT.getToken());
269         myOutputBuffer.writeCString(name);
270         myOutputBuffer.writeString(code);
271     }
272 
273     /**
274      * {@inheritDoc}
275      */
276     @Override
277     public void visitJavaScript(final String name, final String code,
278             final Document scope) {
279         myOutputBuffer.writeByte(ElementType.JAVA_SCRIPT_WITH_SCOPE.getToken());
280         myOutputBuffer.writeCString(name);
281 
282         final long start = myOutputBuffer.getPosition();
283         myOutputBuffer.writeInt(0);
284         myOutputBuffer.writeString(code);
285 
286         scope.accept(this);
287 
288         final int size = (int) (myOutputBuffer.getPosition() - start);
289         myOutputBuffer.writeIntAt(start, size);
290     }
291 
292     /**
293      * {@inheritDoc}
294      */
295     @Override
296     public void visitLong(final String name, final long value) {
297         myOutputBuffer.writeByte(ElementType.LONG.getToken());
298         myOutputBuffer.writeCString(name);
299         myOutputBuffer.writeLong(value);
300     }
301 
302     /**
303      * {@inheritDoc}
304      */
305     @Override
306     public void visitMaxKey(final String name) {
307         myOutputBuffer.writeByte(ElementType.MAX_KEY.getToken());
308         myOutputBuffer.writeCString(name);
309     }
310 
311     /**
312      * {@inheritDoc}
313      */
314     @Override
315     public void visitMinKey(final String name) {
316         myOutputBuffer.writeByte(ElementType.MIN_KEY.getToken());
317         myOutputBuffer.writeCString(name);
318     }
319 
320     /**
321      * {@inheritDoc}
322      */
323     @Override
324     public void visitMongoTimestamp(final String name, final long value) {
325         myOutputBuffer.writeByte(ElementType.MONGO_TIMESTAMP.getToken());
326         myOutputBuffer.writeCString(name);
327         myOutputBuffer.writeLong(value);
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
333     @Override
334     public void visitNull(final String name) {
335         myOutputBuffer.writeByte(ElementType.NULL.getToken());
336         myOutputBuffer.writeCString(name);
337     }
338 
339     /**
340      * {@inheritDoc}
341      */
342     @Override
343     public void visitObjectId(final String name, final ObjectId id) {
344         myOutputBuffer.writeByte(ElementType.OBJECT_ID.getToken());
345         myOutputBuffer.writeCString(name);
346         // Just to be complicated the Object ID is big endian.
347         myOutputBuffer.writeInt(EndianUtils.swap(id.getTimestamp()));
348         myOutputBuffer.writeLong(EndianUtils.swap(id.getMachineId()));
349     }
350 
351     /**
352      * {@inheritDoc}
353      */
354     @Override
355     public void visitRegularExpression(final String name, final String pattern,
356             final String options) {
357         myOutputBuffer.writeByte(ElementType.REGEX.getToken());
358         myOutputBuffer.writeCString(name);
359         myOutputBuffer.writeCString(pattern);
360         myOutputBuffer.writeCString(options);
361     }
362 
363     /**
364      * {@inheritDoc}
365      */
366     @Override
367     public void visitString(final String name, final String value) {
368         myOutputBuffer.writeByte(ElementType.STRING.getToken());
369         myOutputBuffer.writeCString(name);
370         myOutputBuffer.writeString(value);
371     }
372 
373     /**
374      * {@inheritDoc}
375      */
376     @Override
377     public void visitSymbol(final String name, final String symbol) {
378         myOutputBuffer.writeByte(ElementType.SYMBOL.getToken());
379         myOutputBuffer.writeCString(name);
380         myOutputBuffer.writeString(symbol);
381     }
382 
383     /**
384      * {@inheritDoc}
385      */
386     @Override
387     public void visitTimestamp(final String name, final long timestamp) {
388         myOutputBuffer.writeByte(ElementType.UTC_TIMESTAMP.getToken());
389         myOutputBuffer.writeCString(name);
390         myOutputBuffer.writeLong(timestamp);
391     }
392 
393     /**
394      * Writes the internal buffer to the provided stream.
395      * 
396      * @param out
397      *            The stream to write the internal buffer to.
398      * @throws IOException
399      *             On a failure writing the buffer to the <tt>out</tt> stream.
400      */
401     public void writeTo(final OutputStream out) throws IOException {
402         myOutputBuffer.writeTo(out);
403     }
404 
405     /**
406      * Returns the visitor's output buffer.
407      * 
408      * @return The visitor's output buffer.
409      */
410     protected RandomAccessOutputStream getOutputBuffer() {
411         return myOutputBuffer;
412     }
413 
414 }