View Javadoc
1   /*
2    * #%L
3    * BsonOutputStream.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.nio.charset.Charset;
25  
26  import com.allanbank.mongodb.bson.Document;
27  
28  /**
29   * A wrapper for an {@link OutputStream} to handle writing BSON primitives.
30   * 
31   * @api.yes This class is part of the driver's API. Public and protected members
32   *          will be deprecated for at least 1 non-bugfix release (version
33   *          numbers are <major>.<minor>.<bugfix>) before being
34   *          removed or modified.
35   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
36   */
37  public class BsonOutputStream {
38  
39      /** UTF-8 Character set for encoding strings. */
40      public final static Charset UTF8 = StringDecoder.UTF8;
41  
42      /** Any thrown exceptions. */
43      protected IOException myError;
44  
45      /** Output buffer for spooling the written document. */
46      protected final OutputStream myOutput;
47  
48      /** The encoder for strings. */
49      protected final StringEncoder myStringEncoder;
50  
51      /** The visitor for writing BSON documents. */
52      protected final WriteVisitor myWriteVisitor;
53  
54      /**
55       * Creates a new {@link BsonOutputStream}.
56       * 
57       * @param output
58       *            The underlying Stream to write to.
59       */
60      public BsonOutputStream(final OutputStream output) {
61          this(output, new StringEncoderCache());
62      }
63  
64      /**
65       * Creates a new {@link BsonOutputStream}.
66       * 
67       * @param output
68       *            The underlying Stream to write to.
69       * @param cache
70       *            The cache for encoding string.
71       */
72      public BsonOutputStream(final OutputStream output,
73              final StringEncoderCache cache) {
74          myOutput = output;
75          myStringEncoder = new StringEncoder(cache);
76          myWriteVisitor = new WriteVisitor(this);
77      }
78  
79      /**
80       * Returns the I/O exception encountered by the visitor.
81       * 
82       * @return The I/O exception encountered by the visitor.
83       */
84      public IOException getError() {
85          return myError;
86      }
87  
88      /**
89       * Returns the maximum number of strings that may have their encoded form
90       * cached.
91       * 
92       * @return The maximum number of strings that may have their encoded form
93       *         cached.
94       * @deprecated The cache {@link StringEncoderCache} should be controlled
95       *             directory. This method will be removed after the 2.1.0
96       *             release.
97       */
98      @Deprecated
99      public int getMaxCachedStringEntries() {
100         return myStringEncoder.getCache().getMaxCacheEntries();
101     }
102 
103     /**
104      * Returns the maximum length for a string that the stream is allowed to
105      * cache.
106      * 
107      * @return The maximum length for a string that the stream is allowed to
108      *         cache.
109      * @deprecated The cache {@link StringDecoderCache} should be controlled
110      *             directory. This method will be removed after the 2.1.0
111      *             release.
112      */
113     @Deprecated
114     public int getMaxCachedStringLength() {
115         return myStringEncoder.getCache().getMaxCacheLength();
116     }
117 
118     /**
119      * Returns the encoder value.
120      * 
121      * @return The encoder value.
122      */
123     public StringEncoder getStringEncoder() {
124         return myStringEncoder;
125     }
126 
127     /**
128      * Returns true if the visitor had an I/O error.
129      * 
130      * @return True if the visitor had an I/O error, false otherwise.
131      */
132     public boolean hasError() {
133         return (myError != null);
134     }
135 
136     /**
137      * Clears any errors.
138      */
139     public void reset() {
140         myError = null;
141     }
142 
143     /**
144      * Sets the value of maximum number of strings that may have their encoded
145      * form cached.
146      * 
147      * @param maxCacheEntries
148      *            The new value for the maximum number of strings that may have
149      *            their encoded form cached.
150      * @deprecated The cache {@link StringEncoderCache} should be controlled
151      *             directory. This method will be removed after the 2.1.0
152      *             release.
153      */
154     @Deprecated
155     public void setMaxCachedStringEntries(final int maxCacheEntries) {
156         myStringEncoder.getCache().setMaxCacheEntries(maxCacheEntries);
157     }
158 
159     /**
160      * Sets the value of length for a string that the stream is allowed to cache
161      * to the new value. This can be used to stop a single long string from
162      * pushing useful values out of the cache.
163      * 
164      * @param maxlength
165      *            The new value for the length for a string that the encoder is
166      *            allowed to cache.
167      * @deprecated The cache {@link StringEncoderCache} should be controlled
168      *             directory. This method will be removed after the 2.1.0
169      *             release.
170      */
171     @Deprecated
172     public void setMaxCachedStringLength(final int maxlength) {
173         myStringEncoder.getCache().setMaxCacheLength(maxlength);
174 
175     }
176 
177     /**
178      * Returns the size of the writing the {@link Document} as a BSON document.
179      * 
180      * @param document
181      *            The document to determine the size of.
182      * @return The size of the writing {@link Document} as a BSON document.
183      * @deprecated Replaced with {@link Document#size()}. This method will be
184      *             removed after the 2.2.0 release.
185      */
186     @Deprecated
187     public int sizeOf(final Document document) {
188         return (int) document.size();
189     }
190 
191     /**
192      * Returns the size of the writing the <tt>strings</tt> as a c-string.
193      * 
194      * @param strings
195      *            The 'C' strings to determine the size of.
196      * @return The size of the writing the <tt>strings</tt> as a c-string.
197      */
198     public int sizeOfCString(final String... strings) {
199         int size = 0;
200         for (final String string : strings) {
201             size += myWriteVisitor.utf8Size(string);
202         }
203         return (size + 1);
204     }
205 
206     /**
207      * Returns the size of the writing the <tt>string</tt> as a string.
208      * 
209      * @param string
210      *            The 'UTF8' string to determine the size of.
211      * @return The size of the writing the <tt>string</tt> as a string.
212      */
213     public int sizeOfString(final String string) {
214         return 4 + myWriteVisitor.utf8Size(string) + 1;
215     }
216 
217     /**
218      * Writes a single byte to the stream.
219      * 
220      * @param b
221      *            The byte to write.
222      */
223     public void writeByte(final byte b) {
224         try {
225             myOutput.write(b);
226         }
227         catch (final IOException ioe) {
228             myError = ioe;
229         }
230     }
231 
232     /**
233      * Writes a sequence of bytes to the under lying stream.
234      * 
235      * @param data
236      *            The bytes to write.
237      */
238     public void writeBytes(final byte[] data) {
239         try {
240             myOutput.write(data);
241         }
242         catch (final IOException ioe) {
243             myError = ioe;
244         }
245     }
246 
247     /**
248      * Writes a "Cstring" to the stream.
249      * 
250      * @param strings
251      *            The CString to write. The strings are concatenated into a
252      *            single CString value.
253      */
254     public void writeCString(final String... strings) {
255         for (final String string : strings) {
256             writeUtf8(string);
257         }
258         writeByte((byte) 0);
259     }
260 
261     /**
262      * Writes a BSON {@link Document} to the stream.
263      * 
264      * @param document
265      *            The {@link Document} to write.
266      * @throws IOException
267      *             On a failure writing the document.
268      */
269     public void writeDocument(final Document document) throws IOException {
270         try {
271             document.accept(myWriteVisitor);
272             if (myWriteVisitor.hasError()) {
273                 throw myWriteVisitor.getError();
274             }
275         }
276         finally {
277             myWriteVisitor.reset();
278         }
279     }
280 
281     /**
282      * Write the integer value in little-endian byte order.
283      * 
284      * @param value
285      *            The integer to write.
286      */
287     public void writeInt(final int value) {
288         try {
289             myOutput.write(value);
290             myOutput.write(value >> 8);
291             myOutput.write(value >> 16);
292             myOutput.write(value >> 24);
293         }
294         catch (final IOException ioe) {
295             myError = ioe;
296         }
297     }
298 
299     /**
300      * Write the long value in little-endian byte order.
301      * 
302      * @param value
303      *            The long to write.
304      */
305     public void writeLong(final long value) {
306         try {
307             myOutput.write((int) value);
308             myOutput.write((int) (value >> 8));
309             myOutput.write((int) (value >> 16));
310             myOutput.write((int) (value >> 24));
311             myOutput.write((int) (value >> 32));
312             myOutput.write((int) (value >> 40));
313             myOutput.write((int) (value >> 48));
314             myOutput.write((int) (value >> 56));
315         }
316         catch (final IOException ioe) {
317             myError = ioe;
318         }
319     }
320 
321     /**
322      * Writes a "string" to the stream.
323      * 
324      * @param string
325      *            The String to write.
326      */
327     public void writeString(final String string) {
328         writeInt(myStringEncoder.encodeSize(string) + 1);
329         writeUtf8(string);
330         writeByte((byte) 0);
331     }
332 
333     /**
334      * Writes a sequence of bytes to the under lying stream.
335      * 
336      * @param data
337      *            The bytes to write.
338      * @param offset
339      *            The offset into the buffer to start writing data from.
340      * @param length
341      *            The number of bytes to write.
342      */
343     protected void writeBytes(final byte[] data, final int offset,
344             final int length) {
345         try {
346             myOutput.write(data, offset, length);
347         }
348         catch (final IOException ioe) {
349             myError = ioe;
350         }
351     }
352 
353     /**
354      * Writes the string as a UTF-8 string. This method handles the
355      * "normal/easy" cases and delegates to the full character set if things get
356      * complicated.
357      * 
358      * @param string
359      *            The string to encode.
360      */
361     protected void writeUtf8(final String string) {
362         try {
363             myStringEncoder.encode(string, myOutput);
364         }
365         catch (final IOException ioe) {
366             myError = ioe;
367         }
368     }
369 
370 }