/* * Copyright (c) 2008-2015 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.codecs.Encoder; import org.bson.codecs.EncoderContext; import org.bson.codecs.configuration.CodecRegistry; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.util.Collection; import java.util.Map; import java.util.Set; /** * A {@code BsonDocument} that begins its life as a document of any type and an {@code Encoder} for that document, which lets an instance of * any class with an Encoder be treated as a BsonDocument. If any methods are called which required access to the individual elements of the * document, then, on demand, the document will be unwrapped into a BsonDocument using a {@code BsonDocumentWriter} and the {@code Encoder}. * But if all that is done with this document is to encode it, then the {@code Encoder} will be used to do that. * * @param <T> the type of the document that is wrapped * @see org.bson.BsonDocumentWriter * @since 3.0 */ public final class BsonDocumentWrapper<T> extends BsonDocument { private static final long serialVersionUID = 1L; private final transient T wrappedDocument; private final transient Encoder<T> encoder; private BsonDocument unwrapped; /** * A helper to convert an document of type Object to a BsonDocument * * <p>If not already a BsonDocument it looks up the documents' class in the codecRegistry and wraps it into a BsonDocumentWrapper</p> * * @param document the document to convert * @param codecRegistry the codecRegistry that can be used in the conversion of the Object * @return a BsonDocument */ @SuppressWarnings({"rawtypes", "unchecked"}) public static BsonDocument asBsonDocument(final Object document, final CodecRegistry codecRegistry) { if (document == null) { return null; } if (document instanceof BsonDocument) { return (BsonDocument) document; } else { return new BsonDocumentWrapper(document, codecRegistry.get(document.getClass())); } } /** * Construct a new instance with the given document and encoder for the document. * * @param wrappedDocument the wrapped document * @param encoder the encoder for the wrapped document */ public BsonDocumentWrapper(final T wrappedDocument, final Encoder<T> encoder) { if (wrappedDocument == null) { throw new IllegalArgumentException("Document can not be null"); } this.wrappedDocument = wrappedDocument; this.encoder = encoder; } /** * Get the wrapped document. * * @return the wrapped document */ public T getWrappedDocument() { return wrappedDocument; } /** * Get the encoder to use for the wrapped document. * * @return the encoder */ public Encoder<T> getEncoder() { return encoder; } /** * Determine whether the document has been unwrapped already. * * @return true if the wrapped document has been unwrapped already */ public boolean isUnwrapped() { return unwrapped != null; } @Override public int size() { return getUnwrapped().size(); } @Override public boolean isEmpty() { return getUnwrapped().isEmpty(); } @Override public boolean containsKey(final Object key) { return getUnwrapped().containsKey(key); } @Override public boolean containsValue(final Object value) { return getUnwrapped().containsValue(value); } @Override public BsonValue get(final Object key) { return getUnwrapped().get(key); } @Override public BsonValue put(final String key, final BsonValue value) { return getUnwrapped().put(key, value); } @Override public BsonValue remove(final Object key) { return getUnwrapped().remove(key); } @Override public void putAll(final Map<? extends String, ? extends BsonValue> m) { super.putAll(m); } @Override public void clear() { super.clear(); } @Override public Set<String> keySet() { return getUnwrapped().keySet(); } @Override public Collection<BsonValue> values() { return getUnwrapped().values(); } @Override public Set<Entry<String, BsonValue>> entrySet() { return getUnwrapped().entrySet(); } @Override public boolean equals(final Object o) { return getUnwrapped().equals(o); } @Override public int hashCode() { return getUnwrapped().hashCode(); } @Override public String toString() { return getUnwrapped().toString(); } @Override public BsonDocument clone() { return getUnwrapped().clone(); } private BsonDocument getUnwrapped() { if (encoder == null) { throw new BsonInvalidOperationException("Can not unwrap a BsonDocumentWrapper with no Encoder"); } if (unwrapped == null) { BsonDocument unwrapped = new BsonDocument(); BsonWriter writer = new BsonDocumentWriter(unwrapped); encoder.encode(writer, wrappedDocument, EncoderContext.builder().build()); this.unwrapped = unwrapped; } return unwrapped; } // see https://docs.oracle.com/javase/6/docs/platform/serialization/spec/output.html private Object writeReplace() { return getUnwrapped(); } // see https://docs.oracle.com/javase/6/docs/platform/serialization/spec/input.html private void readObject(final ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); } }