org.bson.AbstractBsonWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongo-java-driver Show documentation
Show all versions of mongo-java-driver Show documentation
The MongoDB Java Driver uber-artifact, containing mongodb-driver, mongodb-driver-core, and bson
The newest version!
/*
* Copyright (c) 2008-2014 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bson;
import org.bson.types.ObjectId;
import java.io.Closeable;
import java.util.Arrays;
import java.util.Stack;
import static java.lang.String.format;
/**
* Represents a BSON writer for some external format (see subclasses).
*
* @since 3.0
*/
public abstract class AbstractBsonWriter implements BsonWriter, Closeable {
private final BsonWriterSettings settings;
private final Stack fieldNameValidatorStack = new Stack();
private State state;
private Context context;
private int serializationDepth;
private boolean closed;
/**
* Initializes a new instance of the BsonWriter class.
*
* @param settings The writer settings.
*/
protected AbstractBsonWriter(final BsonWriterSettings settings) {
this(settings, new NoOpFieldNameValidator());
}
/**
* Initializes a new instance of the BsonWriter class.
*
* @param settings The writer settings.
* @param validator the field name validator
*/
protected AbstractBsonWriter(final BsonWriterSettings settings, final FieldNameValidator validator) {
if (validator == null) {
throw new IllegalArgumentException("Validator can not be null");
}
this.settings = settings;
fieldNameValidatorStack.push(validator);
state = State.INITIAL;
}
/**
* The name of the field being written.
*
* @return the name of the field
*/
protected String getName() {
return context.name;
}
/**
* Returns whether this writer has been closed.
*
* @return true if the {@link #close()} method has been called.
*/
protected boolean isClosed() {
return closed;
}
/**
* Sets the current state of the writer. The current state determines what sort of actions are valid for this writer at this time.
*
* @param state the state to set this writer to.
*/
protected void setState(final State state) {
this.state = state;
}
/**
* Gets the current state of this writer. The current state determines what sort of actions are valid for this writer at this time.
*
* @return the current state of the writer.
*/
protected State getState() {
return state;
}
/**
* Get the context, which will indicate which state the writer is in, for example which part of a document it's currently writing.
*
* @return the current context.
*/
protected Context getContext() {
return context;
}
/**
* Set the context, which will indicate which state the writer is in, for example which part of a document it's currently writing.
*
* @param context the new context for this writer
*/
protected void setContext(final Context context) {
this.context = context;
}
/**
* Handles the logic to start writing a document
*/
protected abstract void doWriteStartDocument();
/**
* Handles the logic of writing the end of a document
*/
protected abstract void doWriteEndDocument();
/**
* Handles the logic to start writing an array
*/
protected abstract void doWriteStartArray();
/**
* Handles the logic of writing the end of an array
*/
protected abstract void doWriteEndArray();
/**
* Handles the logic of writing a {@code BsonBinary} value
*
* @param value the {@code BsonBinary} value to write
*/
protected abstract void doWriteBinaryData(BsonBinary value);
/**
* Handles the logic of writing a boolean value
*
* @param value the {@code boolean} value to write
*/
protected abstract void doWriteBoolean(boolean value);
/**
* Handles the logic of writing a date time value
*
* @param value the {@code long} value to write
*/
protected abstract void doWriteDateTime(long value);
/**
* Handles the logic of writing a DbPointer value
*
* @param value the {@code BsonDbPointer} value to write
*/
protected abstract void doWriteDBPointer(BsonDbPointer value);
/**
* Handles the logic of writing a Double value
*
* @param value the {@code double} value to write
*/
protected abstract void doWriteDouble(double value);
/**
* Handles the logic of writing an int32 value
*
* @param value the {@code int} value to write
*/
protected abstract void doWriteInt32(int value);
/**
* Handles the logic of writing an int64 value
*
* @param value the {@code long} value to write
*/
protected abstract void doWriteInt64(long value);
/**
* Handles the logic of writing a JavaScript function
*
* @param value the {@code String} value to write
*/
protected abstract void doWriteJavaScript(String value);
/**
* Handles the logic of writing a scoped JavaScript function
*
* @param value the {@code boolean} value to write
*/
protected abstract void doWriteJavaScriptWithScope(String value);
/**
* Handles the logic of writing a Max key
*/
protected abstract void doWriteMaxKey();
/**
* Handles the logic of writing a Min key
*/
protected abstract void doWriteMinKey();
/**
* Handles the logic of writing a Null value
*/
protected abstract void doWriteNull();
/**
* Handles the logic of writing an ObjectId
*
* @param value the {@code ObjectId} value to write
*/
protected abstract void doWriteObjectId(ObjectId value);
/**
* Handles the logic of writing a regular expression
*
* @param value the {@code BsonRegularExpression} value to write
*/
protected abstract void doWriteRegularExpression(BsonRegularExpression value);
/**
* Handles the logic of writing a String
*
* @param value the {@code String} value to write
*/
protected abstract void doWriteString(String value);
/**
* Handles the logic of writing a Symbol
*
* @param value the {@code boolean} value to write
*/
protected abstract void doWriteSymbol(String value);
/**
* Handles the logic of writing a timestamp
*
* @param value the {@code BsonTimestamp} value to write
*/
protected abstract void doWriteTimestamp(BsonTimestamp value);
/**
* Handles the logic of writing an Undefined value
*/
protected abstract void doWriteUndefined();
@Override
public void writeStartDocument(final String name) {
writeName(name);
writeStartDocument();
}
@Override
public void writeStartDocument() {
checkPreconditions("writeStartDocument", State.INITIAL, State.VALUE, State.SCOPE_DOCUMENT, State.DONE);
if (context != null && context.name != null) {
fieldNameValidatorStack.push(fieldNameValidatorStack.peek().getValidatorForField(getName()));
}
serializationDepth++;
if (serializationDepth > settings.getMaxSerializationDepth()) {
throw new BsonSerializationException("Maximum serialization depth exceeded (does the object being "
+ "serialized have a circular reference?).");
}
doWriteStartDocument();
setState(State.NAME);
}
@Override
public void writeEndDocument() {
checkPreconditions("writeEndDocument", State.NAME);
BsonContextType contextType = getContext().getContextType();
if (contextType != BsonContextType.DOCUMENT && contextType != BsonContextType.SCOPE_DOCUMENT) {
throwInvalidContextType("WriteEndDocument", contextType, BsonContextType.DOCUMENT, BsonContextType.SCOPE_DOCUMENT);
}
if (context.getParentContext() != null && context.getParentContext().name != null) {
fieldNameValidatorStack.pop();
}
serializationDepth--;
doWriteEndDocument();
if (getContext() == null || getContext().getContextType() == BsonContextType.TOP_LEVEL) {
setState(State.DONE);
} else {
setState(getNextState());
}
}
@Override
public void writeStartArray(final String name) {
writeName(name);
writeStartArray();
}
@Override
public void writeStartArray() {
checkPreconditions("writeStartArray", State.VALUE);
if (context != null && context.name != null) {
fieldNameValidatorStack.push(fieldNameValidatorStack.peek().getValidatorForField(getName()));
}
serializationDepth++;
if (serializationDepth > settings.getMaxSerializationDepth()) {
throw new BsonSerializationException("Maximum serialization depth exceeded (does the object being "
+ "serialized have a circular reference?).");
}
doWriteStartArray();
setState(State.VALUE);
}
@Override
public void writeEndArray() {
checkPreconditions("writeEndArray", State.VALUE);
if (getContext().getContextType() != BsonContextType.ARRAY) {
throwInvalidContextType("WriteEndArray", getContext().getContextType(), BsonContextType.ARRAY);
}
if (context.getParentContext() != null && context.getParentContext().name != null) {
fieldNameValidatorStack.pop();
}
serializationDepth--;
doWriteEndArray();
setState(getNextState());
}
@Override
public void writeBinaryData(final String name, final BsonBinary binary) {
writeName(name);
writeBinaryData(binary);
}
@Override
public void writeBinaryData(final BsonBinary binary) {
checkPreconditions("writeBinaryData", State.VALUE, State.INITIAL);
doWriteBinaryData(binary);
setState(getNextState());
}
@Override
public void writeBoolean(final String name, final boolean value) {
writeName(name);
writeBoolean(value);
}
@Override
public void writeBoolean(final boolean value) {
checkPreconditions("writeBoolean", State.VALUE, State.INITIAL);
doWriteBoolean(value);
setState(getNextState());
}
@Override
public void writeDateTime(final String name, final long value) {
writeName(name);
writeDateTime(value);
}
@Override
public void writeDateTime(final long value) {
checkPreconditions("writeDateTime", State.VALUE, State.INITIAL);
doWriteDateTime(value);
setState(getNextState());
}
@Override
public void writeDBPointer(final String name, final BsonDbPointer value) {
writeName(name);
writeDBPointer(value);
}
@Override
public void writeDBPointer(final BsonDbPointer value) {
checkPreconditions("writeDBPointer", State.VALUE, State.INITIAL);
doWriteDBPointer(value);
setState(getNextState());
}
@Override
public void writeDouble(final String name, final double value) {
writeName(name);
writeDouble(value);
}
@Override
public void writeDouble(final double value) {
checkPreconditions("writeDBPointer", State.VALUE, State.INITIAL);
doWriteDouble(value);
setState(getNextState());
}
@Override
public void writeInt32(final String name, final int value) {
writeName(name);
writeInt32(value);
}
@Override
public void writeInt32(final int value) {
checkPreconditions("writeInt32", State.VALUE);
doWriteInt32(value);
setState(getNextState());
}
@Override
public void writeInt64(final String name, final long value) {
writeName(name);
writeInt64(value);
}
@Override
public void writeInt64(final long value) {
checkPreconditions("writeInt64", State.VALUE);
doWriteInt64(value);
setState(getNextState());
}
@Override
public void writeJavaScript(final String name, final String code) {
writeName(name);
writeJavaScript(code);
}
@Override
public void writeJavaScript(final String code) {
checkPreconditions("writeJavaScript", State.VALUE);
doWriteJavaScript(code);
setState(getNextState());
}
@Override
public void writeJavaScriptWithScope(final String name, final String code) {
writeName(name);
writeJavaScriptWithScope(code);
}
@Override
public void writeJavaScriptWithScope(final String code) {
checkPreconditions("writeJavaScriptWithScope", State.VALUE);
doWriteJavaScriptWithScope(code);
setState(State.SCOPE_DOCUMENT);
}
@Override
public void writeMaxKey(final String name) {
writeName(name);
writeMaxKey();
}
@Override
public void writeMaxKey() {
checkPreconditions("writeMaxKey", State.VALUE);
doWriteMaxKey();
setState(getNextState());
}
@Override
public void writeMinKey(final String name) {
writeName(name);
writeMinKey();
}
@Override
public void writeMinKey() {
checkPreconditions("writeMinKey", State.VALUE);
doWriteMinKey();
setState(getNextState());
}
@Override
public void writeName(final String name) {
if (state != State.NAME) {
throwInvalidState("WriteName", State.NAME);
}
if (name == null) {
throw new IllegalArgumentException("BSON field name can not be null");
}
if (!fieldNameValidatorStack.peek().validate(name)) {
throw new IllegalArgumentException(format("Invalid BSON field name %s", name));
}
context.name = name;
state = State.VALUE;
}
@Override
public void writeNull(final String name) {
writeName(name);
writeNull();
}
@Override
public void writeNull() {
checkPreconditions("writeNull", State.VALUE);
doWriteNull();
setState(getNextState());
}
@Override
public void writeObjectId(final String name, final ObjectId objectId) {
writeName(name);
writeObjectId(objectId);
}
@Override
public void writeObjectId(final ObjectId objectId) {
checkPreconditions("writeObjectId", State.VALUE);
doWriteObjectId(objectId);
setState(getNextState());
}
@Override
public void writeRegularExpression(final String name, final BsonRegularExpression regularExpression) {
writeName(name);
writeRegularExpression(regularExpression);
}
@Override
public void writeRegularExpression(final BsonRegularExpression regularExpression) {
checkPreconditions("writeRegularExpression", State.VALUE);
doWriteRegularExpression(regularExpression);
setState(getNextState());
}
@Override
public void writeString(final String name, final String value) {
writeName(name);
writeString(value);
}
@Override
public void writeString(final String value) {
checkPreconditions("writeString", State.VALUE);
doWriteString(value);
setState(getNextState());
}
@Override
public void writeSymbol(final String name, final String value) {
writeName(name);
writeSymbol(value);
}
@Override
public void writeSymbol(final String value) {
checkPreconditions("writeSymbol", State.VALUE);
doWriteSymbol(value);
setState(getNextState());
}
@Override
public void writeTimestamp(final String name, final BsonTimestamp value) {
writeName(name);
writeTimestamp(value);
}
@Override
public void writeTimestamp(final BsonTimestamp value) {
checkPreconditions("writeTimestamp", State.VALUE);
doWriteTimestamp(value);
setState(getNextState());
}
@Override
public void writeUndefined(final String name) {
writeName(name);
writeUndefined();
}
@Override
public void writeUndefined() {
checkPreconditions("writeUndefined", State.VALUE);
doWriteUndefined();
setState(getNextState());
}
/**
* Returns the next valid state for this writer. For example, transitions from {@link State#VALUE} to {@link State#NAME} once a value
* is written.
*
* @return the next {@code State}
*/
protected State getNextState() {
if (getContext().getContextType() == BsonContextType.ARRAY) {
return State.VALUE;
} else {
return State.NAME;
}
}
/**
* Checks if this writer's current state is in the list of given states.
*
* @param validStates an array of {@code State}s to compare this writer's state to.
* @return true if this writer's state is in the given list.
*/
protected boolean checkState(final State[] validStates) {
for (final State cur : validStates) {
if (cur == getState()) {
return true;
}
}
return false;
}
/**
* Checks the writer is in the correct state. If the writer's current state is in the list of given states, this method will complete
* without exception. Throws an {@link java.lang.IllegalStateException} if the writer is closed. Throws BsonInvalidOperationException
* if the method is trying to do something that is not permitted in the current state.
*
* @param methodName the name of the method being performed that checks are being performed for
* @param validStates the list of valid states for this operation
* @see #throwInvalidState(String, org.bson.AbstractBsonWriter.State...)
*/
protected void checkPreconditions(final String methodName, final State... validStates) {
if (isClosed()) {
throw new IllegalStateException("BsonWriter is closed");
}
if (!checkState(validStates)) {
throwInvalidState(methodName, validStates);
}
}
/**
* Throws an InvalidOperationException when the method called is not valid for the current ContextType.
*
* @param methodName The name of the method.
* @param actualContextType The actual ContextType.
* @param validContextTypes The valid ContextTypes.
* @throws BsonInvalidOperationException when the method called is not valid for the current ContextType.
*/
protected void throwInvalidContextType(final String methodName, final BsonContextType actualContextType,
final BsonContextType... validContextTypes) {
String validContextTypesString = StringUtils.join(" or ", Arrays.asList(validContextTypes));
throw new BsonInvalidOperationException(format("%s can only be called when ContextType is %s, "
+ "not when ContextType is %s.",
methodName, validContextTypesString, actualContextType));
}
/**
* Throws a {@link BsonInvalidOperationException} when the method called is not valid for the current state.
*
* @param methodName The name of the method.
* @param validStates The valid states.
* @throws BsonInvalidOperationException when the method called is not valid for the current state.
*/
protected void throwInvalidState(final String methodName, final State... validStates) {
if (state == State.INITIAL || state == State.SCOPE_DOCUMENT || state == State.DONE) {
if (!methodName.startsWith("end") && !methodName.equals("writeName")) { // NOPMD
//NOPMD collapsing these if statements will not aid readability
String typeName = methodName.substring(5);
if (typeName.startsWith("start")) {
typeName = typeName.substring(5);
}
String article = "A";
if (Arrays.asList('A', 'E', 'I', 'O', 'U').contains(typeName.charAt(0))) {
article = "An";
}
throw new BsonInvalidOperationException(format("%s %s value cannot be written to the root level of a BSON document.",
article, typeName));
}
}
String validStatesString = StringUtils.join(" or ", Arrays.asList(validStates));
throw new BsonInvalidOperationException(format("%s can only be called when State is %s, not when State is %s",
methodName, validStatesString, state));
}
@Override
public void close() {
closed = true;
}
@Override
public void pipe(final BsonReader reader) {
pipeDocument(reader);
}
private void pipeDocument(final BsonReader reader) {
reader.readStartDocument();
writeStartDocument();
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
writeName(reader.readName());
pipeValue(reader);
}
reader.readEndDocument();
writeEndDocument();
}
private void pipeArray(final BsonReader reader) {
reader.readStartArray();
writeStartArray();
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
pipeValue(reader);
}
reader.readEndArray();
writeEndArray();
}
private void pipeJavascriptWithScope(final BsonReader reader) {
writeJavaScriptWithScope(reader.readJavaScriptWithScope());
pipeDocument(reader);
}
private void pipeValue(final BsonReader reader) {
switch (reader.getCurrentBsonType()) {
case DOCUMENT:
pipeDocument(reader);
break;
case ARRAY:
pipeArray(reader);
break;
case DOUBLE:
writeDouble(reader.readDouble());
break;
case STRING:
writeString(reader.readString());
break;
case BINARY:
writeBinaryData(reader.readBinaryData());
break;
case UNDEFINED:
reader.readUndefined();
writeUndefined();
break;
case OBJECT_ID:
writeObjectId(reader.readObjectId());
break;
case BOOLEAN:
writeBoolean(reader.readBoolean());
break;
case DATE_TIME:
writeDateTime(reader.readDateTime());
break;
case NULL:
reader.readNull();
writeNull();
break;
case REGULAR_EXPRESSION:
writeRegularExpression(reader.readRegularExpression());
break;
case JAVASCRIPT:
writeJavaScript(reader.readJavaScript());
break;
case SYMBOL:
writeSymbol(reader.readSymbol());
break;
case JAVASCRIPT_WITH_SCOPE:
pipeJavascriptWithScope(reader);
break;
case INT32:
writeInt32(reader.readInt32());
break;
case TIMESTAMP:
writeTimestamp(reader.readTimestamp());
break;
case INT64:
writeInt64(reader.readInt64());
break;
case MIN_KEY:
reader.readMinKey();
writeMinKey();
break;
case DB_POINTER:
writeDBPointer(reader.readDBPointer());
break;
case MAX_KEY:
reader.readMaxKey();
writeMaxKey();
break;
default:
throw new IllegalArgumentException("unhandled BSON type: " + reader.getCurrentBsonType());
}
}
/**
* The state of a writer. Indicates where in a document the writer is.
*/
public enum State {
/**
* The initial state.
*/
INITIAL,
/**
* The writer is positioned to write a name.
*/
NAME,
/**
* The writer is positioned to write a value.
*/
VALUE,
/**
* The writer is positioned to write a scope document (call WriteStartDocument to start writing the scope document).
*/
SCOPE_DOCUMENT,
/**
* The writer is done.
*/
DONE,
/**
* The writer is closed.
*/
CLOSED
}
/**
* The context for the writer. Records the parent context, creating a bread crumb trail to trace back up to the root context of the
* reader. Also records the {@link org.bson.BsonContextType}, indicating whether the writer is reading a document, array, or other
* complex sub-structure.
*/
public class Context {
private final Context parentContext;
private final BsonContextType contextType;
private String name;
/**
* Creates a new instance, copying values from an existing context.
*
* @param from the {@code Context} to copy values from
*/
public Context(final Context from) {
parentContext = from.parentContext;
contextType = from.contextType;
}
/**
* Creates a new instance.
*
* @param parentContext the context of the parent node
* @param contextType the context type.
*/
public Context(final Context parentContext, final BsonContextType contextType) {
this.parentContext = parentContext;
this.contextType = contextType;
}
/**
* Returns the parent context. Allows users of this context object to transition to this parent context.
*
* @return the context that came before this one
*/
public Context getParentContext() {
return parentContext;
}
/**
* Gets the current context type.
*
* @return the current context type.
*/
public BsonContextType getContextType() {
return contextType;
}
/**
* Copies the values from this {@code Context} into a new instance.
*
* @return the new instance with the same values as this context.
*/
public Context copy() {
return new Context(this);
}
}
/**
* Capture the current state of this writer - its {@link org.bson.AbstractBsonWriter.Context}, {@link
* org.bson.AbstractBsonWriter.State}, field name and depth.
*/
protected class Mark {
private final Context markedContext;
private final State markedState;
private final String currentName;
private final int serializationDepth;
/**
* Creates a new snapshopt of the current state.
*/
protected Mark() {
this.markedContext = AbstractBsonWriter.this.context.copy();
this.markedState = AbstractBsonWriter.this.state;
this.currentName = AbstractBsonWriter.this.context.name;
this.serializationDepth = AbstractBsonWriter.this.serializationDepth;
}
/**
* Resets the {@code AbstractBsonWriter} instance that contains this {@code Mark} to the state the writer was in when the Mark was
* created.
*/
protected void reset() {
setContext(markedContext);
setState(markedState);
AbstractBsonWriter.this.context.name = currentName;
AbstractBsonWriter.this.serializationDepth = serializationDepth;
}
}
}