package org.bson;
import org.bson.io.BsonInput;
import org.bson.io.BsonOutput;
import org.bson.types.Decimal128;
import org.bson.types.ObjectId;
import java.util.List;
import java.util.Stack;
import static java.lang.String.format;
import static org.bson.assertions.Assertions.notNull;
public class BsonBinaryWriter extends AbstractBsonWriter {
private final BsonBinaryWriterSettings binaryWriterSettings;
private final BsonOutput bsonOutput;
private final Stack<Integer> maxDocumentSizeStack = new Stack<Integer>();
private Mark mark;
public BsonBinaryWriter(final BsonOutput bsonOutput, final FieldNameValidator validator) {
this(new BsonWriterSettings(), new BsonBinaryWriterSettings(), bsonOutput, validator);
}
public BsonBinaryWriter(final BsonOutput bsonOutput) {
this(new BsonWriterSettings(), new BsonBinaryWriterSettings(), bsonOutput);
}
public BsonBinaryWriter(final BsonWriterSettings settings, final BsonBinaryWriterSettings binaryWriterSettings,
final BsonOutput bsonOutput) {
this(settings, binaryWriterSettings, bsonOutput, new NoOpFieldNameValidator());
}
public BsonBinaryWriter(final BsonWriterSettings settings, final BsonBinaryWriterSettings binaryWriterSettings,
final BsonOutput bsonOutput, final FieldNameValidator validator) {
super(settings, validator);
this.binaryWriterSettings = binaryWriterSettings;
this.bsonOutput = bsonOutput;
maxDocumentSizeStack.push(binaryWriterSettings.getMaxDocumentSize());
}
@Override
public void close() {
super.close();
}
public BsonOutput getBsonOutput() {
return bsonOutput;
}
public BsonBinaryWriterSettings getBinaryWriterSettings() {
return binaryWriterSettings;
}
@Override
public void flush() {
}
@Override
protected Context getContext() {
return (Context) super.getContext();
}
@Override
protected void doWriteStartDocument() {
if (getState() == State.VALUE) {
bsonOutput.writeByte(BsonType.DOCUMENT.getValue());
writeCurrentName();
}
setContext(new Context(getContext(), BsonContextType.DOCUMENT, bsonOutput.getPosition()));
bsonOutput.writeInt32(0);
}
@Override
protected void doWriteEndDocument() {
bsonOutput.writeByte(0);
backpatchSize();
setContext(getContext().getParentContext());
if (getContext() != null && getContext().getContextType() == BsonContextType.JAVASCRIPT_WITH_SCOPE) {
backpatchSize();
setContext(getContext().getParentContext());
}
}
@Override
protected void doWriteStartArray() {
bsonOutput.writeByte(BsonType.ARRAY.getValue());
writeCurrentName();
setContext(new Context(getContext(), BsonContextType.ARRAY, bsonOutput.getPosition()));
bsonOutput.writeInt32(0);
}
@Override
protected void doWriteEndArray() {
bsonOutput.writeByte(0);
backpatchSize();
setContext(getContext().getParentContext());
}
@Override
protected void doWriteBinaryData(final BsonBinary value) {
bsonOutput.writeByte(BsonType.BINARY.getValue());
writeCurrentName();
int totalLen = value.getData().length;
if (value.getType() == BsonBinarySubType.OLD_BINARY.getValue()) {
totalLen += 4;
}
bsonOutput.writeInt32(totalLen);
bsonOutput.writeByte(value.getType());
if (value.getType() == BsonBinarySubType.OLD_BINARY.getValue()) {
bsonOutput.writeInt32(totalLen - 4);
}
bsonOutput.writeBytes(value.getData());
}
@Override
public void doWriteBoolean(final boolean value) {
bsonOutput.writeByte(BsonType.BOOLEAN.getValue());
writeCurrentName();
bsonOutput.writeByte(value ? 1 : 0);
}
@Override
protected void doWriteDateTime(final long value) {
bsonOutput.writeByte(BsonType.DATE_TIME.getValue());
writeCurrentName();
bsonOutput.writeInt64(value);
}
@Override
protected void doWriteDBPointer(final BsonDbPointer value) {
bsonOutput.writeByte(BsonType.DB_POINTER.getValue());
writeCurrentName();
bsonOutput.writeString(value.getNamespace());
bsonOutput.writeBytes(value.getId().toByteArray());
}
@Override
protected void doWriteDouble(final double value) {
bsonOutput.writeByte(BsonType.DOUBLE.getValue());
writeCurrentName();
bsonOutput.writeDouble(value);
}
@Override
protected void doWriteInt32(final int value) {
bsonOutput.writeByte(BsonType.INT32.getValue());
writeCurrentName();
bsonOutput.writeInt32(value);
}
@Override
protected void doWriteInt64(final long value) {
bsonOutput.writeByte(BsonType.INT64.getValue());
writeCurrentName();
bsonOutput.writeInt64(value);
}
@Override
protected void doWriteDecimal128(final Decimal128 value) {
bsonOutput.writeByte(BsonType.DECIMAL128.getValue());
writeCurrentName();
bsonOutput.writeInt64(value.getLow());
bsonOutput.writeInt64(value.getHigh());
}
@Override
protected void doWriteJavaScript(final String value) {
bsonOutput.writeByte(BsonType.JAVASCRIPT.getValue());
writeCurrentName();
bsonOutput.writeString(value);
}
@Override
protected void doWriteJavaScriptWithScope(final String value) {
bsonOutput.writeByte(BsonType.JAVASCRIPT_WITH_SCOPE.getValue());
writeCurrentName();
setContext(new Context(getContext(), BsonContextType.JAVASCRIPT_WITH_SCOPE, bsonOutput.getPosition()));
bsonOutput.writeInt32(0);
bsonOutput.writeString(value);
}
@Override
protected void doWriteMaxKey() {
bsonOutput.writeByte(BsonType.MAX_KEY.getValue());
writeCurrentName();
}
@Override
protected void doWriteMinKey() {
bsonOutput.writeByte(BsonType.MIN_KEY.getValue());
writeCurrentName();
}
@Override
public void doWriteNull() {
bsonOutput.writeByte(BsonType.NULL.getValue());
writeCurrentName();
}
@Override
public void doWriteObjectId(final ObjectId value) {
bsonOutput.writeByte(BsonType.OBJECT_ID.getValue());
writeCurrentName();
bsonOutput.writeBytes(value.toByteArray());
}
@Override
public void doWriteRegularExpression(final BsonRegularExpression value) {
bsonOutput.writeByte(BsonType.REGULAR_EXPRESSION.getValue());
writeCurrentName();
bsonOutput.writeCString(value.getPattern());
bsonOutput.writeCString(value.getOptions());
}
@Override
public void doWriteString(final String value) {
bsonOutput.writeByte(BsonType.STRING.getValue());
writeCurrentName();
bsonOutput.writeString(value);
}
@Override
public void doWriteSymbol(final String value) {
bsonOutput.writeByte(BsonType.SYMBOL.getValue());
writeCurrentName();
bsonOutput.writeString(value);
}
@Override
public void doWriteTimestamp(final BsonTimestamp value) {
bsonOutput.writeByte(BsonType.TIMESTAMP.getValue());
writeCurrentName();
bsonOutput.writeInt64(value.getValue());
}
@Override
public void doWriteUndefined() {
bsonOutput.writeByte(BsonType.UNDEFINED.getValue());
writeCurrentName();
}
@Override
public void pipe(final BsonReader reader) {
notNull("reader", reader);
pipeDocument(reader, null);
}
@Override
public void pipe(final BsonReader reader, final List<BsonElement> extraElements) {
notNull("reader", reader);
notNull("extraElements", extraElements);
pipeDocument(reader, extraElements);
}
private void pipeDocument(final BsonReader reader, final List<BsonElement> extraElements) {
if (reader instanceof BsonBinaryReader) {
BsonBinaryReader binaryReader = (BsonBinaryReader) reader;
if (getState() == State.VALUE) {
bsonOutput.writeByte(BsonType.DOCUMENT.getValue());
writeCurrentName();
}
BsonInput bsonInput = binaryReader.getBsonInput();
int size = bsonInput.readInt32();
if (size < 5) {
throw new BsonSerializationException("Document size must be at least 5");
}
int pipedDocumentStartPosition = bsonOutput.getPosition();
bsonOutput.writeInt32(size);
byte[] bytes = new byte[size - 4];
bsonInput.readBytes(bytes);
bsonOutput.writeBytes(bytes);
binaryReader.setState(AbstractBsonReader.State.TYPE);
if (extraElements != null) {
bsonOutput.truncateToPosition(bsonOutput.getPosition() - 1);
setContext(new Context(getContext(), BsonContextType.DOCUMENT, pipedDocumentStartPosition));
setState(State.NAME);
pipeExtraElements(extraElements);
bsonOutput.writeByte(0);
bsonOutput.writeInt32(pipedDocumentStartPosition, bsonOutput.getPosition() - pipedDocumentStartPosition);
setContext(getContext().getParentContext());
}
if (getContext() == null) {
setState(State.DONE);
} else {
if (getContext().getContextType() == BsonContextType.JAVASCRIPT_WITH_SCOPE) {
backpatchSize();
setContext(getContext().getParentContext());
}
setState(getNextState());
}
validateSize(bsonOutput.getPosition() - pipedDocumentStartPosition);
} else if (extraElements != null) {
super.pipe(reader, extraElements);
} else {
super.pipe(reader);
}
}
public void pushMaxDocumentSize(final int maxDocumentSize) {
maxDocumentSizeStack.push(maxDocumentSize);
}
public void popMaxDocumentSize() {
maxDocumentSizeStack.pop();
}
public void mark() {
mark = new Mark();
}
public void reset() {
if (mark == null) {
throw new IllegalStateException("Can not reset without first marking");
}
mark.reset();
mark = null;
}
private void writeCurrentName() {
if (getContext().getContextType() == BsonContextType.ARRAY) {
bsonOutput.writeCString(Integer.toString(getContext().index++));
} else {
bsonOutput.writeCString(getName());
}
}
private void backpatchSize() {
int size = bsonOutput.getPosition() - getContext().startPosition;
validateSize(size);
bsonOutput.writeInt32(bsonOutput.getPosition() - size, size);
}
private void validateSize(final int size) {
if (size > maxDocumentSizeStack.peek()) {
throw new BsonMaximumSizeExceededException(format("Document size of %d is larger than maximum of %d.", size,
maxDocumentSizeStack.peek()));
}
}
protected class Context extends AbstractBsonWriter.Context {
private final int startPosition;
private int index;
public Context(final Context parentContext, final BsonContextType contextType, final int startPosition) {
super(parentContext, contextType);
this.startPosition = startPosition;
}
public Context(final Context from) {
super(from);
startPosition = from.startPosition;
index = from.index;
}
@Override
public Context getParentContext() {
return (Context) super.getParentContext();
}
@Override
public Context copy() {
return new Context(this);
}
}
protected class Mark extends AbstractBsonWriter.Mark {
private final int position;
protected Mark() {
this.position = bsonOutput.getPosition();
}
@Override
protected void reset() {
super.reset();
bsonOutput.truncateToPosition(mark.position);
}
}
}