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.Json.ReservedField.BASE_64; 019import static org.modeshape.schematic.document.Json.ReservedField.BINARY_TYPE; 020import static org.modeshape.schematic.document.Json.ReservedField.CODE; 021import static org.modeshape.schematic.document.Json.ReservedField.DATE; 022import static org.modeshape.schematic.document.Json.ReservedField.INCREMENT; 023import static org.modeshape.schematic.document.Json.ReservedField.OBJECT_ID; 024import static org.modeshape.schematic.document.Json.ReservedField.REGEX_OPTIONS; 025import static org.modeshape.schematic.document.Json.ReservedField.REGEX_PATTERN; 026import static org.modeshape.schematic.document.Json.ReservedField.SCOPE; 027import static org.modeshape.schematic.document.Json.ReservedField.TIMESTAMP; 028import static org.modeshape.schematic.document.Json.ReservedField.UUID; 029import java.io.BufferedWriter; 030import java.io.IOException; 031import java.io.OutputStream; 032import java.io.OutputStreamWriter; 033import java.io.Writer; 034import java.lang.reflect.Array; 035import java.text.CharacterIterator; 036import java.text.StringCharacterIterator; 037import java.util.Date; 038import java.util.Iterator; 039import java.util.Map; 040import java.util.UUID; 041import java.util.regex.Pattern; 042import org.modeshape.schematic.document.Binary; 043import org.modeshape.schematic.document.Bson; 044import org.modeshape.schematic.document.Code; 045import org.modeshape.schematic.document.CodeWithScope; 046import org.modeshape.schematic.document.Document; 047import org.modeshape.schematic.document.Document.Field; 048import org.modeshape.schematic.document.Json; 049import org.modeshape.schematic.document.MaxKey; 050import org.modeshape.schematic.document.MinKey; 051import org.modeshape.schematic.document.ObjectId; 052import org.modeshape.schematic.document.Symbol; 053import org.modeshape.schematic.document.Timestamp; 054 055public class CompactJsonWriter implements JsonWriter { 056 057 @Override 058 public void write( Object object, 059 OutputStream stream ) throws IOException { 060 Writer writer = new BufferedWriter(new OutputStreamWriter(stream, Json.UTF8)); 061 write(object, writer); 062 writer.flush(); 063 } 064 065 @Override 066 public void write( Object object, 067 Writer writer ) throws IOException { 068 if (object == null) { 069 writeNull(writer); 070 } else if (object instanceof String) { 071 write((String)object, writer); 072 } else if (object instanceof Boolean) { 073 write(((Boolean)object).booleanValue(), writer); 074 } else if (object instanceof Integer) { 075 write(((Integer)object).intValue(), writer); 076 } else if (object instanceof Long) { 077 write(((Long)object).longValue(), writer); 078 } else if (object instanceof Float) { 079 write(((Float)object).floatValue(), writer); 080 } else if (object instanceof Double) { 081 write(((Double)object).doubleValue(), writer); 082 } else if (object.getClass().isArray()) { 083 writeArray(object, writer); 084 } else if (object instanceof ArrayEditor) { 085 write((Iterable<?>)((ArrayEditor)object).unwrap(), writer); 086 } else if (object instanceof DocumentEditor) { 087 write(((DocumentEditor)object).unwrap(), writer); 088 } else if (object instanceof Iterable) { // must check before 'BsonObject' because of inheritance 089 write((Iterable<?>)object, writer); 090 } else if (object instanceof Map) { 091 write((Document)object, writer); 092 } else if (object instanceof Binary) { 093 write((Binary)object, writer); 094 } else if (object instanceof Symbol) { 095 write((Symbol)object, writer); 096 } else if (object instanceof Pattern) { 097 write((Pattern)object, writer); 098 } else if (object instanceof Date) { 099 write((Date)object, writer); 100 } else if (object instanceof UUID) { 101 write((UUID)object, writer); 102 } else if (object instanceof CodeWithScope) { // must check before 'Code' because of inheritance 103 write((CodeWithScope)object, writer); 104 } else if (object instanceof Code) { 105 write((Code)object, writer); 106 } else if (object instanceof Timestamp) { 107 write((Timestamp)object, writer); 108 } else if (object instanceof Field) { 109 write((Field)object, writer); 110 } else if (object instanceof ObjectId) { 111 write((ObjectId)object, writer); 112 } else if (object instanceof MaxKey) { 113 write((MaxKey)object, writer); 114 } else if (object instanceof MinKey) { 115 write((MinKey)object, writer); 116 } 117 } 118 119 @Override 120 public void write( Object object, 121 StringBuilder writer ) { 122 try { 123 write(object, new StringBuilderWriter(writer)); 124 } catch (IOException e) { 125 throw new AssertionError(e); 126 } 127 } 128 129 @Override 130 public String write( Object object ) { 131 StringBuilder sb = new StringBuilder(); 132 write(object, sb); 133 return sb.toString(); 134 } 135 136 protected void write( boolean value, 137 Writer writer ) throws IOException { 138 writer.append(Boolean.toString(value)); 139 } 140 141 protected void write( int value, 142 Writer writer ) throws IOException { 143 writer.append(Integer.toString(value)); 144 } 145 146 protected void write( long value, 147 Writer writer ) throws IOException { 148 writer.append(Long.toString(value)); 149 } 150 151 protected void write( float value, 152 Writer writer ) throws IOException { 153 writer.append(Float.toString(value)); 154 } 155 156 protected void write( double value, 157 Writer writer ) throws IOException { 158 writer.append(Double.toString(value)); 159 } 160 161 protected void writeNull( Writer writer ) throws IOException { 162 writer.append("null"); 163 } 164 165 protected void write( String value, 166 Writer writer ) throws IOException { 167 // We need to escape certain characters found within the string ... 168 writer.append('"'); 169 CharacterIterator iter = new StringCharacterIterator(value); 170 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { 171 switch (c) { 172 case '"': 173 case '\'': 174 // Escape the single- or double-quote characters .. 175 writer.append('\\').append(c); 176 break; 177 case '/': 178 // JSON allows you to escape forward slashes (more for JavaScript), but we won't ... 179 // writer.append('\\'); 180 writer.append(c); 181 break; 182 case '\\': 183 // These characters need to be escaped. 184 writer.append('\\').append(c); 185 break; 186 case '\b': 187 // These characters need to be escaped. 188 writer.append("\\b"); 189 break; 190 case '\f': 191 // These characters need to be escaped. 192 writer.append("\\f"); 193 break; 194 case '\n': 195 // These characters need to be escaped. 196 writer.append("\\n"); 197 break; 198 case '\r': 199 // These characters need to be escaped. 200 writer.append("\\r"); 201 break; 202 case '\t': 203 // These characters need to be escaped. 204 writer.append("\\t"); 205 break; 206 default: 207 if (c < ' ') { 208 // Characters less than the first printable character ' ' are non-printable 209 // and must be escaped as unicode ... 210 String t = "000" + Integer.toHexString(c); 211 writer.append("\\u").append(t.substring(t.length() - 4)); 212 } else { 213 // Otherwise it's a normal character 214 writer.append(c); 215 } 216 break; 217 } 218 } 219 writer.append('"'); 220 } 221 222 protected void write( Symbol value, 223 Writer writer ) throws IOException { 224 writer.append('"').append(value.getSymbol()).append('"'); 225 } 226 227 protected void write( ObjectId value, 228 Writer writer ) throws IOException { 229 write(new BasicDocument(OBJECT_ID, value.getBytesInBase16()), writer); 230 } 231 232 protected void write( Date value, 233 Writer writer ) throws IOException { 234 String isoDate = Bson.getDateFormatter().format(value); 235 write(new BasicDocument(DATE, isoDate), writer); 236 } 237 238 protected void write( Timestamp value, 239 Writer writer ) throws IOException { 240 write(new BasicDocument(TIMESTAMP, value.getTime(), INCREMENT, value.getInc()), writer); 241 } 242 243 protected void write( MinKey value, 244 Writer writer ) throws IOException { 245 write("MinKey", writer); 246 } 247 248 protected void write( MaxKey value, 249 Writer writer ) throws IOException { 250 write("MaxKey", writer); 251 } 252 253 protected void write( Pattern value, 254 Writer writer ) throws IOException { 255 BasicDocument obj = new BasicDocument(REGEX_PATTERN, value.pattern()); 256 String options = BsonUtils.regexFlagsFor(value); 257 if (options.length() != 0) { 258 obj.put(REGEX_OPTIONS, options); 259 } 260 write(obj, writer); 261 } 262 263 protected void write( Binary value, 264 Writer writer ) throws IOException { 265 int type = value.getType() - 0; 266 String base64 = value.getBytesInBase64(); 267 write(new BasicDocument(BINARY_TYPE, type, BASE_64, base64), writer); 268 } 269 270 protected void write( UUID value, 271 Writer writer ) throws IOException { 272 write(new BasicDocument(UUID, value.toString()), writer); 273 } 274 275 protected void write( Code value, 276 Writer writer ) throws IOException { 277 write(new BasicDocument(CODE, value.getCode()), writer); 278 } 279 280 protected void write( CodeWithScope value, 281 Writer writer ) throws IOException { 282 write(new BasicDocument(CODE, value.getCode(), SCOPE, value.getScope()), writer); 283 } 284 285 protected void write( Field field, 286 Writer writer ) throws IOException { 287 writer.append('"').append(field.getName()).append('"').append(' ').append(':').append(' '); 288 write(field.getValue(), writer); 289 } 290 291 protected void write( Document bson, 292 Writer writer ) throws IOException { 293 writer.append('{').append(' '); 294 Iterator<Field> iter = bson.fields().iterator(); 295 if (iter.hasNext()) { 296 write(iter.next(), writer); 297 writer.append(' '); 298 while (iter.hasNext()) { 299 writer.append(',').append(' '); 300 write(iter.next(), writer); 301 writer.append(' '); 302 } 303 } 304 writer.append('}'); 305 } 306 307 protected void write( Iterable<?> arrayValue, 308 Writer writer ) throws IOException { 309 writer.append('['); 310 Iterator<?> iter = arrayValue.iterator(); 311 if (iter.hasNext()) { 312 writer.append(' '); 313 write(iter.next(), writer); 314 while (iter.hasNext()) { 315 writer.append(' ').append(',').append(' '); 316 write(iter.next(), writer); 317 } 318 } 319 writer.append(' ').append(']'); 320 } 321 322 protected void writeArray( Object array, 323 Writer writer ) throws IOException { 324 // Could transform this into a List, but this is more efficient ... 325 writer.append('['); 326 for (int i = 0, len = Array.getLength(array); i < len; i++) { 327 if (i > 0) { 328 writer.append(' ').append(',').append(' '); 329 } 330 write(Array.get(array, i), writer); 331 } 332 writer.append(' ').append(']'); 333 } 334 335 protected static final class StringBuilderWriter extends Writer { 336 337 private final StringBuilder builder; 338 339 public StringBuilderWriter( final StringBuilder builder ) { 340 this.builder = builder; 341 } 342 343 @Override 344 public void write( final char[] cbuf, 345 final int off, 346 final int len ) { 347 builder.append(cbuf, off, len); 348 } 349 350 @Override 351 public Writer append( char c ) { 352 builder.append(c); 353 return this; 354 } 355 356 @Override 357 public Writer append( CharSequence csq ) { 358 builder.append(csq); 359 return this; 360 } 361 362 @Override 363 public Writer append( CharSequence csq, 364 int start, 365 int end ) { 366 builder.append(csq, start, end); 367 return this; 368 } 369 370 @Override 371 public void write( final int c ) { 372 builder.append(c); 373 } 374 375 @Override 376 public void write( final char[] cbuf ) { 377 builder.append(cbuf); 378 } 379 380 @Override 381 public void write( final String str ) { 382 builder.append(str); 383 } 384 385 @Override 386 public void write( final String str, 387 final int off, 388 final int len ) { 389 builder.append(str, off, len); 390 } 391 392 @Override 393 public void flush() { 394 } 395 396 @Override 397 public void close() { 398 } 399 } 400 401}