/* * The MIT License * * Copyright 2015 Tim Boudreau. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.mastfrog.acteur.mongo.async; import com.mastfrog.util.Checks; import io.netty.buffer.ByteBuf; import io.netty.util.CharsetUtil; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetEncoder; import javax.xml.bind.DatatypeConverter; import org.bson.AbstractBsonWriter; import org.bson.BSONException; import org.bson.BsonBinary; import org.bson.BsonContextType; import org.bson.BsonDbPointer; import org.bson.BsonRegularExpression; import org.bson.BsonTimestamp; import org.bson.json.JsonWriterSettings; import org.bson.types.ObjectId; import org.openide.util.Exceptions; /** * * @author Tim Boudreau */ class PlainJsonWriter extends AbstractBsonWriter { private final CharsetEncoder enc = CharsetUtil.UTF_8.newEncoder(); private final ByteBuf buf; private final byte[] TRUE = "true".getBytes(CharsetUtil.US_ASCII); private final byte[] FALSE = "false".getBytes(CharsetUtil.US_ASCII); private final JsonWriterSettings settings; public PlainJsonWriter(ByteBuf buf, JsonWriterSettings settings) { super(settings); this.settings = settings; this.buf = buf; setContext(new Context(null, BsonContextType.TOP_LEVEL, "")); } protected Context getContext() { return (Context) super.getContext(); } @Override public void flush() { // do nothing } private void writeNameHelper(final String name) { switch (getContext().getContextType()) { case ARRAY: // don't write Array element names in JSON if (getContext().hasElements) { rawWriteChar(','); } break; case DOCUMENT: case SCOPE_DOCUMENT: Checks.notNull("name", name); if (getContext().hasElements) { rawWriteChar(','); } if (settings.isIndent()) { rawWriteString(settings.getNewLineCharacters()); rawWriteString(getContext().indentation); } else { rawWriteChar(' '); } rawWriteChar('"'); _writeString(name); rawWriteChar('"'); rawWriteString(" : "); break; case TOP_LEVEL: break; default: throw new BSONException("Invalid contextType."); } getContext().hasElements = true; } private void _writeString(String s) { for (char c : s.toCharArray()) { _writeChar(c); } } private void writeChar(char c) { _writeChar(c); } private void _writeChar(char c) { switch (c) { case '\\': buf.writeByte('\\').writeByte('\\'); break; case '"': buf.writeByte('\\').writeByte('"'); break; default: if (c <= 20 || c >= 128) { buf.writeByte('\\').writeByte('u'); intString((int) c); } else { // buf.writeChar((int) c); rawWriteChar(c); } } } private final CharBuffer tmp = CharBuffer.allocate(1); private void rawWriteChar(char c) { if (c < 256) { buf.writeByte((byte) c); } else { tmp.put(0, c); tmp.rewind(); tmp.limit(1); try { buf.writeBytes(enc.encode(tmp)); } catch (CharacterCodingException ex) { throw new IllegalArgumentException(ex); } } } private void rawWriteString(String s) { buf.writeBytes(s.getBytes(CharsetUtil.US_ASCII)); } private void intString(int val) { try { char[] result = new char[]{0, 0, 0, 0}; char[] s = Integer.toString(val).toCharArray(); for (int i = 0; i < s.length; i++) { int index = (4 - s.length) + i; result[index] = s[i]; } ByteBuffer buf = CharsetUtil.UTF_8.newEncoder().encode(CharBuffer.wrap(result)); this.buf.writeBytes(buf); } catch (CharacterCodingException ex) { Exceptions.printStackTrace(ex); } } @Override protected void doWriteStartDocument() { if (getState() == State.VALUE || getState() == State.SCOPE_DOCUMENT) { writeNameHelper(getName()); } rawWriteChar('{'); BsonContextType contextType = (getState() == State.SCOPE_DOCUMENT) ? BsonContextType.SCOPE_DOCUMENT : BsonContextType.DOCUMENT; setContext(new Context(getContext(), contextType, settings.getIndentCharacters())); } @Override protected void doWriteEndDocument() { if (settings.isIndent() && getContext().hasElements) { _writeString(settings.getNewLineCharacters()); if (getContext().getParentContext() != null) { _writeString(getContext().getParentContext().indentation); } rawWriteChar('}'); } else { rawWriteChar('}'); } if (getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) { setContext(getContext().getParentContext()); writeEndDocument(); } else { setContext(getContext().getParentContext()); } } @Override protected void doWriteStartArray() { writeNameHelper(getName()); rawWriteChar('['); setContext(new Context(getContext(), BsonContextType.ARRAY, settings.getIndentCharacters())); } @Override protected void doWriteEndArray() { rawWriteChar(']'); setContext(getContext().getParentContext()); } @Override protected void doWriteBinaryData(BsonBinary bb) { writeNameHelper(getName()); rawWriteChar('"'); _writeString(DatatypeConverter.printBase64Binary(bb.getData())); rawWriteChar('"'); } @Override public void writeName(String name) { super.writeName(name); } @Override protected void doWriteBoolean(boolean bln) { writeNameHelper(getName()); buf.writeBytes(bln ? TRUE : FALSE); } @Override protected void doWriteDateTime(long l) { writeNameHelper(getName()); _writeString(Long.toString(l)); } @Override protected void doWriteDBPointer(BsonDbPointer value) { writeStartDocument(); writeString("$ref", value.getNamespace()); writeObjectId("$id", value.getId()); writeEndDocument(); } @Override protected void doWriteDouble(double d) { writeNameHelper(getName()); _writeString(Double.toString(d)); setState(getNextState()); } @Override protected void doWriteInt32(int i) { writeNameHelper(getName()); _writeString(Integer.toString(i)); } @Override protected void doWriteInt64(long l) { writeNameHelper(getName()); _writeString(Long.toString(l)); } @Override protected void doWriteJavaScript(String code) { writeStartDocument(); writeString("$code", code); writeEndDocument(); } @Override protected void doWriteJavaScriptWithScope(String code) { writeStartDocument(); writeString("$code", code); writeName("$scope"); } @Override protected void doWriteMaxKey() { // do nothing } @Override protected void doWriteMinKey() { // do nothing } @Override protected void doWriteNull() { writeNameHelper(getName()); _writeString("null"); } @Override protected void doWriteObjectId(ObjectId oi) { writeNameHelper(getName()); setState(getNextState()); rawWriteChar('"'); rawWriteString(oi.toString()); rawWriteChar('"'); } @Override protected void doWriteRegularExpression(BsonRegularExpression bre) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override protected void doWriteString(String string) { writeNameHelper(getName()); setState(getNextState()); rawWriteChar('"'); _writeString(string); rawWriteChar('"'); } @Override protected void doWriteSymbol(String string) { _writeString(string); } @Override protected void doWriteTimestamp(BsonTimestamp bt) { writeNameHelper(getName()); _writeString(Long.toString(bt.getTime())); } @Override protected void doWriteUndefined() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } class Context extends AbstractBsonWriter.Context { private final String indentation; private boolean hasElements; /** * Creates a new context. * * @param parentContext the parent context that can be used for * going back up to the parent level * @param contextType the type of this context * @param indentChars the String to use for indentation at this * level. */ public Context(final Context parentContext, final BsonContextType contextType, final String indentChars) { super(parentContext, contextType); this.indentation = (parentContext == null) ? indentChars : parentContext.indentation + indentChars; } @Override public Context getParentContext() { return (Context) super.getParentContext(); } } }