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 }