summaryrefslogtreecommitdiff
path: root/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/core/src/main/java/com/google/protobuf/LazyFieldLite.java')
-rw-r--r--java/core/src/main/java/com/google/protobuf/LazyFieldLite.java458
1 files changed, 458 insertions, 0 deletions
diff --git a/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java
new file mode 100644
index 00000000..2febaace
--- /dev/null
+++ b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java
@@ -0,0 +1,458 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+
+/**
+ * LazyFieldLite encapsulates the logic of lazily parsing message fields. It stores
+ * the message in a ByteString initially and then parses it on-demand.
+ *
+ * LazyFieldLite is thread-compatible: concurrent reads are safe once the proto that this
+ * LazyFieldLite is a part of is no longer being mutated by its Builder. However, explicit
+ * synchronization is needed under read/write situations.
+ *
+ * When a LazyFieldLite is used in the context of a MessageLite object, its behavior is considered
+ * to be immutable and none of the setter methods in its API are expected to be invoked. All of the
+ * getters are expected to be thread-safe. When used in the context of a MessageLite.Builder,
+ * setters can be invoked, but there is no guarantee of thread safety.
+ *
+ * TODO(yatin,dweis): Consider splitting this class's functionality and put the mutable methods
+ * into a separate builder class to allow us to give stronger compile-time guarantees.
+ *
+ * This class is internal implementation detail of the protobuf library, so you don't need to use it
+ * directly.
+ *
+ * @author xiangl@google.com (Xiang Li)
+ */
+public class LazyFieldLite {
+ private static final ExtensionRegistryLite EMPTY_REGISTRY =
+ ExtensionRegistryLite.getEmptyRegistry();
+
+ /**
+ * The value associated with the LazyFieldLite object is stored in one or more of the following
+ * three fields (delayedBytes, value, memoizedBytes). They should together be interpreted as
+ * follows.
+ * 1) delayedBytes can be non-null, while value and memoizedBytes is null. The object will be in
+ * this state while the value for the object has not yet been parsed.
+ * 2) Both delayedBytes and value are non-null. The object transitions to this state as soon as
+ * some caller needs to access the value (by invoking getValue()).
+ * 3) memoizedBytes is merely an optimization for calls to LazyFieldLite.toByteString() to avoid
+ * recomputing the ByteString representation on each call. Instead, when the value is parsed
+ * from delayedBytes, we will also assign the contents of delayedBytes to memoizedBytes (since
+ * that is the ByteString representation of value).
+ * 4) Finally, if the LazyFieldLite was created directly with a parsed MessageLite value, then
+ * delayedBytes will be null, and memoizedBytes will be initialized only upon the first call to
+ * LazyFieldLite.toByteString().
+ *
+ * Given the above conditions, any caller that needs a serialized representation of this object
+ * must first check if the memoizedBytes or delayedBytes ByteString is non-null and use it
+ * directly; if both of those are null, it can look at the parsed value field. Similarly, any
+ * caller that needs a parsed value must first check if the value field is already non-null, if
+ * not it must parse the value from delayedBytes.
+ */
+
+ /**
+ * A delayed-parsed version of the contents of this field. When this field is non-null, then the
+ * "value" field is allowed to be null until the time that the value needs to be read.
+ *
+ * When delayedBytes is non-null then {@code extensionRegistry} is required to also be non-null.
+ * {@code value} and {@code memoizedBytes} will be initialized lazily.
+ */
+ private ByteString delayedBytes;
+
+ /**
+ * An {@code ExtensionRegistryLite} for parsing bytes. It is non-null on a best-effort basis. It
+ * is only guaranteed to be non-null if this message was initialized using bytes and an
+ * {@code ExtensionRegistry}. If it directly had a value set then it will be null, unless it has
+ * been merged with another {@code LazyFieldLite} that had an {@code ExtensionRegistry}.
+ */
+ private ExtensionRegistryLite extensionRegistry;
+
+ /**
+ * The parsed value. When this is null and a caller needs access to the MessageLite value, then
+ * {@code delayedBytes} will be parsed lazily at that time.
+ */
+ protected volatile MessageLite value;
+
+ /**
+ * The memoized bytes for {@code value}. This is an optimization for the toByteString() method to
+ * not have to recompute its return-value on each invocation.
+ * TODO(yatin): Figure out whether this optimization is actually necessary.
+ */
+ private volatile ByteString memoizedBytes;
+
+ /**
+ * Constructs a LazyFieldLite with bytes that will be parsed lazily.
+ */
+ public LazyFieldLite(ExtensionRegistryLite extensionRegistry, ByteString bytes) {
+ checkArguments(extensionRegistry, bytes);
+ this.extensionRegistry = extensionRegistry;
+ this.delayedBytes = bytes;
+ }
+
+ /**
+ * Constructs a LazyFieldLite with no contents, and no ability to parse extensions.
+ */
+ public LazyFieldLite() {
+ }
+
+ /**
+ * Constructs a LazyFieldLite instance with a value. The LazyFieldLite may not be able to parse
+ * the extensions in the value as it has no ExtensionRegistry.
+ */
+ public static LazyFieldLite fromValue(MessageLite value) {
+ LazyFieldLite lf = new LazyFieldLite();
+ lf.setValue(value);
+ return lf;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof LazyFieldLite)) {
+ return false;
+ }
+
+ LazyFieldLite other = (LazyFieldLite) o;
+
+ // Lazy fields do not work well with equals... If both are delayedBytes, we do not have a
+ // mechanism to deserialize them so we rely on bytes equality. Otherwise we coerce into an
+ // actual message (if necessary) and call equals on the message itself. This implies that two
+ // messages can by unequal but then be turned equal simply be invoking a getter on a lazy field.
+ MessageLite value1 = value;
+ MessageLite value2 = other.value;
+ if (value1 == null && value2 == null) {
+ return toByteString().equals(other.toByteString());
+ } else if (value1 != null && value2 != null) {
+ return value1.equals(value2);
+ } else if (value1 != null) {
+ return value1.equals(other.getValue(value1.getDefaultInstanceForType()));
+ } else {
+ return getValue(value2.getDefaultInstanceForType()).equals(value2);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ // We can't provide a memoizable hash code for lazy fields. The byte strings may have different
+ // hash codes but evaluate to equivalent messages. And we have no facility for constructing
+ // a message here if we were not already holding a value.
+ return 1;
+ }
+
+ /**
+ * Determines whether this LazyFieldLite instance represents the default instance of this type.
+ */
+ public boolean containsDefaultInstance() {
+ return memoizedBytes == ByteString.EMPTY
+ || value == null && (delayedBytes == null || delayedBytes == ByteString.EMPTY);
+ }
+
+ /**
+ * Clears the value state of this instance.
+ *
+ * <p>LazyField is not thread-safe for write access. Synchronizations are needed
+ * under read/write situations.
+ */
+ public void clear() {
+ // Don't clear the ExtensionRegistry. It might prove useful later on when merging in another
+ // value, but there is no guarantee that it will contain all extensions that were directly set
+ // on the values that need to be merged.
+ delayedBytes = null;
+ value = null;
+ memoizedBytes = null;
+ }
+
+ /**
+ * Overrides the contents of this LazyField.
+ *
+ * <p>LazyField is not thread-safe for write access. Synchronizations are needed
+ * under read/write situations.
+ */
+ public void set(LazyFieldLite other) {
+ this.delayedBytes = other.delayedBytes;
+ this.value = other.value;
+ this.memoizedBytes = other.memoizedBytes;
+ // If the other LazyFieldLite was created by directly setting the value rather than first by
+ // parsing, then it will not have an extensionRegistry. In this case we hold on to the existing
+ // extensionRegistry, which has no guarantees that it has all the extensions that will be
+ // directly set on the value.
+ if (other.extensionRegistry != null) {
+ this.extensionRegistry = other.extensionRegistry;
+ }
+ }
+
+ /**
+ * Returns message instance. It may do some thread-safe delayed parsing of bytes.
+ *
+ * @param defaultInstance its message's default instance. It's also used to get parser for the
+ * message type.
+ */
+ public MessageLite getValue(MessageLite defaultInstance) {
+ ensureInitialized(defaultInstance);
+ return value;
+ }
+
+ /**
+ * Sets the value of the instance and returns the old value without delay parsing anything.
+ *
+ * <p>LazyField is not thread-safe for write access. Synchronizations are needed
+ * under read/write situations.
+ */
+ public MessageLite setValue(MessageLite value) {
+ MessageLite originalValue = this.value;
+ this.delayedBytes = null;
+ this.memoizedBytes = null;
+ this.value = value;
+ return originalValue;
+ }
+
+ /**
+ * Merges another instance's contents. In some cases may drop some extensions if both fields
+ * contain data. If the other field has an {@code ExtensionRegistry} but this does not, then this
+ * field will copy over that {@code ExtensionRegistry}.
+ *
+ * <p>LazyField is not thread-safe for write access. Synchronizations are needed
+ * under read/write situations.
+ */
+ public void merge(LazyFieldLite other) {
+ if (other.containsDefaultInstance()) {
+ return;
+ }
+
+ if (this.containsDefaultInstance()) {
+ set(other);
+ return;
+ }
+
+ // If the other field has an extension registry but this does not, copy over the other extension
+ // registry.
+ if (this.extensionRegistry == null) {
+ this.extensionRegistry = other.extensionRegistry;
+ }
+
+ // In the case that both of them are not parsed we simply concatenate the bytes to save time. In
+ // the (probably rare) case that they have different extension registries there is a chance that
+ // some of the extensions may be dropped, but the tradeoff of making this operation fast seems
+ // to outway the benefits of combining the extension registries, which is not normally done for
+ // lite protos anyways.
+ if (this.delayedBytes != null && other.delayedBytes != null) {
+ this.delayedBytes = this.delayedBytes.concat(other.delayedBytes);
+ return;
+ }
+
+ // At least one is parsed and both contain data. We won't drop any extensions here directly, but
+ // in the case that the extension registries are not the same then we might in the future if we
+ // need to serialze and parse a message again.
+ if (this.value == null && other.value != null) {
+ setValue(mergeValueAndBytes(other.value, this.delayedBytes, this.extensionRegistry));
+ return;
+ } else if (this.value != null && other.value == null) {
+ setValue(mergeValueAndBytes(this.value, other.delayedBytes, other.extensionRegistry));
+ return;
+ }
+
+ // At this point we have two fully parsed messages. We can't merge directly from one to the
+ // other because only generated builder code contains methods to mergeFrom another parsed
+ // message. We have to serialize one instance and then merge the bytes into the other. This may
+ // drop extensions from one of the messages if one of the values had an extension set on it
+ // directly.
+ //
+ // To mitigate this we prefer serializing a message that has an extension registry, and
+ // therefore a chance that all extensions set on it are in that registry.
+ //
+ // NOTE: The check for other.extensionRegistry not being null must come first because at this
+ // point in time if other.extensionRegistry is not null then this.extensionRegistry will not be
+ // null either.
+ if (other.extensionRegistry != null) {
+ setValue(mergeValueAndBytes(this.value, other.toByteString(), other.extensionRegistry));
+ return;
+ } else if (this.extensionRegistry != null) {
+ setValue(mergeValueAndBytes(other.value, this.toByteString(), this.extensionRegistry));
+ return;
+ } else {
+ // All extensions from the other message will be dropped because we have no registry.
+ setValue(mergeValueAndBytes(this.value, other.toByteString(), EMPTY_REGISTRY));
+ return;
+ }
+ }
+
+ /**
+ * Merges another instance's contents from a stream.
+ *
+ * <p>LazyField is not thread-safe for write access. Synchronizations are needed
+ * under read/write situations.
+ */
+ public void mergeFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry)
+ throws IOException {
+ if (this.containsDefaultInstance()) {
+ setByteString(input.readBytes(), extensionRegistry);
+ return;
+ }
+
+ // If the other field has an extension registry but this does not, copy over the other extension
+ // registry.
+ if (this.extensionRegistry == null) {
+ this.extensionRegistry = extensionRegistry;
+ }
+
+ // In the case that both of them are not parsed we simply concatenate the bytes to save time. In
+ // the (probably rare) case that they have different extension registries there is a chance that
+ // some of the extensions may be dropped, but the tradeoff of making this operation fast seems
+ // to outway the benefits of combining the extension registries, which is not normally done for
+ // lite protos anyways.
+ if (this.delayedBytes != null) {
+ setByteString(this.delayedBytes.concat(input.readBytes()), this.extensionRegistry);
+ return;
+ }
+
+ // We are parsed and both contain data. We won't drop any extensions here directly, but in the
+ // case that the extension registries are not the same then we might in the future if we
+ // need to serialize and parse a message again.
+ try {
+ setValue(value.toBuilder().mergeFrom(input, extensionRegistry).build());
+ } catch (InvalidProtocolBufferException e) {
+ // Nothing is logged and no exceptions are thrown. Clients will be unaware that a proto
+ // was invalid.
+ }
+ }
+
+ private static MessageLite mergeValueAndBytes(
+ MessageLite value, ByteString otherBytes, ExtensionRegistryLite extensionRegistry) {
+ try {
+ return value.toBuilder().mergeFrom(otherBytes, extensionRegistry).build();
+ } catch (InvalidProtocolBufferException e) {
+ // Nothing is logged and no exceptions are thrown. Clients will be unaware that a proto
+ // was invalid.
+ return value;
+ }
+ }
+
+ /**
+ * Sets this field with bytes to delay-parse.
+ */
+ public void setByteString(ByteString bytes, ExtensionRegistryLite extensionRegistry) {
+ checkArguments(extensionRegistry, bytes);
+ this.delayedBytes = bytes;
+ this.extensionRegistry = extensionRegistry;
+ this.value = null;
+ this.memoizedBytes = null;
+ }
+
+ /**
+ * Due to the optional field can be duplicated at the end of serialized
+ * bytes, which will make the serialized size changed after LazyField
+ * parsed. Be careful when using this method.
+ */
+ public int getSerializedSize() {
+ // We *must* return delayed bytes size if it was ever set because the dependent messages may
+ // have memoized serialized size based off of it.
+ if (memoizedBytes != null) {
+ return memoizedBytes.size();
+ } else if (delayedBytes != null) {
+ return delayedBytes.size();
+ } else if (value != null) {
+ return value.getSerializedSize();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Returns a BytesString for this field in a thread-safe way.
+ */
+ public ByteString toByteString() {
+ if (memoizedBytes != null) {
+ return memoizedBytes;
+ }
+ // We *must* return delayed bytes if it was set because the dependent messages may have
+ // memoized serialized size based off of it.
+ if (delayedBytes != null) {
+ return delayedBytes;
+ }
+ synchronized (this) {
+ if (memoizedBytes != null) {
+ return memoizedBytes;
+ }
+ if (value == null) {
+ memoizedBytes = ByteString.EMPTY;
+ } else {
+ memoizedBytes = value.toByteString();
+ }
+ return memoizedBytes;
+ }
+ }
+
+ /**
+ * Might lazily parse the bytes that were previously passed in. Is thread-safe.
+ */
+ protected void ensureInitialized(MessageLite defaultInstance) {
+ if (value != null) {
+ return;
+ }
+ synchronized (this) {
+ if (value != null) {
+ return;
+ }
+ try {
+ if (delayedBytes != null) {
+ // The extensionRegistry shouldn't be null here since we have delayedBytes.
+ MessageLite parsedValue = defaultInstance.getParserForType()
+ .parseFrom(delayedBytes, extensionRegistry);
+ this.value = parsedValue;
+ this.memoizedBytes = delayedBytes;
+ } else {
+ this.value = defaultInstance;
+ this.memoizedBytes = ByteString.EMPTY;
+ }
+ } catch (InvalidProtocolBufferException e) {
+ // Nothing is logged and no exceptions are thrown. Clients will be unaware that this proto
+ // was invalid.
+ this.value = defaultInstance;
+ this.memoizedBytes = ByteString.EMPTY;
+ }
+ }
+ }
+
+
+ private static void checkArguments(ExtensionRegistryLite extensionRegistry, ByteString bytes) {
+ if (extensionRegistry == null) {
+ throw new NullPointerException("found null ExtensionRegistry");
+ }
+ if (bytes == null) {
+ throw new NullPointerException("found null ByteString");
+ }
+ }
+}