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}