diff options
Diffstat (limited to 'java/core/src/main/java/com/google')
65 files changed, 18774 insertions, 4205 deletions
diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessage.java b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java index 9f418f2b..fc3c2a5d 100644 --- a/java/core/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java @@ -32,9 +32,9 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FileDescriptor.Syntax; import com.google.protobuf.Descriptors.OneofDescriptor; import com.google.protobuf.Internal.EnumLite; - import java.io.IOException; import java.io.InputStream; import java.util.Arrays; @@ -50,17 +50,51 @@ import java.util.Map; * * @author kenton@google.com Kenton Varda */ -public abstract class AbstractMessage extends AbstractMessageLite - implements Message { +public abstract class AbstractMessage + // TODO(dweis): Update GeneratedMessage to parameterize with MessageType and BuilderType. + extends AbstractMessageLite + implements Message { + + @Override public boolean isInitialized() { return MessageReflection.isInitialized(this); } + /** + * Interface for the parent of a Builder that allows the builder to + * communicate invalidations back to the parent for use when using nested + * builders. + */ + protected interface BuilderParent { + + /** + * A builder becomes dirty whenever a field is modified -- including fields + * in nested builders -- and becomes clean when build() is called. Thus, + * when a builder becomes dirty, all its parents become dirty as well, and + * when it becomes clean, all its children become clean. The dirtiness + * state is used to invalidate certain cached values. + * <br> + * To this end, a builder calls markDirty() on its parent whenever it + * transitions from clean to dirty. The parent must propagate this call to + * its own parent, unless it was already dirty, in which case the + * grandparent must necessarily already be dirty as well. The parent can + * only transition back to "clean" after calling build() on all children. + */ + void markDirty(); + } + /** Create a nested builder. */ + protected Message.Builder newBuilderForType(BuilderParent parent) { + throw new UnsupportedOperationException("Nested builder is not supported for this type."); + } + + + @Override public List<String> findInitializationErrors() { return MessageReflection.findMissingFields(this); } + @Override public String getInitializationErrorString() { return MessageReflection.delimitWithCommas(findInitializationErrors()); } @@ -83,12 +117,24 @@ public abstract class AbstractMessage extends AbstractMessageLite return TextFormat.printToString(this); } + @Override public void writeTo(final CodedOutputStream output) throws IOException { MessageReflection.writeMessageTo(this, getAllFields(), output, false); } protected int memoizedSize = -1; + @Override + int getMemoizedSerializedSize() { + return memoizedSize; + } + + @Override + void setMemoizedSerializedSize(int size) { + memoizedSize = size; + } + + @Override public int getSerializedSize() { int size = memoizedSize; if (size != -1) { @@ -127,7 +173,7 @@ public abstract class AbstractMessage extends AbstractMessageLite } return hash; } - + private static ByteString toByteString(Object value) { if (value instanceof byte[]) { return ByteString.copyFrom((byte[]) value); @@ -135,7 +181,7 @@ public abstract class AbstractMessage extends AbstractMessageLite return (ByteString) value; } } - + /** * Compares two bytes fields. The parameters must be either a byte array or a * ByteString object. They can be of different type though. @@ -146,7 +192,7 @@ public abstract class AbstractMessage extends AbstractMessageLite } return toByteString(a).equals(toByteString(b)); } - + /** * Converts a list of MapEntry messages into a Map used for equals() and * hashCode(). @@ -177,7 +223,7 @@ public abstract class AbstractMessage extends AbstractMessageLite } return result; } - + /** * Compares two map fields. The parameters must be a list of MapEntry * messages. @@ -188,13 +234,13 @@ public abstract class AbstractMessage extends AbstractMessageLite Map mb = convertMapEntryListToMap((List) b); return MapFieldLite.equals(ma, mb); } - + /** * Compares two set of fields. * This method is used to implement {@link AbstractMessage#equals(Object)} * and {@link AbstractMutableMessage#equals(Object)}. It takes special care * of bytes fields because immutable messages and mutable messages use - * different Java type to reprensent a bytes field and this method should be + * different Java type to represent a bytes field and this method should be * able to compare immutable messages, mutable messages and also an immutable * message to a mutable message. */ @@ -240,7 +286,7 @@ public abstract class AbstractMessage extends AbstractMessageLite } return true; } - + /** * Calculates the hash code of a map field. {@code value} must be a list of * MapEntry messages. @@ -288,12 +334,16 @@ public abstract class AbstractMessage extends AbstractMessageLite * other methods. */ @SuppressWarnings("unchecked") - public static abstract class Builder<BuilderType extends Builder> - extends AbstractMessageLite.Builder<BuilderType> + public static abstract class Builder<BuilderType extends Builder<BuilderType>> + extends AbstractMessageLite.Builder implements Message.Builder { // The compiler produces an error if this is not declared explicitly. + // Method isn't abstract to bypass Java 1.6 compiler issue: + // http://bugs.java.com/view_bug.do?bug_id=6908259 @Override - public abstract BuilderType clone(); + public BuilderType clone() { + throw new UnsupportedOperationException("clone() should be implemented in subclasses."); + } /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ @Override @@ -314,6 +364,7 @@ public abstract class AbstractMessage extends AbstractMessageLite throw new UnsupportedOperationException("clearOneof() is not implemented."); } + @Override public BuilderType clear() { for (final Map.Entry<FieldDescriptor, Object> entry : getAllFields().entrySet()) { @@ -322,15 +373,27 @@ public abstract class AbstractMessage extends AbstractMessageLite return (BuilderType) this; } + @Override public List<String> findInitializationErrors() { return MessageReflection.findMissingFields(this); } + @Override public String getInitializationErrorString() { return MessageReflection.delimitWithCommas(findInitializationErrors()); } + @Override + protected BuilderType internalMergeFrom(AbstractMessageLite other) { + return mergeFrom((Message) other); + } + + @Override public BuilderType mergeFrom(final Message other) { + return mergeFrom(other, other.getAllFields()); + } + + BuilderType mergeFrom(final Message other, Map<FieldDescriptor, Object> allFields) { if (other.getDescriptorForType() != getDescriptorForType()) { throw new IllegalArgumentException( "mergeFrom(Message) can only merge messages of the same type."); @@ -345,8 +408,7 @@ public abstract class AbstractMessage extends AbstractMessageLite // TODO(kenton): Provide a function somewhere called makeDeepCopy() // which allows people to make secure deep copies of messages. - for (final Map.Entry<FieldDescriptor, Object> entry : - other.getAllFields().entrySet()) { + for (final Map.Entry<FieldDescriptor, Object> entry : allFields.entrySet()) { final FieldDescriptor field = entry.getKey(); if (field.isRepeated()) { for (final Object element : (List)entry.getValue()) { @@ -384,8 +446,12 @@ public abstract class AbstractMessage extends AbstractMessageLite final CodedInputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { + boolean discardUnknown = + getDescriptorForType().getFile().getSyntax() == Syntax.PROTO3 + ? input.shouldDiscardUnknownFieldsProto3() + : input.shouldDiscardUnknownFields(); final UnknownFieldSet.Builder unknownFields = - UnknownFieldSet.newBuilder(getUnknownFields()); + discardUnknown ? null : UnknownFieldSet.newBuilder(getUnknownFields()); while (true) { final int tag = input.readTag(); if (tag == 0) { @@ -403,10 +469,13 @@ public abstract class AbstractMessage extends AbstractMessageLite break; } } - setUnknownFields(unknownFields.build()); + if (unknownFields != null) { + setUnknownFields(unknownFields.build()); + } return (BuilderType) this; } + @Override public BuilderType mergeUnknownFields(final UnknownFieldSet unknownFields) { setUnknownFields( UnknownFieldSet.newBuilder(getUnknownFields()) @@ -415,17 +484,19 @@ public abstract class AbstractMessage extends AbstractMessageLite return (BuilderType) this; } + @Override public Message.Builder getFieldBuilder(final FieldDescriptor field) { throw new UnsupportedOperationException( "getFieldBuilder() called on an unsupported message type."); } - public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, - int index) { + @Override + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, int index) { throw new UnsupportedOperationException( "getRepeatedFieldBuilder() called on an unsupported message type."); } + @Override public String toString() { return TextFormat.printToString(this); } @@ -440,6 +511,31 @@ public abstract class AbstractMessage extends AbstractMessageLite MessageReflection.findMissingFields(message)); } + /** + * Used to support nested builders and called to mark this builder as clean. + * Clean builders will propagate the {@link BuilderParent#markDirty()} event + * to their parent builders, while dirty builders will not, as their parents + * should be dirty already. + * + * NOTE: Implementations that don't support nested builders don't need to + * override this method. + */ + void markClean() { + throw new IllegalStateException("Should be overridden by subclasses."); + } + + /** + * Used to support nested builders and called when this nested builder is + * no longer used by its parent builder and should release the reference + * to its parent builder. + * + * NOTE: Implementations that don't support nested builders don't need to + * override this method. + */ + void dispose() { + throw new IllegalStateException("Should be overridden by subclasses."); + } + // =============================================================== // The following definitions seem to be required in order to make javac // not produce weird errors like: @@ -462,7 +558,7 @@ public abstract class AbstractMessage extends AbstractMessageLite @Override public BuilderType mergeFrom(final ByteString data) throws InvalidProtocolBufferException { - return super.mergeFrom(data); + return (BuilderType) super.mergeFrom(data); } @Override @@ -470,20 +566,20 @@ public abstract class AbstractMessage extends AbstractMessageLite final ByteString data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - return super.mergeFrom(data, extensionRegistry); + return (BuilderType) super.mergeFrom(data, extensionRegistry); } @Override public BuilderType mergeFrom(final byte[] data) throws InvalidProtocolBufferException { - return super.mergeFrom(data); + return (BuilderType) super.mergeFrom(data); } @Override public BuilderType mergeFrom( final byte[] data, final int off, final int len) throws InvalidProtocolBufferException { - return super.mergeFrom(data, off, len); + return (BuilderType) super.mergeFrom(data, off, len); } @Override @@ -491,7 +587,7 @@ public abstract class AbstractMessage extends AbstractMessageLite final byte[] data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - return super.mergeFrom(data, extensionRegistry); + return (BuilderType) super.mergeFrom(data, extensionRegistry); } @Override @@ -499,13 +595,13 @@ public abstract class AbstractMessage extends AbstractMessageLite final byte[] data, final int off, final int len, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - return super.mergeFrom(data, off, len, extensionRegistry); + return (BuilderType) super.mergeFrom(data, off, len, extensionRegistry); } @Override public BuilderType mergeFrom(final InputStream input) throws IOException { - return super.mergeFrom(input); + return (BuilderType) super.mergeFrom(input); } @Override @@ -513,7 +609,7 @@ public abstract class AbstractMessage extends AbstractMessageLite final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { - return super.mergeFrom(input, extensionRegistry); + return (BuilderType) super.mergeFrom(input, extensionRegistry); } @Override @@ -530,4 +626,44 @@ public abstract class AbstractMessage extends AbstractMessageLite return super.mergeDelimitedFrom(input, extensionRegistry); } } + + /** + * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashLong(long n) { + return (int) (n ^ (n >>> 32)); + } + // + /** + * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashBoolean(boolean b) { + return b ? 1231 : 1237; + } + // + /** + * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashEnum(EnumLite e) { + return e.getNumber(); + } + // + /** + * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashEnumList(List<? extends EnumLite> list) { + int hash = 1; + for (EnumLite e : list) { + hash = 31 * hash + hashEnum(e); + } + return hash; + } } diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java index 12384983..b22bbaab 100644 --- a/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java @@ -30,11 +30,15 @@ package com.google.protobuf; +import static com.google.protobuf.Internal.checkNotNull; + import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; /** * A partial implementation of the {@link MessageLite} interface which @@ -43,9 +47,12 @@ import java.util.Collection; * * @author kenton@google.com Kenton Varda */ -public abstract class AbstractMessageLite implements MessageLite { +public abstract class AbstractMessageLite< + MessageType extends AbstractMessageLite<MessageType, BuilderType>, + BuilderType extends AbstractMessageLite.Builder<MessageType, BuilderType>> + implements MessageLite { protected int memoizedHashCode = 0; - + @Override public ByteString toByteString() { try { final ByteString.CodedBuilder out = @@ -53,12 +60,11 @@ public abstract class AbstractMessageLite implements MessageLite { writeTo(out.getCodedOutput()); return out.build(); } catch (IOException e) { - throw new RuntimeException( - "Serializing to a ByteString threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getSerializingExceptionMessage("ByteString"), e); } } + @Override public byte[] toByteArray() { try { final byte[] result = new byte[getSerializedSize()]; @@ -67,12 +73,11 @@ public abstract class AbstractMessageLite implements MessageLite { output.checkNoSpaceLeft(); return result; } catch (IOException e) { - throw new RuntimeException( - "Serializing to a byte array threw an IOException " + - "(should never happen).", e); + throw new RuntimeException(getSerializingExceptionMessage("byte array"), e); } } + @Override public void writeTo(final OutputStream output) throws IOException { final int bufferSize = CodedOutputStream.computePreferredBufferSize(getSerializedSize()); @@ -82,6 +87,7 @@ public abstract class AbstractMessageLite implements MessageLite { codedOutput.flush(); } + @Override public void writeDelimitedTo(final OutputStream output) throws IOException { final int serialized = getSerializedSize(); final int bufferSize = CodedOutputStream.computePreferredBufferSize( @@ -93,6 +99,16 @@ public abstract class AbstractMessageLite implements MessageLite { codedOutput.flush(); } + // We'd like these to be abstract but some folks are extending this class directly. They shouldn't + // be doing that and they should feel bad. + int getMemoizedSerializedSize() { + throw new UnsupportedOperationException(); + } + + void setMemoizedSerializedSize(int size) { + throw new UnsupportedOperationException(); + } + /** * Package private helper method for AbstractParser to create @@ -102,6 +118,11 @@ public abstract class AbstractMessageLite implements MessageLite { return new UninitializedMessageException(this); } + private String getSerializingExceptionMessage(String target) { + return "Serializing " + getClass().getName() + " to a " + target + + " threw an IOException (should never happen)."; + } + protected static void checkByteStringIsUtf8(ByteString byteString) throws IllegalArgumentException { if (!byteString.isValidUtf8()) { @@ -109,36 +130,43 @@ public abstract class AbstractMessageLite implements MessageLite { } } - protected static <T> void addAll(final Iterable<T> values, - final Collection<? super T> list) { + // For binary compatibility + @Deprecated + protected static <T> void addAll(final Iterable<T> values, final Collection<? super T> list) { + Builder.addAll(values, (List) list); + } + + protected static <T> void addAll(final Iterable<T> values, final List<? super T> list) { Builder.addAll(values, list); } - + /** * A partial implementation of the {@link Message.Builder} interface which * implements as many methods of that interface as possible in terms of * other methods. */ @SuppressWarnings("unchecked") - public static abstract class Builder<BuilderType extends Builder> + public abstract static class Builder< + MessageType extends AbstractMessageLite<MessageType, BuilderType>, + BuilderType extends Builder<MessageType, BuilderType>> implements MessageLite.Builder { // The compiler produces an error if this is not declared explicitly. @Override public abstract BuilderType clone(); - public BuilderType mergeFrom(final CodedInputStream input) - throws IOException { + @Override + public BuilderType mergeFrom(final CodedInputStream input) throws IOException { return mergeFrom(input, ExtensionRegistryLite.getEmptyRegistry()); } // Re-defined here for return type covariance. + @Override public abstract BuilderType mergeFrom( - final CodedInputStream input, - final ExtensionRegistryLite extensionRegistry) + final CodedInputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException; - public BuilderType mergeFrom(final ByteString data) - throws InvalidProtocolBufferException { + @Override + public BuilderType mergeFrom(final ByteString data) throws InvalidProtocolBufferException { try { final CodedInputStream input = data.newCodedInput(); mergeFrom(input); @@ -147,15 +175,13 @@ public abstract class AbstractMessageLite implements MessageLite { } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a ByteString threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("ByteString"), e); } } + @Override public BuilderType mergeFrom( - final ByteString data, - final ExtensionRegistryLite extensionRegistry) + final ByteString data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { try { final CodedInputStream input = data.newCodedInput(); @@ -165,20 +191,18 @@ public abstract class AbstractMessageLite implements MessageLite { } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a ByteString threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("ByteString"), e); } } - public BuilderType mergeFrom(final byte[] data) - throws InvalidProtocolBufferException { + @Override + public BuilderType mergeFrom(final byte[] data) throws InvalidProtocolBufferException { return mergeFrom(data, 0, data.length); } - public BuilderType mergeFrom(final byte[] data, final int off, - final int len) - throws InvalidProtocolBufferException { + @Override + public BuilderType mergeFrom(final byte[] data, final int off, final int len) + throws InvalidProtocolBufferException { try { final CodedInputStream input = CodedInputStream.newInstance(data, off, len); @@ -188,21 +212,21 @@ public abstract class AbstractMessageLite implements MessageLite { } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a byte array threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("byte array"), e); } } - public BuilderType mergeFrom( - final byte[] data, - final ExtensionRegistryLite extensionRegistry) + @Override + public BuilderType mergeFrom(final byte[] data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return mergeFrom(data, 0, data.length, extensionRegistry); } + @Override public BuilderType mergeFrom( - final byte[] data, final int off, final int len, + final byte[] data, + final int off, + final int len, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { try { @@ -214,12 +238,11 @@ public abstract class AbstractMessageLite implements MessageLite { } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a byte array threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("byte array"), e); } } + @Override public BuilderType mergeFrom(final InputStream input) throws IOException { final CodedInputStream codedInput = CodedInputStream.newInstance(input); mergeFrom(codedInput); @@ -227,10 +250,9 @@ public abstract class AbstractMessageLite implements MessageLite { return (BuilderType) this; } + @Override public BuilderType mergeFrom( - final InputStream input, - final ExtensionRegistryLite extensionRegistry) - throws IOException { + final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { final CodedInputStream codedInput = CodedInputStream.newInstance(input); mergeFrom(codedInput, extensionRegistry); codedInput.checkLastTagWas(0); @@ -292,10 +314,9 @@ public abstract class AbstractMessageLite implements MessageLite { } } + @Override public boolean mergeDelimitedFrom( - final InputStream input, - final ExtensionRegistryLite extensionRegistry) - throws IOException { + final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { final int firstByte = input.read(); if (firstByte == -1) { return false; @@ -306,12 +327,49 @@ public abstract class AbstractMessageLite implements MessageLite { return true; } - public boolean mergeDelimitedFrom(final InputStream input) - throws IOException { + @Override + public boolean mergeDelimitedFrom(final InputStream input) throws IOException { return mergeDelimitedFrom(input, ExtensionRegistryLite.getEmptyRegistry()); } + @Override + @SuppressWarnings("unchecked") // isInstance takes care of this + public BuilderType mergeFrom(final MessageLite other) { + if (!getDefaultInstanceForType().getClass().isInstance(other)) { + throw new IllegalArgumentException( + "mergeFrom(MessageLite) can only merge messages of the same type."); + } + + return internalMergeFrom((MessageType) other); + } + + protected abstract BuilderType internalMergeFrom(MessageType message); + + private String getReadingExceptionMessage(String target) { + return "Reading " + getClass().getName() + " from a " + target + + " threw an IOException (should never happen)."; + } + + // We check nulls as we iterate to avoid iterating over values twice. + private static <T> void addAllCheckingNulls(Iterable<T> values, List<? super T> list) { + if (list instanceof ArrayList && values instanceof Collection) { + ((ArrayList<T>) list).ensureCapacity(list.size() + ((Collection<T>) values).size()); + } + int begin = list.size(); + for (T value : values) { + if (value == null) { + // encountered a null value so we must undo our modifications prior to throwing + String message = "Element at index " + (list.size() - begin) + " is null."; + for (int i = list.size() - 1; i >= begin; i--) { + list.remove(i); + } + throw new NullPointerException(message); + } + list.add(value); + } + } + /** * Construct an UninitializedMessageException reporting missing fields in * the given message. @@ -321,41 +379,50 @@ public abstract class AbstractMessageLite implements MessageLite { return new UninitializedMessageException(message); } + // For binary compatibility. + @Deprecated + protected static <T> void addAll(final Iterable<T> values, final Collection<? super T> list) { + addAll(values, (List<T>) list); + } + /** - * Adds the {@code values} to the {@code list}. This is a helper method - * used by generated code. Users should ignore it. + * Adds the {@code values} to the {@code list}. This is a helper method used by generated code. + * Users should ignore it. * - * @throws NullPointerException if {@code values} or any of the elements of - * {@code values} is null. When that happens, some elements of - * {@code values} may have already been added to the result {@code list}. + * @throws NullPointerException if {@code values} or any of the elements of {@code values} is + * null. */ - protected static <T> void addAll(final Iterable<T> values, - final Collection<? super T> list) { - if (values == null) { - throw new NullPointerException(); - } + protected static <T> void addAll(final Iterable<T> values, final List<? super T> list) { + checkNotNull(values); if (values instanceof LazyStringList) { // For StringOrByteStringLists, check the underlying elements to avoid // forcing conversions of ByteStrings to Strings. - checkForNullValues(((LazyStringList) values).getUnderlyingElements()); - list.addAll((Collection<T>) values); - } else if (values instanceof Collection) { - checkForNullValues(values); - list.addAll((Collection<T>) values); - } else { - for (final T value : values) { + // TODO(dweis): Could we just prohibit nulls in all protobuf lists and get rid of this? Is + // if even possible to hit this condition as all protobuf methods check for null first, + // right? + List<?> lazyValues = ((LazyStringList) values).getUnderlyingElements(); + LazyStringList lazyList = (LazyStringList) list; + int begin = list.size(); + for (Object value : lazyValues) { if (value == null) { - throw new NullPointerException(); + // encountered a null value so we must undo our modifications prior to throwing + String message = "Element at index " + (lazyList.size() - begin) + " is null."; + for (int i = lazyList.size() - 1; i >= begin; i--) { + lazyList.remove(i); + } + throw new NullPointerException(message); + } + if (value instanceof ByteString) { + lazyList.add((ByteString) value); + } else { + lazyList.add((String) value); } - list.add(value); } - } - } - - private static void checkForNullValues(final Iterable<?> values) { - for (final Object value : values) { - if (value == null) { - throw new NullPointerException(); + } else { + if (values instanceof PrimitiveNonBoxingCollection) { + list.addAll((Collection<T>) values); + } else { + addAllCheckingNulls(values, list); } } } diff --git a/java/core/src/main/java/com/google/protobuf/AbstractParser.java b/java/core/src/main/java/com/google/protobuf/AbstractParser.java index 1a4c6311..ba570e3d 100644 --- a/java/core/src/main/java/com/google/protobuf/AbstractParser.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractParser.java @@ -31,9 +31,9 @@ package com.google.protobuf; import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream; - import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; /** * A partial implementation of the {@link Parser} interface which implements @@ -78,26 +78,27 @@ public abstract class AbstractParser<MessageType extends MessageLite> private static final ExtensionRegistryLite EMPTY_REGISTRY = ExtensionRegistryLite.getEmptyRegistry(); + @Override public MessageType parsePartialFrom(CodedInputStream input) throws InvalidProtocolBufferException { return parsePartialFrom(input, EMPTY_REGISTRY); } - public MessageType parseFrom(CodedInputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialFrom(input, extensionRegistry)); } - public MessageType parseFrom(CodedInputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parseFrom(CodedInputStream input) throws InvalidProtocolBufferException { return parseFrom(input, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(ByteString data, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { MessageType message; try { CodedInputStream input = data.newCodedInput(); @@ -113,24 +114,49 @@ public abstract class AbstractParser<MessageType extends MessageLite> } } - public MessageType parsePartialFrom(ByteString data) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(ByteString data) throws InvalidProtocolBufferException { return parsePartialFrom(data, EMPTY_REGISTRY); } - public MessageType parseFrom(ByteString data, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(ByteString data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized(parsePartialFrom(data, extensionRegistry)); } - public MessageType parseFrom(ByteString data) + @Override + public MessageType parseFrom(ByteString data) throws InvalidProtocolBufferException { + return parseFrom(data, EMPTY_REGISTRY); + } + + @Override + public MessageType parseFrom(ByteBuffer data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { + MessageType message; + try { + CodedInputStream input = CodedInputStream.newInstance(data); + message = parsePartialFrom(input, extensionRegistry); + try { + input.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + } catch (InvalidProtocolBufferException e) { + throw e; + } + + return checkMessageInitialized(message); + } + + @Override + public MessageType parseFrom(ByteBuffer data) throws InvalidProtocolBufferException { return parseFrom(data, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parsePartialFrom( + byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { try { CodedInputStream input = CodedInputStream.newInstance(data, off, len); @@ -146,47 +172,50 @@ public abstract class AbstractParser<MessageType extends MessageLite> } } + @Override public MessageType parsePartialFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException { return parsePartialFrom(data, off, len, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(byte[] data, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parsePartialFrom(byte[] data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return parsePartialFrom(data, 0, data.length, extensionRegistry); } - public MessageType parsePartialFrom(byte[] data) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(byte[] data) throws InvalidProtocolBufferException { return parsePartialFrom(data, 0, data.length, EMPTY_REGISTRY); } - public MessageType parseFrom(byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom( + byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialFrom(data, off, len, extensionRegistry)); } + @Override public MessageType parseFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException { return parseFrom(data, off, len, EMPTY_REGISTRY); } - public MessageType parseFrom(byte[] data, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(byte[] data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return parseFrom(data, 0, data.length, extensionRegistry); } - public MessageType parseFrom(byte[] data) - throws InvalidProtocolBufferException { + @Override + public MessageType parseFrom(byte[] data) throws InvalidProtocolBufferException { return parseFrom(data, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parsePartialFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { CodedInputStream codedInput = CodedInputStream.newInstance(input); MessageType message = parsePartialFrom(codedInput, extensionRegistry); @@ -198,26 +227,26 @@ public abstract class AbstractParser<MessageType extends MessageLite> return message; } - public MessageType parsePartialFrom(InputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(InputStream input) throws InvalidProtocolBufferException { return parsePartialFrom(input, EMPTY_REGISTRY); } - public MessageType parseFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialFrom(input, extensionRegistry)); } - public MessageType parseFrom(InputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parseFrom(InputStream input) throws InvalidProtocolBufferException { return parseFrom(input, EMPTY_REGISTRY); } + @Override public MessageType parsePartialDelimitedFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) + InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { int size; try { @@ -227,27 +256,27 @@ public abstract class AbstractParser<MessageType extends MessageLite> } size = CodedInputStream.readRawVarint32(firstByte, input); } catch (IOException e) { - throw new InvalidProtocolBufferException(e.getMessage()); + throw new InvalidProtocolBufferException(e); } InputStream limitedInput = new LimitedInputStream(input, size); return parsePartialFrom(limitedInput, extensionRegistry); } + @Override public MessageType parsePartialDelimitedFrom(InputStream input) throws InvalidProtocolBufferException { return parsePartialDelimitedFrom(input, EMPTY_REGISTRY); } - public MessageType parseDelimitedFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseDelimitedFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialDelimitedFrom(input, extensionRegistry)); } - public MessageType parseDelimitedFrom(InputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parseDelimitedFrom(InputStream input) throws InvalidProtocolBufferException { return parseDelimitedFrom(input, EMPTY_REGISTRY); } } diff --git a/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java b/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java index bb6446b2..b17db6e0 100644 --- a/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java @@ -34,19 +34,25 @@ import com.google.protobuf.Internal.ProtobufList; import java.util.AbstractList; import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; /** * An abstract implementation of {@link ProtobufList} which manages mutability semantics. All mutate - * methods are check if the list is mutable before proceeding. Subclasses must invoke + * methods must check if the list is mutable before proceeding. Subclasses must invoke * {@link #ensureIsMutable()} manually when overriding those methods. + * <p> + * This implementation assumes all subclasses are array based, supporting random access. */ abstract class AbstractProtobufList<E> extends AbstractList<E> implements ProtobufList<E> { + protected static final int DEFAULT_CAPACITY = 10; + /** * Whether or not this list is modifiable. */ private boolean isMutable; - + /** * Constructs a mutable list by default. */ @@ -55,6 +61,44 @@ abstract class AbstractProtobufList<E> extends AbstractList<E> implements Protob } @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof List)) { + return false; + } + // Handle lists that do not support RandomAccess as efficiently as possible by using an iterator + // based approach in our super class. Otherwise our index based approach will avoid those + // allocations. + if (!(o instanceof RandomAccess)) { + return super.equals(o); + } + + List<?> other = (List<?>) o; + final int size = size(); + if (size != other.size()) { + return false; + } + for (int i = 0; i < size; i++) { + if (!get(i).equals(other.get(i))) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + final int size = size(); + int hashCode = 1; + for (int i = 0; i < size; i++) { + hashCode = (31 * hashCode) + get(i).hashCode(); + } + return hashCode; + } + + @Override public boolean add(E e) { ensureIsMutable(); return super.add(e); diff --git a/java/core/src/main/java/com/google/protobuf/Android.java b/java/core/src/main/java/com/google/protobuf/Android.java new file mode 100644 index 00000000..cad54783 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Android.java @@ -0,0 +1,57 @@ +// 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; + +final class Android { + + private static final Class<?> MEMORY_CLASS = getClassForName("libcore.io.Memory"); + private static final boolean IS_ROBOLECTRIC = + getClassForName("org.robolectric.Robolectric") != null; + + /** Returns {@code true} if running on an Android device. */ + static boolean isOnAndroidDevice() { + return MEMORY_CLASS != null && !IS_ROBOLECTRIC; + } + + /** Returns the memory class or {@code null} if not on Android device. */ + static Class<?> getMemoryClass() { + return MEMORY_CLASS; + } + + @SuppressWarnings("unchecked") + private static <T> Class<T> getClassForName(String name) { + try { + return (Class<T>) Class.forName(name); + } catch (Throwable e) { + return null; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java index 70e042f5..4d7a9727 100644 --- a/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java @@ -30,37 +30,35 @@ package com.google.protobuf; -import com.google.protobuf.Internal.BooleanList; +import static com.google.protobuf.Internal.checkNotNull; +import com.google.protobuf.Internal.BooleanList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** * An implementation of {@link BooleanList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class BooleanArrayList - extends AbstractProtobufList<Boolean> implements BooleanList, RandomAccess { - - private static final int DEFAULT_CAPACITY = 10; - +final class BooleanArrayList extends AbstractProtobufList<Boolean> + implements BooleanList, RandomAccess, PrimitiveNonBoxingCollection { + private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static BooleanArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private boolean[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -71,35 +69,70 @@ final class BooleanArrayList * Constructs a new mutable {@code BooleanArrayList} with default capacity. */ BooleanArrayList() { - this(DEFAULT_CAPACITY); + this(new boolean[DEFAULT_CAPACITY], 0); } /** - * Constructs a new mutable {@code BooleanArrayList} with the provided capacity. + * Constructs a new mutable {@code BooleanArrayList} + * containing the same elements as {@code other}. */ - BooleanArrayList(int capacity) { - array = new boolean[capacity]; - size = 0; + private BooleanArrayList(boolean[] other, int size) { + array = other; + this.size = size; } - /** - * Constructs a new mutable {@code BooleanArrayList} containing the same elements as - * {@code other}. - */ - BooleanArrayList(List<Boolean> other) { - if (other instanceof BooleanArrayList) { - BooleanArrayList list = (BooleanArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new boolean[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + @Override + protected void removeRange(int fromIndex, int toIndex) { + ensureIsMutable(); + if (toIndex < fromIndex) { + throw new IndexOutOfBoundsException("toIndex < fromIndex"); + } + + System.arraycopy(array, toIndex, array, fromIndex, size - toIndex); + size -= (toIndex - fromIndex); + modCount++; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BooleanArrayList)) { + return super.equals(o); + } + BooleanArrayList other = (BooleanArrayList) o; + if (size != other.size) { + return false; + } + + final boolean[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + Internal.hashBoolean(array[i]); + } + return result; } - + + @Override + public BooleanList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new BooleanArrayList(Arrays.copyOf(array, capacity), size); + } + @Override public Boolean get(int index) { return getBoolean(index); @@ -151,7 +184,7 @@ final class BooleanArrayList if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -159,10 +192,10 @@ final class BooleanArrayList // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; boolean[] newArray = new boolean[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -176,38 +209,36 @@ final class BooleanArrayList @Override public boolean addAll(Collection<? extends Boolean> collection) { ensureIsMutable(); - - if (collection == null) { - throw new NullPointerException(); - } - + + checkNotNull(collection); + // We specialize when adding another BooleanArrayList to avoid boxing elements. if (!(collection instanceof BooleanArrayList)) { return super.addAll(collection); } - + BooleanArrayList list = (BooleanArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -227,7 +258,9 @@ final class BooleanArrayList ensureIsMutable(); ensureIndexInRange(index); boolean value = array[index]; - System.arraycopy(array, index + 1, array, index, size - index); + if (index < size - 1) { + System.arraycopy(array, index + 1, array, index, size - index); + } size--; modCount++; return value; @@ -236,7 +269,7 @@ final class BooleanArrayList /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java b/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java new file mode 100644 index 00000000..6157a52f --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java @@ -0,0 +1,185 @@ +// 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 static java.lang.Math.max; +import static java.lang.Math.min; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.ref.SoftReference; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +/** + * Utility class to provide efficient writing of {@link ByteBuffer}s to {@link OutputStream}s. + */ +final class ByteBufferWriter { + private ByteBufferWriter() {} + + /** + * Minimum size for a cached buffer. This prevents us from allocating buffers that are too + * small to be easily reused. + */ + // TODO(nathanmittler): tune this property or allow configuration? + private static final int MIN_CACHED_BUFFER_SIZE = 1024; + + /** + * Maximum size for a cached buffer. If a larger buffer is required, it will be allocated + * but not cached. + */ + // TODO(nathanmittler): tune this property or allow configuration? + private static final int MAX_CACHED_BUFFER_SIZE = 16 * 1024; + + /** + * The fraction of the requested buffer size under which the buffer will be reallocated. + */ + // TODO(nathanmittler): tune this property or allow configuration? + private static final float BUFFER_REALLOCATION_THRESHOLD = 0.5f; + + /** + * Keeping a soft reference to a thread-local buffer. This buffer is used for writing a + * {@link ByteBuffer} to an {@link OutputStream} when no zero-copy alternative was available. + * Using a "soft" reference since VMs may keep this reference around longer than "weak" + * (e.g. HotSpot will maintain soft references until memory pressure warrants collection). + */ + private static final ThreadLocal<SoftReference<byte[]>> BUFFER = + new ThreadLocal<SoftReference<byte[]>>(); + + /** + * This is a hack for GAE, where {@code FileOutputStream} is unavailable. + */ + private static final Class<?> FILE_OUTPUT_STREAM_CLASS = safeGetClass("java.io.FileOutputStream"); + private static final long CHANNEL_FIELD_OFFSET = getChannelFieldOffset(FILE_OUTPUT_STREAM_CLASS); + + /** + * For testing purposes only. Clears the cached buffer to force a new allocation on the next + * invocation. + */ + static void clearCachedBuffer() { + BUFFER.set(null); + } + + /** + * Writes the remaining content of the buffer to the given stream. The buffer {@code position} + * will remain unchanged by this method. + */ + static void write(ByteBuffer buffer, OutputStream output) throws IOException { + final int initialPos = buffer.position(); + try { + if (buffer.hasArray()) { + // Optimized write for array-backed buffers. + // Note that we're taking the risk that a malicious OutputStream could modify the array. + output.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); + } else if (!writeToChannel(buffer, output)){ + // Read all of the data from the buffer to an array. + // TODO(nathanmittler): Consider performance improvements for other "known" stream types. + final byte[] array = getOrCreateBuffer(buffer.remaining()); + while (buffer.hasRemaining()) { + int length = min(buffer.remaining(), array.length); + buffer.get(array, 0, length); + output.write(array, 0, length); + } + } + } finally { + // Restore the initial position. + buffer.position(initialPos); + } + } + + private static byte[] getOrCreateBuffer(int requestedSize) { + requestedSize = max(requestedSize, MIN_CACHED_BUFFER_SIZE); + + byte[] buffer = getBuffer(); + // Only allocate if we need to. + if (buffer == null || needToReallocate(requestedSize, buffer.length)) { + buffer = new byte[requestedSize]; + + // Only cache the buffer if it's not too big. + if (requestedSize <= MAX_CACHED_BUFFER_SIZE) { + setBuffer(buffer); + } + } + return buffer; + } + + private static boolean needToReallocate(int requestedSize, int bufferLength) { + // First check against just the requested length to avoid the multiply. + return bufferLength < requestedSize + && bufferLength < requestedSize * BUFFER_REALLOCATION_THRESHOLD; + } + + private static byte[] getBuffer() { + SoftReference<byte[]> sr = BUFFER.get(); + return sr == null ? null : sr.get(); + } + + private static void setBuffer(byte[] value) { + BUFFER.set(new SoftReference<byte[]>(value)); + } + + private static boolean writeToChannel(ByteBuffer buffer, OutputStream output) throws IOException { + if (CHANNEL_FIELD_OFFSET >= 0 && FILE_OUTPUT_STREAM_CLASS.isInstance(output)) { + // Use a channel to write out the ByteBuffer. This will automatically empty the buffer. + WritableByteChannel channel = null; + try { + channel = (WritableByteChannel) UnsafeUtil.getObject(output, CHANNEL_FIELD_OFFSET); + } catch (ClassCastException e) { + // Absorb. + } + if (channel != null) { + channel.write(buffer); + return true; + } + } + return false; + } + + private static Class<?> safeGetClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + private static long getChannelFieldOffset(Class<?> clazz) { + try { + if (clazz != null && UnsafeUtil.hasUnsafeArrayOperations()) { + Field field = clazz.getDeclaredField("channel"); + return UnsafeUtil.objectFieldOffset(field); + } + } catch (Throwable e) { + // Absorb + } + return -1; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ByteOutput.java b/java/core/src/main/java/com/google/protobuf/ByteOutput.java new file mode 100644 index 00000000..ee588753 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ByteOutput.java @@ -0,0 +1,116 @@ +// 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; +import java.nio.ByteBuffer; + +/** + * An output target for raw bytes. This interface provides semantics that support two types of + * writing: + * + * <p><b>Traditional write operations:</b> + * (as defined by {@link java.io.OutputStream}) where the target method is responsible for either + * copying the data or completing the write before returning from the method call. + * + * <p><b>Lazy write operations:</b> where the caller guarantees that it will never modify the + * provided buffer and it can therefore be considered immutable. The target method is free to + * maintain a reference to the buffer beyond the scope of the method call (e.g. until the write + * operation completes). + */ +@ExperimentalApi +public abstract class ByteOutput { + /** + * Writes a single byte. + * + * @param value the byte to be written + * @throws IOException thrown if an error occurred while writing + */ + public abstract void write(byte value) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} must copy {@code value} if it will + * not be processed prior to the return of this method call, since {@code value} may be + * reused/altered by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written + * @param offset the offset of the start of the writable range + * @param length the number of bytes to write starting from {@code offset} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void write(byte[] value, int offset, int length) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} is free to retain a reference to the value + * beyond the scope of this method call (e.g. write later) since it is considered immutable and is + * guaranteed not to change by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written + * @param offset the offset of the start of the writable range + * @param length the number of bytes to write starting from {@code offset} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void writeLazy(byte[] value, int offset, int length) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} must copy {@code value} if it will + * not be processed prior to the return of this method call, since {@code value} may be + * reused/altered by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written. Upon returning from this call, the {@code position} of + * this buffer will be set to the {@code limit} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void write(ByteBuffer value) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} is free to retain a reference to the value + * beyond the scope of this method call (e.g. write later) since it is considered immutable and is + * guaranteed not to change by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written. Upon returning from this call, the {@code position} of + * this buffer will be set to the {@code limit} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void writeLazy(ByteBuffer value) throws IOException; +} diff --git a/java/core/src/main/java/com/google/protobuf/ByteString.java b/java/core/src/main/java/com/google/protobuf/ByteString.java index 305236f3..d67bb54a 100644 --- a/java/core/src/main/java/com/google/protobuf/ByteString.java +++ b/java/core/src/main/java/com/google/protobuf/ByteString.java @@ -1,4 +1,32 @@ -// Copyright 2007 Google Inc. All rights reserved. +// 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; @@ -15,6 +43,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -22,14 +51,12 @@ import java.util.List; import java.util.NoSuchElementException; /** - * Immutable sequence of bytes. Substring is supported by sharing the reference - * to the immutable underlying bytes, as with {@link String}. Concatenation is - * likewise supported without copying (long strings) by building a tree of - * pieces in {@link RopeByteString}. - * <p> - * Like {@link String}, the contents of a {@link ByteString} can never be - * observed to change, not even in the presence of a data race or incorrect - * API usage in the client code. + * Immutable sequence of bytes. Substring is supported by sharing the reference to the immutable + * underlying bytes. Concatenation is likewise supported without copying (long strings) by building + * a tree of pieces in {@link RopeByteString}. + * + * <p>Like {@link String}, the contents of a {@link ByteString} can never be observed to change, not + * even in the presence of a data race or incorrect API usage in the client code. * * @author crazybob@google.com Bob Lee * @author kenton@google.com Kenton Varda @@ -60,6 +87,48 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { public static final ByteString EMPTY = new LiteralByteString(Internal.EMPTY_BYTE_ARRAY); /** + * An interface to efficiently copy {@code byte[]}. + * + * <p>One of the noticeable costs of copying a byte[] into a new array using + * {@code System.arraycopy} is nullification of a new buffer before the copy. It has been shown + * the Hotspot VM is capable to intrisicfy {@code Arrays.copyOfRange} operation to avoid this + * expensive nullification and provide substantial performance gain. Unfortunately this does not + * hold on Android runtimes and could make the copy slightly slower due to additional code in + * the {@code Arrays.copyOfRange}. Thus we provide two different implementation for array copier + * for Hotspot and Android runtimes. + */ + private interface ByteArrayCopier { + /** + * Copies the specified range of the specified array into a new array + */ + byte[] copyFrom(byte[] bytes, int offset, int size); + } + + /** Implementation of {@code ByteArrayCopier} which uses {@link System#arraycopy}. */ + private static final class SystemByteArrayCopier implements ByteArrayCopier { + @Override + public byte[] copyFrom(byte[] bytes, int offset, int size) { + byte[] copy = new byte[size]; + System.arraycopy(bytes, offset, copy, 0, size); + return copy; + } + } + + /** Implementation of {@code ByteArrayCopier} which uses {@link Arrays#copyOfRange}. */ + private static final class ArraysByteArrayCopier implements ByteArrayCopier { + @Override + public byte[] copyFrom(byte[] bytes, int offset, int size) { + return Arrays.copyOfRange(bytes, offset, offset + size); + } + } + + private static final ByteArrayCopier byteArrayCopier; + static { + byteArrayCopier = + Android.isOnAndroidDevice() ? new SystemByteArrayCopier() : new ArraysByteArrayCopier(); + } + + /** * Cached hash value. Intentionally accessed via a data race, which * is safe because of the Java Memory Model's "no out-of-thin-air values" * guarantees for ints. A value of 0 implies that the hash has not been set. @@ -77,7 +146,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @param index index of byte * @return the value - * @throws ArrayIndexOutOfBoundsException {@code index < 0 or index >= size} + * @throws IndexOutOfBoundsException {@code index < 0 or index >= size} */ public abstract byte byteAt(int index); @@ -109,7 +178,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { public byte nextByte() { try { return byteAt(position++); - } catch (ArrayIndexOutOfBoundsException e) { + } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(e.getMessage()); } } @@ -220,9 +289,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @return new {@code ByteString} */ public static ByteString copyFrom(byte[] bytes, int offset, int size) { - byte[] copy = new byte[size]; - System.arraycopy(bytes, offset, copy, 0, size); - return new LiteralByteString(copy); + return new LiteralByteString(byteArrayCopier.copyFrom(bytes, offset, size)); } /** @@ -234,7 +301,19 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { public static ByteString copyFrom(byte[] bytes) { return copyFrom(bytes, 0, bytes.length); } - + + /** + * Wraps the given bytes into a {@code ByteString}. Intended for internal only usage. + */ + static ByteString wrap(ByteBuffer buffer) { + if (buffer.hasArray()) { + final int offset = buffer.arrayOffset(); + return ByteString.wrap(buffer.array(), offset + buffer.position(), buffer.remaining()); + } else { + return new NioByteString(buffer); + } + } + /** * Wraps the given bytes into a {@code ByteString}. Intended for internal only * usage to force a classload of ByteString before LiteralByteString. @@ -327,7 +406,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * immutable tree of byte arrays ("chunks") of the stream data. The * first chunk is small, with subsequent chunks each being double * the size, up to 8K. - * + * * <p>Each byte read from the input stream will be copied twice to ensure * that the resulting ByteString is truly immutable. * @@ -478,7 +557,9 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { // Create a balanced concatenation of the next "length" elements from the // iterable. private static ByteString balancedConcat(Iterator<ByteString> iterator, int length) { - assert length >= 1; + if (length < 1) { + throw new IllegalArgumentException(String.format("length (%s) must be >= 1", length)); + } ByteString result; if (length == 1) { result = iterator.next(); @@ -559,12 +640,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { } /** - * Writes the complete contents of this byte string to - * the specified output stream argument. - * - * <p>It is assumed that the {@link OutputStream} will not modify the contents passed it - * it. It may be possible for a malicious {@link OutputStream} to corrupt - * the data underlying the {@link ByteString}. + * Writes a copy of the contents of this byte string to the specified output stream argument. * * @param out the output stream to which to write the data. * @throws IOException if an I/O error occurs. @@ -578,8 +654,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @param sourceOffset offset within these bytes * @param numberToWrite number of bytes to write * @throws IOException if an I/O error occurs. - * @throws IndexOutOfBoundsException if an offset or size is negative or too - * large + * @throws IndexOutOfBoundsException if an offset or size is negative or too large */ final void writeTo(OutputStream out, int sourceOffset, int numberToWrite) throws IOException { @@ -597,6 +672,21 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { throws IOException; /** + * Writes this {@link ByteString} to the provided {@link ByteOutput}. Calling + * this method may result in multiple operations on the target {@link ByteOutput}. + * + * <p>This method may expose internal backing buffers of the {@link ByteString} to the {@link + * ByteOutput} in order to avoid additional copying overhead. It would be possible for a malicious + * {@link ByteOutput} to corrupt the {@link ByteString}. Use with caution! + * + * @param byteOutput the output target to receive the bytes + * @throws IOException if an I/O error occurs + * @see UnsafeByteOperations#unsafeWriteTo(ByteString, ByteOutput) + */ + abstract void writeTo(ByteOutput byteOutput) throws IOException; + + + /** * Constructs a read-only {@code java.nio.ByteBuffer} whose content * is equal to the contents of this byte string. * The result uses the same backing array as the byte string, if possible. @@ -737,6 +827,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { return true; } + /** * Check equality of the substring of given length of this object starting at * zero with another {@code ByteString} substring starting at offset. @@ -1102,7 +1193,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @param index the index position to be tested * @param size the length of the array - * @throws ArrayIndexOutOfBoundsException if the index does not fall within the array. + * @throws IndexOutOfBoundsException if the index does not fall within the array. */ static void checkIndex(int index, int size) { if ((index | (size - (index + 1))) < 0) { @@ -1120,7 +1211,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @param endIndex the end index of the range (exclusive) * @param size the size of the array. * @return the length of the range. - * @throws ArrayIndexOutOfBoundsException some or all of the range falls outside of the array. + * @throws IndexOutOfBoundsException some or all of the range falls outside of the array. */ static int checkRange(int startIndex, int endIndex, int size) { final int length = endIndex - startIndex; @@ -1143,7 +1234,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { return String.format("<ByteString@%s size=%d>", Integer.toHexString(System.identityHashCode(this)), size()); } - + /** * This class implements a {@link com.google.protobuf.ByteString} backed by a * single array of bytes, contiguous in memory. It supports substring by @@ -1236,6 +1327,11 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { } @Override + final void writeTo(ByteOutput output) throws IOException { + output.writeLazy(bytes, getOffsetIntoBytes(), size()); + } + + @Override protected final String toStringInternal(Charset charset) { return new String(bytes, getOffsetIntoBytes(), size(), charset); } @@ -1362,7 +1458,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { return 0; } } - + /** * This class is used to represent the substring of a {@link ByteString} over a * single byte array. In terms of the public API of {@link ByteString}, you end diff --git a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java index b3118ee0..1297462e 100644 --- a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java @@ -30,55 +30,119 @@ package com.google.protobuf; +import static com.google.protobuf.Internal.EMPTY_BYTE_ARRAY; +import static com.google.protobuf.Internal.EMPTY_BYTE_BUFFER; +import static com.google.protobuf.Internal.UTF_8; +import static com.google.protobuf.Internal.checkNotNull; +import static com.google.protobuf.WireFormat.FIXED32_SIZE; +import static com.google.protobuf.WireFormat.FIXED64_SIZE; +import static com.google.protobuf.WireFormat.MAX_VARINT_SIZE; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; /** * Reads and decodes protocol message fields. * - * This class contains two kinds of methods: methods that read specific - * protocol message constructs and field types (e.g. {@link #readTag()} and - * {@link #readInt32()}) and methods that read low-level values (e.g. - * {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading - * encoded protocol messages, you should use the former methods, but if you are - * reading some other format of your own design, use the latter. + * <p>This class contains two kinds of methods: methods that read specific protocol message + * constructs and field types (e.g. {@link #readTag()} and {@link #readInt32()}) and methods that + * read low-level values (e.g. {@link #readRawVarint32()} and {@link #readRawBytes}). If you are + * reading encoded protocol messages, you should use the former methods, but if you are reading some + * other format of your own design, use the latter. * * @author kenton@google.com Kenton Varda */ -public final class CodedInputStream { +public abstract class CodedInputStream { + private static final int DEFAULT_BUFFER_SIZE = 4096; + private static final int DEFAULT_RECURSION_LIMIT = 100; + // Integer.MAX_VALUE == 0x7FFFFFF == INT_MAX from limits.h + private static final int DEFAULT_SIZE_LIMIT = Integer.MAX_VALUE; + /** - * Create a new CodedInputStream wrapping the given InputStream. + * Whether to enable our custom UTF-8 decode codepath which does not use {@link StringCoding}. + * Currently disabled. */ + private static final boolean ENABLE_CUSTOM_UTF8_DECODE = false; + + /** Visible for subclasses. See setRecursionLimit() */ + int recursionDepth; + + int recursionLimit = DEFAULT_RECURSION_LIMIT; + + /** Visible for subclasses. See setSizeLimit() */ + int sizeLimit = DEFAULT_SIZE_LIMIT; + + /** Create a new CodedInputStream wrapping the given InputStream. */ public static CodedInputStream newInstance(final InputStream input) { - return new CodedInputStream(input); + return newInstance(input, DEFAULT_BUFFER_SIZE); } - /** - * Create a new CodedInputStream wrapping the given byte array. - */ + /** Create a new CodedInputStream wrapping the given InputStream. */ + static CodedInputStream newInstance(final InputStream input, int bufferSize) { + if (input == null) { + // TODO(nathanmittler): Ideally we should throw here. This is done for backward compatibility. + return newInstance(EMPTY_BYTE_ARRAY); + } + return new StreamDecoder(input, bufferSize); + } + + /** Create a new CodedInputStream wrapping the given {@code Iterable <ByteBuffer>}. */ + public static CodedInputStream newInstance(final Iterable<ByteBuffer> input) { + if (!UnsafeDirectNioDecoder.isSupported()) { + return newInstance(new IterableByteBufferInputStream(input)); + } + return newInstance(input, false); + } + + /** Create a new CodedInputStream wrapping the given {@code Iterable <ByteBuffer>}. */ + static CodedInputStream newInstance( + final Iterable<ByteBuffer> bufs, final boolean bufferIsImmutable) { + // flag is to check the type of input's ByteBuffers. + // flag equals 1: all ByteBuffers have array. + // flag equals 2: all ByteBuffers are direct ByteBuffers. + // flag equals 3: some ByteBuffers are direct and some have array. + // flag greater than 3: other cases. + int flag = 0; + // Total size of the input + int totalSize = 0; + for (ByteBuffer buf : bufs) { + totalSize += buf.remaining(); + if (buf.hasArray()) { + flag |= 1; + } else if (buf.isDirect()) { + flag |= 2; + } else { + flag |= 4; + } + } + if (flag == 2) { + return new IterableDirectByteBufferDecoder(bufs, totalSize, bufferIsImmutable); + } else { + // TODO(yilunchong): add another decoders to deal case 1 and 3. + return newInstance(new IterableByteBufferInputStream(bufs)); + } + } + + /** Create a new CodedInputStream wrapping the given byte array. */ public static CodedInputStream newInstance(final byte[] buf) { return newInstance(buf, 0, buf.length); } - /** - * Create a new CodedInputStream wrapping the given byte array slice. - */ - public static CodedInputStream newInstance(final byte[] buf, final int off, - final int len) { - return newInstance(buf, off, len, false); + /** Create a new CodedInputStream wrapping the given byte array slice. */ + public static CodedInputStream newInstance(final byte[] buf, final int off, final int len) { + return newInstance(buf, off, len, false /* bufferIsImmutable */); } - /** - * Create a new CodedInputStream wrapping the given byte array slice. - */ - public static CodedInputStream newInstance(final byte[] buf, final int off, - final int len, boolean bufferIsImmutable) { - CodedInputStream result = new CodedInputStream(buf, off, len, bufferIsImmutable); + /** Create a new CodedInputStream wrapping the given byte array slice. */ + static CodedInputStream newInstance( + final byte[] buf, final int off, final int len, final boolean bufferIsImmutable) { + ArrayDecoder result = new ArrayDecoder(buf, off, len, bufferIsImmutable); try { // Some uses of CodedInputStream can be more efficient if they know // exactly how many bytes are available. By pushing the end point of the @@ -100,571 +164,415 @@ public final class CodedInputStream { } /** - * Create a new CodedInputStream wrapping the given ByteBuffer. The data - * starting from the ByteBuffer's current position to its limit will be read. - * The returned CodedInputStream may or may not share the underlying data - * in the ByteBuffer, therefore the ByteBuffer cannot be changed while the - * CodedInputStream is in use. - * Note that the ByteBuffer's position won't be changed by this function. - * Concurrent calls with the same ByteBuffer object are safe if no other - * thread is trying to alter the ByteBuffer's status. + * Create a new CodedInputStream wrapping the given ByteBuffer. The data starting from the + * ByteBuffer's current position to its limit will be read. The returned CodedInputStream may or + * may not share the underlying data in the ByteBuffer, therefore the ByteBuffer cannot be changed + * while the CodedInputStream is in use. Note that the ByteBuffer's position won't be changed by + * this function. Concurrent calls with the same ByteBuffer object are safe if no other thread is + * trying to alter the ByteBuffer's status. */ public static CodedInputStream newInstance(ByteBuffer buf) { + return newInstance(buf, false /* bufferIsImmutable */); + } + + /** Create a new CodedInputStream wrapping the given buffer. */ + static CodedInputStream newInstance(ByteBuffer buf, boolean bufferIsImmutable) { if (buf.hasArray()) { - return newInstance(buf.array(), buf.arrayOffset() + buf.position(), - buf.remaining()); - } else { - ByteBuffer temp = buf.duplicate(); - byte[] buffer = new byte[temp.remaining()]; - temp.get(buffer); - return newInstance(buffer); + return newInstance( + buf.array(), buf.arrayOffset() + buf.position(), buf.remaining(), bufferIsImmutable); + } + + if (buf.isDirect() && UnsafeDirectNioDecoder.isSupported()) { + return new UnsafeDirectNioDecoder(buf, bufferIsImmutable); } + + // The buffer is non-direct and does not expose the underlying array. Using the ByteBuffer API + // to access individual bytes is very slow, so just copy the buffer to an array. + // TODO(nathanmittler): Re-evaluate with Java 9 + byte[] buffer = new byte[buf.remaining()]; + buf.duplicate().get(buffer); + return newInstance(buffer, 0, buffer.length, true); } + /** Disable construction/inheritance outside of this class. */ + private CodedInputStream() {} + // ----------------------------------------------------------------- /** - * Attempt to read a field tag, returning zero if we have reached EOF. - * Protocol message parsers use this to read tags, since a protocol message - * may legally end wherever a tag occurs, and zero is not a valid tag number. + * Attempt to read a field tag, returning zero if we have reached EOF. Protocol message parsers + * use this to read tags, since a protocol message may legally end wherever a tag occurs, and zero + * is not a valid tag number. */ - public int readTag() throws IOException { - if (isAtEnd()) { - lastTag = 0; - return 0; - } - - lastTag = readRawVarint32(); - if (WireFormat.getTagFieldNumber(lastTag) == 0) { - // If we actually read zero (or any tag number corresponding to field - // number zero), that's not a valid tag. - throw InvalidProtocolBufferException.invalidTag(); - } - return lastTag; - } + public abstract int readTag() throws IOException; /** - * Verifies that the last call to readTag() returned the given tag value. - * This is used to verify that a nested group ended with the correct - * end tag. + * Verifies that the last call to readTag() returned the given tag value. This is used to verify + * that a nested group ended with the correct end tag. * - * @throws InvalidProtocolBufferException {@code value} does not match the - * last tag. + * @throws InvalidProtocolBufferException {@code value} does not match the last tag. */ - public void checkLastTagWas(final int value) - throws InvalidProtocolBufferException { - if (lastTag != value) { - throw InvalidProtocolBufferException.invalidEndTag(); - } - } + public abstract void checkLastTagWas(final int value) throws InvalidProtocolBufferException; - public int getLastTag() { - return lastTag; - } + public abstract int getLastTag(); /** * Reads and discards a single field, given its tag value. * - * @return {@code false} if the tag is an endgroup tag, in which case - * nothing is skipped. Otherwise, returns {@code true}. + * @return {@code false} if the tag is an endgroup tag, in which case nothing is skipped. + * Otherwise, returns {@code true}. */ - public boolean skipField(final int tag) throws IOException { - switch (WireFormat.getTagWireType(tag)) { - case WireFormat.WIRETYPE_VARINT: - skipRawVarint(); - return true; - case WireFormat.WIRETYPE_FIXED64: - skipRawBytes(8); - return true; - case WireFormat.WIRETYPE_LENGTH_DELIMITED: - skipRawBytes(readRawVarint32()); - return true; - case WireFormat.WIRETYPE_START_GROUP: - skipMessage(); - checkLastTagWas( - WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), - WireFormat.WIRETYPE_END_GROUP)); - return true; - case WireFormat.WIRETYPE_END_GROUP: - return false; - case WireFormat.WIRETYPE_FIXED32: - skipRawBytes(4); - return true; - default: - throw InvalidProtocolBufferException.invalidWireType(); - } - } + public abstract boolean skipField(final int tag) throws IOException; /** - * Reads a single field and writes it to output in wire format, - * given its tag value. + * Reads a single field and writes it to output in wire format, given its tag value. * - * @return {@code false} if the tag is an endgroup tag, in which case - * nothing is skipped. Otherwise, returns {@code true}. - */ - public boolean skipField(final int tag, final CodedOutputStream output) - throws IOException { - switch (WireFormat.getTagWireType(tag)) { - case WireFormat.WIRETYPE_VARINT: { - long value = readInt64(); - output.writeRawVarint32(tag); - output.writeUInt64NoTag(value); - return true; - } - case WireFormat.WIRETYPE_FIXED64: { - long value = readRawLittleEndian64(); - output.writeRawVarint32(tag); - output.writeFixed64NoTag(value); - return true; - } - case WireFormat.WIRETYPE_LENGTH_DELIMITED: { - ByteString value = readBytes(); - output.writeRawVarint32(tag); - output.writeBytesNoTag(value); - return true; - } - case WireFormat.WIRETYPE_START_GROUP: { - output.writeRawVarint32(tag); - skipMessage(output); - int endtag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), - WireFormat.WIRETYPE_END_GROUP); - checkLastTagWas(endtag); - output.writeRawVarint32(endtag); - return true; - } - case WireFormat.WIRETYPE_END_GROUP: { - return false; - } - case WireFormat.WIRETYPE_FIXED32: { - int value = readRawLittleEndian32(); - output.writeRawVarint32(tag); - output.writeFixed32NoTag(value); - return true; - } - default: - throw InvalidProtocolBufferException.invalidWireType(); - } - } - - /** - * Reads and discards an entire message. This will read either until EOF - * or until an endgroup tag, whichever comes first. + * @return {@code false} if the tag is an endgroup tag, in which case nothing is skipped. + * Otherwise, returns {@code true}. + * @deprecated use {@code UnknownFieldSet} or {@code UnknownFieldSetLite} to skip to an output + * stream. */ - public void skipMessage() throws IOException { - while (true) { - final int tag = readTag(); - if (tag == 0 || !skipField(tag)) { - return; - } - } - } + @Deprecated + public abstract boolean skipField(final int tag, final CodedOutputStream output) + throws IOException; /** - * Reads an entire message and writes it to output in wire format. - * This will read either until EOF or until an endgroup tag, + * Reads and discards an entire message. This will read either until EOF or until an endgroup tag, * whichever comes first. */ - public void skipMessage(CodedOutputStream output) throws IOException { - while (true) { - final int tag = readTag(); - if (tag == 0 || !skipField(tag, output)) { - return; - } - } - } + public abstract void skipMessage() throws IOException; /** - * Collects the bytes skipped and returns the data in a ByteBuffer. + * Reads an entire message and writes it to output in wire format. This will read either until EOF + * or until an endgroup tag, whichever comes first. */ - private class SkippedDataSink implements RefillCallback { - private int lastPos = bufferPos; - private ByteArrayOutputStream byteArrayStream; - - @Override - public void onRefill() { - if (byteArrayStream == null) { - byteArrayStream = new ByteArrayOutputStream(); - } - byteArrayStream.write(buffer, lastPos, bufferPos - lastPos); - lastPos = 0; - } - - /** - * Gets skipped data in a ByteBuffer. This method should only be - * called once. - */ - ByteBuffer getSkippedData() { - if (byteArrayStream == null) { - return ByteBuffer.wrap(buffer, lastPos, bufferPos - lastPos); - } else { - byteArrayStream.write(buffer, lastPos, bufferPos); - return ByteBuffer.wrap(byteArrayStream.toByteArray()); - } - } - } + public abstract void skipMessage(CodedOutputStream output) throws IOException; // ----------------------------------------------------------------- /** Read a {@code double} field value from the stream. */ - public double readDouble() throws IOException { - return Double.longBitsToDouble(readRawLittleEndian64()); - } + public abstract double readDouble() throws IOException; /** Read a {@code float} field value from the stream. */ - public float readFloat() throws IOException { - return Float.intBitsToFloat(readRawLittleEndian32()); - } + public abstract float readFloat() throws IOException; /** Read a {@code uint64} field value from the stream. */ - public long readUInt64() throws IOException { - return readRawVarint64(); - } + public abstract long readUInt64() throws IOException; /** Read an {@code int64} field value from the stream. */ - public long readInt64() throws IOException { - return readRawVarint64(); - } + public abstract long readInt64() throws IOException; /** Read an {@code int32} field value from the stream. */ - public int readInt32() throws IOException { - return readRawVarint32(); - } + public abstract int readInt32() throws IOException; /** Read a {@code fixed64} field value from the stream. */ - public long readFixed64() throws IOException { - return readRawLittleEndian64(); - } + public abstract long readFixed64() throws IOException; /** Read a {@code fixed32} field value from the stream. */ - public int readFixed32() throws IOException { - return readRawLittleEndian32(); - } + public abstract int readFixed32() throws IOException; /** Read a {@code bool} field value from the stream. */ - public boolean readBool() throws IOException { - return readRawVarint64() != 0; - } + public abstract boolean readBool() throws IOException; /** - * Read a {@code string} field value from the stream. - * If the stream contains malformed UTF-8, + * Read a {@code string} field value from the stream. If the stream contains malformed UTF-8, * replace the offending bytes with the standard UTF-8 replacement character. */ - public String readString() throws IOException { - final int size = readRawVarint32(); - if (size <= (bufferSize - bufferPos) && size > 0) { - // Fast path: We already have the bytes in a contiguous buffer, so - // just copy directly from it. - final String result = new String(buffer, bufferPos, size, Internal.UTF_8); - bufferPos += size; - return result; - } else if (size == 0) { - return ""; - } else { - // Slow path: Build a byte array first then copy it. - return new String(readRawBytesSlowPath(size), Internal.UTF_8); - } - } + public abstract String readString() throws IOException; /** - * Read a {@code string} field value from the stream. - * If the stream contains malformed UTF-8, + * Read a {@code string} field value from the stream. If the stream contains malformed UTF-8, * throw exception {@link InvalidProtocolBufferException}. */ - public String readStringRequireUtf8() throws IOException { - final int size = readRawVarint32(); - final byte[] bytes; - int pos = bufferPos; - if (size <= (bufferSize - pos) && size > 0) { - // Fast path: We already have the bytes in a contiguous buffer, so - // just copy directly from it. - bytes = buffer; - bufferPos = pos + size; - } else if (size == 0) { - return ""; - } else { - // Slow path: Build a byte array first then copy it. - bytes = readRawBytesSlowPath(size); - pos = 0; - } - // TODO(martinrb): We could save a pass by validating while decoding. - if (!Utf8.isValidUtf8(bytes, pos, pos + size)) { - throw InvalidProtocolBufferException.invalidUtf8(); - } - return new String(bytes, pos, size, Internal.UTF_8); - } + public abstract String readStringRequireUtf8() throws IOException; /** Read a {@code group} field value from the stream. */ - public void readGroup(final int fieldNumber, - final MessageLite.Builder builder, - final ExtensionRegistryLite extensionRegistry) - throws IOException { - if (recursionDepth >= recursionLimit) { - throw InvalidProtocolBufferException.recursionLimitExceeded(); - } - ++recursionDepth; - builder.mergeFrom(this, extensionRegistry); - checkLastTagWas( - WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); - --recursionDepth; - } + public abstract void readGroup( + final int fieldNumber, + final MessageLite.Builder builder, + final ExtensionRegistryLite extensionRegistry) + throws IOException; /** Read a {@code group} field value from the stream. */ - public <T extends MessageLite> T readGroup( - final int fieldNumber, - final Parser<T> parser, - final ExtensionRegistryLite extensionRegistry) - throws IOException { - if (recursionDepth >= recursionLimit) { - throw InvalidProtocolBufferException.recursionLimitExceeded(); - } - ++recursionDepth; - T result = parser.parsePartialFrom(this, extensionRegistry); - checkLastTagWas( - WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); - --recursionDepth; - return result; - } + public abstract <T extends MessageLite> T readGroup( + final int fieldNumber, final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) + throws IOException; /** - * Reads a {@code group} field value from the stream and merges it into the - * given {@link UnknownFieldSet}. + * Reads a {@code group} field value from the stream and merges it into the given {@link + * UnknownFieldSet}. * - * @deprecated UnknownFieldSet.Builder now implements MessageLite.Builder, so - * you can just call {@link #readGroup}. + * @deprecated UnknownFieldSet.Builder now implements MessageLite.Builder, so you can just call + * {@link #readGroup}. */ @Deprecated - public void readUnknownGroup(final int fieldNumber, - final MessageLite.Builder builder) - throws IOException { - // We know that UnknownFieldSet will ignore any ExtensionRegistry so it - // is safe to pass null here. (We can't call - // ExtensionRegistry.getEmptyRegistry() because that would make this - // class depend on ExtensionRegistry, which is not part of the lite - // library.) - readGroup(fieldNumber, builder, null); - } + public abstract void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) + throws IOException; /** Read an embedded message field value from the stream. */ - public void readMessage(final MessageLite.Builder builder, - final ExtensionRegistryLite extensionRegistry) - throws IOException { - final int length = readRawVarint32(); - if (recursionDepth >= recursionLimit) { - throw InvalidProtocolBufferException.recursionLimitExceeded(); - } - final int oldLimit = pushLimit(length); - ++recursionDepth; - builder.mergeFrom(this, extensionRegistry); - checkLastTagWas(0); - --recursionDepth; - popLimit(oldLimit); - } + public abstract void readMessage( + final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry) + throws IOException; /** Read an embedded message field value from the stream. */ - public <T extends MessageLite> T readMessage( - final Parser<T> parser, - final ExtensionRegistryLite extensionRegistry) - throws IOException { - int length = readRawVarint32(); - if (recursionDepth >= recursionLimit) { - throw InvalidProtocolBufferException.recursionLimitExceeded(); - } - final int oldLimit = pushLimit(length); - ++recursionDepth; - T result = parser.parsePartialFrom(this, extensionRegistry); - checkLastTagWas(0); - --recursionDepth; - popLimit(oldLimit); - return result; - } + public abstract <T extends MessageLite> T readMessage( + final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException; /** Read a {@code bytes} field value from the stream. */ - public ByteString readBytes() throws IOException { - final int size = readRawVarint32(); - if (size <= (bufferSize - bufferPos) && size > 0) { - // Fast path: We already have the bytes in a contiguous buffer, so - // just copy directly from it. - final ByteString result = bufferIsImmutable && enableAliasing - ? ByteString.wrap(buffer, bufferPos, size) - : ByteString.copyFrom(buffer, bufferPos, size); - bufferPos += size; - return result; - } else if (size == 0) { - return ByteString.EMPTY; - } else { - // Slow path: Build a byte array first then copy it. - return ByteString.wrap(readRawBytesSlowPath(size)); - } - } + public abstract ByteString readBytes() throws IOException; /** Read a {@code bytes} field value from the stream. */ - public byte[] readByteArray() throws IOException { - final int size = readRawVarint32(); - if (size <= (bufferSize - bufferPos) && size > 0) { - // Fast path: We already have the bytes in a contiguous buffer, so - // just copy directly from it. - final byte[] result = - Arrays.copyOfRange(buffer, bufferPos, bufferPos + size); - bufferPos += size; - return result; - } else { - // Slow path: Build a byte array first then copy it. - return readRawBytesSlowPath(size); - } - } + public abstract byte[] readByteArray() throws IOException; /** Read a {@code bytes} field value from the stream. */ - public ByteBuffer readByteBuffer() throws IOException { - final int size = readRawVarint32(); - if (size <= (bufferSize - bufferPos) && size > 0) { - // Fast path: We already have the bytes in a contiguous buffer. - // When aliasing is enabled, we can return a ByteBuffer pointing directly - // into the underlying byte array without copy if the CodedInputStream is - // constructed from a byte array. If aliasing is disabled or the input is - // from an InputStream or ByteString, we have to make a copy of the bytes. - ByteBuffer result = input == null && !bufferIsImmutable && enableAliasing - ? ByteBuffer.wrap(buffer, bufferPos, size).slice() - : ByteBuffer.wrap(Arrays.copyOfRange( - buffer, bufferPos, bufferPos + size)); - bufferPos += size; - return result; - } else if (size == 0) { - return Internal.EMPTY_BYTE_BUFFER; - } else { - // Slow path: Build a byte array first then copy it. - return ByteBuffer.wrap(readRawBytesSlowPath(size)); - } - } + public abstract ByteBuffer readByteBuffer() throws IOException; /** Read a {@code uint32} field value from the stream. */ - public int readUInt32() throws IOException { - return readRawVarint32(); - } + public abstract int readUInt32() throws IOException; /** - * Read an enum field value from the stream. Caller is responsible - * for converting the numeric value to an actual enum. + * Read an enum field value from the stream. Caller is responsible for converting the numeric + * value to an actual enum. */ - public int readEnum() throws IOException { - return readRawVarint32(); - } + public abstract int readEnum() throws IOException; /** Read an {@code sfixed32} field value from the stream. */ - public int readSFixed32() throws IOException { - return readRawLittleEndian32(); - } + public abstract int readSFixed32() throws IOException; /** Read an {@code sfixed64} field value from the stream. */ - public long readSFixed64() throws IOException { - return readRawLittleEndian64(); - } + public abstract long readSFixed64() throws IOException; /** Read an {@code sint32} field value from the stream. */ - public int readSInt32() throws IOException { - return decodeZigZag32(readRawVarint32()); - } + public abstract int readSInt32() throws IOException; /** Read an {@code sint64} field value from the stream. */ - public long readSInt64() throws IOException { - return decodeZigZag64(readRawVarint64()); - } + public abstract long readSInt64() throws IOException; // ================================================================= + /** Read a raw Varint from the stream. If larger than 32 bits, discard the upper bits. */ + public abstract int readRawVarint32() throws IOException; + + /** Read a raw Varint from the stream. */ + public abstract long readRawVarint64() throws IOException; + + /** Variant of readRawVarint64 for when uncomfortably close to the limit. */ + /* Visible for testing */ + abstract long readRawVarint64SlowPath() throws IOException; + + /** Read a 32-bit little-endian integer from the stream. */ + public abstract int readRawLittleEndian32() throws IOException; + + /** Read a 64-bit little-endian integer from the stream. */ + public abstract long readRawLittleEndian64() throws IOException; + + // ----------------------------------------------------------------- + /** - * Read a raw Varint from the stream. If larger than 32 bits, discard the - * upper bits. + * Enables {@link ByteString} aliasing of the underlying buffer, trading off on buffer pinning for + * data copies. Only valid for buffer-backed streams. */ - public int readRawVarint32() throws IOException { - // See implementation notes for readRawVarint64 - fastpath: { - int pos = bufferPos; - - if (bufferSize == pos) { - break fastpath; - } + public abstract void enableAliasing(boolean enabled); - final byte[] buffer = this.buffer; - int x; - if ((x = buffer[pos++]) >= 0) { - bufferPos = pos; - return x; - } else if (bufferSize - pos < 9) { - break fastpath; - } else if ((x ^= (buffer[pos++] << 7)) < 0) { - x ^= (~0 << 7); - } else if ((x ^= (buffer[pos++] << 14)) >= 0) { - x ^= (~0 << 7) ^ (~0 << 14); - } else if ((x ^= (buffer[pos++] << 21)) < 0) { - x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); - } else { - int y = buffer[pos++]; - x ^= y << 28; - x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); - if (y < 0 && - buffer[pos++] < 0 && - buffer[pos++] < 0 && - buffer[pos++] < 0 && - buffer[pos++] < 0 && - buffer[pos++] < 0) { - break fastpath; // Will throw malformedVarint() - } - } - bufferPos = pos; - return x; + /** + * Set the maximum message recursion depth. In order to prevent malicious messages from causing + * stack overflows, {@code CodedInputStream} limits how deeply messages may be nested. The default + * limit is 64. + * + * @return the old limit. + */ + public final int setRecursionLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException("Recursion limit cannot be negative: " + limit); } - return (int) readRawVarint64SlowPath(); + final int oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; } - private void skipRawVarint() throws IOException { - if (bufferSize - bufferPos >= 10) { - final byte[] buffer = this.buffer; - int pos = bufferPos; - for (int i = 0; i < 10; i++) { - if (buffer[pos++] >= 0) { - bufferPos = pos; - return; - } - } + /** + * Only valid for {@link InputStream}-backed streams. + * + * <p>Set the maximum message size. In order to prevent malicious messages from exhausting memory + * or causing integer overflows, {@code CodedInputStream} limits how large a message may be. The + * default limit is {@code Integer.MAX_INT}. You should set this limit as small as you can without + * harming your app's functionality. Note that size limits only apply when reading from an {@code + * InputStream}, not when constructed around a raw byte array. + * + * <p>If you want to read several messages from a single CodedInputStream, you could call {@link + * #resetSizeCounter()} after each one to avoid hitting the size limit. + * + * @return the old limit. + */ + public final int setSizeLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException("Size limit cannot be negative: " + limit); } - skipRawVarintSlowPath(); + final int oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; } - private void skipRawVarintSlowPath() throws IOException { - for (int i = 0; i < 10; i++) { - if (readRawByte() >= 0) { - return; - } - } - throw InvalidProtocolBufferException.malformedVarint(); + + private boolean explicitDiscardUnknownFields = false; + + private static volatile boolean proto3DiscardUnknownFieldsDefault = false; + + static void setProto3DiscardUnknownsByDefaultForTest() { + proto3DiscardUnknownFieldsDefault = true; + } + + static void setProto3KeepUnknownsByDefaultForTest() { + proto3DiscardUnknownFieldsDefault = false; + } + + static boolean getProto3DiscardUnknownFieldsDefault() { + return proto3DiscardUnknownFieldsDefault; } /** - * Reads a varint from the input one byte at a time, so that it does not - * read any bytes after the end of the varint. If you simply wrapped the - * stream in a CodedInputStream and used {@link #readRawVarint32(InputStream)} - * then you would probably end up reading past the end of the varint since - * CodedInputStream buffers its input. + * Sets this {@code CodedInputStream} to discard unknown fields. Only applies to full runtime + * messages; lite messages will always preserve unknowns. + * + * <p>Note calling this function alone will have NO immediate effect on the underlying input data. + * The unknown fields will be discarded during parsing. This affects both Proto2 and Proto3 full + * runtime. */ - static int readRawVarint32(final InputStream input) throws IOException { - final int firstByte = input.read(); - if (firstByte == -1) { - throw InvalidProtocolBufferException.truncatedMessage(); - } - return readRawVarint32(firstByte, input); + final void discardUnknownFields() { + explicitDiscardUnknownFields = true; + } + + /** + * Reverts the unknown fields preservation behavior for Proto2 and Proto3 full runtime to their + * default. + */ + final void unsetDiscardUnknownFields() { + explicitDiscardUnknownFields = false; + } + + /** + * Whether unknown fields in this input stream should be discarded during parsing into full + * runtime messages. + */ + final boolean shouldDiscardUnknownFields() { + return explicitDiscardUnknownFields; } /** - * Like {@link #readRawVarint32(InputStream)}, but expects that the caller - * has already read one byte. This allows the caller to determine if EOF - * has been reached before attempting to read. + * Whether unknown fields in this input stream should be discarded during parsing for proto3 full + * runtime messages. + * + * <p>This function was temporarily introduced before proto3 unknown fields behavior is changed. + * TODO(liujisi): remove this and related code in GeneratedMessage after proto3 unknown + * fields migration is done. + */ + final boolean shouldDiscardUnknownFieldsProto3() { + return explicitDiscardUnknownFields ? true : proto3DiscardUnknownFieldsDefault; + } + + /** + * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). Only valid for {@link + * InputStream}-backed streams. + */ + public abstract void resetSizeCounter(); + + /** + * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This is called when + * descending into a length-delimited embedded message. + * + * <p>Note that {@code pushLimit()} does NOT affect how many bytes the {@code CodedInputStream} + * reads from an underlying {@code InputStream} when refreshing its buffer. If you need to prevent + * reading past a certain point in the underlying {@code InputStream} (e.g. because you expect it + * to contain more data after the end of the message which you need to handle differently) then + * you must place a wrapper around your {@code InputStream} which limits the amount of data that + * can be read from it. + * + * @return the old limit. */ - public static int readRawVarint32( - final int firstByte, final InputStream input) throws IOException { + public abstract int pushLimit(int byteLimit) throws InvalidProtocolBufferException; + + /** + * Discards the current limit, returning to the previous limit. + * + * @param oldLimit The old limit, as returned by {@code pushLimit}. + */ + public abstract void popLimit(final int oldLimit); + + /** + * Returns the number of bytes to be read before the current limit. If no limit is set, returns + * -1. + */ + public abstract int getBytesUntilLimit(); + + /** + * Returns true if the stream has reached the end of the input. This is the case if either the end + * of the underlying input source has been reached or if the stream has reached a limit created + * using {@link #pushLimit(int)}. + */ + public abstract boolean isAtEnd() throws IOException; + + /** + * The total bytes read up to the current position. Calling {@link #resetSizeCounter()} resets + * this value to zero. + */ + public abstract int getTotalBytesRead(); + + /** + * Read one byte from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached. + */ + public abstract byte readRawByte() throws IOException; + + /** + * Read a fixed size of bytes from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached. + */ + public abstract byte[] readRawBytes(final int size) throws IOException; + + /** + * Reads and discards {@code size} bytes. + * + * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached. + */ + public abstract void skipRawBytes(final int size) throws IOException; + + /** + * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into values that can be + * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits + * to be varint encoded, thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 32-bit integer, stored in a signed int because Java has no explicit + * unsigned support. + * @return A signed 32-bit integer. + */ + public static int decodeZigZag32(final int n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be + * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits + * to be varint encoded, thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 64-bit integer, stored in a signed int because Java has no explicit + * unsigned support. + * @return A signed 64-bit integer. + */ + public static long decodeZigZag64(final long n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Like {@link #readRawVarint32(InputStream)}, but expects that the caller has already read one + * byte. This allows the caller to determine if EOF has been reached before attempting to read. + */ + public static int readRawVarint32(final int firstByte, final InputStream input) + throws IOException { if ((firstByte & 0x80) == 0) { return firstByte; } @@ -694,589 +602,3341 @@ public final class CodedInputStream { throw InvalidProtocolBufferException.malformedVarint(); } - /** Read a raw Varint from the stream. */ - public long readRawVarint64() throws IOException { - // Implementation notes: - // - // Optimized for one-byte values, expected to be common. - // The particular code below was selected from various candidates - // empirically, by winning VarintBenchmark. - // - // Sign extension of (signed) Java bytes is usually a nuisance, but - // we exploit it here to more easily obtain the sign of bytes read. - // Instead of cleaning up the sign extension bits by masking eagerly, - // we delay until we find the final (positive) byte, when we clear all - // accumulated bits with one xor. We depend on javac to constant fold. - fastpath: { - int pos = bufferPos; - - if (bufferSize == pos) { - break fastpath; + /** + * Reads a varint from the input one byte at a time, so that it does not read any bytes after the + * end of the varint. If you simply wrapped the stream in a CodedInputStream and used {@link + * #readRawVarint32(InputStream)} then you would probably end up reading past the end of the + * varint since CodedInputStream buffers its input. + */ + static int readRawVarint32(final InputStream input) throws IOException { + final int firstByte = input.read(); + if (firstByte == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + return readRawVarint32(firstByte, input); + } + + /** A {@link CodedInputStream} implementation that uses a backing array as the input. */ + private static final class ArrayDecoder extends CodedInputStream { + private final byte[] buffer; + private final boolean immutable; + private int limit; + private int bufferSizeAfterLimit; + private int pos; + private int startPos; + private int lastTag; + private boolean enableAliasing; + + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + + private ArrayDecoder(final byte[] buffer, final int offset, final int len, boolean immutable) { + this.buffer = buffer; + limit = offset + len; + pos = offset; + startPos = pos; + this.immutable = immutable; + } + + @Override + public int readTag() throws IOException { + if (isAtEnd()) { + lastTag = 0; + return 0; } - final byte[] buffer = this.buffer; - long x; - int y; - if ((y = buffer[pos++]) >= 0) { - bufferPos = pos; - return y; - } else if (bufferSize - pos < 9) { - break fastpath; - } else if ((y ^= (buffer[pos++] << 7)) < 0) { - x = y ^ (~0 << 7); - } else if ((y ^= (buffer[pos++] << 14)) >= 0) { - x = y ^ ((~0 << 7) ^ (~0 << 14)); - } else if ((y ^= (buffer[pos++] << 21)) < 0) { - x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); - } else if ((x = ((long) y) ^ ((long) buffer[pos++] << 28)) >= 0L) { - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); - } else if ((x ^= ((long) buffer[pos++] << 35)) < 0L) { - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); - } else if ((x ^= ((long) buffer[pos++] << 42)) >= 0L) { - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); - } else if ((x ^= ((long) buffer[pos++] << 49)) < 0L) { - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) - ^ (~0L << 49); - } else { - x ^= ((long) buffer[pos++] << 56); - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) - ^ (~0L << 49) ^ (~0L << 56); - if (x < 0L) { - if (buffer[pos++] < 0L) { - break fastpath; // Will throw malformedVarint() + lastTag = readRawVarint32(); + if (WireFormat.getTagFieldNumber(lastTag) == 0) { + // If we actually read zero (or any tag number corresponding to field + // number zero), that's not a valid tag. + throw InvalidProtocolBufferException.invalidTag(); + } + return lastTag; + } + + @Override + public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { + if (lastTag != value) { + throw InvalidProtocolBufferException.invalidEndTag(); + } + } + + @Override + public int getLastTag() { + return lastTag; + } + + @Override + public boolean skipField(final int tag) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + skipRawVarint(); + return true; + case WireFormat.WIRETYPE_FIXED64: + skipRawBytes(FIXED64_SIZE); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP)); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + skipRawBytes(FIXED32_SIZE); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public boolean skipField(final int tag, final CodedOutputStream output) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + { + long value = readInt64(); + output.writeRawVarint32(tag); + output.writeUInt64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_FIXED64: + { + long value = readRawLittleEndian64(); + output.writeRawVarint32(tag); + output.writeFixed64NoTag(value); + return true; } + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + { + ByteString value = readBytes(); + output.writeRawVarint32(tag); + output.writeBytesNoTag(value); + return true; + } + case WireFormat.WIRETYPE_START_GROUP: + { + output.writeRawVarint32(tag); + skipMessage(output); + int endtag = + WireFormat.makeTag( + WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP); + checkLastTagWas(endtag); + output.writeRawVarint32(endtag); + return true; + } + case WireFormat.WIRETYPE_END_GROUP: + { + return false; + } + case WireFormat.WIRETYPE_FIXED32: + { + int value = readRawLittleEndian32(); + output.writeRawVarint32(tag); + output.writeFixed32NoTag(value); + return true; + } + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public void skipMessage() throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag)) { + return; } } - bufferPos = pos; - return x; } - return readRawVarint64SlowPath(); - } - /** Variant of readRawVarint64 for when uncomfortably close to the limit. */ - /* Visible for testing */ - long readRawVarint64SlowPath() throws IOException { - long result = 0; - for (int shift = 0; shift < 64; shift += 7) { - final byte b = readRawByte(); - result |= (long) (b & 0x7F) << shift; - if ((b & 0x80) == 0) { + @Override + public void skipMessage(CodedOutputStream output) throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag, output)) { + return; + } + } + } + + + // ----------------------------------------------------------------- + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + @Override + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public long readInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public int readInt32() throws IOException { + return readRawVarint32(); + } + + @Override + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + @Override + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + @Override + public boolean readBool() throws IOException { + return readRawVarint64() != 0; + } + + @Override + public String readString() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= (limit - pos)) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final String result = new String(buffer, pos, size, UTF_8); + pos += size; return result; } + + if (size == 0) { + return ""; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); } - throw InvalidProtocolBufferException.malformedVarint(); - } - /** Read a 32-bit little-endian integer from the stream. */ - public int readRawLittleEndian32() throws IOException { - int pos = bufferPos; + @Override + public String readStringRequireUtf8() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= (limit - pos)) { + if (ENABLE_CUSTOM_UTF8_DECODE) { + String result = Utf8.decodeUtf8(buffer, pos, size); + pos += size; + return result; + } else { + // TODO(martinrb): We could save a pass by validating while decoding. + if (!Utf8.isValidUtf8(buffer, pos, pos + size)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + final int tempPos = pos; + pos += size; + return new String(buffer, tempPos, size, UTF_8); + } + } - // hand-inlined ensureAvailable(4); - if (bufferSize - pos < 4) { - refillBuffer(4); - pos = bufferPos; + if (size == 0) { + return ""; + } + if (size <= 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); } - final byte[] buffer = this.buffer; - bufferPos = pos + 4; - return (((buffer[pos] & 0xff)) | - ((buffer[pos + 1] & 0xff) << 8) | - ((buffer[pos + 2] & 0xff) << 16) | - ((buffer[pos + 3] & 0xff) << 24)); - } + @Override + public void readGroup( + final int fieldNumber, + final MessageLite.Builder builder, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + } - /** Read a 64-bit little-endian integer from the stream. */ - public long readRawLittleEndian64() throws IOException { - int pos = bufferPos; - - // hand-inlined ensureAvailable(8); - if (bufferSize - pos < 8) { - refillBuffer(8); - pos = bufferPos; - } - - final byte[] buffer = this.buffer; - bufferPos = pos + 8; - return ((((long) buffer[pos] & 0xffL)) | - (((long) buffer[pos + 1] & 0xffL) << 8) | - (((long) buffer[pos + 2] & 0xffL) << 16) | - (((long) buffer[pos + 3] & 0xffL) << 24) | - (((long) buffer[pos + 4] & 0xffL) << 32) | - (((long) buffer[pos + 5] & 0xffL) << 40) | - (((long) buffer[pos + 6] & 0xffL) << 48) | - (((long) buffer[pos + 7] & 0xffL) << 56)); - } - /** - * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers - * into values that can be efficiently encoded with varint. (Otherwise, - * negative values must be sign-extended to 64 bits to be varint encoded, - * thus always taking 10 bytes on the wire.) - * - * @param n An unsigned 32-bit integer, stored in a signed int because - * Java has no explicit unsigned support. - * @return A signed 32-bit integer. - */ - public static int decodeZigZag32(final int n) { - return (n >>> 1) ^ -(n & 1); - } + @Override + public <T extends MessageLite> T readGroup( + final int fieldNumber, + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + return result; + } - /** - * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers - * into values that can be efficiently encoded with varint. (Otherwise, - * negative values must be sign-extended to 64 bits to be varint encoded, - * thus always taking 10 bytes on the wire.) - * - * @param n An unsigned 64-bit integer, stored in a signed int because - * Java has no explicit unsigned support. - * @return A signed 64-bit integer. - */ - public static long decodeZigZag64(final long n) { - return (n >>> 1) ^ -(n & 1); - } + @Deprecated + @Override + public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) + throws IOException { + readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry()); + } - // ----------------------------------------------------------------- + @Override + public void readMessage( + final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry) + throws IOException { + final int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } - private final byte[] buffer; - private final boolean bufferIsImmutable; - private int bufferSize; - private int bufferSizeAfterLimit; - private int bufferPos; - private final InputStream input; - private int lastTag; - private boolean enableAliasing = false; - /** - * The total number of bytes read before the current buffer. The total - * bytes read up to the current position can be computed as - * {@code totalBytesRetired + bufferPos}. This value may be negative if - * reading started in the middle of the current buffer (e.g. if the - * constructor that takes a byte array and an offset was used). - */ - private int totalBytesRetired; + @Override + public <T extends MessageLite> T readMessage( + final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException { + int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + return result; + } - /** The absolute position of the end of the current message. */ - private int currentLimit = Integer.MAX_VALUE; + @Override + public ByteString readBytes() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= (limit - pos)) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final ByteString result = + immutable && enableAliasing + ? ByteString.wrap(buffer, pos, size) + : ByteString.copyFrom(buffer, pos, size); + pos += size; + return result; + } + if (size == 0) { + return ByteString.EMPTY; + } + // Slow path: Build a byte array first then copy it. + return ByteString.wrap(readRawBytes(size)); + } - /** See setRecursionLimit() */ - private int recursionDepth; - private int recursionLimit = DEFAULT_RECURSION_LIMIT; + @Override + public byte[] readByteArray() throws IOException { + final int size = readRawVarint32(); + return readRawBytes(size); + } - /** See setSizeLimit() */ - private int sizeLimit = DEFAULT_SIZE_LIMIT; + @Override + public ByteBuffer readByteBuffer() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= (limit - pos)) { + // Fast path: We already have the bytes in a contiguous buffer. + // When aliasing is enabled, we can return a ByteBuffer pointing directly + // into the underlying byte array without copy if the CodedInputStream is + // constructed from a byte array. If aliasing is disabled or the input is + // from an InputStream or ByteString, we have to make a copy of the bytes. + ByteBuffer result = + !immutable && enableAliasing + ? ByteBuffer.wrap(buffer, pos, size).slice() + : ByteBuffer.wrap(Arrays.copyOfRange(buffer, pos, pos + size)); + pos += size; + // TODO(nathanmittler): Investigate making the ByteBuffer be made read-only + return result; + } - private static final int DEFAULT_RECURSION_LIMIT = 100; - private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB - private static final int BUFFER_SIZE = 4096; - - private CodedInputStream(final byte[] buffer, final int off, final int len, boolean bufferIsImmutable) { - this.buffer = buffer; - bufferSize = off + len; - bufferPos = off; - totalBytesRetired = -off; - input = null; - this.bufferIsImmutable = bufferIsImmutable; - } + if (size == 0) { + return EMPTY_BYTE_BUFFER; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); + } - private CodedInputStream(final InputStream input) { - buffer = new byte[BUFFER_SIZE]; - bufferSize = 0; - bufferPos = 0; - totalBytesRetired = 0; - this.input = input; - bufferIsImmutable = false; - } + @Override + public int readUInt32() throws IOException { + return readRawVarint32(); + } - public void enableAliasing(boolean enabled) { - this.enableAliasing = enabled; - } + @Override + public int readEnum() throws IOException { + return readRawVarint32(); + } - /** - * Set the maximum message recursion depth. In order to prevent malicious - * messages from causing stack overflows, {@code CodedInputStream} limits - * how deeply messages may be nested. The default limit is 64. - * - * @return the old limit. - */ - public int setRecursionLimit(final int limit) { - if (limit < 0) { - throw new IllegalArgumentException( - "Recursion limit cannot be negative: " + limit); + @Override + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); } - final int oldLimit = recursionLimit; - recursionLimit = limit; - return oldLimit; - } - /** - * Set the maximum message size. In order to prevent malicious - * messages from exhausting memory or causing integer overflows, - * {@code CodedInputStream} limits how large a message may be. - * The default limit is 64MB. You should set this limit as small - * as you can without harming your app's functionality. Note that - * size limits only apply when reading from an {@code InputStream}, not - * when constructed around a raw byte array (nor with - * {@link ByteString#newCodedInput}). - * <p> - * If you want to read several messages from a single CodedInputStream, you - * could call {@link #resetSizeCounter()} after each one to avoid hitting the - * size limit. - * - * @return the old limit. - */ - public int setSizeLimit(final int limit) { - if (limit < 0) { - throw new IllegalArgumentException( - "Size limit cannot be negative: " + limit); + @Override + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); } - final int oldLimit = sizeLimit; - sizeLimit = limit; - return oldLimit; - } - /** - * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). - */ - public void resetSizeCounter() { - totalBytesRetired = -bufferPos; + @Override + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + @Override + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + // ================================================================= + + @Override + public int readRawVarint32() throws IOException { + // See implementation notes for readRawVarint64 + fastpath: + { + int tempPos = pos; + + if (limit == tempPos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + int x; + if ((x = buffer[tempPos++]) >= 0) { + pos = tempPos; + return x; + } else if (limit - tempPos < 9) { + break fastpath; + } else if ((x ^= (buffer[tempPos++] << 7)) < 0) { + x ^= (~0 << 7); + } else if ((x ^= (buffer[tempPos++] << 14)) >= 0) { + x ^= (~0 << 7) ^ (~0 << 14); + } else if ((x ^= (buffer[tempPos++] << 21)) < 0) { + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); + } else { + int y = buffer[tempPos++]; + x ^= y << 28; + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); + if (y < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0) { + break fastpath; // Will throw malformedVarint() + } + } + pos = tempPos; + return x; + } + return (int) readRawVarint64SlowPath(); + } + + private void skipRawVarint() throws IOException { + if (limit - pos >= MAX_VARINT_SIZE) { + skipRawVarintFastPath(); + } else { + skipRawVarintSlowPath(); + } + } + + private void skipRawVarintFastPath() throws IOException { + for (int i = 0; i < MAX_VARINT_SIZE; i++) { + if (buffer[pos++] >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + private void skipRawVarintSlowPath() throws IOException { + for (int i = 0; i < MAX_VARINT_SIZE; i++) { + if (readRawByte() >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + @Override + public long readRawVarint64() throws IOException { + // Implementation notes: + // + // Optimized for one-byte values, expected to be common. + // The particular code below was selected from various candidates + // empirically, by winning VarintBenchmark. + // + // Sign extension of (signed) Java bytes is usually a nuisance, but + // we exploit it here to more easily obtain the sign of bytes read. + // Instead of cleaning up the sign extension bits by masking eagerly, + // we delay until we find the final (positive) byte, when we clear all + // accumulated bits with one xor. We depend on javac to constant fold. + fastpath: + { + int tempPos = pos; + + if (limit == tempPos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + long x; + int y; + if ((y = buffer[tempPos++]) >= 0) { + pos = tempPos; + return y; + } else if (limit - tempPos < 9) { + break fastpath; + } else if ((y ^= (buffer[tempPos++] << 7)) < 0) { + x = y ^ (~0 << 7); + } else if ((y ^= (buffer[tempPos++] << 14)) >= 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14)); + } else if ((y ^= (buffer[tempPos++] << 21)) < 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); + } else if ((x = y ^ ((long) buffer[tempPos++] << 28)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + } else if ((x ^= ((long) buffer[tempPos++] << 35)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); + } else if ((x ^= ((long) buffer[tempPos++] << 42)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); + } else if ((x ^= ((long) buffer[tempPos++] << 49)) < 0L) { + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49); + } else { + x ^= ((long) buffer[tempPos++] << 56); + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49) + ^ (~0L << 56); + if (x < 0L) { + if (buffer[tempPos++] < 0L) { + break fastpath; // Will throw malformedVarint() + } + } + } + pos = tempPos; + return x; + } + return readRawVarint64SlowPath(); + } + + @Override + long readRawVarint64SlowPath() throws IOException { + long result = 0; + for (int shift = 0; shift < 64; shift += 7) { + final byte b = readRawByte(); + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + @Override + public int readRawLittleEndian32() throws IOException { + int tempPos = pos; + + if (limit - tempPos < FIXED32_SIZE) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + + final byte[] buffer = this.buffer; + pos = tempPos + FIXED32_SIZE; + return (((buffer[tempPos] & 0xff)) + | ((buffer[tempPos + 1] & 0xff) << 8) + | ((buffer[tempPos + 2] & 0xff) << 16) + | ((buffer[tempPos + 3] & 0xff) << 24)); + } + + @Override + public long readRawLittleEndian64() throws IOException { + int tempPos = pos; + + if (limit - tempPos < FIXED64_SIZE) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + + final byte[] buffer = this.buffer; + pos = tempPos + FIXED64_SIZE; + return (((buffer[tempPos] & 0xffL)) + | ((buffer[tempPos + 1] & 0xffL) << 8) + | ((buffer[tempPos + 2] & 0xffL) << 16) + | ((buffer[tempPos + 3] & 0xffL) << 24) + | ((buffer[tempPos + 4] & 0xffL) << 32) + | ((buffer[tempPos + 5] & 0xffL) << 40) + | ((buffer[tempPos + 6] & 0xffL) << 48) + | ((buffer[tempPos + 7] & 0xffL) << 56)); + } + + @Override + public void enableAliasing(boolean enabled) { + this.enableAliasing = enabled; + } + + @Override + public void resetSizeCounter() { + startPos = pos; + } + + @Override + public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { + if (byteLimit < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + byteLimit += getTotalBytesRead(); + final int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; + } + + private void recomputeBufferSizeAfterLimit() { + limit += bufferSizeAfterLimit; + final int bufferEnd = limit - startPos; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + limit -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } + } + + @Override + public void popLimit(final int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + @Override + public int getBytesUntilLimit() { + if (currentLimit == Integer.MAX_VALUE) { + return -1; + } + + return currentLimit - getTotalBytesRead(); + } + + @Override + public boolean isAtEnd() throws IOException { + return pos == limit; + } + + @Override + public int getTotalBytesRead() { + return pos - startPos; + } + + @Override + public byte readRawByte() throws IOException { + if (pos == limit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + return buffer[pos++]; + } + + @Override + public byte[] readRawBytes(final int length) throws IOException { + if (length > 0 && length <= (limit - pos)) { + final int tempPos = pos; + pos += length; + return Arrays.copyOfRange(buffer, tempPos, pos); + } + + if (length <= 0) { + if (length == 0) { + return Internal.EMPTY_BYTE_ARRAY; + } else { + throw InvalidProtocolBufferException.negativeSize(); + } + } + throw InvalidProtocolBufferException.truncatedMessage(); + } + + @Override + public void skipRawBytes(final int length) throws IOException { + if (length >= 0 && length <= (limit - pos)) { + // We have all the bytes we need already. + pos += length; + return; + } + + if (length < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); + } } /** - * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This - * is called when descending into a length-delimited embedded message. - * - * <p>Note that {@code pushLimit()} does NOT affect how many bytes the - * {@code CodedInputStream} reads from an underlying {@code InputStream} when - * refreshing its buffer. If you need to prevent reading past a certain - * point in the underlying {@code InputStream} (e.g. because you expect it to - * contain more data after the end of the message which you need to handle - * differently) then you must place a wrapper around your {@code InputStream} - * which limits the amount of data that can be read from it. - * - * @return the old limit. + * A {@link CodedInputStream} implementation that uses a backing direct ByteBuffer as the input. + * Requires the use of {@code sun.misc.Unsafe} to perform fast reads on the buffer. */ - public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { - if (byteLimit < 0) { - throw InvalidProtocolBufferException.negativeSize(); + private static final class UnsafeDirectNioDecoder extends CodedInputStream { + /** The direct buffer that is backing this stream. */ + private final ByteBuffer buffer; + + /** + * If {@code true}, indicates that the buffer is backing a {@link ByteString} and is therefore + * considered to be an immutable input source. + */ + private final boolean immutable; + + /** The unsafe address of the content of {@link #buffer}. */ + private final long address; + + /** The unsafe address of the current read limit of the buffer. */ + private long limit; + + /** The unsafe address of the current read position of the buffer. */ + private long pos; + + /** The unsafe address of the starting read position. */ + private long startPos; + + /** The amount of available data in the buffer beyond {@link #limit}. */ + private int bufferSizeAfterLimit; + + /** The last tag that was read from this stream. */ + private int lastTag; + + /** + * If {@code true}, indicates that calls to read {@link ByteString} or {@code byte[]} + * <strong>may</strong> return slices of the underlying buffer, rather than copies. + */ + private boolean enableAliasing; + + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + + static boolean isSupported() { + return UnsafeUtil.hasUnsafeByteBufferOperations(); + } + + private UnsafeDirectNioDecoder(ByteBuffer buffer, boolean immutable) { + this.buffer = buffer; + address = UnsafeUtil.addressOffset(buffer); + limit = address + buffer.limit(); + pos = address + buffer.position(); + startPos = pos; + this.immutable = immutable; + } + + @Override + public int readTag() throws IOException { + if (isAtEnd()) { + lastTag = 0; + return 0; + } + + lastTag = readRawVarint32(); + if (WireFormat.getTagFieldNumber(lastTag) == 0) { + // If we actually read zero (or any tag number corresponding to field + // number zero), that's not a valid tag. + throw InvalidProtocolBufferException.invalidTag(); + } + return lastTag; } - byteLimit += totalBytesRetired + bufferPos; - final int oldLimit = currentLimit; - if (byteLimit > oldLimit) { + + @Override + public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { + if (lastTag != value) { + throw InvalidProtocolBufferException.invalidEndTag(); + } + } + + @Override + public int getLastTag() { + return lastTag; + } + + @Override + public boolean skipField(final int tag) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + skipRawVarint(); + return true; + case WireFormat.WIRETYPE_FIXED64: + skipRawBytes(FIXED64_SIZE); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP)); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + skipRawBytes(FIXED32_SIZE); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public boolean skipField(final int tag, final CodedOutputStream output) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + { + long value = readInt64(); + output.writeRawVarint32(tag); + output.writeUInt64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_FIXED64: + { + long value = readRawLittleEndian64(); + output.writeRawVarint32(tag); + output.writeFixed64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + { + ByteString value = readBytes(); + output.writeRawVarint32(tag); + output.writeBytesNoTag(value); + return true; + } + case WireFormat.WIRETYPE_START_GROUP: + { + output.writeRawVarint32(tag); + skipMessage(output); + int endtag = + WireFormat.makeTag( + WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP); + checkLastTagWas(endtag); + output.writeRawVarint32(endtag); + return true; + } + case WireFormat.WIRETYPE_END_GROUP: + { + return false; + } + case WireFormat.WIRETYPE_FIXED32: + { + int value = readRawLittleEndian32(); + output.writeRawVarint32(tag); + output.writeFixed32NoTag(value); + return true; + } + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public void skipMessage() throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag)) { + return; + } + } + } + + @Override + public void skipMessage(CodedOutputStream output) throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag, output)) { + return; + } + } + } + + + // ----------------------------------------------------------------- + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + @Override + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public long readInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public int readInt32() throws IOException { + return readRawVarint32(); + } + + @Override + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + @Override + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + @Override + public boolean readBool() throws IOException { + return readRawVarint64() != 0; + } + + @Override + public String readString() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= remaining()) { + // TODO(nathanmittler): Is there a way to avoid this copy? + // TODO(anuraaga): It might be possible to share the optimized loop with + // readStringRequireUtf8 by implementing Java replacement logic there. + // The same as readBytes' logic + byte[] bytes = new byte[size]; + UnsafeUtil.copyMemory(pos, bytes, 0, size); + String result = new String(bytes, UTF_8); + pos += size; + return result; + } + + if (size == 0) { + return ""; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } throw InvalidProtocolBufferException.truncatedMessage(); } - currentLimit = byteLimit; - recomputeBufferSizeAfterLimit(); + @Override + public String readStringRequireUtf8() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= remaining()) { + if (ENABLE_CUSTOM_UTF8_DECODE) { + final int bufferPos = bufferPos(pos); + String result = Utf8.decodeUtf8(buffer, bufferPos, size); + pos += size; + return result; + } else { + // TODO(nathanmittler): Is there a way to avoid this copy? + // The same as readBytes' logic + byte[] bytes = new byte[size]; + UnsafeUtil.copyMemory(pos, bytes, 0, size); + // TODO(martinrb): We could save a pass by validating while decoding. + if (!Utf8.isValidUtf8(bytes)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } - return oldLimit; - } + String result = new String(bytes, UTF_8); + pos += size; + return result; + } + } - private void recomputeBufferSizeAfterLimit() { - bufferSize += bufferSizeAfterLimit; - final int bufferEnd = totalBytesRetired + bufferSize; - if (bufferEnd > currentLimit) { - // Limit is in current buffer. - bufferSizeAfterLimit = bufferEnd - currentLimit; - bufferSize -= bufferSizeAfterLimit; - } else { - bufferSizeAfterLimit = 0; + if (size == 0) { + return ""; + } + if (size <= 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); } - } - /** - * Discards the current limit, returning to the previous limit. - * - * @param oldLimit The old limit, as returned by {@code pushLimit}. - */ - public void popLimit(final int oldLimit) { - currentLimit = oldLimit; - recomputeBufferSizeAfterLimit(); - } + @Override + public void readGroup( + final int fieldNumber, + final MessageLite.Builder builder, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + } - /** - * Returns the number of bytes to be read before the current limit. - * If no limit is set, returns -1. - */ - public int getBytesUntilLimit() { - if (currentLimit == Integer.MAX_VALUE) { - return -1; + + @Override + public <T extends MessageLite> T readGroup( + final int fieldNumber, + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + return result; } - final int currentAbsolutePosition = totalBytesRetired + bufferPos; - return currentLimit - currentAbsolutePosition; - } + @Deprecated + @Override + public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) + throws IOException { + readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry()); + } - /** - * Returns true if the stream has reached the end of the input. This is the - * case if either the end of the underlying input source has been reached or - * if the stream has reached a limit created using {@link #pushLimit(int)}. - */ - public boolean isAtEnd() throws IOException { - return bufferPos == bufferSize && !tryRefillBuffer(1); - } + @Override + public void readMessage( + final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry) + throws IOException { + final int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } - /** - * The total bytes read up to the current position. Calling - * {@link #resetSizeCounter()} resets this value to zero. - */ - public int getTotalBytesRead() { - return totalBytesRetired + bufferPos; - } - private interface RefillCallback { - void onRefill(); - } + @Override + public <T extends MessageLite> T readMessage( + final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException { + int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + return result; + } - private RefillCallback refillCallback = null; + @Override + public ByteString readBytes() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= remaining()) { + if (immutable && enableAliasing) { + final ByteBuffer result = slice(pos, pos + size); + pos += size; + return ByteString.wrap(result); + } else { + // Use UnsafeUtil to copy the memory to bytes instead of using ByteBuffer ways. + byte[] bytes = new byte[size]; + UnsafeUtil.copyMemory(pos, bytes, 0, size); + pos += size; + return ByteString.wrap(bytes); + } + } - /** - * Reads more bytes from the input, making at least {@code n} bytes available - * in the buffer. Caller must ensure that the requested space is not yet - * available, and that the requested space is less than BUFFER_SIZE. - * - * @throws InvalidProtocolBufferException The end of the stream or the current - * limit was reached. - */ - private void refillBuffer(int n) throws IOException { - if (!tryRefillBuffer(n)) { + if (size == 0) { + return ByteString.EMPTY; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } throw InvalidProtocolBufferException.truncatedMessage(); } + + @Override + public byte[] readByteArray() throws IOException { + return readRawBytes(readRawVarint32()); + } + + @Override + public ByteBuffer readByteBuffer() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= remaining()) { + // "Immutable" implies that buffer is backing a ByteString. + // Disallow slicing in this case to prevent the caller from modifying the contents + // of the ByteString. + if (!immutable && enableAliasing) { + final ByteBuffer result = slice(pos, pos + size); + pos += size; + return result; + } else { + // The same as readBytes' logic + byte[] bytes = new byte[size]; + UnsafeUtil.copyMemory(pos, bytes, 0, size); + pos += size; + return ByteBuffer.wrap(bytes); + } + // TODO(nathanmittler): Investigate making the ByteBuffer be made read-only + } + + if (size == 0) { + return EMPTY_BYTE_BUFFER; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); + } + + @Override + public int readUInt32() throws IOException { + return readRawVarint32(); + } + + @Override + public int readEnum() throws IOException { + return readRawVarint32(); + } + + @Override + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); + } + + @Override + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); + } + + @Override + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + @Override + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + // ================================================================= + + @Override + public int readRawVarint32() throws IOException { + // See implementation notes for readRawVarint64 + fastpath: + { + long tempPos = pos; + + if (limit == tempPos) { + break fastpath; + } + + int x; + if ((x = UnsafeUtil.getByte(tempPos++)) >= 0) { + pos = tempPos; + return x; + } else if (limit - tempPos < 9) { + break fastpath; + } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) { + x ^= (~0 << 7); + } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) { + x ^= (~0 << 7) ^ (~0 << 14); + } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) { + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); + } else { + int y = UnsafeUtil.getByte(tempPos++); + x ^= y << 28; + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); + if (y < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0) { + break fastpath; // Will throw malformedVarint() + } + } + pos = tempPos; + return x; + } + return (int) readRawVarint64SlowPath(); + } + + private void skipRawVarint() throws IOException { + if (remaining() >= MAX_VARINT_SIZE) { + skipRawVarintFastPath(); + } else { + skipRawVarintSlowPath(); + } + } + + private void skipRawVarintFastPath() throws IOException { + for (int i = 0; i < MAX_VARINT_SIZE; i++) { + if (UnsafeUtil.getByte(pos++) >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + private void skipRawVarintSlowPath() throws IOException { + for (int i = 0; i < MAX_VARINT_SIZE; i++) { + if (readRawByte() >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + @Override + public long readRawVarint64() throws IOException { + // Implementation notes: + // + // Optimized for one-byte values, expected to be common. + // The particular code below was selected from various candidates + // empirically, by winning VarintBenchmark. + // + // Sign extension of (signed) Java bytes is usually a nuisance, but + // we exploit it here to more easily obtain the sign of bytes read. + // Instead of cleaning up the sign extension bits by masking eagerly, + // we delay until we find the final (positive) byte, when we clear all + // accumulated bits with one xor. We depend on javac to constant fold. + fastpath: + { + long tempPos = pos; + + if (limit == tempPos) { + break fastpath; + } + + long x; + int y; + if ((y = UnsafeUtil.getByte(tempPos++)) >= 0) { + pos = tempPos; + return y; + } else if (limit - tempPos < 9) { + break fastpath; + } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) { + x = y ^ (~0 << 7); + } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14)); + } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); + } else if ((x = y ^ ((long) UnsafeUtil.getByte(tempPos++) << 28)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 35)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); + } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 42)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); + } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 49)) < 0L) { + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49); + } else { + x ^= ((long) UnsafeUtil.getByte(tempPos++) << 56); + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49) + ^ (~0L << 56); + if (x < 0L) { + if (UnsafeUtil.getByte(tempPos++) < 0L) { + break fastpath; // Will throw malformedVarint() + } + } + } + pos = tempPos; + return x; + } + return readRawVarint64SlowPath(); + } + + @Override + long readRawVarint64SlowPath() throws IOException { + long result = 0; + for (int shift = 0; shift < 64; shift += 7) { + final byte b = readRawByte(); + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + @Override + public int readRawLittleEndian32() throws IOException { + long tempPos = pos; + + if (limit - tempPos < FIXED32_SIZE) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + + pos = tempPos + FIXED32_SIZE; + return (((UnsafeUtil.getByte(tempPos) & 0xff)) + | ((UnsafeUtil.getByte(tempPos + 1) & 0xff) << 8) + | ((UnsafeUtil.getByte(tempPos + 2) & 0xff) << 16) + | ((UnsafeUtil.getByte(tempPos + 3) & 0xff) << 24)); + } + + @Override + public long readRawLittleEndian64() throws IOException { + long tempPos = pos; + + if (limit - tempPos < FIXED64_SIZE) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + + pos = tempPos + FIXED64_SIZE; + return (((UnsafeUtil.getByte(tempPos) & 0xffL)) + | ((UnsafeUtil.getByte(tempPos + 1) & 0xffL) << 8) + | ((UnsafeUtil.getByte(tempPos + 2) & 0xffL) << 16) + | ((UnsafeUtil.getByte(tempPos + 3) & 0xffL) << 24) + | ((UnsafeUtil.getByte(tempPos + 4) & 0xffL) << 32) + | ((UnsafeUtil.getByte(tempPos + 5) & 0xffL) << 40) + | ((UnsafeUtil.getByte(tempPos + 6) & 0xffL) << 48) + | ((UnsafeUtil.getByte(tempPos + 7) & 0xffL) << 56)); + } + + @Override + public void enableAliasing(boolean enabled) { + this.enableAliasing = enabled; + } + + @Override + public void resetSizeCounter() { + startPos = pos; + } + + @Override + public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { + if (byteLimit < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + byteLimit += getTotalBytesRead(); + final int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; + } + + @Override + public void popLimit(final int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + @Override + public int getBytesUntilLimit() { + if (currentLimit == Integer.MAX_VALUE) { + return -1; + } + + return currentLimit - getTotalBytesRead(); + } + + @Override + public boolean isAtEnd() throws IOException { + return pos == limit; + } + + @Override + public int getTotalBytesRead() { + return (int) (pos - startPos); + } + + @Override + public byte readRawByte() throws IOException { + if (pos == limit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + return UnsafeUtil.getByte(pos++); + } + + @Override + public byte[] readRawBytes(final int length) throws IOException { + if (length >= 0 && length <= remaining()) { + byte[] bytes = new byte[length]; + slice(pos, pos + length).get(bytes); + pos += length; + return bytes; + } + + if (length <= 0) { + if (length == 0) { + return EMPTY_BYTE_ARRAY; + } else { + throw InvalidProtocolBufferException.negativeSize(); + } + } + + throw InvalidProtocolBufferException.truncatedMessage(); + } + + @Override + public void skipRawBytes(final int length) throws IOException { + if (length >= 0 && length <= remaining()) { + // We have all the bytes we need already. + pos += length; + return; + } + + if (length < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); + } + + private void recomputeBufferSizeAfterLimit() { + limit += bufferSizeAfterLimit; + final int bufferEnd = (int) (limit - startPos); + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + limit -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } + } + + private int remaining() { + return (int) (limit - pos); + } + + private int bufferPos(long pos) { + return (int) (pos - address); + } + + private ByteBuffer slice(long begin, long end) throws IOException { + int prevPos = buffer.position(); + int prevLimit = buffer.limit(); + try { + buffer.position(bufferPos(begin)); + buffer.limit(bufferPos(end)); + return buffer.slice(); + } catch (IllegalArgumentException e) { + throw InvalidProtocolBufferException.truncatedMessage(); + } finally { + buffer.position(prevPos); + buffer.limit(prevLimit); + } + } } /** - * Tries to read more bytes from the input, making at least {@code n} bytes - * available in the buffer. Caller must ensure that the requested space is - * not yet available, and that the requested space is less than BUFFER_SIZE. - * - * @return {@code true} if the bytes could be made available; {@code false} - * if the end of the stream or the current limit was reached. + * Implementation of {@link CodedInputStream} that uses an {@link InputStream} as the data source. */ - private boolean tryRefillBuffer(int n) throws IOException { - if (bufferPos + n <= bufferSize) { - throw new IllegalStateException( - "refillBuffer() called when " + n + - " bytes were already available in buffer"); + private static final class StreamDecoder extends CodedInputStream { + private final InputStream input; + private final byte[] buffer; + /** bufferSize represents how many bytes are currently filled in the buffer */ + private int bufferSize; + + private int bufferSizeAfterLimit; + private int pos; + private int lastTag; + + /** + * The total number of bytes read before the current buffer. The total bytes read up to the + * current position can be computed as {@code totalBytesRetired + pos}. This value may be + * negative if reading started in the middle of the current buffer (e.g. if the constructor that + * takes a byte array and an offset was used). + */ + private int totalBytesRetired; + + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + + private StreamDecoder(final InputStream input, int bufferSize) { + checkNotNull(input, "input"); + this.input = input; + this.buffer = new byte[bufferSize]; + this.bufferSize = 0; + pos = 0; + totalBytesRetired = 0; } - if (totalBytesRetired + bufferPos + n > currentLimit) { - // Oops, we hit a limit. - return false; + @Override + public int readTag() throws IOException { + if (isAtEnd()) { + lastTag = 0; + return 0; + } + + lastTag = readRawVarint32(); + if (WireFormat.getTagFieldNumber(lastTag) == 0) { + // If we actually read zero (or any tag number corresponding to field + // number zero), that's not a valid tag. + throw InvalidProtocolBufferException.invalidTag(); + } + return lastTag; + } + + @Override + public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { + if (lastTag != value) { + throw InvalidProtocolBufferException.invalidEndTag(); + } + } + + @Override + public int getLastTag() { + return lastTag; + } + + @Override + public boolean skipField(final int tag) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + skipRawVarint(); + return true; + case WireFormat.WIRETYPE_FIXED64: + skipRawBytes(FIXED64_SIZE); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP)); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + skipRawBytes(FIXED32_SIZE); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public boolean skipField(final int tag, final CodedOutputStream output) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + { + long value = readInt64(); + output.writeRawVarint32(tag); + output.writeUInt64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_FIXED64: + { + long value = readRawLittleEndian64(); + output.writeRawVarint32(tag); + output.writeFixed64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + { + ByteString value = readBytes(); + output.writeRawVarint32(tag); + output.writeBytesNoTag(value); + return true; + } + case WireFormat.WIRETYPE_START_GROUP: + { + output.writeRawVarint32(tag); + skipMessage(output); + int endtag = + WireFormat.makeTag( + WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP); + checkLastTagWas(endtag); + output.writeRawVarint32(endtag); + return true; + } + case WireFormat.WIRETYPE_END_GROUP: + { + return false; + } + case WireFormat.WIRETYPE_FIXED32: + { + int value = readRawLittleEndian32(); + output.writeRawVarint32(tag); + output.writeFixed32NoTag(value); + return true; + } + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public void skipMessage() throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag)) { + return; + } + } + } + + @Override + public void skipMessage(CodedOutputStream output) throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag, output)) { + return; + } + } + } + + /** Collects the bytes skipped and returns the data in a ByteBuffer. */ + private class SkippedDataSink implements RefillCallback { + private int lastPos = pos; + private ByteArrayOutputStream byteArrayStream; + + @Override + public void onRefill() { + if (byteArrayStream == null) { + byteArrayStream = new ByteArrayOutputStream(); + } + byteArrayStream.write(buffer, lastPos, pos - lastPos); + lastPos = 0; + } + + /** Gets skipped data in a ByteBuffer. This method should only be called once. */ + ByteBuffer getSkippedData() { + if (byteArrayStream == null) { + return ByteBuffer.wrap(buffer, lastPos, pos - lastPos); + } else { + byteArrayStream.write(buffer, lastPos, pos); + return ByteBuffer.wrap(byteArrayStream.toByteArray()); + } + } + } + + + // ----------------------------------------------------------------- + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + @Override + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public long readInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public int readInt32() throws IOException { + return readRawVarint32(); + } + + @Override + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + @Override + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + @Override + public boolean readBool() throws IOException { + return readRawVarint64() != 0; } - if (refillCallback != null) { - refillCallback.onRefill(); + @Override + public String readString() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= (bufferSize - pos)) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final String result = new String(buffer, pos, size, UTF_8); + pos += size; + return result; + } + if (size == 0) { + return ""; + } + if (size <= bufferSize) { + refillBuffer(size); + String result = new String(buffer, pos, size, UTF_8); + pos += size; + return result; + } + // Slow path: Build a byte array first then copy it. + return new String(readRawBytesSlowPath(size), UTF_8); + } + + @Override + public String readStringRequireUtf8() throws IOException { + final int size = readRawVarint32(); + final byte[] bytes; + final int oldPos = pos; + final int tempPos; + if (size <= (bufferSize - oldPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + bytes = buffer; + pos = oldPos + size; + tempPos = oldPos; + } else if (size == 0) { + return ""; + } else if (size <= bufferSize) { + refillBuffer(size); + bytes = buffer; + tempPos = 0; + pos = tempPos + size; + } else { + // Slow path: Build a byte array first then copy it. + bytes = readRawBytesSlowPath(size); + tempPos = 0; + } + if (ENABLE_CUSTOM_UTF8_DECODE) { + return Utf8.decodeUtf8(bytes, tempPos, size); + } else { + // TODO(martinrb): We could save a pass by validating while decoding. + if (!Utf8.isValidUtf8(bytes, tempPos, tempPos + size)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + return new String(bytes, tempPos, size, UTF_8); + } + } + + @Override + public void readGroup( + final int fieldNumber, + final MessageLite.Builder builder, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + } + + + @Override + public <T extends MessageLite> T readGroup( + final int fieldNumber, + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + return result; + } + + @Deprecated + @Override + public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) + throws IOException { + readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry()); + } + + @Override + public void readMessage( + final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry) + throws IOException { + final int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } + + + @Override + public <T extends MessageLite> T readMessage( + final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException { + int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + return result; + } + + @Override + public ByteString readBytes() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - pos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final ByteString result = ByteString.copyFrom(buffer, pos, size); + pos += size; + return result; + } + if (size == 0) { + return ByteString.EMPTY; + } + return readBytesSlowPath(size); + } + + @Override + public byte[] readByteArray() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - pos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final byte[] result = Arrays.copyOfRange(buffer, pos, pos + size); + pos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return readRawBytesSlowPath(size); + } + } + + @Override + public ByteBuffer readByteBuffer() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - pos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer. + ByteBuffer result = ByteBuffer.wrap(Arrays.copyOfRange(buffer, pos, pos + size)); + pos += size; + return result; + } + if (size == 0) { + return Internal.EMPTY_BYTE_BUFFER; + } + // Slow path: Build a byte array first then copy it. + return ByteBuffer.wrap(readRawBytesSlowPath(size)); + } + + @Override + public int readUInt32() throws IOException { + return readRawVarint32(); + } + + @Override + public int readEnum() throws IOException { + return readRawVarint32(); + } + + @Override + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); + } + + @Override + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); + } + + @Override + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + @Override + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + // ================================================================= + + @Override + public int readRawVarint32() throws IOException { + // See implementation notes for readRawVarint64 + fastpath: + { + int tempPos = pos; + + if (bufferSize == tempPos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + int x; + if ((x = buffer[tempPos++]) >= 0) { + pos = tempPos; + return x; + } else if (bufferSize - tempPos < 9) { + break fastpath; + } else if ((x ^= (buffer[tempPos++] << 7)) < 0) { + x ^= (~0 << 7); + } else if ((x ^= (buffer[tempPos++] << 14)) >= 0) { + x ^= (~0 << 7) ^ (~0 << 14); + } else if ((x ^= (buffer[tempPos++] << 21)) < 0) { + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); + } else { + int y = buffer[tempPos++]; + x ^= y << 28; + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); + if (y < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0 + && buffer[tempPos++] < 0) { + break fastpath; // Will throw malformedVarint() + } + } + pos = tempPos; + return x; + } + return (int) readRawVarint64SlowPath(); + } + + private void skipRawVarint() throws IOException { + if (bufferSize - pos >= MAX_VARINT_SIZE) { + skipRawVarintFastPath(); + } else { + skipRawVarintSlowPath(); + } } - if (input != null) { - int pos = bufferPos; - if (pos > 0) { - if (bufferSize > pos) { - System.arraycopy(buffer, pos, buffer, 0, bufferSize - pos); + private void skipRawVarintFastPath() throws IOException { + for (int i = 0; i < MAX_VARINT_SIZE; i++) { + if (buffer[pos++] >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + private void skipRawVarintSlowPath() throws IOException { + for (int i = 0; i < MAX_VARINT_SIZE; i++) { + if (readRawByte() >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + @Override + public long readRawVarint64() throws IOException { + // Implementation notes: + // + // Optimized for one-byte values, expected to be common. + // The particular code below was selected from various candidates + // empirically, by winning VarintBenchmark. + // + // Sign extension of (signed) Java bytes is usually a nuisance, but + // we exploit it here to more easily obtain the sign of bytes read. + // Instead of cleaning up the sign extension bits by masking eagerly, + // we delay until we find the final (positive) byte, when we clear all + // accumulated bits with one xor. We depend on javac to constant fold. + fastpath: + { + int tempPos = pos; + + if (bufferSize == tempPos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + long x; + int y; + if ((y = buffer[tempPos++]) >= 0) { + pos = tempPos; + return y; + } else if (bufferSize - tempPos < 9) { + break fastpath; + } else if ((y ^= (buffer[tempPos++] << 7)) < 0) { + x = y ^ (~0 << 7); + } else if ((y ^= (buffer[tempPos++] << 14)) >= 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14)); + } else if ((y ^= (buffer[tempPos++] << 21)) < 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); + } else if ((x = y ^ ((long) buffer[tempPos++] << 28)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + } else if ((x ^= ((long) buffer[tempPos++] << 35)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); + } else if ((x ^= ((long) buffer[tempPos++] << 42)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); + } else if ((x ^= ((long) buffer[tempPos++] << 49)) < 0L) { + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49); + } else { + x ^= ((long) buffer[tempPos++] << 56); + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49) + ^ (~0L << 56); + if (x < 0L) { + if (buffer[tempPos++] < 0L) { + break fastpath; // Will throw malformedVarint() + } + } + } + pos = tempPos; + return x; + } + return readRawVarint64SlowPath(); + } + + @Override + long readRawVarint64SlowPath() throws IOException { + long result = 0; + for (int shift = 0; shift < 64; shift += 7) { + final byte b = readRawByte(); + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + @Override + public int readRawLittleEndian32() throws IOException { + int tempPos = pos; + + if (bufferSize - tempPos < FIXED32_SIZE) { + refillBuffer(FIXED32_SIZE); + tempPos = pos; + } + + final byte[] buffer = this.buffer; + pos = tempPos + FIXED32_SIZE; + return (((buffer[tempPos] & 0xff)) + | ((buffer[tempPos + 1] & 0xff) << 8) + | ((buffer[tempPos + 2] & 0xff) << 16) + | ((buffer[tempPos + 3] & 0xff) << 24)); + } + + @Override + public long readRawLittleEndian64() throws IOException { + int tempPos = pos; + + if (bufferSize - tempPos < FIXED64_SIZE) { + refillBuffer(FIXED64_SIZE); + tempPos = pos; + } + + final byte[] buffer = this.buffer; + pos = tempPos + FIXED64_SIZE; + return (((buffer[tempPos] & 0xffL)) + | ((buffer[tempPos + 1] & 0xffL) << 8) + | ((buffer[tempPos + 2] & 0xffL) << 16) + | ((buffer[tempPos + 3] & 0xffL) << 24) + | ((buffer[tempPos + 4] & 0xffL) << 32) + | ((buffer[tempPos + 5] & 0xffL) << 40) + | ((buffer[tempPos + 6] & 0xffL) << 48) + | ((buffer[tempPos + 7] & 0xffL) << 56)); + } + + // ----------------------------------------------------------------- + + @Override + public void enableAliasing(boolean enabled) { + // TODO(nathanmittler): Ideally we should throw here. Do nothing for backward compatibility. + } + + @Override + public void resetSizeCounter() { + totalBytesRetired = -pos; + } + + @Override + public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { + if (byteLimit < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + byteLimit += totalBytesRetired + pos; + final int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; + } + + private void recomputeBufferSizeAfterLimit() { + bufferSize += bufferSizeAfterLimit; + final int bufferEnd = totalBytesRetired + bufferSize; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + bufferSize -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } + } + + @Override + public void popLimit(final int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + @Override + public int getBytesUntilLimit() { + if (currentLimit == Integer.MAX_VALUE) { + return -1; + } + + final int currentAbsolutePosition = totalBytesRetired + pos; + return currentLimit - currentAbsolutePosition; + } + + @Override + public boolean isAtEnd() throws IOException { + return pos == bufferSize && !tryRefillBuffer(1); + } + + @Override + public int getTotalBytesRead() { + return totalBytesRetired + pos; + } + + private interface RefillCallback { + void onRefill(); + } + + private RefillCallback refillCallback = null; + + /** + * Reads more bytes from the input, making at least {@code n} bytes available in the buffer. + * Caller must ensure that the requested space is not yet available, and that the requested + * space is less than BUFFER_SIZE. + * + * @throws InvalidProtocolBufferException The end of the stream or the current limit was + * reached. + */ + private void refillBuffer(int n) throws IOException { + if (!tryRefillBuffer(n)) { + // We have to distinguish the exception between sizeLimitExceeded and truncatedMessage. So + // we just throw an sizeLimitExceeded exception here if it exceeds the sizeLimit + if (n > sizeLimit - totalBytesRetired - pos) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } else { + throw InvalidProtocolBufferException.truncatedMessage(); + } + } + } + + /** + * Tries to read more bytes from the input, making at least {@code n} bytes available in the + * buffer. Caller must ensure that the requested space is not yet available, and that the + * requested space is less than BUFFER_SIZE. + * + * @return {@code true} If the bytes could be made available; {@code false} 1. Current at the + * end of the stream 2. The current limit was reached 3. The total size limit was reached + */ + private boolean tryRefillBuffer(int n) throws IOException { + if (pos + n <= bufferSize) { + throw new IllegalStateException( + "refillBuffer() called when " + n + " bytes were already available in buffer"); + } + + // Check whether the size of total message needs to read is bigger than the size limit. + // We shouldn't throw an exception here as isAtEnd() function needs to get this function's + // return as the result. + if (n > sizeLimit - totalBytesRetired - pos) { + return false; + } + + // Shouldn't throw the exception here either. + if (totalBytesRetired + pos + n > currentLimit) { + // Oops, we hit a limit. + return false; + } + + if (refillCallback != null) { + refillCallback.onRefill(); + } + + int tempPos = pos; + if (tempPos > 0) { + if (bufferSize > tempPos) { + System.arraycopy(buffer, tempPos, buffer, 0, bufferSize - tempPos); } - totalBytesRetired += pos; - bufferSize -= pos; - bufferPos = 0; + totalBytesRetired += tempPos; + bufferSize -= tempPos; + pos = 0; } - int bytesRead = input.read(buffer, bufferSize, buffer.length - bufferSize); + // Here we should refill the buffer as many bytes as possible. + int bytesRead = + input.read( + buffer, + bufferSize, + Math.min( + // the size of allocated but unused bytes in the buffer + buffer.length - bufferSize, + // do not exceed the total bytes limit + sizeLimit - totalBytesRetired - bufferSize)); if (bytesRead == 0 || bytesRead < -1 || bytesRead > buffer.length) { throw new IllegalStateException( - "InputStream#read(byte[]) returned invalid result: " + bytesRead + - "\nThe InputStream implementation is buggy."); + "InputStream#read(byte[]) returned invalid result: " + + bytesRead + + "\nThe InputStream implementation is buggy."); } if (bytesRead > 0) { bufferSize += bytesRead; - // Integer-overflow-conscious check against sizeLimit - if (totalBytesRetired + n - sizeLimit > 0) { - throw InvalidProtocolBufferException.sizeLimitExceeded(); - } recomputeBufferSizeAfterLimit(); return (bufferSize >= n) ? true : tryRefillBuffer(n); } + + return false; } - return false; - } + @Override + public byte readRawByte() throws IOException { + if (pos == bufferSize) { + refillBuffer(1); + } + return buffer[pos++]; + } - /** - * Read one byte from the input. - * - * @throws InvalidProtocolBufferException The end of the stream or the current - * limit was reached. - */ - public byte readRawByte() throws IOException { - if (bufferPos == bufferSize) { - refillBuffer(1); + @Override + public byte[] readRawBytes(final int size) throws IOException { + final int tempPos = pos; + if (size <= (bufferSize - tempPos) && size > 0) { + pos = tempPos + size; + return Arrays.copyOfRange(buffer, tempPos, tempPos + size); + } else { + return readRawBytesSlowPath(size); + } } - return buffer[bufferPos++]; - } - /** - * Read a fixed size of bytes from the input. - * - * @throws InvalidProtocolBufferException The end of the stream or the current - * limit was reached. - */ - public byte[] readRawBytes(final int size) throws IOException { - final int pos = bufferPos; - if (size <= (bufferSize - pos) && size > 0) { - bufferPos = pos + size; - return Arrays.copyOfRange(buffer, pos, pos + size); - } else { - return readRawBytesSlowPath(size); + /** + * Exactly like readRawBytes, but caller must have already checked the fast path: (size <= + * (bufferSize - pos) && size > 0) + */ + private byte[] readRawBytesSlowPath(final int size) throws IOException { + // Attempt to read the data in one byte array when it's safe to do. + byte[] result = readRawBytesSlowPathOneChunk(size); + if (result != null) { + return result; + } + + final int originalBufferPos = pos; + final int bufferedBytes = bufferSize - pos; + + // Mark the current buffer consumed. + totalBytesRetired += bufferSize; + pos = 0; + bufferSize = 0; + + // Determine the number of bytes we need to read from the input stream. + int sizeLeft = size - bufferedBytes; + + // The size is very large. For security reasons we read them in small + // chunks. + List<byte[]> chunks = readRawBytesSlowPathRemainingChunks(sizeLeft); + + // OK, got everything. Now concatenate it all into one buffer. + final byte[] bytes = new byte[size]; + + // Start by copying the leftover bytes from this.buffer. + System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); + + // And now all the chunks. + int tempPos = bufferedBytes; + for (final byte[] chunk : chunks) { + System.arraycopy(chunk, 0, bytes, tempPos, chunk.length); + tempPos += chunk.length; + } + + // Done. + return bytes; + } + + /** + * Attempts to read the data in one byte array when it's safe to do. Returns null if the size to + * read is too large and needs to be allocated in smaller chunks for security reasons. + */ + private byte[] readRawBytesSlowPathOneChunk(final int size) throws IOException { + if (size == 0) { + return Internal.EMPTY_BYTE_ARRAY; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + + // Integer-overflow-conscious check that the message size so far has not exceeded sizeLimit. + int currentMessageSize = totalBytesRetired + pos + size; + if (currentMessageSize - sizeLimit > 0) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } + + // Verify that the message size so far has not exceeded currentLimit. + if (currentMessageSize > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - pos); + throw InvalidProtocolBufferException.truncatedMessage(); + } + + final int bufferedBytes = bufferSize - pos; + // Determine the number of bytes we need to read from the input stream. + int sizeLeft = size - bufferedBytes; + // TODO(nathanmittler): Consider using a value larger than DEFAULT_BUFFER_SIZE. + if (sizeLeft < DEFAULT_BUFFER_SIZE || sizeLeft <= input.available()) { + // Either the bytes we need are known to be available, or the required buffer is + // within an allowed threshold - go ahead and allocate the buffer now. + final byte[] bytes = new byte[size]; + + // Copy all of the buffered bytes to the result buffer. + System.arraycopy(buffer, pos, bytes, 0, bufferedBytes); + totalBytesRetired += bufferSize; + pos = 0; + bufferSize = 0; + + // Fill the remaining bytes from the input stream. + int tempPos = bufferedBytes; + while (tempPos < bytes.length) { + int n = input.read(bytes, tempPos, size - tempPos); + if (n == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + totalBytesRetired += n; + tempPos += n; + } + + return bytes; + } + + return null; + } + + /** Reads the remaining data in small chunks from the input stream. */ + private List<byte[]> readRawBytesSlowPathRemainingChunks(int sizeLeft) throws IOException { + // The size is very large. For security reasons, we can't allocate the + // entire byte array yet. The size comes directly from the input, so a + // maliciously-crafted message could provide a bogus very large size in + // order to trick the app into allocating a lot of memory. We avoid this + // by allocating and reading only a small chunk at a time, so that the + // malicious message must actually *be* extremely large to cause + // problems. Meanwhile, we limit the allowed size of a message elsewhere. + final List<byte[]> chunks = new ArrayList<byte[]>(); + + while (sizeLeft > 0) { + // TODO(nathanmittler): Consider using a value larger than DEFAULT_BUFFER_SIZE. + final byte[] chunk = new byte[Math.min(sizeLeft, DEFAULT_BUFFER_SIZE)]; + int tempPos = 0; + while (tempPos < chunk.length) { + final int n = input.read(chunk, tempPos, chunk.length - tempPos); + if (n == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + totalBytesRetired += n; + tempPos += n; + } + sizeLeft -= chunk.length; + chunks.add(chunk); + } + + return chunks; + } + + /** + * Like readBytes, but caller must have already checked the fast path: (size <= (bufferSize - + * pos) && size > 0 || size == 0) + */ + private ByteString readBytesSlowPath(final int size) throws IOException { + final byte[] result = readRawBytesSlowPathOneChunk(size); + if (result != null) { + return ByteString.wrap(result); + } + + final int originalBufferPos = pos; + final int bufferedBytes = bufferSize - pos; + + // Mark the current buffer consumed. + totalBytesRetired += bufferSize; + pos = 0; + bufferSize = 0; + + // Determine the number of bytes we need to read from the input stream. + int sizeLeft = size - bufferedBytes; + + // The size is very large. For security reasons we read them in small + // chunks. + List<byte[]> chunks = readRawBytesSlowPathRemainingChunks(sizeLeft); + + // Wrap the byte arrays into a single ByteString. + List<ByteString> byteStrings = new ArrayList<ByteString>(1 + chunks.size()); + byteStrings.add(ByteString.copyFrom(buffer, originalBufferPos, bufferedBytes)); + for (byte[] chunk : chunks) { + byteStrings.add(ByteString.wrap(chunk)); + } + return ByteString.copyFrom(byteStrings); + } + + @Override + public void skipRawBytes(final int size) throws IOException { + if (size <= (bufferSize - pos) && size >= 0) { + // We have all the bytes we need already. + pos += size; + } else { + skipRawBytesSlowPath(size); + } + } + + /** + * Exactly like skipRawBytes, but caller must have already checked the fast path: (size <= + * (bufferSize - pos) && size >= 0) + */ + private void skipRawBytesSlowPath(final int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + + if (totalBytesRetired + pos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - pos); + // Then fail. + throw InvalidProtocolBufferException.truncatedMessage(); + } + + // Skipping more bytes than are in the buffer. First skip what we have. + int tempPos = bufferSize - pos; + pos = bufferSize; + + // Keep refilling the buffer until we get to the point we wanted to skip to. + // This has the side effect of ensuring the limits are updated correctly. + refillBuffer(1); + while (size - tempPos > bufferSize) { + tempPos += bufferSize; + pos = bufferSize; + refillBuffer(1); + } + + pos = size - tempPos; } } /** - * Exactly like readRawBytes, but caller must have already checked the fast - * path: (size <= (bufferSize - pos) && size > 0) + * Implementation of {@link CodedInputStream} that uses an {@link Iterable <ByteBuffer>} as the + * data source. Requires the use of {@code sun.misc.Unsafe} to perform fast reads on the buffer. */ - private byte[] readRawBytesSlowPath(final int size) throws IOException { - if (size <= 0) { + private static final class IterableDirectByteBufferDecoder extends CodedInputStream { + /** The object that need to decode. */ + private Iterable<ByteBuffer> input; + /** The {@link Iterator} with type {@link ByteBuffer} of {@code input} */ + private Iterator<ByteBuffer> iterator; + /** The current ByteBuffer; */ + private ByteBuffer currentByteBuffer; + /** + * If {@code true}, indicates that all the buffer are backing a {@link ByteString} and are + * therefore considered to be an immutable input source. + */ + private boolean immutable; + /** + * If {@code true}, indicates that calls to read {@link ByteString} or {@code byte[]} + * <strong>may</strong> return slices of the underlying buffer, rather than copies. + */ + private boolean enableAliasing; + /** The global total message length limit */ + private int totalBufferSize; + /** The amount of available data in the input beyond {@link #currentLimit}. */ + private int bufferSizeAfterCurrentLimit; + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + /** The last tag that was read from this stream. */ + private int lastTag; + /** Total Bytes have been Read from the {@link Iterable} {@link ByteBuffer} */ + private int totalBytesRead; + /** The start position offset of the whole message, used as to reset the totalBytesRead */ + private int startOffset; + /** The current position for current ByteBuffer */ + private long currentByteBufferPos; + + private long currentByteBufferStartPos; + /** + * If the current ByteBuffer is unsafe-direct based, currentAddress is the start address of this + * ByteBuffer; otherwise should be zero. + */ + private long currentAddress; + /** The limit position for current ByteBuffer */ + private long currentByteBufferLimit; + + /** + * The constructor of {@code Iterable<ByteBuffer>} decoder. + * + * @param inputBufs The input data. + * @param size The total size of the input data. + * @param immutableFlag whether the input data is immutable. + */ + private IterableDirectByteBufferDecoder( + Iterable<ByteBuffer> inputBufs, int size, boolean immutableFlag) { + totalBufferSize = size; + input = inputBufs; + iterator = input.iterator(); + immutable = immutableFlag; + startOffset = totalBytesRead = 0; if (size == 0) { - return Internal.EMPTY_BYTE_ARRAY; + currentByteBuffer = EMPTY_BYTE_BUFFER; + currentByteBufferPos = 0; + currentByteBufferStartPos = 0; + currentByteBufferLimit = 0; + currentAddress = 0; } else { + tryGetNextByteBuffer(); + } + } + + /** To get the next ByteBuffer from {@code input}, and then update the parameters */ + private void getNextByteBuffer() throws InvalidProtocolBufferException { + if (!iterator.hasNext()) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + tryGetNextByteBuffer(); + } + + private void tryGetNextByteBuffer() { + currentByteBuffer = iterator.next(); + totalBytesRead += (int) (currentByteBufferPos - currentByteBufferStartPos); + currentByteBufferPos = currentByteBuffer.position(); + currentByteBufferStartPos = currentByteBufferPos; + currentByteBufferLimit = currentByteBuffer.limit(); + currentAddress = UnsafeUtil.addressOffset(currentByteBuffer); + currentByteBufferPos += currentAddress; + currentByteBufferStartPos += currentAddress; + currentByteBufferLimit += currentAddress; + } + + @Override + public int readTag() throws IOException { + if (isAtEnd()) { + lastTag = 0; + return 0; + } + + lastTag = readRawVarint32(); + if (WireFormat.getTagFieldNumber(lastTag) == 0) { + // If we actually read zero (or any tag number corresponding to field + // number zero), that's not a valid tag. + throw InvalidProtocolBufferException.invalidTag(); + } + return lastTag; + } + + @Override + public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { + if (lastTag != value) { + throw InvalidProtocolBufferException.invalidEndTag(); + } + } + + @Override + public int getLastTag() { + return lastTag; + } + + @Override + public boolean skipField(final int tag) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + skipRawVarint(); + return true; + case WireFormat.WIRETYPE_FIXED64: + skipRawBytes(FIXED64_SIZE); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP)); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + skipRawBytes(FIXED32_SIZE); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public boolean skipField(final int tag, final CodedOutputStream output) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + { + long value = readInt64(); + output.writeRawVarint32(tag); + output.writeUInt64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_FIXED64: + { + long value = readRawLittleEndian64(); + output.writeRawVarint32(tag); + output.writeFixed64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + { + ByteString value = readBytes(); + output.writeRawVarint32(tag); + output.writeBytesNoTag(value); + return true; + } + case WireFormat.WIRETYPE_START_GROUP: + { + output.writeRawVarint32(tag); + skipMessage(output); + int endtag = + WireFormat.makeTag( + WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP); + checkLastTagWas(endtag); + output.writeRawVarint32(endtag); + return true; + } + case WireFormat.WIRETYPE_END_GROUP: + { + return false; + } + case WireFormat.WIRETYPE_FIXED32: + { + int value = readRawLittleEndian32(); + output.writeRawVarint32(tag); + output.writeFixed32NoTag(value); + return true; + } + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + @Override + public void skipMessage() throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag)) { + return; + } + } + } + + @Override + public void skipMessage(CodedOutputStream output) throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag, output)) { + return; + } + } + } + + // ----------------------------------------------------------------- + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + @Override + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public long readInt64() throws IOException { + return readRawVarint64(); + } + + @Override + public int readInt32() throws IOException { + return readRawVarint32(); + } + + @Override + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + @Override + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + @Override + public boolean readBool() throws IOException { + return readRawVarint64() != 0; + } + + @Override + public String readString() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= currentByteBufferLimit - currentByteBufferPos) { + byte[] bytes = new byte[size]; + UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size); + String result = new String(bytes, UTF_8); + currentByteBufferPos += size; + return result; + } else if (size > 0 && size <= remaining()) { + // TODO(yilunchong): To use an underlying bytes[] instead of allocating a new bytes[] + byte[] bytes = new byte[size]; + readRawBytesTo(bytes, 0, size); + String result = new String(bytes, UTF_8); + return result; + } + + if (size == 0) { + return ""; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); + } + + @Override + public String readStringRequireUtf8() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= currentByteBufferLimit - currentByteBufferPos) { + if (ENABLE_CUSTOM_UTF8_DECODE) { + final int bufferPos = (int) (currentByteBufferPos - currentByteBufferStartPos); + String result = Utf8.decodeUtf8(currentByteBuffer, bufferPos, size); + currentByteBufferPos += size; + return result; + } else { + byte[] bytes = new byte[size]; + UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size); + if (!Utf8.isValidUtf8(bytes)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + String result = new String(bytes, UTF_8); + currentByteBufferPos += size; + return result; + } + } + if (size >= 0 && size <= remaining()) { + byte[] bytes = new byte[size]; + readRawBytesTo(bytes, 0, size); + if (ENABLE_CUSTOM_UTF8_DECODE) { + return Utf8.decodeUtf8(bytes, 0, size); + } else { + if (!Utf8.isValidUtf8(bytes)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + String result = new String(bytes, UTF_8); + return result; + } + } + + if (size == 0) { + return ""; + } + if (size <= 0) { throw InvalidProtocolBufferException.negativeSize(); } + throw InvalidProtocolBufferException.truncatedMessage(); } - // Verify that the message size so far has not exceeded sizeLimit. - int currentMessageSize = totalBytesRetired + bufferPos + size; - if (currentMessageSize > sizeLimit) { - throw InvalidProtocolBufferException.sizeLimitExceeded(); + @Override + public void readGroup( + final int fieldNumber, + final MessageLite.Builder builder, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; } - // Verify that the message size so far has not exceeded currentLimit. - if (currentMessageSize > currentLimit) { - // Read to the end of the stream anyway. - skipRawBytes(currentLimit - totalBytesRetired - bufferPos); + + @Override + public <T extends MessageLite> T readGroup( + final int fieldNumber, + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + return result; + } + + @Deprecated + @Override + public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) + throws IOException { + readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry()); + } + + @Override + public void readMessage( + final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry) + throws IOException { + final int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } + + + @Override + public <T extends MessageLite> T readMessage( + final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException { + int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + return result; + } + + @Override + public ByteString readBytes() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= currentByteBufferLimit - currentByteBufferPos) { + if (immutable && enableAliasing) { + final int idx = (int) (currentByteBufferPos - currentAddress); + final ByteString result = ByteString.wrap(slice(idx, idx + size)); + currentByteBufferPos += size; + return result; + } else { + byte[] bytes; + bytes = new byte[size]; + UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size); + currentByteBufferPos += size; + return ByteString.wrap(bytes); + } + } else if (size > 0 && size <= remaining()) { + byte[] temp = new byte[size]; + readRawBytesTo(temp, 0, size); + return ByteString.wrap(temp); + } + + if (size == 0) { + return ByteString.EMPTY; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } throw InvalidProtocolBufferException.truncatedMessage(); } - // We need the input stream to proceed. - if (input == null) { + @Override + public byte[] readByteArray() throws IOException { + return readRawBytes(readRawVarint32()); + } + + @Override + public ByteBuffer readByteBuffer() throws IOException { + final int size = readRawVarint32(); + if (size > 0 && size <= currentRemaining()) { + if (!immutable && enableAliasing) { + currentByteBufferPos += size; + return slice( + (int) (currentByteBufferPos - currentAddress - size), + (int) (currentByteBufferPos - currentAddress)); + } else { + byte[] bytes = new byte[size]; + UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size); + currentByteBufferPos += size; + return ByteBuffer.wrap(bytes); + } + } else if (size > 0 && size <= remaining()) { + byte[] temp = new byte[size]; + readRawBytesTo(temp, 0, size); + return ByteBuffer.wrap(temp); + } + + if (size == 0) { + return EMPTY_BYTE_BUFFER; + } + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } throw InvalidProtocolBufferException.truncatedMessage(); } - final int originalBufferPos = bufferPos; - final int bufferedBytes = bufferSize - bufferPos; + @Override + public int readUInt32() throws IOException { + return readRawVarint32(); + } - // Mark the current buffer consumed. - totalBytesRetired += bufferSize; - bufferPos = 0; - bufferSize = 0; + @Override + public int readEnum() throws IOException { + return readRawVarint32(); + } - // Determine the number of bytes we need to read from the input stream. - int sizeLeft = size - bufferedBytes; - // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. - if (sizeLeft < BUFFER_SIZE || sizeLeft <= input.available()) { - // Either the bytes we need are known to be available, or the required buffer is - // within an allowed threshold - go ahead and allocate the buffer now. - final byte[] bytes = new byte[size]; + @Override + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); + } - // Copy all of the buffered bytes to the result buffer. - System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); + @Override + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); + } - // Fill the remaining bytes from the input stream. - int pos = bufferedBytes; - while (pos < bytes.length) { - int n = input.read(bytes, pos, size - pos); - if (n == -1) { - throw InvalidProtocolBufferException.truncatedMessage(); + @Override + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + @Override + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + @Override + public int readRawVarint32() throws IOException { + fastpath: + { + long tempPos = currentByteBufferPos; + + if (currentByteBufferLimit == currentByteBufferPos) { + break fastpath; } - totalBytesRetired += n; - pos += n; + + int x; + if ((x = UnsafeUtil.getByte(tempPos++)) >= 0) { + currentByteBufferPos++; + return x; + } else if (currentByteBufferLimit - currentByteBufferPos < 10) { + break fastpath; + } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) { + x ^= (~0 << 7); + } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) { + x ^= (~0 << 7) ^ (~0 << 14); + } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) { + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); + } else { + int y = UnsafeUtil.getByte(tempPos++); + x ^= y << 28; + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); + if (y < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0 + && UnsafeUtil.getByte(tempPos++) < 0) { + break fastpath; // Will throw malformedVarint() + } + } + currentByteBufferPos = tempPos; + return x; } + return (int) readRawVarint64SlowPath(); + } - return bytes; + @Override + public long readRawVarint64() throws IOException { + fastpath: + { + long tempPos = currentByteBufferPos; + + if (currentByteBufferLimit == currentByteBufferPos) { + break fastpath; + } + + long x; + int y; + if ((y = UnsafeUtil.getByte(tempPos++)) >= 0) { + currentByteBufferPos++; + return y; + } else if (currentByteBufferLimit - currentByteBufferPos < 10) { + break fastpath; + } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) { + x = y ^ (~0 << 7); + } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14)); + } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); + } else if ((x = y ^ ((long) UnsafeUtil.getByte(tempPos++) << 28)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 35)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); + } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 42)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); + } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 49)) < 0L) { + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49); + } else { + x ^= ((long) UnsafeUtil.getByte(tempPos++) << 56); + x ^= + (~0L << 7) + ^ (~0L << 14) + ^ (~0L << 21) + ^ (~0L << 28) + ^ (~0L << 35) + ^ (~0L << 42) + ^ (~0L << 49) + ^ (~0L << 56); + if (x < 0L) { + if (UnsafeUtil.getByte(tempPos++) < 0L) { + break fastpath; // Will throw malformedVarint() + } + } + } + currentByteBufferPos = tempPos; + return x; + } + return readRawVarint64SlowPath(); } - // The size is very large. For security reasons, we can't allocate the - // entire byte array yet. The size comes directly from the input, so a - // maliciously-crafted message could provide a bogus very large size in - // order to trick the app into allocating a lot of memory. We avoid this - // by allocating and reading only a small chunk at a time, so that the - // malicious message must actually *be* extremely large to cause - // problems. Meanwhile, we limit the allowed size of a message elsewhere. - final List<byte[]> chunks = new ArrayList<byte[]>(); - - while (sizeLeft > 0) { - // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. - final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; - int pos = 0; - while (pos < chunk.length) { - final int n = input.read(chunk, pos, chunk.length - pos); - if (n == -1) { - throw InvalidProtocolBufferException.truncatedMessage(); + @Override + long readRawVarint64SlowPath() throws IOException { + long result = 0; + for (int shift = 0; shift < 64; shift += 7) { + final byte b = readRawByte(); + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; } - totalBytesRetired += n; - pos += n; } - sizeLeft -= chunk.length; - chunks.add(chunk); + throw InvalidProtocolBufferException.malformedVarint(); } - // OK, got everything. Now concatenate it all into one buffer. - final byte[] bytes = new byte[size]; + @Override + public int readRawLittleEndian32() throws IOException { + if (currentRemaining() >= FIXED32_SIZE) { + long tempPos = currentByteBufferPos; + currentByteBufferPos += FIXED32_SIZE; + return (((UnsafeUtil.getByte(tempPos) & 0xff)) + | ((UnsafeUtil.getByte(tempPos + 1) & 0xff) << 8) + | ((UnsafeUtil.getByte(tempPos + 2) & 0xff) << 16) + | ((UnsafeUtil.getByte(tempPos + 3) & 0xff) << 24)); + } + return ((readRawByte() & 0xff) + | ((readRawByte() & 0xff) << 8) + | ((readRawByte() & 0xff) << 16) + | ((readRawByte() & 0xff) << 24)); + } - // Start by copying the leftover bytes from this.buffer. - System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); + @Override + public long readRawLittleEndian64() throws IOException { + if (currentRemaining() >= FIXED64_SIZE) { + long tempPos = currentByteBufferPos; + currentByteBufferPos += FIXED64_SIZE; + return (((UnsafeUtil.getByte(tempPos) & 0xffL)) + | ((UnsafeUtil.getByte(tempPos + 1) & 0xffL) << 8) + | ((UnsafeUtil.getByte(tempPos + 2) & 0xffL) << 16) + | ((UnsafeUtil.getByte(tempPos + 3) & 0xffL) << 24) + | ((UnsafeUtil.getByte(tempPos + 4) & 0xffL) << 32) + | ((UnsafeUtil.getByte(tempPos + 5) & 0xffL) << 40) + | ((UnsafeUtil.getByte(tempPos + 6) & 0xffL) << 48) + | ((UnsafeUtil.getByte(tempPos + 7) & 0xffL) << 56)); + } + return ((readRawByte() & 0xffL) + | ((readRawByte() & 0xffL) << 8) + | ((readRawByte() & 0xffL) << 16) + | ((readRawByte() & 0xffL) << 24) + | ((readRawByte() & 0xffL) << 32) + | ((readRawByte() & 0xffL) << 40) + | ((readRawByte() & 0xffL) << 48) + | ((readRawByte() & 0xffL) << 56)); + } - // And now all the chunks. - int pos = bufferedBytes; - for (final byte[] chunk : chunks) { - System.arraycopy(chunk, 0, bytes, pos, chunk.length); - pos += chunk.length; + @Override + public void enableAliasing(boolean enabled) { + this.enableAliasing = enabled; } - // Done. - return bytes; - } + @Override + public void resetSizeCounter() { + startOffset = (int) (totalBytesRead + currentByteBufferPos - currentByteBufferStartPos); + } - /** - * Reads and discards {@code size} bytes. - * - * @throws InvalidProtocolBufferException The end of the stream or the current - * limit was reached. - */ - public void skipRawBytes(final int size) throws IOException { - if (size <= (bufferSize - bufferPos) && size >= 0) { - // We have all the bytes we need already. - bufferPos += size; - } else { - skipRawBytesSlowPath(size); + @Override + public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { + if (byteLimit < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + byteLimit += getTotalBytesRead(); + final int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; } - } - /** - * Exactly like skipRawBytes, but caller must have already checked the fast - * path: (size <= (bufferSize - pos) && size >= 0) - */ - private void skipRawBytesSlowPath(final int size) throws IOException { - if (size < 0) { - throw InvalidProtocolBufferException.negativeSize(); + private void recomputeBufferSizeAfterLimit() { + totalBufferSize += bufferSizeAfterCurrentLimit; + final int bufferEnd = totalBufferSize - startOffset; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterCurrentLimit = bufferEnd - currentLimit; + totalBufferSize -= bufferSizeAfterCurrentLimit; + } else { + bufferSizeAfterCurrentLimit = 0; + } } - if (totalBytesRetired + bufferPos + size > currentLimit) { - // Read to the end of the stream anyway. - skipRawBytes(currentLimit - totalBytesRetired - bufferPos); - // Then fail. + @Override + public void popLimit(final int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + @Override + public int getBytesUntilLimit() { + if (currentLimit == Integer.MAX_VALUE) { + return -1; + } + + return currentLimit - getTotalBytesRead(); + } + + @Override + public boolean isAtEnd() throws IOException { + return totalBytesRead + currentByteBufferPos - currentByteBufferStartPos == totalBufferSize; + } + + @Override + public int getTotalBytesRead() { + return (int) + (totalBytesRead - startOffset + currentByteBufferPos - currentByteBufferStartPos); + } + + @Override + public byte readRawByte() throws IOException { + if (currentRemaining() == 0) { + getNextByteBuffer(); + } + return UnsafeUtil.getByte(currentByteBufferPos++); + } + + @Override + public byte[] readRawBytes(final int length) throws IOException { + if (length >= 0 && length <= currentRemaining()) { + byte[] bytes = new byte[length]; + UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, length); + currentByteBufferPos += length; + return bytes; + } + if (length >= 0 && length <= remaining()) { + byte[] bytes = new byte[length]; + readRawBytesTo(bytes, 0, length); + return bytes; + } + + if (length <= 0) { + if (length == 0) { + return EMPTY_BYTE_ARRAY; + } else { + throw InvalidProtocolBufferException.negativeSize(); + } + } + throw InvalidProtocolBufferException.truncatedMessage(); } - // Skipping more bytes than are in the buffer. First skip what we have. - int pos = bufferSize - bufferPos; - bufferPos = bufferSize; + /** + * Try to get raw bytes from {@code input} with the size of {@code length} and copy to {@code + * bytes} array. If the size is bigger than the number of remaining bytes in the input, then + * throw {@code truncatedMessage} exception. + * + * @param bytes + * @param offset + * @param length + * @throws IOException + */ + private void readRawBytesTo(byte[] bytes, int offset, final int length) throws IOException { + if (length >= 0 && length <= remaining()) { + int l = length; + while (l > 0) { + if (currentRemaining() == 0) { + getNextByteBuffer(); + } + int bytesToCopy = Math.min(l, (int) currentRemaining()); + UnsafeUtil.copyMemory(currentByteBufferPos, bytes, length - l + offset, bytesToCopy); + l -= bytesToCopy; + currentByteBufferPos += bytesToCopy; + } + return; + } - // Keep refilling the buffer until we get to the point we wanted to skip to. - // This has the side effect of ensuring the limits are updated correctly. - refillBuffer(1); - while (size - pos > bufferSize) { - pos += bufferSize; - bufferPos = bufferSize; - refillBuffer(1); + if (length <= 0) { + if (length == 0) { + return; + } else { + throw InvalidProtocolBufferException.negativeSize(); + } + } + throw InvalidProtocolBufferException.truncatedMessage(); + } + + @Override + public void skipRawBytes(final int length) throws IOException { + if (length >= 0 + && length + <= (totalBufferSize + - totalBytesRead + - currentByteBufferPos + + currentByteBufferStartPos)) { + // We have all the bytes we need already. + int l = length; + while (l > 0) { + if (currentRemaining() == 0) { + getNextByteBuffer(); + } + int rl = Math.min(l, (int) currentRemaining()); + l -= rl; + currentByteBufferPos += rl; + } + return; + } + + if (length < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + throw InvalidProtocolBufferException.truncatedMessage(); + } + + // TODO: optimize to fastpath + private void skipRawVarint() throws IOException { + for (int i = 0; i < MAX_VARINT_SIZE; i++) { + if (readRawByte() >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); } - bufferPos = size - pos; + /** + * Try to get the number of remaining bytes in {@code input}. + * + * @return the number of remaining bytes in {@code input}. + */ + private int remaining() { + return (int) + (totalBufferSize - totalBytesRead - currentByteBufferPos + currentByteBufferStartPos); + } + + /** + * Try to get the number of remaining bytes in {@code currentByteBuffer}. + * + * @return the number of remaining bytes in {@code currentByteBuffer} + */ + private long currentRemaining() { + return (currentByteBufferLimit - currentByteBufferPos); + } + + private ByteBuffer slice(int begin, int end) throws IOException { + int prevPos = currentByteBuffer.position(); + int prevLimit = currentByteBuffer.limit(); + try { + currentByteBuffer.position(begin); + currentByteBuffer.limit(end); + return currentByteBuffer.slice(); + } catch (IllegalArgumentException e) { + throw InvalidProtocolBufferException.truncatedMessage(); + } finally { + currentByteBuffer.position(prevPos); + currentByteBuffer.limit(prevLimit); + } + } } } diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java index d8ebad21..7b1ac651 100644 --- a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java +++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -30,11 +30,17 @@ package com.google.protobuf; -import com.google.protobuf.Utf8.UnpairedSurrogateException; +import static com.google.protobuf.WireFormat.FIXED32_SIZE; +import static com.google.protobuf.WireFormat.FIXED64_SIZE; +import static com.google.protobuf.WireFormat.MAX_VARINT_SIZE; +import static java.lang.Math.max; +import com.google.protobuf.Utf8.UnpairedSurrogateException; import java.io.IOException; import java.io.OutputStream; +import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,20 +55,16 @@ import java.util.logging.Logger; * you are writing some other format of your own design, use the latter. * * <p>This class is totally unsynchronized. - * - * @author kneton@google.com Kenton Varda */ -public final class CodedOutputStream { - +public abstract class CodedOutputStream extends ByteOutput { private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName()); + private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = UnsafeUtil.hasUnsafeArrayOperations(); - // TODO(dweis): Consider migrating to a ByteBuffer. - private final byte[] buffer; - private final int limit; - private int position; - private int totalBytesWritten = 0; - - private final OutputStream output; + /** + * @deprecated Use {@link #computeFixed32SizeNoTag(int)} instead. + */ + @Deprecated + public static final int LITTLE_ENDIAN_32_SIZE = FIXED32_SIZE; /** * The buffer size used in {@link #newInstance(OutputStream)}. @@ -77,40 +79,33 @@ public final class CodedOutputStream { * CodedOutputStream. */ static int computePreferredBufferSize(int dataLength) { - if (dataLength > DEFAULT_BUFFER_SIZE) return DEFAULT_BUFFER_SIZE; + if (dataLength > DEFAULT_BUFFER_SIZE) { + return DEFAULT_BUFFER_SIZE; + } return dataLength; } - private CodedOutputStream(final byte[] buffer, final int offset, - final int length) { - output = null; - this.buffer = buffer; - position = offset; - limit = offset + length; - } - - private CodedOutputStream(final OutputStream output, final byte[] buffer) { - this.output = output; - this.buffer = buffer; - position = 0; - limit = buffer.length; - } - /** - * Create a new {@code CodedOutputStream} wrapping the given - * {@code OutputStream}. + * Create a new {@code CodedOutputStream} wrapping the given {@code OutputStream}. + * + * <p> NOTE: The provided {@link OutputStream} <strong>MUST NOT</strong> retain access or + * modify the provided byte arrays. Doing so may result in corrupted data, which would be + * difficult to debug. */ public static CodedOutputStream newInstance(final OutputStream output) { return newInstance(output, DEFAULT_BUFFER_SIZE); } /** - * Create a new {@code CodedOutputStream} wrapping the given - * {@code OutputStream} with a given buffer size. + * Create a new {@code CodedOutputStream} wrapping the given {@code OutputStream} with a given + * buffer size. + * + * <p> NOTE: The provided {@link OutputStream} <strong>MUST NOT</strong> retain access or + * modify the provided byte arrays. Doing so may result in corrupted data, which would be + * difficult to debug. */ - public static CodedOutputStream newInstance(final OutputStream output, - final int bufferSize) { - return new CodedOutputStream(output, new byte[bufferSize]); + public static CodedOutputStream newInstance(final OutputStream output, final int bufferSize) { + return new OutputStreamEncoder(output, bufferSize); } /** @@ -131,149 +126,197 @@ public final class CodedOutputStream { * array is faster than writing to an {@code OutputStream}. See also * {@link ByteString#newCodedBuilder}. */ - public static CodedOutputStream newInstance(final byte[] flatArray, - final int offset, - final int length) { - return new CodedOutputStream(flatArray, offset, length); + public static CodedOutputStream newInstance( + final byte[] flatArray, final int offset, final int length) { + return new ArrayEncoder(flatArray, offset, length); + } + + /** Create a new {@code CodedOutputStream} that writes to the given {@link ByteBuffer}. */ + public static CodedOutputStream newInstance(ByteBuffer buffer) { + if (buffer.hasArray()) { + return new HeapNioEncoder(buffer); + } + if (buffer.isDirect() && !buffer.isReadOnly()) { + return UnsafeDirectNioEncoder.isSupported() + ? newUnsafeInstance(buffer) + : newSafeInstance(buffer); + } + throw new IllegalArgumentException("ByteBuffer is read-only"); + } + + /** For testing purposes only. */ + static CodedOutputStream newUnsafeInstance(ByteBuffer buffer) { + return new UnsafeDirectNioEncoder(buffer); + } + + /** For testing purposes only. */ + static CodedOutputStream newSafeInstance(ByteBuffer buffer) { + return new SafeDirectNioEncoder(buffer); } /** - * Create a new {@code CodedOutputStream} that writes to the given ByteBuffer. + * Configures serialization to be deterministic. + * + * <p>The deterministic serialization guarantees that for a given binary, equal (defined by the + * {@code equals()} methods in protos) messages will always be serialized to the same bytes. This + * implies: + * + * <ul> + * <li>repeated serialization of a message will return the same bytes + * <li>different processes of the same binary (which may be executing on different machines) will + * serialize equal messages to the same bytes. + * </ul> + * + * <p>Note the deterministic serialization is NOT canonical across languages; it is also unstable + * across different builds with schema changes due to unknown fields. Users who need canonical + * serialization, e.g. persistent storage in a canonical form, fingerprinting, etc, should define + * their own canonicalization specification and implement the serializer using reflection APIs + * rather than relying on this API. + * + * <p> Once set, the serializer will: (Note this is an implementation detail and may subject to + * change in the future) + * + * <ul> + * <li> sort map entries by keys in lexicographical order or numerical order. Note: For string + * keys, the order is based on comparing the Unicode value of each character in the strings. + * The order may be different from the deterministic serialization in other languages where + * maps are sorted on the lexicographical order of the UTF8 encoded keys. + * </ul> */ - public static CodedOutputStream newInstance(ByteBuffer byteBuffer) { - return newInstance(byteBuffer, DEFAULT_BUFFER_SIZE); + public void useDeterministicSerialization() { + serializationDeterministic = true; } + boolean isSerializationDeterministic() { + return serializationDeterministic; + } + private boolean serializationDeterministic; + /** - * Create a new {@code CodedOutputStream} that writes to the given ByteBuffer. + * Create a new {@code CodedOutputStream} that writes to the given {@link ByteBuffer}. + * + * @deprecated the size parameter is no longer used since use of an internal buffer is useless + * (and wasteful) when writing to a {@link ByteBuffer}. Use {@link #newInstance(ByteBuffer)} + * instead. */ + @Deprecated public static CodedOutputStream newInstance(ByteBuffer byteBuffer, - int bufferSize) { - return newInstance(new ByteBufferOutputStream(byteBuffer), bufferSize); + @SuppressWarnings("unused") int unused) { + return newInstance(byteBuffer); } - private static class ByteBufferOutputStream extends OutputStream { - private final ByteBuffer byteBuffer; - public ByteBufferOutputStream(ByteBuffer byteBuffer) { - this.byteBuffer = byteBuffer; + /** + * Create a new {@code CodedOutputStream} that writes to the provided {@link ByteOutput}. + * + * <p> NOTE: The {@link ByteOutput} <strong>MUST NOT</strong> modify the provided buffers. Doing + * so may result in corrupted data, which would be difficult to debug. + * + * @param byteOutput the output target for encoded bytes. + * @param bufferSize the size of the internal scratch buffer to be used for string encoding. + * Setting this to {@code 0} will disable buffering, requiring an allocation for each encoded + * string. + */ + static CodedOutputStream newInstance(ByteOutput byteOutput, int bufferSize) { + if (bufferSize < 0) { + throw new IllegalArgumentException("bufferSize must be positive"); } - @Override - public void write(int b) throws IOException { - byteBuffer.put((byte) b); - } + return new ByteOutputEncoder(byteOutput, bufferSize); + } - @Override - public void write(byte[] data, int offset, int length) throws IOException { - byteBuffer.put(data, offset, length); - } + // Disallow construction outside of this class. + private CodedOutputStream() { } // ----------------------------------------------------------------- - /** Write a {@code double} field, including tag, to the stream. */ - public void writeDouble(final int fieldNumber, final double value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); - writeDoubleNoTag(value); - } + /** Encode and write a tag. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeTag(int fieldNumber, int wireType) throws IOException; - /** Write a {@code float} field, including tag, to the stream. */ - public void writeFloat(final int fieldNumber, final float value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); - writeFloatNoTag(value); + /** Write an {@code int32} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeInt32(int fieldNumber, int value) throws IOException; + + /** Write a {@code uint32} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt32(int fieldNumber, int value) throws IOException; + + /** Write a {@code sint32} field, including tag, to the stream. */ + public final void writeSInt32(final int fieldNumber, final int value) throws IOException { + writeUInt32(fieldNumber, encodeZigZag32(value)); } - /** Write a {@code uint64} field, including tag, to the stream. */ - public void writeUInt64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeUInt64NoTag(value); + /** Write a {@code fixed32} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed32(int fieldNumber, int value) throws IOException; + + /** Write an {@code sfixed32} field, including tag, to the stream. */ + public final void writeSFixed32(final int fieldNumber, final int value) throws IOException { + writeFixed32(fieldNumber, value); } /** Write an {@code int64} field, including tag, to the stream. */ - public void writeInt64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeInt64NoTag(value); + public final void writeInt64(final int fieldNumber, final long value) throws IOException { + writeUInt64(fieldNumber, value); } - /** Write an {@code int32} field, including tag, to the stream. */ - public void writeInt32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeInt32NoTag(value); - } + /** Write a {@code uint64} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt64(int fieldNumber, long value) throws IOException; - /** Write a {@code fixed64} field, including tag, to the stream. */ - public void writeFixed64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); - writeFixed64NoTag(value); + /** Write an {@code sint64} field, including tag, to the stream. */ + public final void writeSInt64(final int fieldNumber, final long value) throws IOException { + writeUInt64(fieldNumber, encodeZigZag64(value)); } - /** Write a {@code fixed32} field, including tag, to the stream. */ - public void writeFixed32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); - writeFixed32NoTag(value); - } + /** Write a {@code fixed64} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed64(int fieldNumber, long value) throws IOException; - /** Write a {@code bool} field, including tag, to the stream. */ - public void writeBool(final int fieldNumber, final boolean value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeBoolNoTag(value); + /** Write an {@code sfixed64} field, including tag, to the stream. */ + public final void writeSFixed64(final int fieldNumber, final long value) throws IOException { + writeFixed64(fieldNumber, value); } - /** Write a {@code string} field, including tag, to the stream. */ - public void writeString(final int fieldNumber, final String value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeStringNoTag(value); + /** Write a {@code float} field, including tag, to the stream. */ + public final void writeFloat(final int fieldNumber, final float value) throws IOException { + writeFixed32(fieldNumber, Float.floatToRawIntBits(value)); } - /** Write a {@code group} field, including tag, to the stream. */ - public void writeGroup(final int fieldNumber, final MessageLite value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); - writeGroupNoTag(value); - writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); + /** Write a {@code double} field, including tag, to the stream. */ + public final void writeDouble(final int fieldNumber, final double value) throws IOException { + writeFixed64(fieldNumber, Double.doubleToRawLongBits(value)); } + /** Write a {@code bool} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeBool(int fieldNumber, boolean value) throws IOException; - /** Write an embedded message field, including tag, to the stream. */ - public void writeMessage(final int fieldNumber, final MessageLite value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeMessageNoTag(value); + /** + * Write an enum field, including tag, to the stream. The provided value is the numeric + * value used to represent the enum value on the wire (not the enum ordinal value). + */ + public final void writeEnum(final int fieldNumber, final int value) throws IOException { + writeInt32(fieldNumber, value); } + /** Write a {@code string} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeString(int fieldNumber, String value) throws IOException; /** Write a {@code bytes} field, including tag, to the stream. */ - public void writeBytes(final int fieldNumber, final ByteString value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeBytesNoTag(value); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeBytes(int fieldNumber, ByteString value) throws IOException; /** Write a {@code bytes} field, including tag, to the stream. */ - public void writeByteArray(final int fieldNumber, final byte[] value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeByteArrayNoTag(value); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeByteArray(int fieldNumber, byte[] value) throws IOException; /** Write a {@code bytes} field, including tag, to the stream. */ - public void writeByteArray(final int fieldNumber, - final byte[] value, - final int offset, - final int length) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeByteArrayNoTag(value, offset, length); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeByteArray(int fieldNumber, byte[] value, int offset, int length) + throws IOException; /** * Write a {@code bytes} field, including tag, to the stream. @@ -285,317 +328,223 @@ public final class CodedOutputStream { * of a ByteBuffer, you can call * {@code writeByteBuffer(fieldNumber, byteBuffer.slice())}. */ - public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeByteBufferNoTag(value); - } - - /** Write a {@code uint32} field, including tag, to the stream. */ - public void writeUInt32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeUInt32NoTag(value); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeByteBuffer(int fieldNumber, ByteBuffer value) throws IOException; /** - * Write an enum field, including tag, to the stream. Caller is responsible - * for converting the enum value to its numeric value. + * Write a single byte. */ - public void writeEnum(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeEnumNoTag(value); + public final void writeRawByte(final byte value) throws IOException { + write(value); } - /** Write an {@code sfixed32} field, including tag, to the stream. */ - public void writeSFixed32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); - writeSFixed32NoTag(value); + /** Write a single byte, represented by an integer value. */ + public final void writeRawByte(final int value) throws IOException { + write((byte) value); } - /** Write an {@code sfixed64} field, including tag, to the stream. */ - public void writeSFixed64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); - writeSFixed64NoTag(value); + /** Write an array of bytes. */ + public final void writeRawBytes(final byte[] value) throws IOException { + write(value, 0, value.length); } - /** Write an {@code sint32} field, including tag, to the stream. */ - public void writeSInt32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeSInt32NoTag(value); + /** + * Write part of an array of bytes. + */ + public final void writeRawBytes(final byte[] value, int offset, int length) throws IOException { + write(value, offset, length); } - /** Write an {@code sint64} field, including tag, to the stream. */ - public void writeSInt64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeSInt64NoTag(value); + /** Write a byte string. */ + public final void writeRawBytes(final ByteString value) throws IOException { + value.writeTo(this); } /** + * Write a ByteBuffer. This method will write all content of the ByteBuffer + * regardless of the current position and limit (i.e., the number of bytes + * to be written is value.capacity(), not value.remaining()). Furthermore, + * this method doesn't alter the state of the passed-in ByteBuffer. Its + * position, limit, mark, etc. will remain unchanged. If you only want to + * write the remaining bytes of a ByteBuffer, you can call + * {@code writeRawBytes(byteBuffer.slice())}. + */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeRawBytes(final ByteBuffer value) throws IOException; + + /** Write an embedded message field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException; + + + /** * Write a MessageSet extension field to the stream. For historical reasons, * the wire format differs from normal fields. */ - public void writeMessageSetExtension(final int fieldNumber, - final MessageLite value) - throws IOException { - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); - writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); - writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException; /** * Write an unparsed MessageSet extension field to the stream. For * historical reasons, the wire format differs from normal fields. */ - public void writeRawMessageSetExtension(final int fieldNumber, - final ByteString value) - throws IOException { - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); - writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); - writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException; // ----------------------------------------------------------------- - /** Write a {@code double} field to the stream. */ - public void writeDoubleNoTag(final double value) throws IOException { - writeRawLittleEndian64(Double.doubleToRawLongBits(value)); - } + /** Write an {@code int32} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeInt32NoTag(final int value) throws IOException; - /** Write a {@code float} field to the stream. */ - public void writeFloatNoTag(final float value) throws IOException { - writeRawLittleEndian32(Float.floatToRawIntBits(value)); + /** Write a {@code uint32} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt32NoTag(int value) throws IOException; + + /** Write a {@code sint32} field to the stream. */ + public final void writeSInt32NoTag(final int value) throws IOException { + writeUInt32NoTag(encodeZigZag32(value)); } - /** Write a {@code uint64} field to the stream. */ - public void writeUInt64NoTag(final long value) throws IOException { - writeRawVarint64(value); + /** Write a {@code fixed32} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed32NoTag(int value) throws IOException; + + /** Write a {@code sfixed32} field to the stream. */ + public final void writeSFixed32NoTag(final int value) throws IOException { + writeFixed32NoTag(value); } /** Write an {@code int64} field to the stream. */ - public void writeInt64NoTag(final long value) throws IOException { - writeRawVarint64(value); + public final void writeInt64NoTag(final long value) throws IOException { + writeUInt64NoTag(value); } - /** Write an {@code int32} field to the stream. */ - public void writeInt32NoTag(final int value) throws IOException { - if (value >= 0) { - writeRawVarint32(value); - } else { - // Must sign-extend. - writeRawVarint64(value); - } + /** Write a {@code uint64} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt64NoTag(long value) throws IOException; + + /** Write a {@code sint64} field to the stream. */ + public final void writeSInt64NoTag(final long value) throws IOException { + writeUInt64NoTag(encodeZigZag64(value)); } /** Write a {@code fixed64} field to the stream. */ - public void writeFixed64NoTag(final long value) throws IOException { - writeRawLittleEndian64(value); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed64NoTag(long value) throws IOException; - /** Write a {@code fixed32} field to the stream. */ - public void writeFixed32NoTag(final int value) throws IOException { - writeRawLittleEndian32(value); + /** Write a {@code sfixed64} field to the stream. */ + public final void writeSFixed64NoTag(final long value) throws IOException { + writeFixed64NoTag(value); } - /** Write a {@code bool} field to the stream. */ - public void writeBoolNoTag(final boolean value) throws IOException { - writeRawByte(value ? 1 : 0); + /** Write a {@code float} field to the stream. */ + public final void writeFloatNoTag(final float value) throws IOException { + writeFixed32NoTag(Float.floatToRawIntBits(value)); } - /** Write a {@code string} field to the stream. */ - // TODO(dweis): Document behavior on ill-formed UTF-16 input. - public void writeStringNoTag(final String value) throws IOException { - try { - efficientWriteStringNoTag(value); - } catch (UnpairedSurrogateException e) { - logger.log(Level.WARNING, - "Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", e); - inefficientWriteStringNoTag(value); - } + /** Write a {@code double} field to the stream. */ + public final void writeDoubleNoTag(final double value) throws IOException { + writeFixed64NoTag(Double.doubleToRawLongBits(value)); } - /** Write a {@code string} field to the stream. */ - private void inefficientWriteStringNoTag(final String value) throws IOException { - // Unfortunately there does not appear to be any way to tell Java to encode - // UTF-8 directly into our buffer, so we have to let it create its own byte - // array and then copy. - // TODO(dweis): Consider using nio Charset methods instead. - final byte[] bytes = value.getBytes(Internal.UTF_8); - writeRawVarint32(bytes.length); - writeRawBytes(bytes); + /** Write a {@code bool} field to the stream. */ + public final void writeBoolNoTag(final boolean value) throws IOException { + write((byte) (value ? 1 : 0)); } /** - * Write a {@code string} field to the stream efficiently. If the {@code string} is malformed, - * this method rolls back its changes and throws an {@link UnpairedSurrogateException} with the - * intent that the caller will catch and retry with {@link #inefficientWriteStringNoTag(String)}. - * - * @param value the string to write to the stream - * - * @throws UnpairedSurrogateException when {@code value} is ill-formed UTF-16. - */ - private void efficientWriteStringNoTag(final String value) throws IOException { - // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), - // and at most 3 times of it. We take advantage of this in both branches below. - final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; - final int maxLengthVarIntSize = computeRawVarint32Size(maxLength); - - // If we are streaming and the potential length is too big to fit in our buffer, we take the - // slower path. Otherwise, we're good to try the fast path. - if (output != null && maxLengthVarIntSize + maxLength > limit - position) { - // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes() - // does the same internally and then does *another copy* to return a byte[] of exactly the - // right size. We can skip that copy and just writeRawBytes up to the actualLength of the - // UTF-8 encoded bytes. - final byte[] encodedBytes = new byte[maxLength]; - int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength); - writeRawVarint32(actualLength); - writeRawBytes(encodedBytes, 0, actualLength); - } else { - // Optimize for the case where we know this length results in a constant varint length as this - // saves a pass for measuring the length of the string. - final int minLengthVarIntSize = computeRawVarint32Size(value.length()); - int oldPosition = position; - final int length; - try { - if (minLengthVarIntSize == maxLengthVarIntSize) { - position = oldPosition + minLengthVarIntSize; - int newPosition = Utf8.encode(value, buffer, position, limit - position); - // Since this class is stateful and tracks the position, we rewind and store the state, - // prepend the length, then reset it back to the end of the string. - position = oldPosition; - length = newPosition - oldPosition - minLengthVarIntSize; - writeRawVarint32(length); - position = newPosition; - } else { - length = Utf8.encodedLength(value); - writeRawVarint32(length); - position = Utf8.encode(value, buffer, position, limit - position); - } - } catch (UnpairedSurrogateException e) { - // Be extra careful and restore the original position for retrying the write with the less - // efficient path. - position = oldPosition; - throw e; - } catch (ArrayIndexOutOfBoundsException e) { - throw new OutOfSpaceException(e); - } - totalBytesWritten += length; - } - } - - /** Write a {@code group} field to the stream. */ - public void writeGroupNoTag(final MessageLite value) throws IOException { - value.writeTo(this); - } - - - /** Write an embedded message field to the stream. */ - public void writeMessageNoTag(final MessageLite value) throws IOException { - writeRawVarint32(value.getSerializedSize()); - value.writeTo(this); + * Write an enum field to the stream. The provided value is the numeric + * value used to represent the enum value on the wire (not the enum ordinal value). + */ + public final void writeEnumNoTag(final int value) throws IOException { + writeInt32NoTag(value); } + /** Write a {@code string} field to the stream. */ + // TODO(dweis): Document behavior on ill-formed UTF-16 input. + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeStringNoTag(String value) throws IOException; /** Write a {@code bytes} field to the stream. */ - public void writeBytesNoTag(final ByteString value) throws IOException { - writeRawVarint32(value.size()); - writeRawBytes(value); - } + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeBytesNoTag(final ByteString value) throws IOException; /** Write a {@code bytes} field to the stream. */ - public void writeByteArrayNoTag(final byte[] value) throws IOException { - writeRawVarint32(value.length); - writeRawBytes(value); + public final void writeByteArrayNoTag(final byte[] value) throws IOException { + writeByteArrayNoTag(value, 0, value.length); } - /** Write a {@code bytes} field to the stream. */ - public void writeByteArrayNoTag(final byte[] value, - final int offset, - final int length) throws IOException { - writeRawVarint32(length); - writeRawBytes(value, offset, length); - } + /** Write an embedded message field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeMessageNoTag(final MessageLite value) throws IOException; - /** - * Write a {@code bytes} field to the stream. This method will write all - * content of the ByteBuffer regardless of the current position and limit - * (i.e., the number of bytes to be written is value.capacity(), not - * value.remaining()). Furthermore, this method doesn't alter the state of - * the passed-in ByteBuffer. Its position, limit, mark, etc. will remain - * unchanged. If you only want to write the remaining bytes of a ByteBuffer, - * you can call {@code writeByteBufferNoTag(byteBuffer.slice())}. - */ - public void writeByteBufferNoTag(final ByteBuffer value) throws IOException { - writeRawVarint32(value.capacity()); - writeRawBytes(value); - } - /** Write a {@code uint32} field to the stream. */ - public void writeUInt32NoTag(final int value) throws IOException { - writeRawVarint32(value); - } + //================================================================= - /** - * Write an enum field to the stream. Caller is responsible - * for converting the enum value to its numeric value. - */ - public void writeEnumNoTag(final int value) throws IOException { - writeInt32NoTag(value); - } + @ExperimentalApi + @Override + public abstract void write(byte value) throws IOException; - /** Write an {@code sfixed32} field to the stream. */ - public void writeSFixed32NoTag(final int value) throws IOException { - writeRawLittleEndian32(value); - } + @ExperimentalApi + @Override + public abstract void write(byte[] value, int offset, int length) throws IOException; - /** Write an {@code sfixed64} field to the stream. */ - public void writeSFixed64NoTag(final long value) throws IOException { - writeRawLittleEndian64(value); - } + @ExperimentalApi + @Override + public abstract void writeLazy(byte[] value, int offset, int length) throws IOException; - /** Write an {@code sint32} field to the stream. */ - public void writeSInt32NoTag(final int value) throws IOException { - writeRawVarint32(encodeZigZag32(value)); - } + @Override + public abstract void write(ByteBuffer value) throws IOException; - /** Write an {@code sint64} field to the stream. */ - public void writeSInt64NoTag(final long value) throws IOException { - writeRawVarint64(encodeZigZag64(value)); - } + @ExperimentalApi + @Override + public abstract void writeLazy(ByteBuffer value) throws IOException; // ================================================================= + // ================================================================= /** - * Compute the number of bytes that would be needed to encode a - * {@code double} field, including tag. + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. */ - public static int computeDoubleSize(final int fieldNumber, - final double value) { - return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value); + public static int computeInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode a - * {@code float} field, including tag. + * {@code uint32} field, including tag. */ - public static int computeFloatSize(final int fieldNumber, final float value) { - return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value); + public static int computeUInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field, including tag. + */ + public static int computeSInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode a - * {@code uint64} field, including tag. + * {@code fixed32} field, including tag. */ - public static int computeUInt64Size(final int fieldNumber, final long value) { - return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value); + public static int computeFixed32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field, including tag. + */ + public static int computeSFixed32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value); } /** @@ -607,73 +556,83 @@ public final class CodedOutputStream { } /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value); + } + + /** * Compute the number of bytes that would be needed to encode an - * {@code int32} field, including tag. + * {@code sint64} field, including tag. */ - public static int computeInt32Size(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value); + public static int computeSInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeSInt64SizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode a * {@code fixed64} field, including tag. */ - public static int computeFixed64Size(final int fieldNumber, - final long value) { + public static int computeFixed64Size(final int fieldNumber, final long value) { return computeTagSize(fieldNumber) + computeFixed64SizeNoTag(value); } /** - * Compute the number of bytes that would be needed to encode a - * {@code fixed32} field, including tag. + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field, including tag. */ - public static int computeFixed32Size(final int fieldNumber, - final int value) { - return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value); + public static int computeSFixed64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode a - * {@code bool} field, including tag. + * {@code float} field, including tag. */ - public static int computeBoolSize(final int fieldNumber, - final boolean value) { - return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value); + public static int computeFloatSize(final int fieldNumber, final float value) { + return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode a - * {@code string} field, including tag. + * {@code double} field, including tag. */ - public static int computeStringSize(final int fieldNumber, - final String value) { - return computeTagSize(fieldNumber) + computeStringSizeNoTag(value); + public static int computeDoubleSize(final int fieldNumber, final double value) { + return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode a - * {@code group} field, including tag. + * {@code bool} field, including tag. */ - public static int computeGroupSize(final int fieldNumber, - final MessageLite value) { - return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value); + public static int computeBoolSize(final int fieldNumber, final boolean value) { + return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode an - * embedded message field, including tag. + * enum field, including tag. The provided value is the numeric + * value used to represent the enum value on the wire (not the enum ordinal value). */ - public static int computeMessageSize(final int fieldNumber, - final MessageLite value) { - return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value); + public static int computeEnumSize(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field, including tag. + */ + public static int computeStringSize(final int fieldNumber, final String value) { + return computeTagSize(fieldNumber) + computeStringSizeNoTag(value); } /** * Compute the number of bytes that would be needed to encode a * {@code bytes} field, including tag. */ - public static int computeBytesSize(final int fieldNumber, - final ByteString value) { + public static int computeBytesSize(final int fieldNumber, final ByteString value) { return computeTagSize(fieldNumber) + computeBytesSizeNoTag(value); } @@ -681,8 +640,7 @@ public final class CodedOutputStream { * Compute the number of bytes that would be needed to encode a * {@code bytes} field, including tag. */ - public static int computeByteArraySize(final int fieldNumber, - final byte[] value) { + public static int computeByteArraySize(final int fieldNumber, final byte[] value) { return computeTagSize(fieldNumber) + computeByteArraySizeNoTag(value); } @@ -690,8 +648,7 @@ public final class CodedOutputStream { * Compute the number of bytes that would be needed to encode a * {@code bytes} field, including tag. */ - public static int computeByteBufferSize(final int fieldNumber, - final ByteBuffer value) { + public static int computeByteBufferSize(final int fieldNumber, final ByteBuffer value) { return computeTagSize(fieldNumber) + computeByteBufferSizeNoTag(value); } @@ -699,167 +656,207 @@ public final class CodedOutputStream { * Compute the number of bytes that would be needed to encode an * embedded message in lazy field, including tag. */ - public static int computeLazyFieldSize(final int fieldNumber, - final LazyFieldLite value) { + public static int computeLazyFieldSize(final int fieldNumber, final LazyFieldLite value) { return computeTagSize(fieldNumber) + computeLazyFieldSizeNoTag(value); } /** + * Compute the number of bytes that would be needed to encode an + * embedded message field, including tag. + */ + public static int computeMessageSize(final int fieldNumber, final MessageLite value) { + return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value); + } + + + /** * Compute the number of bytes that would be needed to encode a - * {@code uint32} field, including tag. + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. */ - public static int computeUInt32Size(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value); + public static int computeMessageSetExtensionSize(final int fieldNumber, final MessageLite value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeMessageSize(WireFormat.MESSAGE_SET_MESSAGE, value); } /** * Compute the number of bytes that would be needed to encode an - * enum field, including tag. Caller is responsible for converting the - * enum value to its numeric value. + * unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. */ - public static int computeEnumSize(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value); + public static int computeRawMessageSetExtensionSize( + final int fieldNumber, final ByteString value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeBytesSize(WireFormat.MESSAGE_SET_MESSAGE, value); } /** * Compute the number of bytes that would be needed to encode an - * {@code sfixed32} field, including tag. + * lazily parsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. */ - public static int computeSFixed32Size(final int fieldNumber, - final int value) { - return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value); + public static int computeLazyFieldMessageSetExtensionSize( + final int fieldNumber, final LazyFieldLite value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeLazyFieldSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + // ----------------------------------------------------------------- + + /** Compute the number of bytes that would be needed to encode a tag. */ + public static int computeTagSize(final int fieldNumber) { + return computeUInt32SizeNoTag(WireFormat.makeTag(fieldNumber, 0)); } /** * Compute the number of bytes that would be needed to encode an - * {@code sfixed64} field, including tag. + * {@code int32} field, including tag. */ - public static int computeSFixed64Size(final int fieldNumber, - final long value) { - return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value); + public static int computeInt32SizeNoTag(final int value) { + if (value >= 0) { + return computeUInt32SizeNoTag(value); + } else { + // Must sign-extend. + return MAX_VARINT_SIZE; + } } /** - * Compute the number of bytes that would be needed to encode an - * {@code sint32} field, including tag. + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field. */ - public static int computeSInt32Size(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value); + public static int computeUInt32SizeNoTag(final int value) { + if ((value & (~0 << 7)) == 0) { + return 1; + } + if ((value & (~0 << 14)) == 0) { + return 2; + } + if ((value & (~0 << 21)) == 0) { + return 3; + } + if ((value & (~0 << 28)) == 0) { + return 4; + } + return 5; } /** * Compute the number of bytes that would be needed to encode an - * {@code sint64} field, including tag. + * {@code sint32} field. */ - public static int computeSInt64Size(final int fieldNumber, final long value) { - return computeTagSize(fieldNumber) + computeSInt64SizeNoTag(value); + public static int computeSInt32SizeNoTag(final int value) { + return computeUInt32SizeNoTag(encodeZigZag32(value)); } /** * Compute the number of bytes that would be needed to encode a - * MessageSet extension to the stream. For historical reasons, - * the wire format differs from normal fields. + * {@code fixed32} field. */ - public static int computeMessageSetExtensionSize( - final int fieldNumber, final MessageLite value) { - return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + - computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + - computeMessageSize(WireFormat.MESSAGE_SET_MESSAGE, value); + public static int computeFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) { + return FIXED32_SIZE; } /** * Compute the number of bytes that would be needed to encode an - * unparsed MessageSet extension field to the stream. For - * historical reasons, the wire format differs from normal fields. + * {@code sfixed32} field. */ - public static int computeRawMessageSetExtensionSize( - final int fieldNumber, final ByteString value) { - return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + - computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + - computeBytesSize(WireFormat.MESSAGE_SET_MESSAGE, value); + public static int computeSFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) { + return FIXED32_SIZE; } /** * Compute the number of bytes that would be needed to encode an - * lazily parsed MessageSet extension field to the stream. For - * historical reasons, the wire format differs from normal fields. + * {@code int64} field, including tag. */ - public static int computeLazyFieldMessageSetExtensionSize( - final int fieldNumber, final LazyFieldLite value) { - return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + - computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + - computeLazyFieldSize(WireFormat.MESSAGE_SET_MESSAGE, value); + public static int computeInt64SizeNoTag(final long value) { + return computeUInt64SizeNoTag(value); } - // ----------------------------------------------------------------- - /** * Compute the number of bytes that would be needed to encode a - * {@code double} field, including tag. + * {@code uint64} field, including tag. */ - public static int computeDoubleSizeNoTag(final double value) { - return LITTLE_ENDIAN_64_SIZE; + public static int computeUInt64SizeNoTag(long value) { + // handle two popular special cases up front ... + if ((value & (~0L << 7)) == 0L) { + return 1; + } + if (value < 0L) { + return 10; + } + // ... leaving us with 8 remaining, which we can divide and conquer + int n = 2; + if ((value & (~0L << 35)) != 0L) { + n += 4; value >>>= 28; + } + if ((value & (~0L << 21)) != 0L) { + n += 2; value >>>= 14; + } + if ((value & (~0L << 14)) != 0L) { + n += 1; + } + return n; } /** - * Compute the number of bytes that would be needed to encode a - * {@code float} field, including tag. + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field. */ - public static int computeFloatSizeNoTag(final float value) { - return LITTLE_ENDIAN_32_SIZE; + public static int computeSInt64SizeNoTag(final long value) { + return computeUInt64SizeNoTag(encodeZigZag64(value)); } /** * Compute the number of bytes that would be needed to encode a - * {@code uint64} field, including tag. + * {@code fixed64} field. */ - public static int computeUInt64SizeNoTag(final long value) { - return computeRawVarint64Size(value); + public static int computeFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) { + return FIXED64_SIZE; } /** * Compute the number of bytes that would be needed to encode an - * {@code int64} field, including tag. + * {@code sfixed64} field. */ - public static int computeInt64SizeNoTag(final long value) { - return computeRawVarint64Size(value); + public static int computeSFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) { + return FIXED64_SIZE; } /** - * Compute the number of bytes that would be needed to encode an - * {@code int32} field, including tag. + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. */ - public static int computeInt32SizeNoTag(final int value) { - if (value >= 0) { - return computeRawVarint32Size(value); - } else { - // Must sign-extend. - return 10; - } + public static int computeFloatSizeNoTag(@SuppressWarnings("unused") final float unused) { + return FIXED32_SIZE; } /** * Compute the number of bytes that would be needed to encode a - * {@code fixed64} field. + * {@code double} field, including tag. */ - public static int computeFixed64SizeNoTag(final long value) { - return LITTLE_ENDIAN_64_SIZE; + public static int computeDoubleSizeNoTag(@SuppressWarnings("unused") final double unused) { + return FIXED64_SIZE; } /** * Compute the number of bytes that would be needed to encode a - * {@code fixed32} field. + * {@code bool} field. */ - public static int computeFixed32SizeNoTag(final int value) { - return LITTLE_ENDIAN_32_SIZE; + public static int computeBoolSizeNoTag(@SuppressWarnings("unused") final boolean unused) { + return 1; } /** - * Compute the number of bytes that would be needed to encode a - * {@code bool} field. + * Compute the number of bytes that would be needed to encode an enum field. + * The provided value is the numeric value used to represent the enum value on the wire + * (not the enum ordinal value). */ - public static int computeBoolSizeNoTag(final boolean value) { - return 1; + public static int computeEnumSizeNoTag(final int value) { + return computeInt32SizeNoTag(value); } /** @@ -876,24 +873,7 @@ public final class CodedOutputStream { length = bytes.length; } - return computeRawVarint32Size(length) + length; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code group} field. - */ - public static int computeGroupSizeNoTag(final MessageLite value) { - return value.getSerializedSize(); - } - - /** - * Compute the number of bytes that would be needed to encode an embedded - * message field. - */ - public static int computeMessageSizeNoTag(final MessageLite value) { - final int size = value.getSerializedSize(); - return computeRawVarint32Size(size) + size; + return computeLengthDelimitedFieldSize(length); } /** @@ -901,8 +881,7 @@ public final class CodedOutputStream { * message stored in lazy field. */ public static int computeLazyFieldSizeNoTag(final LazyFieldLite value) { - final int size = value.getSerializedSize(); - return computeRawVarint32Size(size) + size; + return computeLengthDelimitedFieldSize(value.getSerializedSize()); } /** @@ -910,8 +889,7 @@ public final class CodedOutputStream { * {@code bytes} field. */ public static int computeBytesSizeNoTag(final ByteString value) { - return computeRawVarint32Size(value.size()) + - value.size(); + return computeLengthDelimitedFieldSize(value.size()); } /** @@ -919,7 +897,7 @@ public final class CodedOutputStream { * {@code bytes} field. */ public static int computeByteArraySizeNoTag(final byte[] value) { - return computeRawVarint32Size(value.length) + value.length; + return computeLengthDelimitedFieldSize(value.length); } /** @@ -927,98 +905,65 @@ public final class CodedOutputStream { * {@code bytes} field. */ public static int computeByteBufferSizeNoTag(final ByteBuffer value) { - return computeRawVarint32Size(value.capacity()) + value.capacity(); + return computeLengthDelimitedFieldSize(value.capacity()); } /** - * Compute the number of bytes that would be needed to encode a - * {@code uint32} field. + * Compute the number of bytes that would be needed to encode an embedded + * message field. */ - public static int computeUInt32SizeNoTag(final int value) { - return computeRawVarint32Size(value); + public static int computeMessageSizeNoTag(final MessageLite value) { + return computeLengthDelimitedFieldSize(value.getSerializedSize()); } - /** - * Compute the number of bytes that would be needed to encode an enum field. - * Caller is responsible for converting the enum value to its numeric value. - */ - public static int computeEnumSizeNoTag(final int value) { - return computeInt32SizeNoTag(value); - } - /** - * Compute the number of bytes that would be needed to encode an - * {@code sfixed32} field. - */ - public static int computeSFixed32SizeNoTag(final int value) { - return LITTLE_ENDIAN_32_SIZE; + static int computeLengthDelimitedFieldSize(int fieldLength) { + return computeUInt32SizeNoTag(fieldLength) + fieldLength; } /** - * Compute the number of bytes that would be needed to encode an - * {@code sfixed64} field. - */ - public static int computeSFixed64SizeNoTag(final long value) { - return LITTLE_ENDIAN_64_SIZE; - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sint32} field. + * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 32-bit integer. + * @return An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. */ - public static int computeSInt32SizeNoTag(final int value) { - return computeRawVarint32Size(encodeZigZag32(value)); + public static int encodeZigZag32(final int n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 31); } /** - * Compute the number of bytes that would be needed to encode an - * {@code sint64} field. + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 64-bit integer. + * @return An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. */ - public static int computeSInt64SizeNoTag(final long value) { - return computeRawVarint64Size(encodeZigZag64(value)); + public static long encodeZigZag64(final long n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 63); } // ================================================================= /** - * Internal helper that writes the current buffer to the output. The - * buffer position is reset to its initial value when this returns. - */ - private void refreshBuffer() throws IOException { - if (output == null) { - // We're writing to a single buffer. - throw new OutOfSpaceException(); - } - - // Since we have an output stream, this is our buffer - // and buffer offset == 0 - output.write(buffer, 0, position); - position = 0; - } - - /** * Flushes the stream and forces any buffered bytes to be written. This * does not flush the underlying OutputStream. */ - public void flush() throws IOException { - if (output != null) { - refreshBuffer(); - } - } + public abstract void flush() throws IOException; /** * If writing to a flat array, return the space left in the array. * Otherwise, throws {@code UnsupportedOperationException}. */ - public int spaceLeft() { - if (output == null) { - return limit - position; - } else { - throw new UnsupportedOperationException( - "spaceLeft() can only be called on CodedOutputStreams that are " + - "writing to a flat array."); - } - } + public abstract int spaceLeft(); /** * Verifies that {@link #spaceLeft()} returns zero. It's common to create @@ -1027,10 +972,9 @@ public final class CodedOutputStream { * after writing verifies that the message was actually as big as expected, * which can help catch bugs. */ - public void checkNoSpaceLeft() { + public final void checkNoSpaceLeft() { if (spaceLeft() != 0) { - throw new IllegalStateException( - "Did not write as much data as expected."); + throw new IllegalStateException("Did not write as much data as expected."); } } @@ -1049,9 +993,17 @@ public final class CodedOutputStream { super(MESSAGE); } + OutOfSpaceException(String explanationMessage) { + super(MESSAGE + ": " + explanationMessage); + } + OutOfSpaceException(Throwable cause) { super(MESSAGE, cause); } + + OutOfSpaceException(String explanationMessage, Throwable cause) { + super(MESSAGE + ": " + explanationMessage, cause); + } } /** @@ -1059,274 +1011,1997 @@ public final class CodedOutputStream { * returned value is not guaranteed to be accurate if exceptions have been * found in the middle of writing. */ - public int getTotalBytesWritten() { - return totalBytesWritten; - } + public abstract int getTotalBytesWritten(); + + // ================================================================= - /** Write a single byte. */ - public void writeRawByte(final byte value) throws IOException { - if (position == limit) { - refreshBuffer(); + /** Write a {@code bytes} field to the stream. Visible for testing. */ + abstract void writeByteArrayNoTag(final byte[] value, final int offset, final int length) + throws IOException; + + final void inefficientWriteStringNoTag(String value, UnpairedSurrogateException cause) + throws IOException { + logger.log(Level.WARNING, + "Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", cause); + + // Unfortunately there does not appear to be any way to tell Java to encode + // UTF-8 directly into our buffer, so we have to let it create its own byte + // array and then copy. + // TODO(dweis): Consider using nio Charset methods instead. + final byte[] bytes = value.getBytes(Internal.UTF_8); + try { + writeUInt32NoTag(bytes.length); + writeLazy(bytes, 0, bytes.length); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } catch (OutOfSpaceException e) { + throw e; } + } - buffer[position++] = value; - ++totalBytesWritten; + // ================================================================= + + /** + * Write a {@code group} field, including tag, to the stream. + * + * @deprecated groups are deprecated. + */ + @Deprecated + public final void writeGroup(final int fieldNumber, final MessageLite value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); + writeGroupNoTag(value); + writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); } - /** Write a single byte, represented by an integer value. */ - public void writeRawByte(final int value) throws IOException { - writeRawByte((byte) value); + + /** + * Write a {@code group} field to the stream. + * + * @deprecated groups are deprecated. + */ + @Deprecated + public final void writeGroupNoTag(final MessageLite value) throws IOException { + value.writeTo(this); } - /** Write a byte string. */ - public void writeRawBytes(final ByteString value) throws IOException { - writeRawBytes(value, 0, value.size()); + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field, including tag. + * + * @deprecated groups are deprecated. + */ + @Deprecated + public static int computeGroupSize(final int fieldNumber, final MessageLite value) { + return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value); } - /** Write an array of bytes. */ - public void writeRawBytes(final byte[] value) throws IOException { - writeRawBytes(value, 0, value.length); + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field. + */ + @Deprecated + public static int computeGroupSizeNoTag(final MessageLite value) { + return value.getSerializedSize(); } + /** - * Write a ByteBuffer. This method will write all content of the ByteBuffer - * regardless of the current position and limit (i.e., the number of bytes - * to be written is value.capacity(), not value.remaining()). Furthermore, - * this method doesn't alter the state of the passed-in ByteBuffer. Its - * position, limit, mark, etc. will remain unchanged. If you only want to - * write the remaining bytes of a ByteBuffer, you can call - * {@code writeRawBytes(byteBuffer.slice())}. + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + * + * @deprecated use {@link #writeUInt32NoTag} instead. */ - public void writeRawBytes(final ByteBuffer value) throws IOException { - if (value.hasArray()) { - writeRawBytes(value.array(), value.arrayOffset(), value.capacity()); - } else { - ByteBuffer duplicated = value.duplicate(); - duplicated.clear(); - writeRawBytesInternal(duplicated); - } + @Deprecated + public final void writeRawVarint32(int value) throws IOException { + writeUInt32NoTag(value); } - /** Write a ByteBuffer that isn't backed by an array. */ - private void writeRawBytesInternal(final ByteBuffer value) - throws IOException { - int length = value.remaining(); - if (limit - position >= length) { - // We have room in the current buffer. - value.get(buffer, position, length); - position += length; - totalBytesWritten += length; - } else { - // Write extends past current buffer. Fill the rest of this buffer and - // flush. - final int bytesWritten = limit - position; - value.get(buffer, position, bytesWritten); - length -= bytesWritten; - position = limit; - totalBytesWritten += bytesWritten; - refreshBuffer(); - - // Now deal with the rest. - // Since we have an output stream, this is our buffer - // and buffer offset == 0 - while (length > limit) { - // Copy data into the buffer before writing it to OutputStream. - // TODO(xiaofeng): Introduce ZeroCopyOutputStream to avoid this copy. - value.get(buffer, 0, limit); - output.write(buffer, 0, limit); - length -= limit; - totalBytesWritten += limit; + /** + * Encode and write a varint. + * + * @deprecated use {@link #writeUInt64NoTag} instead. + */ + @Deprecated + public final void writeRawVarint64(long value) throws IOException { + writeUInt64NoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a varint. + * {@code value} is treated as unsigned, so it won't be sign-extended if + * negative. + * + * @deprecated use {@link #computeUInt32SizeNoTag(int)} instead. + */ + @Deprecated + public static int computeRawVarint32Size(final int value) { + return computeUInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a varint. + * + * @deprecated use {@link #computeUInt64SizeNoTag(long)} instead. + */ + @Deprecated + public static int computeRawVarint64Size(long value) { + return computeUInt64SizeNoTag(value); + } + + /** + * Write a little-endian 32-bit integer. + * + * @deprecated Use {@link #writeFixed32NoTag} instead. + */ + @Deprecated + public final void writeRawLittleEndian32(final int value) throws IOException { + writeFixed32NoTag(value); + } + + /** + * Write a little-endian 64-bit integer. + * + * @deprecated Use {@link #writeFixed64NoTag} instead. + */ + @Deprecated + public final void writeRawLittleEndian64(final long value) throws IOException { + writeFixed64NoTag(value); + } + + // ================================================================= + + /** + * A {@link CodedOutputStream} that writes directly to a byte array. + */ + private static class ArrayEncoder extends CodedOutputStream { + private final byte[] buffer; + private final int offset; + private final int limit; + private int position; + + ArrayEncoder(byte[] buffer, int offset, int length) { + if (buffer == null) { + throw new NullPointerException("buffer"); } - value.get(buffer, 0, length); - position = length; - totalBytesWritten += length; + if ((offset | length | (buffer.length - (offset + length))) < 0) { + throw new IllegalArgumentException(String.format( + "Array range is invalid. Buffer.length=%d, offset=%d, length=%d", + buffer.length, offset, length)); + } + this.buffer = buffer; + this.offset = offset; + position = offset; + limit = offset + length; } - } - /** Write part of an array of bytes. */ - public void writeRawBytes(final byte[] value, int offset, int length) - throws IOException { - if (limit - position >= length) { - // We have room in the current buffer. - System.arraycopy(value, offset, buffer, position, length); - position += length; - totalBytesWritten += length; - } else { - // Write extends past current buffer. Fill the rest of this buffer and - // flush. - final int bytesWritten = limit - position; - System.arraycopy(value, offset, buffer, position, bytesWritten); - offset += bytesWritten; - length -= bytesWritten; - position = limit; - totalBytesWritten += bytesWritten; - refreshBuffer(); - - // Now deal with the rest. - // Since we have an output stream, this is our buffer - // and buffer offset == 0 - if (length <= limit) { - // Fits in new buffer. - System.arraycopy(value, offset, buffer, 0, length); - position = length; + @Override + public final void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public final void writeInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + @Override + public final void writeUInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + @Override + public final void writeFixed32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + @Override + public final void writeUInt64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + @Override + public final void writeFixed64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + @Override + public final void writeBool(final int fieldNumber, final boolean value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + write((byte) (value ? 1 : 0)); + } + + @Override + public final void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public final void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public final void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + public final void writeByteArray( + final int fieldNumber, final byte[] value, final int offset, final int length) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value, offset, length); + } + + @Override + public final void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public final void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public final void writeByteArrayNoTag(final byte[] value, int offset, int length) + throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public final void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); } else { - // Write is very big. Let's do it all at once. - output.write(value, offset, length); + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); } - totalBytesWritten += length; } - } - /** Write part of a byte string. */ - public void writeRawBytes(final ByteString value, int offset, int length) - throws IOException { - if (limit - position >= length) { - // We have room in the current buffer. - value.copyTo(buffer, offset, position, length); - position += length; - totalBytesWritten += length; - } else { - // Write extends past current buffer. Fill the rest of this buffer and - // flush. - final int bytesWritten = limit - position; - value.copyTo(buffer, offset, position, bytesWritten); - offset += bytesWritten; - length -= bytesWritten; - position = limit; - totalBytesWritten += bytesWritten; - refreshBuffer(); - - // Now deal with the rest. - // Since we have an output stream, this is our buffer - // and buffer offset == 0 - if (length <= limit) { - // Fits in new buffer. - value.copyTo(buffer, offset, 0, length); - position = length; + @Override + public final void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + + @Override + public final void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public final void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public final void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + + @Override + public final void write(byte value) throws IOException { + try { + buffer[position++] = value; + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); + } + } + + @Override + public final void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); } else { - value.writeTo(output, offset, length); + // Must sign-extend. + writeUInt64NoTag(value); } - totalBytesWritten += length; } - } - /** Encode and write a tag. */ - public void writeTag(final int fieldNumber, final int wireType) - throws IOException { - writeRawVarint32(WireFormat.makeTag(fieldNumber, wireType)); + @Override + public final void writeUInt32NoTag(int value) throws IOException { + if (HAS_UNSAFE_ARRAY_OPERATIONS && spaceLeft() >= MAX_VARINT_SIZE) { + while (true) { + if ((value & ~0x7F) == 0) { + UnsafeUtil.putByte(buffer, position++, (byte) value); + return; + } else { + UnsafeUtil.putByte(buffer, position++, (byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } else { + try { + while (true) { + if ((value & ~0x7F) == 0) { + buffer[position++] = (byte) value; + return; + } else { + buffer[position++] = (byte) ((value & 0x7F) | 0x80); + value >>>= 7; + } + } + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); + } + } + } + + @Override + public final void writeFixed32NoTag(int value) throws IOException { + try { + buffer[position++] = (byte) (value & 0xFF); + buffer[position++] = (byte) ((value >> 8) & 0xFF); + buffer[position++] = (byte) ((value >> 16) & 0xFF); + buffer[position++] = (byte) ((value >> 24) & 0xFF); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); + } + } + + @Override + public final void writeUInt64NoTag(long value) throws IOException { + if (HAS_UNSAFE_ARRAY_OPERATIONS && spaceLeft() >= MAX_VARINT_SIZE) { + while (true) { + if ((value & ~0x7FL) == 0) { + UnsafeUtil.putByte(buffer, position++, (byte) value); + return; + } else { + UnsafeUtil.putByte(buffer, position++, (byte) (((int) value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } else { + try { + while (true) { + if ((value & ~0x7FL) == 0) { + buffer[position++] = (byte) value; + return; + } else { + buffer[position++] = (byte) (((int) value & 0x7F) | 0x80); + value >>>= 7; + } + } + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); + } + } + } + + @Override + public final void writeFixed64NoTag(long value) throws IOException { + try { + buffer[position++] = (byte) ((int) (value) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 8) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 16) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 24) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 32) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 40) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 48) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 56) & 0xFF); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); + } + } + + @Override + public final void write(byte[] value, int offset, int length) throws IOException { + try { + System.arraycopy(value, offset, buffer, position, length); + position += length; + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e); + } + } + + @Override + public final void writeLazy(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + @Override + public final void write(ByteBuffer value) throws IOException { + final int length = value.remaining(); + try { + value.get(buffer, position, length); + position += length; + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e); + } + } + + @Override + public final void writeLazy(ByteBuffer value) throws IOException { + write(value); + } + + @Override + public final void writeStringNoTag(String value) throws IOException { + final int oldPosition = position; + try { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength); + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + if (minLengthVarIntSize == maxLengthVarIntSize) { + position = oldPosition + minLengthVarIntSize; + int newPosition = Utf8.encode(value, buffer, position, spaceLeft()); + // Since this class is stateful and tracks the position, we rewind and store the state, + // prepend the length, then reset it back to the end of the string. + position = oldPosition; + int length = newPosition - oldPosition - minLengthVarIntSize; + writeUInt32NoTag(length); + position = newPosition; + } else { + int length = Utf8.encodedLength(value); + writeUInt32NoTag(length); + position = Utf8.encode(value, buffer, position, spaceLeft()); + } + } catch (UnpairedSurrogateException e) { + // Roll back the change - we fall back to inefficient path. + position = oldPosition; + + // TODO(nathanmittler): We should throw an IOException here instead. + inefficientWriteStringNoTag(value, e); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void flush() { + // Do nothing. + } + + @Override + public final int spaceLeft() { + return limit - position; + } + + @Override + public final int getTotalBytesWritten() { + return position - offset; + } } - /** Compute the number of bytes that would be needed to encode a tag. */ - public static int computeTagSize(final int fieldNumber) { - return computeRawVarint32Size(WireFormat.makeTag(fieldNumber, 0)); + /** + * A {@link CodedOutputStream} that writes directly to a heap {@link ByteBuffer}. Writes are + * done directly to the underlying array. The buffer position is only updated after a flush. + */ + private static final class HeapNioEncoder extends ArrayEncoder { + private final ByteBuffer byteBuffer; + private int initialPosition; + + HeapNioEncoder(ByteBuffer byteBuffer) { + super(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), + byteBuffer.remaining()); + this.byteBuffer = byteBuffer; + this.initialPosition = byteBuffer.position(); + } + + @Override + public void flush() { + // Update the position on the buffer. + byteBuffer.position(initialPosition + getTotalBytesWritten()); + } } /** - * Encode and write a varint. {@code value} is treated as - * unsigned, so it won't be sign-extended if negative. + * A {@link CodedOutputStream} that writes directly to a direct {@link ByteBuffer}, using only + * safe operations.. */ - public void writeRawVarint32(int value) throws IOException { - while (true) { - if ((value & ~0x7F) == 0) { - writeRawByte(value); - return; + private static final class SafeDirectNioEncoder extends CodedOutputStream { + private final ByteBuffer originalBuffer; + private final ByteBuffer buffer; + private final int initialPosition; + + SafeDirectNioEncoder(ByteBuffer buffer) { + this.originalBuffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.LITTLE_ENDIAN); + initialPosition = buffer.position(); + } + + @Override + public void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public void writeInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + @Override + public void writeUInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + @Override + public void writeFixed32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + @Override + public void writeUInt64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + @Override + public void writeFixed64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + @Override + public void writeBool(final int fieldNumber, final boolean value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + write((byte) (value ? 1 : 0)); + } + + @Override + public void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + public void writeByteArray( + final int fieldNumber, final byte[] value, final int offset, final int length) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value, offset, length); + } + + @Override + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + + @Override + public void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + + @Override + public void write(byte value) throws IOException { + try { + buffer.put(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); } else { - writeRawByte((value & 0x7F) | 0x80); - value >>>= 7; + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); + } + } + + @Override + public void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } + + @Override + public void writeUInt32NoTag(int value) throws IOException { + try { + while (true) { + if ((value & ~0x7F) == 0) { + buffer.put((byte) value); + return; + } else { + buffer.put((byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeFixed32NoTag(int value) throws IOException { + try { + buffer.putInt(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeUInt64NoTag(long value) throws IOException { + try { + while (true) { + if ((value & ~0x7FL) == 0) { + buffer.put((byte) value); + return; + } else { + buffer.put((byte) (((int) value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeFixed64NoTag(long value) throws IOException { + try { + buffer.putLong(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void write(byte[] value, int offset, int length) throws IOException { + try { + buffer.put(value, offset, length); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + @Override + public void write(ByteBuffer value) throws IOException { + try { + buffer.put(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + write(value); + } + + @Override + public void writeStringNoTag(String value) throws IOException { + final int startPos = buffer.position(); + try { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxEncodedSize = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxEncodedSize); + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + if (minLengthVarIntSize == maxLengthVarIntSize) { + // Save the current position and increment past the length field. We'll come back + // and write the length field after the encoding is complete. + final int startOfBytes = buffer.position() + minLengthVarIntSize; + buffer.position(startOfBytes); + + // Encode the string. + encode(value); + + // Now go back to the beginning and write the length. + int endOfBytes = buffer.position(); + buffer.position(startPos); + writeUInt32NoTag(endOfBytes - startOfBytes); + + // Reposition the buffer past the written data. + buffer.position(endOfBytes); + } else { + final int length = Utf8.encodedLength(value); + writeUInt32NoTag(length); + encode(value); + } + } catch (UnpairedSurrogateException e) { + // Roll back the change and convert to an IOException. + buffer.position(startPos); + + // TODO(nathanmittler): We should throw an IOException here instead. + inefficientWriteStringNoTag(value, e); + } catch (IllegalArgumentException e) { + // Thrown by buffer.position() if out of range. + throw new OutOfSpaceException(e); + } + } + + @Override + public void flush() { + // Update the position of the original buffer. + originalBuffer.position(buffer.position()); + } + + @Override + public int spaceLeft() { + return buffer.remaining(); + } + + @Override + public int getTotalBytesWritten() { + return buffer.position() - initialPosition; + } + + private void encode(String value) throws IOException { + try { + Utf8.encodeUtf8(value, buffer); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); } } } /** - * Compute the number of bytes that would be needed to encode a varint. - * {@code value} is treated as unsigned, so it won't be sign-extended if - * negative. + * A {@link CodedOutputStream} that writes directly to a direct {@link ByteBuffer} using {@code + * sun.misc.Unsafe}. */ - public static int computeRawVarint32Size(final int value) { - if ((value & (~0 << 7)) == 0) return 1; - if ((value & (~0 << 14)) == 0) return 2; - if ((value & (~0 << 21)) == 0) return 3; - if ((value & (~0 << 28)) == 0) return 4; - return 5; - } + private static final class UnsafeDirectNioEncoder extends CodedOutputStream { + private final ByteBuffer originalBuffer; + private final ByteBuffer buffer; + private final long address; + private final long initialPosition; + private final long limit; + private final long oneVarintLimit; + private long position; - /** Encode and write a varint. */ - public void writeRawVarint64(long value) throws IOException { - while (true) { - if ((value & ~0x7FL) == 0) { - writeRawByte((int)value); - return; + UnsafeDirectNioEncoder(ByteBuffer buffer) { + this.originalBuffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.LITTLE_ENDIAN); + address = UnsafeUtil.addressOffset(buffer); + initialPosition = address + buffer.position(); + limit = address + buffer.limit(); + oneVarintLimit = limit - MAX_VARINT_SIZE; + position = initialPosition; + } + + static boolean isSupported() { + return UnsafeUtil.hasUnsafeByteBufferOperations(); + } + + @Override + public void writeTag(int fieldNumber, int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public void writeInt32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + @Override + public void writeUInt32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + @Override + public void writeFixed32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + @Override + public void writeUInt64(int fieldNumber, long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + @Override + public void writeFixed64(int fieldNumber, long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + @Override + public void writeBool(int fieldNumber, boolean value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + write((byte) (value ? 1 : 0)); + } + + @Override + public void writeString(int fieldNumber, String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public void writeBytes(int fieldNumber, ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public void writeByteArray(int fieldNumber, byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + public void writeByteArray(int fieldNumber, byte[] value, int offset, int length) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value, offset, length); + } + + @Override + public void writeByteBuffer(int fieldNumber, ByteBuffer value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public void writeMessage(int fieldNumber, MessageLite value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + + @Override + public void writeMessageSetExtension(int fieldNumber, MessageLite value) throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeRawMessageSetExtension(int fieldNumber, ByteString value) throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeMessageNoTag(MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + + @Override + public void write(byte value) throws IOException { + if (position >= limit) { + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)); + } + UnsafeUtil.putByte(position++, value); + } + + @Override + public void writeBytesNoTag(ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public void writeByteArrayNoTag(byte[] value, int offset, int length) throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public void writeRawBytes(ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); } else { - writeRawByte(((int)value & 0x7F) | 0x80); - value >>>= 7; + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); } } - } - /** Compute the number of bytes that would be needed to encode a varint. */ - public static int computeRawVarint64Size(long value) { - // handle two popular special cases up front ... - if ((value & (~0L << 7)) == 0L) return 1; - if (value < 0L) return 10; - // ... leaving us with 8 remaining, which we can divide and conquer - int n = 2; - if ((value & (~0L << 35)) != 0L) { n += 4; value >>>= 28; } - if ((value & (~0L << 21)) != 0L) { n += 2; value >>>= 14; } - if ((value & (~0L << 14)) != 0L) { n += 1; } - return n; - } + @Override + public void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } - /** Write a little-endian 32-bit integer. */ - public void writeRawLittleEndian32(final int value) throws IOException { - writeRawByte((value ) & 0xFF); - writeRawByte((value >> 8) & 0xFF); - writeRawByte((value >> 16) & 0xFF); - writeRawByte((value >> 24) & 0xFF); - } + @Override + public void writeUInt32NoTag(int value) throws IOException { + if (position <= oneVarintLimit) { + // Optimization to avoid bounds checks on each iteration. + while (true) { + if ((value & ~0x7F) == 0) { + UnsafeUtil.putByte(position++, (byte) value); + return; + } else { + UnsafeUtil.putByte(position++, (byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } else { + while (position < limit) { + if ((value & ~0x7F) == 0) { + UnsafeUtil.putByte(position++, (byte) value); + return; + } else { + UnsafeUtil.putByte(position++, (byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + } + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)); + } + } + + @Override + public void writeFixed32NoTag(int value) throws IOException { + buffer.putInt(bufferPos(position), value); + position += FIXED32_SIZE; + } + + @Override + public void writeUInt64NoTag(long value) throws IOException { + if (position <= oneVarintLimit) { + // Optimization to avoid bounds checks on each iteration. + while (true) { + if ((value & ~0x7FL) == 0) { + UnsafeUtil.putByte(position++, (byte) value); + return; + } else { + UnsafeUtil.putByte(position++, (byte) (((int) value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } else { + while (position < limit) { + if ((value & ~0x7FL) == 0) { + UnsafeUtil.putByte(position++, (byte) value); + return; + } else { + UnsafeUtil.putByte(position++, (byte) (((int) value & 0x7F) | 0x80)); + value >>>= 7; + } + } + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)); + } + } + + @Override + public void writeFixed64NoTag(long value) throws IOException { + buffer.putLong(bufferPos(position), value); + position += FIXED64_SIZE; + } + + @Override + public void write(byte[] value, int offset, int length) throws IOException { + if (value == null + || offset < 0 + || length < 0 + || (value.length - length) < offset + || (limit - length) < position) { + if (value == null) { + throw new NullPointerException("value"); + } + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, length)); + } + + UnsafeUtil.copyMemory(value, offset, position, length); + position += length; + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + @Override + public void write(ByteBuffer value) throws IOException { + try { + int length = value.remaining(); + repositionBuffer(position); + buffer.put(value); + position += length; + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + write(value); + } + + @Override + public void writeStringNoTag(String value) throws IOException { + long prevPos = position; + try { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + int maxEncodedSize = value.length() * Utf8.MAX_BYTES_PER_CHAR; + int maxLengthVarIntSize = computeUInt32SizeNoTag(maxEncodedSize); + int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + if (minLengthVarIntSize == maxLengthVarIntSize) { + // Save the current position and increment past the length field. We'll come back + // and write the length field after the encoding is complete. + int stringStart = bufferPos(position) + minLengthVarIntSize; + buffer.position(stringStart); + + // Encode the string. + Utf8.encodeUtf8(value, buffer); + + // Write the length and advance the position. + int length = buffer.position() - stringStart; + writeUInt32NoTag(length); + position += length; + } else { + // Calculate and write the encoded length. + int length = Utf8.encodedLength(value); + writeUInt32NoTag(length); + + // Write the string and advance the position. + repositionBuffer(position); + Utf8.encodeUtf8(value, buffer); + position += length; + } + } catch (UnpairedSurrogateException e) { + // Roll back the change and convert to an IOException. + position = prevPos; + repositionBuffer(position); + + // TODO(nathanmittler): We should throw an IOException here instead. + inefficientWriteStringNoTag(value, e); + } catch (IllegalArgumentException e) { + // Thrown by buffer.position() if out of range. + throw new OutOfSpaceException(e); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } - public static final int LITTLE_ENDIAN_32_SIZE = 4; + @Override + public void flush() { + // Update the position of the original buffer. + originalBuffer.position(bufferPos(position)); + } - /** Write a little-endian 64-bit integer. */ - public void writeRawLittleEndian64(final long value) throws IOException { - writeRawByte((int)(value ) & 0xFF); - writeRawByte((int)(value >> 8) & 0xFF); - writeRawByte((int)(value >> 16) & 0xFF); - writeRawByte((int)(value >> 24) & 0xFF); - writeRawByte((int)(value >> 32) & 0xFF); - writeRawByte((int)(value >> 40) & 0xFF); - writeRawByte((int)(value >> 48) & 0xFF); - writeRawByte((int)(value >> 56) & 0xFF); + @Override + public int spaceLeft() { + return (int) (limit - position); + } + + @Override + public int getTotalBytesWritten() { + return (int) (position - initialPosition); + } + + private void repositionBuffer(long pos) { + buffer.position(bufferPos(pos)); + } + + private int bufferPos(long pos) { + return (int) (pos - address); + } } - public static final int LITTLE_ENDIAN_64_SIZE = 8; + /** + * Abstract base class for buffered encoders. + */ + private abstract static class AbstractBufferedEncoder extends CodedOutputStream { + final byte[] buffer; + final int limit; + int position; + int totalBytesWritten; + + AbstractBufferedEncoder(int bufferSize) { + if (bufferSize < 0) { + throw new IllegalArgumentException("bufferSize must be >= 0"); + } + // As an optimization, we require that the buffer be able to store at least 2 + // varints so that we can buffer any integer write (tag + value). This reduces the + // number of range checks for a single write to 1 (i.e. if there is not enough space + // to buffer the tag+value, flush and then buffer it). + this.buffer = new byte[max(bufferSize, MAX_VARINT_SIZE * 2)]; + this.limit = buffer.length; + } + + @Override + public final int spaceLeft() { + throw new UnsupportedOperationException( + "spaceLeft() can only be called on CodedOutputStreams that are " + + "writing to a flat array or ByteBuffer."); + } + + @Override + public final int getTotalBytesWritten() { + return totalBytesWritten; + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void buffer(byte value) { + buffer[position++] = value; + totalBytesWritten++; + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferTag(final int fieldNumber, final int wireType) { + bufferUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferInt32NoTag(final int value) { + if (value >= 0) { + bufferUInt32NoTag(value); + } else { + // Must sign-extend. + bufferUInt64NoTag(value); + } + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferUInt32NoTag(int value) { + if (HAS_UNSAFE_ARRAY_OPERATIONS) { + final long originalPos = position; + while (true) { + if ((value & ~0x7F) == 0) { + UnsafeUtil.putByte(buffer, position++, (byte) value); + break; + } else { + UnsafeUtil.putByte(buffer, position++, (byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + } + int delta = (int) (position - originalPos); + totalBytesWritten += delta; + } else { + while (true) { + if ((value & ~0x7F) == 0) { + buffer[position++] = (byte) value; + totalBytesWritten++; + return; + } else { + buffer[position++] = (byte) ((value & 0x7F) | 0x80); + totalBytesWritten++; + value >>>= 7; + } + } + } + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferUInt64NoTag(long value) { + if (HAS_UNSAFE_ARRAY_OPERATIONS) { + final long originalPos = position; + while (true) { + if ((value & ~0x7FL) == 0) { + UnsafeUtil.putByte(buffer, position++, (byte) value); + break; + } else { + UnsafeUtil.putByte(buffer, position++, (byte) (((int) value & 0x7F) | 0x80)); + value >>>= 7; + } + } + int delta = (int) (position - originalPos); + totalBytesWritten += delta; + } else { + while (true) { + if ((value & ~0x7FL) == 0) { + buffer[position++] = (byte) value; + totalBytesWritten++; + return; + } else { + buffer[position++] = (byte) (((int) value & 0x7F) | 0x80); + totalBytesWritten++; + value >>>= 7; + } + } + } + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferFixed32NoTag(int value) { + buffer[position++] = (byte) (value & 0xFF); + buffer[position++] = (byte) ((value >> 8) & 0xFF); + buffer[position++] = (byte) ((value >> 16) & 0xFF); + buffer[position++] = (byte) ((value >> 24) & 0xFF); + totalBytesWritten += FIXED32_SIZE; + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferFixed64NoTag(long value) { + buffer[position++] = (byte) (value & 0xFF); + buffer[position++] = (byte) ((value >> 8) & 0xFF); + buffer[position++] = (byte) ((value >> 16) & 0xFF); + buffer[position++] = (byte) ((value >> 24) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 32) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 40) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 48) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 56) & 0xFF); + totalBytesWritten += FIXED64_SIZE; + } + } /** - * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers - * into values that can be efficiently encoded with varint. (Otherwise, - * negative values must be sign-extended to 64 bits to be varint encoded, - * thus always taking 10 bytes on the wire.) - * - * @param n A signed 32-bit integer. - * @return An unsigned 32-bit integer, stored in a signed int because - * Java has no explicit unsigned support. + * A {@link CodedOutputStream} that decorates a {@link ByteOutput}. It internal buffer only to + * support string encoding operations. All other writes are just passed through to the + * {@link ByteOutput}. */ - public static int encodeZigZag32(final int n) { - // Note: the right-shift must be arithmetic - return (n << 1) ^ (n >> 31); + private static final class ByteOutputEncoder extends AbstractBufferedEncoder { + private final ByteOutput out; + + ByteOutputEncoder(ByteOutput out, int bufferSize) { + super(bufferSize); + if (out == null) { + throw new NullPointerException("out"); + } + this.out = out; + } + + @Override + public void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public void writeInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferInt32NoTag(value); + } + + @Override + public void writeUInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED32_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED64_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + bufferFixed64NoTag(value); + } + + @Override + public void writeBool(final int fieldNumber, final boolean value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + 1); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + buffer((byte) (value ? 1 : 0)); + } + + @Override + public void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + public void writeByteArray( + final int fieldNumber, final byte[] value, final int offset, final int length) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value, offset, length); + } + + @Override + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); + } + } + + @Override + public void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + + @Override + public void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + + @Override + public void write(byte value) throws IOException { + if (position == limit) { + doFlush(); + } + + buffer(value); + } + + @Override + public void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } + + @Override + public void writeUInt32NoTag(int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32NoTag(final int value) throws IOException { + flushIfNotAvailable(FIXED32_SIZE); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64NoTag(long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64NoTag(final long value) throws IOException { + flushIfNotAvailable(FIXED64_SIZE); + bufferFixed64NoTag(value); + } + + @Override + public void writeStringNoTag(String value) throws IOException { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength); + + // If we are streaming and the potential length is too big to fit in our buffer, we take the + // slower path. + if (maxLengthVarIntSize + maxLength > limit) { + // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes() + // does the same internally and then does *another copy* to return a byte[] of exactly the + // right size. We can skip that copy and just writeRawBytes up to the actualLength of the + // UTF-8 encoded bytes. + final byte[] encodedBytes = new byte[maxLength]; + int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength); + writeUInt32NoTag(actualLength); + writeLazy(encodedBytes, 0, actualLength); + return; + } + + // Fast path: we have enough space available in our buffer for the string... + if (maxLengthVarIntSize + maxLength > limit - position) { + // Flush to free up space. + doFlush(); + } + + final int oldPosition = position; + try { + // Optimize for the case where we know this length results in a constant varint length as + // this saves a pass for measuring the length of the string. + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + + if (minLengthVarIntSize == maxLengthVarIntSize) { + position = oldPosition + minLengthVarIntSize; + int newPosition = Utf8.encode(value, buffer, position, limit - position); + // Since this class is stateful and tracks the position, we rewind and store the state, + // prepend the length, then reset it back to the end of the string. + position = oldPosition; + int length = newPosition - oldPosition - minLengthVarIntSize; + bufferUInt32NoTag(length); + position = newPosition; + totalBytesWritten += length; + } else { + int length = Utf8.encodedLength(value); + bufferUInt32NoTag(length); + position = Utf8.encode(value, buffer, position, length); + totalBytesWritten += length; + } + } catch (UnpairedSurrogateException e) { + // Roll back the change and convert to an IOException. + totalBytesWritten -= position - oldPosition; + position = oldPosition; + + // TODO(nathanmittler): We should throw an IOException here instead. + inefficientWriteStringNoTag(value, e); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void flush() throws IOException { + if (position > 0) { + // Flush the buffer. + doFlush(); + } + } + + @Override + public void write(byte[] value, int offset, int length) throws IOException { + flush(); + out.write(value, offset, length); + totalBytesWritten += length; + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + flush(); + out.writeLazy(value, offset, length); + totalBytesWritten += length; + } + + @Override + public void write(ByteBuffer value) throws IOException { + flush(); + int length = value.remaining(); + out.write(value); + totalBytesWritten += length; + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + flush(); + int length = value.remaining(); + out.writeLazy(value); + totalBytesWritten += length; + } + + private void flushIfNotAvailable(int requiredSize) throws IOException { + if (limit - position < requiredSize) { + doFlush(); + } + } + + private void doFlush() throws IOException { + out.write(buffer, 0, position); + position = 0; + } } /** - * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers - * into values that can be efficiently encoded with varint. (Otherwise, - * negative values must be sign-extended to 64 bits to be varint encoded, - * thus always taking 10 bytes on the wire.) - * - * @param n A signed 64-bit integer. - * @return An unsigned 64-bit integer, stored in a signed int because - * Java has no explicit unsigned support. + * An {@link CodedOutputStream} that decorates an {@link OutputStream}. It performs internal + * buffering to optimize writes to the {@link OutputStream}. */ - public static long encodeZigZag64(final long n) { - // Note: the right-shift must be arithmetic - return (n << 1) ^ (n >> 63); + private static final class OutputStreamEncoder extends AbstractBufferedEncoder { + private final OutputStream out; + + OutputStreamEncoder(OutputStream out, int bufferSize) { + super(bufferSize); + if (out == null) { + throw new NullPointerException("out"); + } + this.out = out; + } + + @Override + public void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public void writeInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferInt32NoTag(value); + } + + @Override + public void writeUInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED32_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED64_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + bufferFixed64NoTag(value); + } + + @Override + public void writeBool(final int fieldNumber, final boolean value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + 1); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + buffer((byte) (value ? 1 : 0)); + } + + @Override + public void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + public void writeByteArray( + final int fieldNumber, final byte[] value, final int offset, final int length) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value, offset, length); + } + + @Override + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); + } + } + + @Override + public void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + + @Override + public void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + + @Override + public void write(byte value) throws IOException { + if (position == limit) { + doFlush(); + } + + buffer(value); + } + + @Override + public void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } + + @Override + public void writeUInt32NoTag(int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32NoTag(final int value) throws IOException { + flushIfNotAvailable(FIXED32_SIZE); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64NoTag(long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64NoTag(final long value) throws IOException { + flushIfNotAvailable(FIXED64_SIZE); + bufferFixed64NoTag(value); + } + + @Override + public void writeStringNoTag(String value) throws IOException { + try { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength); + + // If we are streaming and the potential length is too big to fit in our buffer, we take the + // slower path. + if (maxLengthVarIntSize + maxLength > limit) { + // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes() + // does the same internally and then does *another copy* to return a byte[] of exactly the + // right size. We can skip that copy and just writeRawBytes up to the actualLength of the + // UTF-8 encoded bytes. + final byte[] encodedBytes = new byte[maxLength]; + int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength); + writeUInt32NoTag(actualLength); + writeLazy(encodedBytes, 0, actualLength); + return; + } + + // Fast path: we have enough space available in our buffer for the string... + if (maxLengthVarIntSize + maxLength > limit - position) { + // Flush to free up space. + doFlush(); + } + + // Optimize for the case where we know this length results in a constant varint length as + // this saves a pass for measuring the length of the string. + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + int oldPosition = position; + final int length; + try { + if (minLengthVarIntSize == maxLengthVarIntSize) { + position = oldPosition + minLengthVarIntSize; + int newPosition = Utf8.encode(value, buffer, position, limit - position); + // Since this class is stateful and tracks the position, we rewind and store the + // state, prepend the length, then reset it back to the end of the string. + position = oldPosition; + length = newPosition - oldPosition - minLengthVarIntSize; + bufferUInt32NoTag(length); + position = newPosition; + } else { + length = Utf8.encodedLength(value); + bufferUInt32NoTag(length); + position = Utf8.encode(value, buffer, position, length); + } + totalBytesWritten += length; + } catch (UnpairedSurrogateException e) { + // Be extra careful and restore the original position for retrying the write with the + // less efficient path. + totalBytesWritten -= position - oldPosition; + position = oldPosition; + throw e; + } catch (ArrayIndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } catch (UnpairedSurrogateException e) { + inefficientWriteStringNoTag(value, e); + } + } + + @Override + public void flush() throws IOException { + if (position > 0) { + // Flush the buffer. + doFlush(); + } + } + + @Override + public void write(byte[] value, int offset, int length) + throws IOException { + if (limit - position >= length) { + // We have room in the current buffer. + System.arraycopy(value, offset, buffer, position, length); + position += length; + totalBytesWritten += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + System.arraycopy(value, offset, buffer, position, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = limit; + totalBytesWritten += bytesWritten; + doFlush(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= limit) { + // Fits in new buffer. + System.arraycopy(value, offset, buffer, 0, length); + position = length; + } else { + // Write is very big. Let's do it all at once. + out.write(value, offset, length); + } + totalBytesWritten += length; + } + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + @Override + public void write(ByteBuffer value) throws IOException { + int length = value.remaining(); + if (limit - position >= length) { + // We have room in the current buffer. + value.get(buffer, position, length); + position += length; + totalBytesWritten += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + value.get(buffer, position, bytesWritten); + length -= bytesWritten; + position = limit; + totalBytesWritten += bytesWritten; + doFlush(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + while (length > limit) { + // Copy data into the buffer before writing it to OutputStream. + value.get(buffer, 0, limit); + out.write(buffer, 0, limit); + length -= limit; + totalBytesWritten += limit; + } + value.get(buffer, 0, length); + position = length; + totalBytesWritten += length; + } + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + write(value); + } + + private void flushIfNotAvailable(int requiredSize) throws IOException { + if (limit - position < requiredSize) { + doFlush(); + } + } + + private void doFlush() throws IOException { + out.write(buffer, 0, position); + position = 0; + } } } diff --git a/java/core/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java index 5e15cfbe..75b16fe3 100644 --- a/java/core/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java @@ -30,9 +30,10 @@ package com.google.protobuf; +import static com.google.protobuf.Internal.checkNotNull; + import com.google.protobuf.DescriptorProtos.*; import com.google.protobuf.Descriptors.FileDescriptor.Syntax; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; @@ -74,16 +75,28 @@ public final class Descriptors { */ public static final class FileDescriptor extends GenericDescriptor { /** Convert the descriptor to its protocol message representation. */ - public FileDescriptorProto toProto() { return proto; } + @Override + public FileDescriptorProto toProto() { + return proto; + } /** Get the file name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** Returns this object. */ - public FileDescriptor getFile() { return this; } + @Override + public FileDescriptor getFile() { + return this; + } /** Returns the same as getName(). */ - public String getFullName() { return proto.getName(); } + @Override + public String getFullName() { + return proto.getName(); + } /** * Get the proto package name. This is the package name given by the @@ -272,7 +285,7 @@ public final class Descriptors { * because a field has an undefined type or because two messages * were defined with the same name. */ - private static FileDescriptor buildFrom( + public static FileDescriptor buildFrom( final FileDescriptorProto proto, final FileDescriptor[] dependencies, final boolean allowUnknownDependencies) throws DescriptorValidationException { @@ -582,10 +595,16 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public DescriptorProto toProto() { return proto; } + @Override + public DescriptorProto toProto() { + return proto; + } /** Get the type's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the type's fully-qualified name, within the proto language's @@ -598,10 +617,16 @@ public final class Descriptors { * </pre> * {@code Baz}'s full name is "foo.bar.Baz". */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** If this is a nested type, get the outer descriptor, otherwise null. */ public Descriptor getContainingType() { return containingType; } @@ -658,9 +683,7 @@ public final class Descriptors { /** Determines if the given field name is reserved. */ public boolean isReservedName(final String name) { - if (name == null) { - throw new NullPointerException(); - } + checkNotNull(name); for (final String reservedName : proto.getReservedNameList()) { if (reservedName.equals(name)) { return true; @@ -847,6 +870,10 @@ public final class Descriptors { nestedTypes[i].setProto(proto.getNestedType(i)); } + for (int i = 0; i < oneofs.length; i++) { + oneofs[i].setProto(proto.getOneofDecl(i)); + } + for (int i = 0; i < enumTypes.length; i++) { enumTypes[i].setProto(proto.getEnumType(i)); } @@ -875,19 +902,31 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public FieldDescriptorProto toProto() { return proto; } + @Override + public FieldDescriptorProto toProto() { + return proto; + } /** Get the field's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** Get the field's number. */ - public int getNumber() { return proto.getNumber(); } + @Override + public int getNumber() { + return proto.getNumber(); + } /** * Get the field's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the JSON name of this field. */ public String getJsonName() { @@ -901,17 +940,22 @@ public final class Descriptors { public JavaType getJavaType() { return type.getJavaType(); } /** For internal use only. */ + @Override public WireFormat.JavaType getLiteJavaType() { return getLiteType().getJavaType(); } /** Get the {@code FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the field's declared type. */ public Type getType() { return type; } /** For internal use only. */ + @Override public WireFormat.FieldType getLiteType() { return table[type.ordinal()]; } @@ -953,6 +997,7 @@ public final class Descriptors { } /** Is this field declared repeated? */ + @Override public boolean isRepeated() { return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED; } @@ -960,6 +1005,7 @@ public final class Descriptors { /** Does this field have the {@code [packed = true]} option or is this field * packable in proto3 and not explicitly setted to unpacked? */ + @Override public boolean isPacked() { if (!isPackable()) { return false; @@ -1048,6 +1094,7 @@ public final class Descriptors { } /** For enum fields, gets the field's type. */ + @Override public EnumDescriptor getEnumType() { if (getJavaType() != JavaType.ENUM) { throw new UnsupportedOperationException( @@ -1066,6 +1113,7 @@ public final class Descriptors { * @return negative, zero, or positive if {@code this} is less than, * equal to, or greater than {@code other}, respectively. */ + @Override public int compareTo(final FieldDescriptor other) { if (other.containingType != containingType) { throw new IllegalArgumentException( @@ -1123,7 +1171,7 @@ public final class Descriptors { private JavaType javaType; public FieldDescriptorProto.Type toProto() { - return FieldDescriptorProto.Type.valueOf(ordinal() + 1); + return FieldDescriptorProto.Type.forNumber(ordinal() + 1); } public JavaType getJavaType() { return javaType; } @@ -1163,33 +1211,20 @@ public final class Descriptors { private final Object defaultDefault; } - // TODO(xiaofeng): Implement it consistently across different languages. See b/24751348. - private static String fieldNameToLowerCamelCase(String name) { + // This method should match exactly with the ToJsonName() function in C++ + // descriptor.cc. + private static String fieldNameToJsonName(String name) { StringBuilder result = new StringBuilder(name.length()); boolean isNextUpperCase = false; for (int i = 0; i < name.length(); i++) { Character ch = name.charAt(i); - if (Character.isLowerCase(ch)) { - if (isNextUpperCase) { - result.append(Character.toUpperCase(ch)); - } else { - result.append(ch); - } - isNextUpperCase = false; - } else if (Character.isUpperCase(ch)) { - if (i == 0) { - // Force first letter to lower-case. - result.append(Character.toLowerCase(ch)); - } else { - // Capital letters after the first are left as-is. - result.append(ch); - } - isNextUpperCase = false; - } else if (Character.isDigit(ch)) { - result.append(ch); + if (ch == '_') { + isNextUpperCase = true; + } else if (isNextUpperCase) { + result.append(Character.toUpperCase(ch)); isNextUpperCase = false; } else { - isNextUpperCase = true; + result.append(ch); } } return result.toString(); @@ -1208,7 +1243,7 @@ public final class Descriptors { if (proto.hasJsonName()) { jsonName = proto.getJsonName(); } else { - jsonName = fieldNameToLowerCamelCase(proto.getName()); + jsonName = fieldNameToJsonName(proto.getName()); } if (proto.hasType()) { @@ -1466,8 +1501,8 @@ public final class Descriptors { * For internal use only. This is to satisfy the FieldDescriptorLite * interface. */ - public MessageLite.Builder internalMergeFrom( - MessageLite.Builder to, MessageLite from) { + @Override + public MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from) { // FieldDescriptors are only used with non-lite messages so we can just // down-cast and call mergeFrom directly. return ((Message.Builder) to).mergeFrom((Message) from); @@ -1487,19 +1522,31 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public EnumDescriptorProto toProto() { return proto; } + @Override + public EnumDescriptorProto toProto() { + return proto; + } /** Get the type's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the type's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** If this is a nested type, get the outer descriptor, otherwise null. */ public Descriptor getContainingType() { return containingType; } @@ -1533,6 +1580,7 @@ public final class Descriptors { * @param number The value's number. * @return the value's descriptor, or {@code null} if not found. */ + @Override public EnumValueDescriptor findValueByNumber(final int number) { return file.pool.enumValuesByNumber.get( new DescriptorPool.DescriptorIntPair(this, number)); @@ -1659,13 +1707,22 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public EnumValueDescriptorProto toProto() { return proto; } + @Override + public EnumValueDescriptorProto toProto() { + return proto; + } /** Get the value's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** Get the value's number. */ - public int getNumber() { return proto.getNumber(); } + @Override + public int getNumber() { + return proto.getNumber(); + } @Override public String toString() { return proto.getName(); } @@ -1674,10 +1731,16 @@ public final class Descriptors { * Get the value's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the value's enum type. */ public EnumDescriptor getType() { return type; } @@ -1745,19 +1808,31 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public ServiceDescriptorProto toProto() { return proto; } + @Override + public ServiceDescriptorProto toProto() { + return proto; + } /** Get the type's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the type's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the {@code ServiceOptions}, defined in {@code descriptor.proto}. */ public ServiceOptions getOptions() { return proto.getOptions(); } @@ -1835,19 +1910,31 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public MethodDescriptorProto toProto() { return proto; } + @Override + public MethodDescriptorProto toProto() { + return proto; + } /** Get the method's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the method's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the method's service type. */ public ServiceDescriptor getService() { return service; } @@ -2035,7 +2122,7 @@ public final class Descriptors { // Can't happen, because addPackage() only fails when the name // conflicts with a non-package, but we have not yet added any // non-packages at this point. - assert false; + throw new AssertionError(e); } } } @@ -2248,10 +2335,22 @@ public final class Descriptors { * that has the same name as an existing package. */ private static final class PackageDescriptor extends GenericDescriptor { - public Message toProto() { return file.toProto(); } - public String getName() { return name; } - public String getFullName() { return fullName; } - public FileDescriptor getFile() { return file; } + @Override + public Message toProto() { + return file.toProto(); + } + @Override + public String getName() { + return name; + } + @Override + public String getFullName() { + return fullName; + } + @Override + public FileDescriptor getFile() { + return file; + } PackageDescriptor(final String name, final String fullName, final FileDescriptor file) { @@ -2404,6 +2503,10 @@ public final class Descriptors { public int getFieldCount() { return fieldCount; } + public OneofOptions getOptions() { + return proto.getOptions(); + } + /** Get a list of this message type's fields. */ public List<FieldDescriptor> getFields() { return Collections.unmodifiableList(Arrays.asList(fields)); @@ -2413,6 +2516,10 @@ public final class Descriptors { return fields[index]; } + private void setProto(final OneofDescriptorProto proto) { + this.proto = proto; + } + private OneofDescriptor(final OneofDescriptorProto proto, final FileDescriptor file, final Descriptor parent, diff --git a/java/core/src/main/java/com/google/protobuf/DiscardUnknownFieldsParser.java b/java/core/src/main/java/com/google/protobuf/DiscardUnknownFieldsParser.java new file mode 100644 index 00000000..7ae94349 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/DiscardUnknownFieldsParser.java @@ -0,0 +1,71 @@ +// 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; + +/** + * Parsers to discard unknown fields during parsing. + */ +public final class DiscardUnknownFieldsParser { + + /** + * Warps a given {@link Parser} into a new {@link Parser} that discards unknown fields during + * parsing. + * + * <p>Usage example: + * <pre>{@code + * private final static Parser<Foo> FOO_PARSER = DiscardUnknownFieldsParser.wrap(Foo.parser()); + * Foo parseFooDiscardUnknown(ByteBuffer input) throws IOException { + * return FOO_PARSER.parseFrom(input); + * } + * }</pre> + * + * <p>Like all other implementations of {@code Parser}, this parser is stateless and thread-safe. + * + * @param parser The delegated parser that parses messages. + * @return a {@link Parser} that will discard unknown fields during parsing. + */ + public static final <T extends Message> Parser<T> wrap(final Parser<T> parser) { + return new AbstractParser<T>() { + @Override + public T parsePartialFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + input.discardUnknownFields(); + return parser.parsePartialFrom(input, extensionRegistry); + } finally { + input.unsetDiscardUnknownFields(); + } + } + }; + } + + private DiscardUnknownFieldsParser() {} +} diff --git a/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java index bcc9d6ee..5b28b4a8 100644 --- a/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java @@ -30,37 +30,35 @@ package com.google.protobuf; -import com.google.protobuf.Internal.DoubleList; +import static com.google.protobuf.Internal.checkNotNull; +import com.google.protobuf.Internal.DoubleList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** * An implementation of {@link DoubleList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class DoubleArrayList - extends AbstractProtobufList<Double> implements DoubleList, RandomAccess { - - private static final int DEFAULT_CAPACITY = 10; - +final class DoubleArrayList extends AbstractProtobufList<Double> + implements DoubleList, RandomAccess, PrimitiveNonBoxingCollection { + private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static DoubleArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private double[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -71,34 +69,71 @@ final class DoubleArrayList * Constructs a new mutable {@code DoubleArrayList} with default capacity. */ DoubleArrayList() { - this(DEFAULT_CAPACITY); + this(new double[DEFAULT_CAPACITY], 0); } /** - * Constructs a new mutable {@code DoubleArrayList} with the provided capacity. + * Constructs a new mutable {@code DoubleArrayList} + * containing the same elements as {@code other}. */ - DoubleArrayList(int capacity) { - array = new double[capacity]; - size = 0; + private DoubleArrayList(double[] other, int size) { + array = other; + this.size = size; } - /** - * Constructs a new mutable {@code DoubleArrayList} containing the same elements as {@code other}. - */ - DoubleArrayList(List<Double> other) { - if (other instanceof DoubleArrayList) { - DoubleArrayList list = (DoubleArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new double[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + @Override + protected void removeRange(int fromIndex, int toIndex) { + ensureIsMutable(); + if (toIndex < fromIndex) { + throw new IndexOutOfBoundsException("toIndex < fromIndex"); + } + + System.arraycopy(array, toIndex, array, fromIndex, size - toIndex); + size -= (toIndex - fromIndex); + modCount++; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DoubleArrayList)) { + return super.equals(o); + } + DoubleArrayList other = (DoubleArrayList) o; + if (size != other.size) { + return false; + } + + final double[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + long bits = Double.doubleToLongBits(array[i]); + result = (31 * result) + Internal.hashLong(bits); + } + return result; } - + + @Override + public DoubleList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new DoubleArrayList(Arrays.copyOf(array, capacity), size); + } + @Override public Double get(int index) { return getDouble(index); @@ -150,7 +185,7 @@ final class DoubleArrayList if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -158,10 +193,10 @@ final class DoubleArrayList // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; double[] newArray = new double[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -175,38 +210,36 @@ final class DoubleArrayList @Override public boolean addAll(Collection<? extends Double> collection) { ensureIsMutable(); - - if (collection == null) { - throw new NullPointerException(); - } - + + checkNotNull(collection); + // We specialize when adding another DoubleArrayList to avoid boxing elements. if (!(collection instanceof DoubleArrayList)) { return super.addAll(collection); } - + DoubleArrayList list = (DoubleArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -226,7 +259,9 @@ final class DoubleArrayList ensureIsMutable(); ensureIndexInRange(index); double value = array[index]; - System.arraycopy(array, index + 1, array, index, size - index); + if (index < size - 1) { + System.arraycopy(array, index + 1, array, index, size - index); + } size--; modCount++; return value; @@ -235,7 +270,7 @@ final class DoubleArrayList /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/DynamicMessage.java b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java index 3ea1b688..a6a774b7 100644 --- a/java/core/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java @@ -30,11 +30,12 @@ package com.google.protobuf; +import static com.google.protobuf.Internal.checkNotNull; + import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; - import java.io.IOException; import java.io.InputStream; import java.util.Collections; @@ -156,18 +157,22 @@ public final class DynamicMessage extends AbstractMessage { // ----------------------------------------------------------------- // Implementation of Message interface. + @Override public Descriptor getDescriptorForType() { return type; } + @Override public DynamicMessage getDefaultInstanceForType() { return getDefaultInstance(type); } + @Override public Map<FieldDescriptor, Object> getAllFields() { return fields.getAllFields(); } + @Override public boolean hasOneof(OneofDescriptor oneof) { verifyOneofContainingType(oneof); FieldDescriptor field = oneofCases[oneof.getIndex()]; @@ -177,16 +182,19 @@ public final class DynamicMessage extends AbstractMessage { return true; } + @Override public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { verifyOneofContainingType(oneof); return oneofCases[oneof.getIndex()]; } + @Override public boolean hasField(FieldDescriptor field) { verifyContainingType(field); return fields.hasField(field); } + @Override public Object getField(FieldDescriptor field) { verifyContainingType(field); Object result = fields.getField(field); @@ -202,16 +210,19 @@ public final class DynamicMessage extends AbstractMessage { return result; } + @Override public int getRepeatedFieldCount(FieldDescriptor field) { verifyContainingType(field); return fields.getRepeatedFieldCount(field); } + @Override public Object getRepeatedField(FieldDescriptor field, int index) { verifyContainingType(field); return fields.getRepeatedField(field, index); } + @Override public UnknownFieldSet getUnknownFields() { return unknownFields; } @@ -264,19 +275,22 @@ public final class DynamicMessage extends AbstractMessage { return size; } + @Override public Builder newBuilderForType() { return new Builder(type); } + @Override public Builder toBuilder() { return newBuilderForType().mergeFrom(this); } + @Override public Parser<DynamicMessage> getParserForType() { return new AbstractParser<DynamicMessage>() { + @Override public DynamicMessage parsePartialFrom( - CodedInputStream input, - ExtensionRegistryLite extensionRegistry) + CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { Builder builder = newBuilder(type); try { @@ -284,7 +298,7 @@ public final class DynamicMessage extends AbstractMessage { } catch (InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(builder.buildPartial()); } catch (IOException e) { - throw new InvalidProtocolBufferException(e.getMessage()) + throw new InvalidProtocolBufferException(e) .setUnfinishedMessage(builder.buildPartial()); } return builder.buildPartial(); @@ -325,6 +339,20 @@ public final class DynamicMessage extends AbstractMessage { this.fields = FieldSet.newFieldSet(); this.unknownFields = UnknownFieldSet.getDefaultInstance(); this.oneofCases = new FieldDescriptor[type.toProto().getOneofDeclCount()]; + // A MapEntry has all of its fields present at all times. + if (type.getOptions().getMapEntry()) { + populateMapEntry(); + } + } + + private void populateMapEntry() { + for (FieldDescriptor field : type.getFields()) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + fields.setField(field, getDefaultInstance(field.getMessageType())); + } else { + fields.setField(field, field.getDefaultValue()); + } + } } // --------------------------------------------------------------- @@ -337,6 +365,10 @@ public final class DynamicMessage extends AbstractMessage { } else { fields.clear(); } + // A MapEntry has all of its fields present at all times. + if (type.getOptions().getMapEntry()) { + populateMapEntry(); + } unknownFields = UnknownFieldSet.getDefaultInstance(); return this; } @@ -370,6 +402,7 @@ public final class DynamicMessage extends AbstractMessage { } } + @Override public DynamicMessage build() { if (!isInitialized()) { throw newUninitializedMessageException( @@ -394,6 +427,7 @@ public final class DynamicMessage extends AbstractMessage { return buildPartial(); } + @Override public DynamicMessage buildPartial() { fields.makeImmutable(); DynamicMessage result = @@ -411,22 +445,27 @@ public final class DynamicMessage extends AbstractMessage { return result; } + @Override public boolean isInitialized() { return DynamicMessage.isInitialized(type, fields); } + @Override public Descriptor getDescriptorForType() { return type; } + @Override public DynamicMessage getDefaultInstanceForType() { return getDefaultInstance(type); } + @Override public Map<FieldDescriptor, Object> getAllFields() { return fields.getAllFields(); } + @Override public Builder newBuilderForField(FieldDescriptor field) { verifyContainingType(field); @@ -438,6 +477,7 @@ public final class DynamicMessage extends AbstractMessage { return new Builder(field.getMessageType()); } + @Override public boolean hasOneof(OneofDescriptor oneof) { verifyOneofContainingType(oneof); FieldDescriptor field = oneofCases[oneof.getIndex()]; @@ -447,11 +487,13 @@ public final class DynamicMessage extends AbstractMessage { return true; } + @Override public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { verifyOneofContainingType(oneof); return oneofCases[oneof.getIndex()]; } + @Override public Builder clearOneof(OneofDescriptor oneof) { verifyOneofContainingType(oneof); FieldDescriptor field = oneofCases[oneof.getIndex()]; @@ -461,11 +503,13 @@ public final class DynamicMessage extends AbstractMessage { return this; } + @Override public boolean hasField(FieldDescriptor field) { verifyContainingType(field); return fields.hasField(field); } + @Override public Object getField(FieldDescriptor field) { verifyContainingType(field); Object result = fields.getField(field); @@ -481,6 +525,7 @@ public final class DynamicMessage extends AbstractMessage { return result; } + @Override public Builder setField(FieldDescriptor field, Object value) { verifyContainingType(field); ensureIsMutable(); @@ -500,11 +545,20 @@ public final class DynamicMessage extends AbstractMessage { fields.clearField(oldField); } oneofCases[index] = field; + } else if (field.getFile().getSyntax() == Descriptors.FileDescriptor.Syntax.PROTO3) { + if (!field.isRepeated() + && field.getJavaType() != FieldDescriptor.JavaType.MESSAGE + && value.equals(field.getDefaultValue())) { + // In proto3, setting a field to its default value is equivalent to clearing the field. + fields.clearField(field); + return this; + } } fields.setField(field, value); return this; } + @Override public Builder clearField(FieldDescriptor field) { verifyContainingType(field); ensureIsMutable(); @@ -519,24 +573,27 @@ public final class DynamicMessage extends AbstractMessage { return this; } + @Override public int getRepeatedFieldCount(FieldDescriptor field) { verifyContainingType(field); return fields.getRepeatedFieldCount(field); } + @Override public Object getRepeatedField(FieldDescriptor field, int index) { verifyContainingType(field); return fields.getRepeatedField(field, index); } - public Builder setRepeatedField(FieldDescriptor field, - int index, Object value) { + @Override + public Builder setRepeatedField(FieldDescriptor field, int index, Object value) { verifyContainingType(field); ensureIsMutable(); fields.setRepeatedField(field, index, value); return this; } + @Override public Builder addRepeatedField(FieldDescriptor field, Object value) { verifyContainingType(field); ensureIsMutable(); @@ -544,14 +601,15 @@ public final class DynamicMessage extends AbstractMessage { return this; } + @Override public UnknownFieldSet getUnknownFields() { return unknownFields; } + @Override public Builder setUnknownFields(UnknownFieldSet unknownFields) { - if (getDescriptorForType().getFile().getSyntax() - == Descriptors.FileDescriptor.Syntax.PROTO3) { - // Proto3 discards unknown fields. + if (getDescriptorForType().getFile().getSyntax() == Descriptors.FileDescriptor.Syntax.PROTO3 + && CodedInputStream.getProto3DiscardUnknownFieldsDefault()) { return this; } this.unknownFields = unknownFields; @@ -560,9 +618,8 @@ public final class DynamicMessage extends AbstractMessage { @Override public Builder mergeUnknownFields(UnknownFieldSet unknownFields) { - if (getDescriptorForType().getFile().getSyntax() - == Descriptors.FileDescriptor.Syntax.PROTO3) { - // Proto3 discards unknown fields. + if (getDescriptorForType().getFile().getSyntax() == Descriptors.FileDescriptor.Syntax.PROTO3 + && CodedInputStream.getProto3DiscardUnknownFieldsDefault()) { return this; } this.unknownFields = @@ -591,9 +648,7 @@ public final class DynamicMessage extends AbstractMessage { /** Verifies that the value is EnumValueDescriptor and matches Enum Type. */ private void ensureSingularEnumValueDescriptor( FieldDescriptor field, Object value) { - if (value == null) { - throw new NullPointerException(); - } + checkNotNull(value); if (!(value instanceof EnumValueDescriptor)) { throw new IllegalArgumentException( "DynamicMessage should use EnumValueDescriptor to set Enum Value."); diff --git a/java/core/src/main/java/com/google/protobuf/ExperimentalApi.java b/java/core/src/main/java/com/google/protobuf/ExperimentalApi.java index 6f41fb81..3cd4c884 100644 --- a/java/core/src/main/java/com/google/protobuf/ExperimentalApi.java +++ b/java/core/src/main/java/com/google/protobuf/ExperimentalApi.java @@ -1,3 +1,33 @@ +// 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.lang.annotation.Documented; diff --git a/java/core/src/main/java/com/google/protobuf/Extension.java b/java/core/src/main/java/com/google/protobuf/Extension.java index 68d29f33..5df12e64 100644 --- a/java/core/src/main/java/com/google/protobuf/Extension.java +++ b/java/core/src/main/java/com/google/protobuf/Extension.java @@ -42,6 +42,7 @@ public abstract class Extension<ContainingType extends MessageLite, Type> public abstract Descriptors.FieldDescriptor getDescriptor(); /** Returns whether or not this extension is a Lite Extension. */ + @Override final boolean isLite() { return false; } @@ -57,10 +58,7 @@ public abstract class Extension<ContainingType extends MessageLite, Type> PROTO1, } - protected ExtensionType getExtensionType() { - // TODO(liujisi): make this abstract after we fix proto1. - return ExtensionType.IMMUTABLE; - } + protected abstract ExtensionType getExtensionType(); /** * Type of a message extension. @@ -69,7 +67,7 @@ public abstract class Extension<ContainingType extends MessageLite, Type> PROTO1, PROTO2, } - + /** * If the extension is a message extension (i.e., getLiteType() == MESSAGE), * returns the type of the message, otherwise undefined. diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java index 0067392f..a22a74a0 100644 --- a/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java @@ -32,7 +32,6 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; - import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -101,7 +100,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite { /** Get the unmodifiable singleton empty instance. */ public static ExtensionRegistry getEmptyRegistry() { - return EMPTY; + return EMPTY_REGISTRY; } @@ -243,6 +242,11 @@ public class ExtensionRegistry extends ExtensionRegistryLite { add(newExtensionInfo(extension), extension.getExtensionType()); } + /** Add an extension from a generated file to the registry. */ + public void add(final GeneratedMessage.GeneratedExtension<?, ?> extension) { + add((Extension<?, ?>) extension); + } + static ExtensionInfo newExtensionInfo(final Extension<?, ?> extension) { if (extension.getDescriptor().getJavaType() == FieldDescriptor.JavaType.MESSAGE) { @@ -311,7 +315,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite { private final Map<DescriptorIntPair, ExtensionInfo> mutableExtensionsByNumber; ExtensionRegistry(boolean empty) { - super(ExtensionRegistryLite.getEmptyRegistry()); + super(EMPTY_REGISTRY_LITE); this.immutableExtensionsByName = Collections.<String, ExtensionInfo>emptyMap(); this.mutableExtensionsByName = @@ -321,7 +325,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite { this.mutableExtensionsByNumber = Collections.<DescriptorIntPair, ExtensionInfo>emptyMap(); } - private static final ExtensionRegistry EMPTY = new ExtensionRegistry(true); + static final ExtensionRegistry EMPTY_REGISTRY = new ExtensionRegistry(true); private void add( final ExtensionInfo extension, diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java new file mode 100644 index 00000000..89f7ab9b --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java @@ -0,0 +1,96 @@ +// 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 static com.google.protobuf.ExtensionRegistryLite.EMPTY_REGISTRY_LITE; + +/** + * A factory object to create instances of {@link ExtensionRegistryLite}. + * + * <p> + * This factory detects (via reflection) if the full (non-Lite) protocol buffer libraries + * are available, and if so, the instances returned are actually {@link ExtensionRegistry}. + */ +final class ExtensionRegistryFactory { + + static final String FULL_REGISTRY_CLASS_NAME = "com.google.protobuf.ExtensionRegistry"; + + /* Visible for Testing + @Nullable */ + static final Class<?> EXTENSION_REGISTRY_CLASS = reflectExtensionRegistry(); + + /* @Nullable */ + static Class<?> reflectExtensionRegistry() { + try { + return Class.forName(FULL_REGISTRY_CLASS_NAME); + } catch (ClassNotFoundException e) { + // The exception allocation is potentially expensive on Android (where it can be triggered + // many times at start up). Is there a way to ameliorate this? + return null; + } + } + + /** Construct a new, empty instance. */ + public static ExtensionRegistryLite create() { + if (EXTENSION_REGISTRY_CLASS != null) { + try { + return invokeSubclassFactory("newInstance"); + } catch (Exception e) { + // return a Lite registry. + } + } + return new ExtensionRegistryLite(); + } + + /** Get the unmodifiable singleton empty instance. */ + public static ExtensionRegistryLite createEmpty() { + if (EXTENSION_REGISTRY_CLASS != null) { + try { + return invokeSubclassFactory("getEmptyRegistry"); + } catch (Exception e) { + // return a Lite registry. + } + } + return EMPTY_REGISTRY_LITE; + } + + + static boolean isFullRegistry(ExtensionRegistryLite registry) { + return EXTENSION_REGISTRY_CLASS != null + && EXTENSION_REGISTRY_CLASS.isAssignableFrom(registry.getClass()); + } + + private static final ExtensionRegistryLite invokeSubclassFactory(String methodName) + throws Exception { + return (ExtensionRegistryLite) EXTENSION_REGISTRY_CLASS + .getDeclaredMethod(methodName).invoke(null); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java index 65cf7385..f3d48d3a 100644 --- a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java @@ -79,6 +79,22 @@ public class ExtensionRegistryLite { // applications. Need to support this feature on smaller granularity. private static volatile boolean eagerlyParseMessageSets = false; + // Visible for testing. + static final String EXTENSION_CLASS_NAME = "com.google.protobuf.Extension"; + + /* @Nullable */ + static Class<?> resolveExtensionClass() { + try { + return Class.forName(EXTENSION_CLASS_NAME); + } catch (ClassNotFoundException e) { + // See comment in ExtensionRegistryFactory on the potential expense of this. + return null; + } + } + + /* @Nullable */ + private static final Class<?> extensionClass = resolveExtensionClass(); + public static boolean isEagerlyParseMessageSets() { return eagerlyParseMessageSets; } @@ -87,16 +103,25 @@ public class ExtensionRegistryLite { eagerlyParseMessageSets = isEagerlyParse; } - /** Construct a new, empty instance. */ + /** + * Construct a new, empty instance. + * + * <p>This may be an {@code ExtensionRegistry} if the full (non-Lite) proto libraries are + * available. + */ public static ExtensionRegistryLite newInstance() { - return new ExtensionRegistryLite(); + return ExtensionRegistryFactory.create(); } - /** Get the unmodifiable singleton empty instance. */ + /** + * Get the unmodifiable singleton empty instance of either ExtensionRegistryLite or + * {@code ExtensionRegistry} (if the full (non-Lite) proto libraries are available). + */ public static ExtensionRegistryLite getEmptyRegistry() { - return EMPTY; + return ExtensionRegistryFactory.createEmpty(); } + /** Returns an unmodifiable view of the registry. */ public ExtensionRegistryLite getUnmodifiable() { return new ExtensionRegistryLite(this); @@ -128,6 +153,23 @@ public class ExtensionRegistryLite { extension); } + /** + * Add an extension from a lite generated file to the registry only if it is + * a non-lite extension i.e. {@link GeneratedMessageLite.GeneratedExtension}. */ + public final void add(ExtensionLite<?, ?> extension) { + if (GeneratedMessageLite.GeneratedExtension.class.isAssignableFrom(extension.getClass())) { + add((GeneratedMessageLite.GeneratedExtension<?, ?>) extension); + } + if (ExtensionRegistryFactory.isFullRegistry(this)) { + try { + this.getClass().getMethod("add", extensionClass).invoke(this, extension); + } catch (Exception e) { + throw new IllegalArgumentException( + String.format("Could not invoke ExtensionRegistry#add for %s", extension), e); + } + } + } + // ================================================================= // Private stuff. @@ -139,9 +181,11 @@ public class ExtensionRegistryLite { new HashMap<ObjectIntPair, GeneratedMessageLite.GeneratedExtension<?, ?>>(); } + static final ExtensionRegistryLite EMPTY_REGISTRY_LITE = + new ExtensionRegistryLite(true); ExtensionRegistryLite(ExtensionRegistryLite other) { - if (other == EMPTY) { + if (other == EMPTY_REGISTRY_LITE) { this.extensionsByNumber = Collections.emptyMap(); } else { this.extensionsByNumber = @@ -153,11 +197,9 @@ public class ExtensionRegistryLite { GeneratedMessageLite.GeneratedExtension<?, ?>> extensionsByNumber; - private ExtensionRegistryLite(boolean empty) { + ExtensionRegistryLite(boolean empty) { this.extensionsByNumber = Collections.emptyMap(); } - private static final ExtensionRegistryLite EMPTY = - new ExtensionRegistryLite(true); /** A (Object, int) pair, used as a map key. */ private static final class ObjectIntPair { diff --git a/java/core/src/main/java/com/google/protobuf/FieldSet.java b/java/core/src/main/java/com/google/protobuf/FieldSet.java index 47924b65..c09daa32 100644 --- a/java/core/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/FieldSet.java @@ -30,8 +30,9 @@ package com.google.protobuf; -import com.google.protobuf.LazyField.LazyIterator; +import static com.google.protobuf.Internal.checkNotNull; +import com.google.protobuf.LazyField.LazyIterator; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -101,6 +102,11 @@ final class FieldSet<FieldDescriptorType extends @SuppressWarnings("rawtypes") private static final FieldSet DEFAULT_INSTANCE = new FieldSet(true); + /** Returns {@code true} if empty, {@code false} otherwise. */ + boolean isEmpty() { + return fields.isEmpty(); + } + /** Make this FieldSet immutable from this point forward. */ @SuppressWarnings("unchecked") public void makeImmutable() { @@ -121,6 +127,25 @@ final class FieldSet<FieldDescriptorType extends return isImmutable; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof FieldSet)) { + return false; + } + + FieldSet<?> other = (FieldSet<?>) o; + return fields.equals(other.fields); + } + + @Override + public int hashCode() { + return fields.hashCode(); + } + /** * Clones the FieldSet. The returned FieldSet will be mutable even if the * original FieldSet was immutable. @@ -201,6 +226,7 @@ final class FieldSet<FieldDescriptorType extends return fields.entrySet().iterator(); } + /** * Useful for implementing * {@link Message#hasField(Descriptors.FieldDescriptor)}. @@ -365,9 +391,7 @@ final class FieldSet<FieldDescriptorType extends */ private static void verifyType(final WireFormat.FieldType type, final Object value) { - if (value == null) { - throw new NullPointerException(); - } + checkNotNull(value); boolean isValid = false; switch (type.getJavaType()) { @@ -474,7 +498,7 @@ final class FieldSet<FieldDescriptorType extends } /** - * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another + * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another * {@link FieldSet}. */ public void mergeFrom(final FieldSet<FieldDescriptorType> other) { @@ -619,10 +643,11 @@ final class FieldSet<FieldDescriptorType extends * {@link Message#getField(Descriptors.FieldDescriptor)} for * this field. */ - private static void writeElement(final CodedOutputStream output, - final WireFormat.FieldType type, - final int number, - final Object value) throws IOException { + static void writeElement( + final CodedOutputStream output, + final WireFormat.FieldType type, + final int number, + final Object value) throws IOException { // Special case for groups, which need a start and end tag; other fields // can just use writeTag() and writeFieldNoTag(). if (type == WireFormat.FieldType.GROUP) { @@ -785,9 +810,8 @@ final class FieldSet<FieldDescriptorType extends * {@link Message#getField(Descriptors.FieldDescriptor)} for * this field. */ - private static int computeElementSize( - final WireFormat.FieldType type, - final int number, final Object value) { + static int computeElementSize( + final WireFormat.FieldType type, final int number, final Object value) { int tagSize = CodedOutputStream.computeTagSize(number); if (type == WireFormat.FieldType.GROUP) { // Only count the end group tag for proto2 messages as for proto1 the end diff --git a/java/core/src/main/java/com/google/protobuf/FloatArrayList.java b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java index 033b5eed..7c080af3 100644 --- a/java/core/src/main/java/com/google/protobuf/FloatArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java @@ -30,36 +30,35 @@ package com.google.protobuf; -import com.google.protobuf.Internal.FloatList; +import static com.google.protobuf.Internal.checkNotNull; +import com.google.protobuf.Internal.FloatList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** * An implementation of {@link FloatList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class FloatArrayList extends AbstractProtobufList<Float> implements FloatList, RandomAccess { - - private static final int DEFAULT_CAPACITY = 10; - +final class FloatArrayList extends AbstractProtobufList<Float> + implements FloatList, RandomAccess, PrimitiveNonBoxingCollection { + private static final FloatArrayList EMPTY_LIST = new FloatArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static FloatArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private float[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -70,34 +69,70 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL * Constructs a new mutable {@code FloatArrayList} with default capacity. */ FloatArrayList() { - this(DEFAULT_CAPACITY); + this(new float[DEFAULT_CAPACITY], 0); } /** - * Constructs a new mutable {@code FloatArrayList} with the provided capacity. + * Constructs a new mutable {@code FloatArrayList} + * containing the same elements as {@code other}. */ - FloatArrayList(int capacity) { - array = new float[capacity]; - size = 0; + private FloatArrayList(float[] other, int size) { + array = other; + this.size = size; } - /** - * Constructs a new mutable {@code FloatArrayList} containing the same elements as {@code other}. - */ - FloatArrayList(List<Float> other) { - if (other instanceof FloatArrayList) { - FloatArrayList list = (FloatArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new float[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + @Override + protected void removeRange(int fromIndex, int toIndex) { + ensureIsMutable(); + if (toIndex < fromIndex) { + throw new IndexOutOfBoundsException("toIndex < fromIndex"); + } + + System.arraycopy(array, toIndex, array, fromIndex, size - toIndex); + size -= (toIndex - fromIndex); + modCount++; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FloatArrayList)) { + return super.equals(o); + } + FloatArrayList other = (FloatArrayList) o; + if (size != other.size) { + return false; + } + + final float[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + Float.floatToIntBits(array[i]); + } + return result; } - + + @Override + public FloatList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new FloatArrayList(Arrays.copyOf(array, capacity), size); + } + @Override public Float get(int index) { return getFloat(index); @@ -149,7 +184,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -157,10 +192,10 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; float[] newArray = new float[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -174,38 +209,36 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL @Override public boolean addAll(Collection<? extends Float> collection) { ensureIsMutable(); - - if (collection == null) { - throw new NullPointerException(); - } - + + checkNotNull(collection); + // We specialize when adding another FloatArrayList to avoid boxing elements. if (!(collection instanceof FloatArrayList)) { return super.addAll(collection); } - + FloatArrayList list = (FloatArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -225,7 +258,9 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL ensureIsMutable(); ensureIndexInRange(index); float value = array[index]; - System.arraycopy(array, index + 1, array, index, size - index); + if (index < size - 1) { + System.arraycopy(array, index + 1, array, index, size - index); + } size--; modCount++; return value; @@ -234,7 +269,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java index ceb97a4e..60179e37 100644 --- a/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java @@ -80,6 +80,7 @@ public abstract class GeneratedMessage extends AbstractMessage unknownFields = builder.getUnknownFields(); } + @Override public Parser<? extends GeneratedMessage> getParserForType() { throw new UnsupportedOperationException( "This is supposed to be overridden by subclasses."); @@ -102,7 +103,7 @@ public abstract class GeneratedMessage extends AbstractMessage */ protected abstract FieldAccessorTable internalGetFieldAccessorTable(); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Descriptor getDescriptorForType() { return internalGetFieldAccessorTable().descriptor; } @@ -191,7 +192,7 @@ public abstract class GeneratedMessage extends AbstractMessage return true; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Map<FieldDescriptor, Object> getAllFields() { return Collections.unmodifiableMap( getAllFieldsMutable(/* getBytesForString = */ false)); @@ -212,22 +213,22 @@ public abstract class GeneratedMessage extends AbstractMessage getAllFieldsMutable(/* getBytesForString = */ true)); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasOneof(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).get(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Object getField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).get(this); } @@ -244,19 +245,19 @@ public abstract class GeneratedMessage extends AbstractMessage return internalGetFieldAccessorTable().getField(field).getRaw(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int getRepeatedFieldCount(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field) .getRepeatedCount(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Object getRepeatedField(final FieldDescriptor field, final int index) { return internalGetFieldAccessorTable().getField(field) .getRepeated(this, index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public UnknownFieldSet getUnknownFields() { throw new UnsupportedOperationException( "This is supposed to be overridden by subclasses."); @@ -354,33 +355,32 @@ public abstract class GeneratedMessage extends AbstractMessage // Noop for messages without extensions. } - protected abstract Message.Builder newBuilderForType(BuilderParent parent); + /** + * TODO(xiaofeng): remove this after b/29368482 is fixed. We need to move this + * interface to AbstractMessage in order to versioning GeneratedMessage but + * this move breaks binary compatibility for AppEngine. After AppEngine is + * fixed we can exlude this from google3. + */ + protected interface BuilderParent extends AbstractMessage.BuilderParent {} /** - * Interface for the parent of a Builder that allows the builder to - * communicate invalidations back to the parent for use when using nested - * builders. + * TODO(xiaofeng): remove this together with GeneratedMessage.BuilderParent. */ - protected interface BuilderParent { + protected abstract Message.Builder newBuilderForType(BuilderParent parent); - /** - * A builder becomes dirty whenever a field is modified -- including fields - * in nested builders -- and becomes clean when build() is called. Thus, - * when a builder becomes dirty, all its parents become dirty as well, and - * when it becomes clean, all its children become clean. The dirtiness - * state is used to invalidate certain cached values. - * <br> - * To this end, a builder calls markAsDirty() on its parent whenever it - * transitions from clean to dirty. The parent must propagate this call to - * its own parent, unless it was already dirty, in which case the - * grandparent must necessarily already be dirty as well. The parent can - * only transition back to "clean" after calling build() on all children. - */ - void markDirty(); + @Override + protected Message.Builder newBuilderForType(final AbstractMessage.BuilderParent parent) { + return newBuilderForType(new BuilderParent() { + @Override + public void markDirty() { + parent.markDirty(); + } + }); } + @SuppressWarnings("unchecked") - public abstract static class Builder <BuilderType extends Builder> + public abstract static class Builder <BuilderType extends Builder<BuilderType>> extends AbstractMessage.Builder<BuilderType> { private BuilderParent builderParent; @@ -402,6 +402,7 @@ public abstract class GeneratedMessage extends AbstractMessage this.builderParent = builderParent; } + @Override void dispose() { builderParent = null; } @@ -419,6 +420,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Called by the subclass or a builder to notify us that a message was * built and may be cached and therefore invalidations are needed. */ + @Override protected void markClean() { this.isClean = true; } @@ -444,6 +446,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Called by the initialization and clear code paths to allow subclasses to * reset any of their builtin fields back to the initial values. */ + @Override public BuilderType clear() { unknownFields = UnknownFieldSet.getDefaultInstance(); onChanged(); @@ -457,12 +460,12 @@ public abstract class GeneratedMessage extends AbstractMessage */ protected abstract FieldAccessorTable internalGetFieldAccessorTable(); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Descriptor getDescriptorForType() { return internalGetFieldAccessorTable().descriptor; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Map<FieldDescriptor, Object> getAllFields() { return Collections.unmodifiableMap(getAllFieldsMutable()); } @@ -510,39 +513,38 @@ public abstract class GeneratedMessage extends AbstractMessage return result; } - public Message.Builder newBuilderForField( - final FieldDescriptor field) { + @Override + public Message.Builder newBuilderForField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).newBuilder(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Message.Builder getFieldBuilder(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).getBuilder(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, - int index) { + @Override + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, int index) { return internalGetFieldAccessorTable().getField(field).getRepeatedBuilder( this, index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasOneof(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).get(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Object getField(final FieldDescriptor field) { Object object = internalGetFieldAccessorTable().getField(field).get(this); if (field.isRepeated()) { @@ -554,52 +556,52 @@ public abstract class GeneratedMessage extends AbstractMessage } } - public BuilderType setField(final FieldDescriptor field, - final Object value) { + @Override + public BuilderType setField(final FieldDescriptor field, final Object value) { internalGetFieldAccessorTable().getField(field).set(this, value); return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public BuilderType clearField(final FieldDescriptor field) { internalGetFieldAccessorTable().getField(field).clear(this); return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public BuilderType clearOneof(final OneofDescriptor oneof) { internalGetFieldAccessorTable().getOneof(oneof).clear(this); return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int getRepeatedFieldCount(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field) .getRepeatedCount(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Object getRepeatedField(final FieldDescriptor field, - final int index) { + @Override + public Object getRepeatedField(final FieldDescriptor field, final int index) { return internalGetFieldAccessorTable().getField(field) .getRepeated(this, index); } - public BuilderType setRepeatedField(final FieldDescriptor field, - final int index, final Object value) { + @Override + public BuilderType setRepeatedField( + final FieldDescriptor field, final int index, final Object value) { internalGetFieldAccessorTable().getField(field) .setRepeated(this, index, value); return (BuilderType) this; } - public BuilderType addRepeatedField(final FieldDescriptor field, - final Object value) { + @Override + public BuilderType addRepeatedField(final FieldDescriptor field, final Object value) { internalGetFieldAccessorTable().getField(field).addRepeated(this, value); return (BuilderType) this; } - public BuilderType setUnknownFields( - final UnknownFieldSet unknownFields) { + @Override + public BuilderType setUnknownFields(final UnknownFieldSet unknownFields) { this.unknownFields = unknownFields; onChanged(); return (BuilderType) this; @@ -616,7 +618,7 @@ public abstract class GeneratedMessage extends AbstractMessage return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean isInitialized() { for (final FieldDescriptor field : getDescriptorForType().getFields()) { // Check that all required fields are present. @@ -646,7 +648,7 @@ public abstract class GeneratedMessage extends AbstractMessage return true; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final UnknownFieldSet getUnknownFields() { return unknownFields; } @@ -670,7 +672,7 @@ public abstract class GeneratedMessage extends AbstractMessage */ private class BuilderParentImpl implements BuilderParent { - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void markDirty() { onChanged(); } @@ -735,6 +737,7 @@ public abstract class GeneratedMessage extends AbstractMessage public interface ExtendableMessageOrBuilder< MessageType extends ExtendableMessage> extends MessageOrBuilder { // Re-define for return type covariance. + @Override Message getDefaultInstanceForType(); /** Check if a singular extension is present. */ @@ -753,6 +756,33 @@ public abstract class GeneratedMessage extends AbstractMessage <Type> Type getExtension( ExtensionLite<MessageType, List<Type>> extension, int index); + + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + Extension<MessageType, Type> extension); + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + GeneratedExtension<MessageType, Type> extension); + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + Extension<MessageType, List<Type>> extension); + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + GeneratedExtension<MessageType, List<Type>> extension); + /** Get the value of an extension. */ + <Type> Type getExtension( + Extension<MessageType, Type> extension); + /** Get the value of an extension. */ + <Type> Type getExtension( + GeneratedExtension<MessageType, Type> extension); + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + Extension<MessageType, List<Type>> extension, + int index); + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + GeneratedExtension<MessageType, List<Type>> extension, + int index); } /** @@ -795,6 +825,8 @@ public abstract class GeneratedMessage extends AbstractMessage extends GeneratedMessage implements ExtendableMessageOrBuilder<MessageType> { + private static final long serialVersionUID = 1L; + private final FieldSet<FieldDescriptor> extensions; protected ExtendableMessage() { @@ -821,9 +853,9 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Check if a singular extension is present. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) - public final <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + @Override + @SuppressWarnings("unchecked") + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -831,7 +863,8 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Get the number of elements in a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override + @SuppressWarnings("unchecked") public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extensionLite) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); @@ -842,10 +875,9 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Get the value of an extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") - public final <Type> Type getExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -867,11 +899,10 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Get one element of a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extensionLite, - final int index) { + final ExtensionLite<MessageType, List<Type>> extensionLite, final int index) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -880,6 +911,53 @@ public abstract class GeneratedMessage extends AbstractMessage extensions.getRepeatedField(descriptor, index)); } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension( + final GeneratedExtension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final Extension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final GeneratedExtension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension(final Extension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get one element of a repeated extension. */ + @Override + public final <Type> Type getExtension( + final Extension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Get one element of a repeated extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Called by subclasses to check if all extensions are initialized. */ protected boolean extensionsAreInitialized() { return extensions.isInitialized(); @@ -1019,7 +1097,9 @@ public abstract class GeneratedMessage extends AbstractMessage verifyContainingType(field); final Object value = extensions.getField(field); if (value == null) { - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + return Collections.emptyList(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { // Lacking an ExtensionRegistry, we have no way to determine the // extension's real type, so we return a DynamicMessage. return DynamicMessage.getDefaultInstance(field.getMessageType()); @@ -1103,7 +1183,7 @@ public abstract class GeneratedMessage extends AbstractMessage @SuppressWarnings("unchecked") public abstract static class ExtendableBuilder< MessageType extends ExtendableMessage, - BuilderType extends ExtendableBuilder> + BuilderType extends ExtendableBuilder<MessageType, BuilderType>> extends Builder<BuilderType> implements ExtendableMessageOrBuilder<MessageType> { @@ -1155,9 +1235,8 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Check if a singular extension is present. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) - public final <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -1165,7 +1244,7 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Get the number of elements in a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extensionLite) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); @@ -1176,9 +1255,8 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Get the value of an extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) - public final <Type> Type getExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + @Override + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -1200,10 +1278,9 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Get one element of a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extensionLite, - final int index) { + final ExtensionLite<MessageType, List<Type>> extensionLite, final int index) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -1269,6 +1346,95 @@ public abstract class GeneratedMessage extends AbstractMessage return (BuilderType) this; } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension( + final GeneratedExtension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final Extension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final GeneratedExtension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension(final Extension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final Extension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + final Extension<MessageType, Type> extension, final Type value) { + return setExtension((ExtensionLite<MessageType, Type>) extension, value); + } + /** Set the value of an extension. */ + public <Type> BuilderType setExtension( + final GeneratedExtension<MessageType, Type> extension, final Type value) { + return setExtension((ExtensionLite<MessageType, Type>) extension, value); + } + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + final Extension<MessageType, List<Type>> extension, + final int index, final Type value) { + return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value); + } + /** Set the value of one element of a repeated extension. */ + public <Type> BuilderType setExtension( + final GeneratedExtension<MessageType, List<Type>> extension, + final int index, final Type value) { + return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value); + } + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + final Extension<MessageType, List<Type>> extension, final Type value) { + return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value); + } + /** Append a value to a repeated extension. */ + public <Type> BuilderType addExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final Type value) { + return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value); + } + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + final Extension<MessageType, ?> extension) { + return clearExtension((ExtensionLite<MessageType, ?>) extension); + } + /** Clear an extension. */ + public <Type> BuilderType clearExtension( + final GeneratedExtension<MessageType, ?> extension) { + return clearExtension((ExtensionLite<MessageType, ?>) extension); + } + /** Called by subclasses to check if all extensions are initialized. */ protected boolean extensionsAreInitialized() { return extensions.isInitialized(); @@ -1456,10 +1622,9 @@ public abstract class GeneratedMessage extends AbstractMessage // obtained. return new GeneratedExtension<ContainingType, Type>( new CachedDescriptorRetriever() { - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public FieldDescriptor loadDescriptor() { - return scope.getDescriptorForType().getExtensions() - .get(descriptorIndex); + return scope.getDescriptorForType().getExtensions().get(descriptorIndex); } }, singularType, @@ -1487,6 +1652,7 @@ public abstract class GeneratedMessage extends AbstractMessage private volatile FieldDescriptor descriptor; protected abstract FieldDescriptor loadDescriptor(); + @Override public FieldDescriptor getDescriptor() { if (descriptor == null) { synchronized (this) { @@ -1516,6 +1682,7 @@ public abstract class GeneratedMessage extends AbstractMessage // obtained. return new GeneratedExtension<ContainingType, Type>( new CachedDescriptorRetriever() { + @Override protected FieldDescriptor loadDescriptor() { return scope.getDescriptorForType().findFieldByName(name); } @@ -1542,17 +1709,18 @@ public abstract class GeneratedMessage extends AbstractMessage // used to obtain the extension's FieldDescriptor. return new GeneratedExtension<ContainingType, Type>( new CachedDescriptorRetriever() { + @Override protected FieldDescriptor loadDescriptor() { try { - Class clazz = - singularType.getClassLoader().loadClass(descriptorOuterClass); - FileDescriptor file = - (FileDescriptor) clazz.getField("descriptor").get(null); + Class clazz = singularType.getClassLoader().loadClass(descriptorOuterClass); + FileDescriptor file = (FileDescriptor) clazz.getField("descriptor").get(null); return file.findExtensionByName(extensionName); } catch (Exception e) { throw new RuntimeException( - "Cannot load descriptors: " + descriptorOuterClass + - " is not a valid descriptor class name", e); + "Cannot load descriptors: " + + descriptorOuterClass + + " is not a valid descriptor class name", + e); } } }, @@ -1634,12 +1802,13 @@ public abstract class GeneratedMessage extends AbstractMessage if (descriptorRetriever != null) { throw new IllegalStateException("Already initialized."); } - descriptorRetriever = new ExtensionDescriptorRetriever() { - //@Override (Java 1.6 override semantics, but we must support 1.5) - public FieldDescriptor getDescriptor() { - return descriptor; - } - }; + descriptorRetriever = + new ExtensionDescriptorRetriever() { + @Override + public FieldDescriptor getDescriptor() { + return descriptor; + } + }; } private ExtensionDescriptorRetriever descriptorRetriever; @@ -1649,6 +1818,7 @@ public abstract class GeneratedMessage extends AbstractMessage private final Method enumGetValueDescriptor; private final ExtensionType extensionType; + @Override public FieldDescriptor getDescriptor() { if (descriptorRetriever == null) { throw new IllegalStateException( @@ -1661,10 +1831,12 @@ public abstract class GeneratedMessage extends AbstractMessage * If the extension is an embedded message or group, returns the default * instance of the message. */ + @Override public Message getMessageDefaultInstance() { return messageDefaultInstance; } + @Override protected ExtensionType getExtensionType() { return extensionType; } @@ -1675,7 +1847,7 @@ public abstract class GeneratedMessage extends AbstractMessage * EnumValueDescriptors but the native accessors use the generated enum * type. */ - // @Override + @Override @SuppressWarnings("unchecked") protected Object fromReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); @@ -1700,7 +1872,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Like {@link #fromReflectionType(Object)}, but if the type is a repeated * type, this converts a single element. */ - // @Override + @Override protected Object singularFromReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); switch (descriptor.getJavaType()) { @@ -1724,7 +1896,7 @@ public abstract class GeneratedMessage extends AbstractMessage * EnumValueDescriptors but the native accessors use the generated enum * type. */ - // @Override + @Override @SuppressWarnings("unchecked") protected Object toReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); @@ -1748,7 +1920,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Like {@link #toReflectionType(Object)}, but if the type is a repeated * type, this converts a single element. */ - // @Override + @Override protected Object singularToReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); switch (descriptor.getJavaType()) { @@ -1759,22 +1931,22 @@ public abstract class GeneratedMessage extends AbstractMessage } } - // @Override + @Override public int getNumber() { return getDescriptor().getNumber(); } - // @Override + @Override public WireFormat.FieldType getLiteType() { return getDescriptor().getLiteType(); } - // @Override + @Override public boolean isRepeated() { return getDescriptor().isRepeated(); } - // @Override + @Override @SuppressWarnings("unchecked") public Type getDefaultValue() { if (isRepeated()) { @@ -2124,49 +2296,57 @@ public abstract class GeneratedMessage extends AbstractMessage return ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); } + @Override public Object get(final GeneratedMessage message) { return invokeOrDie(getMethod, message); } + @Override public Object get(GeneratedMessage.Builder builder) { return invokeOrDie(getMethodBuilder, builder); } + @Override public Object getRaw(final GeneratedMessage message) { return get(message); } + @Override public Object getRaw(GeneratedMessage.Builder builder) { return get(builder); } + @Override public void set(final Builder builder, final Object value) { invokeOrDie(setMethod, builder, value); } - public Object getRepeated(final GeneratedMessage message, - final int index) { + @Override + public Object getRepeated(final GeneratedMessage message, final int index) { throw new UnsupportedOperationException( "getRepeatedField() called on a singular field."); } - public Object getRepeatedRaw(final GeneratedMessage message, - final int index) { + @Override + public Object getRepeatedRaw(final GeneratedMessage message, final int index) { throw new UnsupportedOperationException( "getRepeatedFieldRaw() called on a singular field."); } + @Override public Object getRepeated(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedField() called on a singular field."); } - public Object getRepeatedRaw(GeneratedMessage.Builder builder, - int index) { + @Override + public Object getRepeatedRaw(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedFieldRaw() called on a singular field."); } - public void setRepeated(final Builder builder, final int index, - final Object value) { + @Override + public void setRepeated(final Builder builder, final int index, final Object value) { throw new UnsupportedOperationException( "setRepeatedField() called on a singular field."); } + @Override public void addRepeated(final Builder builder, final Object value) { throw new UnsupportedOperationException( "addRepeatedField() called on a singular field."); } + @Override public boolean has(final GeneratedMessage message) { if (!hasHasMethod) { if (isOneofField) { @@ -2176,6 +2356,7 @@ public abstract class GeneratedMessage extends AbstractMessage } return (Boolean) invokeOrDie(hasMethod, message); } + @Override public boolean has(GeneratedMessage.Builder builder) { if (!hasHasMethod) { if (isOneofField) { @@ -2185,27 +2366,32 @@ public abstract class GeneratedMessage extends AbstractMessage } return (Boolean) invokeOrDie(hasMethodBuilder, builder); } + @Override public int getRepeatedCount(final GeneratedMessage message) { throw new UnsupportedOperationException( "getRepeatedFieldSize() called on a singular field."); } + @Override public int getRepeatedCount(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "getRepeatedFieldSize() called on a singular field."); } + @Override public void clear(final Builder builder) { invokeOrDie(clearMethod, builder); } + @Override public Message.Builder newBuilder() { throw new UnsupportedOperationException( "newBuilderForField() called on a non-Message type."); } + @Override public Message.Builder getBuilder(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "getFieldBuilder() called on a non-Message type."); } - public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, - int index) { + @Override + public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedFieldBuilder() called on a non-Message type."); } @@ -2249,18 +2435,23 @@ public abstract class GeneratedMessage extends AbstractMessage clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); } + @Override public Object get(final GeneratedMessage message) { return invokeOrDie(getMethod, message); } + @Override public Object get(GeneratedMessage.Builder builder) { return invokeOrDie(getMethodBuilder, builder); } + @Override public Object getRaw(final GeneratedMessage message) { return get(message); } + @Override public Object getRaw(GeneratedMessage.Builder builder) { return get(builder); } + @Override public void set(final Builder builder, final Object value) { // Add all the elements individually. This serves two purposes: // 1) Verifies that each element has the correct type. @@ -2271,54 +2462,64 @@ public abstract class GeneratedMessage extends AbstractMessage addRepeated(builder, element); } } - public Object getRepeated(final GeneratedMessage message, - final int index) { + @Override + public Object getRepeated(final GeneratedMessage message, final int index) { return invokeOrDie(getRepeatedMethod, message, index); } + @Override public Object getRepeated(GeneratedMessage.Builder builder, int index) { return invokeOrDie(getRepeatedMethodBuilder, builder, index); } + @Override public Object getRepeatedRaw(GeneratedMessage message, int index) { return getRepeated(message, index); } - public Object getRepeatedRaw(GeneratedMessage.Builder builder, - int index) { + @Override + public Object getRepeatedRaw(GeneratedMessage.Builder builder, int index) { return getRepeated(builder, index); } - public void setRepeated(final Builder builder, - final int index, final Object value) { + @Override + public void setRepeated(final Builder builder, final int index, final Object value) { invokeOrDie(setRepeatedMethod, builder, index, value); } + @Override public void addRepeated(final Builder builder, final Object value) { invokeOrDie(addRepeatedMethod, builder, value); } + @Override public boolean has(final GeneratedMessage message) { throw new UnsupportedOperationException( "hasField() called on a repeated field."); } + @Override public boolean has(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "hasField() called on a repeated field."); } + @Override public int getRepeatedCount(final GeneratedMessage message) { return (Integer) invokeOrDie(getCountMethod, message); } + @Override public int getRepeatedCount(GeneratedMessage.Builder builder) { return (Integer) invokeOrDie(getCountMethodBuilder, builder); } + @Override public void clear(final Builder builder) { invokeOrDie(clearMethod, builder); } + @Override public Message.Builder newBuilder() { throw new UnsupportedOperationException( "newBuilderForField() called on a non-Message type."); } + @Override public Message.Builder getBuilder(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "getFieldBuilder() called on a non-Message type."); } - public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, - int index) { + @Override + public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedFieldBuilder() called on a non-Message type."); } @@ -2355,6 +2556,8 @@ public abstract class GeneratedMessage extends AbstractMessage field.getNumber()); } + @Override + @SuppressWarnings("unchecked") public Object get(GeneratedMessage message) { List result = new ArrayList(); for (int i = 0; i < getRepeatedCount(message); i++) { @@ -2363,6 +2566,8 @@ public abstract class GeneratedMessage extends AbstractMessage return Collections.unmodifiableList(result); } + @Override + @SuppressWarnings("unchecked") public Object get(Builder builder) { List result = new ArrayList(); for (int i = 0; i < getRepeatedCount(builder); i++) { @@ -2371,14 +2576,17 @@ public abstract class GeneratedMessage extends AbstractMessage return Collections.unmodifiableList(result); } + @Override public Object getRaw(GeneratedMessage message) { return get(message); } + @Override public Object getRaw(GeneratedMessage.Builder builder) { return get(builder); } + @Override public void set(Builder builder, Object value) { clear(builder); for (Object entry : (List) value) { @@ -2386,63 +2594,76 @@ public abstract class GeneratedMessage extends AbstractMessage } } + @Override public Object getRepeated(GeneratedMessage message, int index) { return getMapField(message).getList().get(index); } + @Override public Object getRepeated(Builder builder, int index) { return getMapField(builder).getList().get(index); } + @Override public Object getRepeatedRaw(GeneratedMessage message, int index) { return getRepeated(message, index); } + @Override public Object getRepeatedRaw(Builder builder, int index) { return getRepeated(builder, index); } + @Override public void setRepeated(Builder builder, int index, Object value) { getMutableMapField(builder).getMutableList().set(index, (Message) value); } + @Override public void addRepeated(Builder builder, Object value) { getMutableMapField(builder).getMutableList().add((Message) value); } + @Override public boolean has(GeneratedMessage message) { throw new UnsupportedOperationException( "hasField() is not supported for repeated fields."); } + @Override public boolean has(Builder builder) { throw new UnsupportedOperationException( "hasField() is not supported for repeated fields."); } + @Override public int getRepeatedCount(GeneratedMessage message) { return getMapField(message).getList().size(); } + @Override public int getRepeatedCount(Builder builder) { return getMapField(builder).getList().size(); } + @Override public void clear(Builder builder) { getMutableMapField(builder).getMutableList().clear(); } + @Override public com.google.protobuf.Message.Builder newBuilder() { return mapEntryMessageDefaultInstance.newBuilderForType(); } + @Override public com.google.protobuf.Message.Builder getBuilder(Builder builder) { throw new UnsupportedOperationException( "Nested builder not supported for map fields."); } - public com.google.protobuf.Message.Builder getRepeatedBuilder( - Builder builder, int index) { + @Override + public com.google.protobuf.Message.Builder getRepeatedBuilder(Builder builder, int index) { throw new UnsupportedOperationException( "Nested builder not supported for map fields."); } diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java index 81e1862c..df01547e 100644 --- a/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -30,24 +30,28 @@ package com.google.protobuf; +import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream; import com.google.protobuf.Internal.BooleanList; import com.google.protobuf.Internal.DoubleList; +import com.google.protobuf.Internal.EnumLiteMap; import com.google.protobuf.Internal.FloatList; import com.google.protobuf.Internal.IntList; import com.google.protobuf.Internal.LongList; import com.google.protobuf.Internal.ProtobufList; import com.google.protobuf.WireFormat.FieldType; - import java.io.IOException; +import java.io.InputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Lite version of {@link GeneratedMessage}. @@ -56,49 +60,144 @@ import java.util.Map; */ public abstract class GeneratedMessageLite< MessageType extends GeneratedMessageLite<MessageType, BuilderType>, - BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> - extends AbstractMessageLite - implements Serializable { - - private static final long serialVersionUID = 1L; + BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> + extends AbstractMessageLite<MessageType, BuilderType> { + // BEGIN REGULAR + static final boolean ENABLE_EXPERIMENTAL_RUNTIME_AT_BUILD_TIME = false; + // END REGULAR + // BEGIN EXPERIMENTAL + // static final boolean ENABLE_EXPERIMENTAL_RUNTIME_AT_BUILD_TIME = true; + // END EXPERIMENTAL /** For use by generated code only. Lazily initialized to reduce allocations. */ - protected UnknownFieldSetLite unknownFields = null; - + protected UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance(); + /** For use by generated code only. */ protected int memoizedSerializedSize = -1; - + + @Override @SuppressWarnings("unchecked") // Guaranteed by runtime. public final Parser<MessageType> getParserForType() { return (Parser<MessageType>) dynamicMethod(MethodToInvoke.GET_PARSER); } + @Override @SuppressWarnings("unchecked") // Guaranteed by runtime. public final MessageType getDefaultInstanceForType() { return (MessageType) dynamicMethod(MethodToInvoke.GET_DEFAULT_INSTANCE); } + @Override @SuppressWarnings("unchecked") // Guaranteed by runtime. public final BuilderType newBuilderForType() { return (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER); } + /** + * A reflective toString function. This is primarily intended as a developer aid, while keeping + * binary size down. The first line of the {@code toString()} representation includes a commented + * version of {@code super.toString()} to act as an indicator that this should not be relied on + * for comparisons. + * <p> + * NOTE: This method relies on the field getter methods not being stripped or renamed by proguard. + * If they are, the fields will not be included in the returned string representation. + * <p> + * NOTE: This implementation is liable to change in the future, and should not be relied on in + * code. + */ + @Override + public String toString() { + return MessageLiteToString.toString(this, super.toString()); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + // BEGIN EXPERIMENTAL + // memoizedHashCode = Protobuf.getInstance().schemaFor(this).hashCode(this); + // return memoizedHashCode; + // END EXPERIMENTAL + // BEGIN REGULAR + HashCodeVisitor visitor = new HashCodeVisitor(); + visit(visitor, (MessageType) this); + memoizedHashCode = visitor.hashCode; + return memoizedHashCode; + // END REGULAR + } + + // BEGIN REGULAR + @SuppressWarnings("unchecked") // Guaranteed by runtime + int hashCode(HashCodeVisitor visitor) { + if (memoizedHashCode == 0) { + int inProgressHashCode = visitor.hashCode; + visitor.hashCode = 0; + visit(visitor, (MessageType) this); + memoizedHashCode = visitor.hashCode; + visitor.hashCode = inProgressHashCode; + } + return memoizedHashCode; + } + // END REGULAR + + @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!getDefaultInstanceForType().getClass().isInstance(other)) { + return false; + } + + // BEGIN EXPERIMENTAL + // return Protobuf.getInstance().schemaFor(this).equals(this, (MessageType) other); + // END EXPERIMENTAL + // BEGIN REGULAR + + try { + visit(EqualsVisitor.INSTANCE, (MessageType) other); + } catch (EqualsVisitor.NotEqualsException e) { + return false; + } + return true; + // END REGULAR + } + + // BEGIN REGULAR + /** Same as {@link #equals(Object)} but throws {@code NotEqualsException}. */ + @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime + boolean equals(EqualsVisitor visitor, MessageLite other) { + if (this == other) { + return true; + } + + if (!getDefaultInstanceForType().getClass().isInstance(other)) { + return false; + } + + visit(visitor, (MessageType) other); + return true; + } + // END REGULAR + // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as // mutable during the parsing constructor and immutable after. This allows us to avoid // any unnecessary intermediary allocations while reducing the generated code size. - /** - * Lazily initializes unknown fields. - */ + /** Lazily initializes unknown fields. */ private final void ensureUnknownFieldsInitialized() { - if (unknownFields == null) { + if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) { unknownFields = UnknownFieldSetLite.newInstance(); } } - + /** * Called by subclasses to parse an unknown field. For use by generated code only. - * + * * @return {@code true} unless the tag is an end-group tag. */ protected boolean parseUnknownField(int tag, CodedInputStream input) throws IOException { @@ -106,7 +205,7 @@ public abstract class GeneratedMessageLite< if (WireFormat.getTagWireType(tag) == WireFormat.WIRETYPE_END_GROUP) { return false; } - + ensureUnknownFieldsInitialized(); return unknownFields.mergeFieldFrom(tag, input); } @@ -118,7 +217,7 @@ public abstract class GeneratedMessageLite< ensureUnknownFieldsInitialized(); unknownFields.mergeVarintField(tag, value); } - + /** * Called by subclasses to parse an unknown field. For use by generated code only. */ @@ -126,22 +225,41 @@ public abstract class GeneratedMessageLite< ensureUnknownFieldsInitialized(); unknownFields.mergeLengthDelimitedField(fieldNumber, value); } - + /** * Called by subclasses to complete parsing. For use by generated code only. */ - protected void doneParsing() { - if (unknownFields == null) { - unknownFields = UnknownFieldSetLite.getDefaultInstance(); - } else { - unknownFields.makeImmutable(); - } + protected void makeImmutable() { + // BEGIN REGULAR + dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); + unknownFields.makeImmutable(); + // END REGULAR + // BEGIN EXPERIMENTAL + // Protobuf.getInstance().schemaFor(this).makeImmutable(this); + // END EXPERIMENTAL } + protected final < + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> + BuilderType createBuilder() { + return (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER); + } + + protected final < + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> + BuilderType createBuilder(MessageType prototype) { + return ((BuilderType) createBuilder()).mergeFrom(prototype); + } + + @Override public final boolean isInitialized() { - return dynamicMethod(MethodToInvoke.IS_INITIALIZED, Boolean.TRUE) != null; + return isInitialized((MessageType) this, Boolean.TRUE); } + @Override + @SuppressWarnings("unchecked") public final BuilderType toBuilder() { BuilderType builder = (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER); builder.mergeFrom((MessageType) this); @@ -155,11 +273,18 @@ public abstract class GeneratedMessageLite< * For use by generated code only. */ public static enum MethodToInvoke { + // BEGIN REGULAR IS_INITIALIZED, - PARSE_PARTIAL_FROM, - MERGE_FROM, + VISIT, + MERGE_FROM_STREAM, MAKE_IMMUTABLE, - NEW_INSTANCE, + // END REGULAR + // Rely on/modify instance state + GET_MEMOIZED_IS_INITIALIZED, + SET_MEMOIZED_IS_INITIALIZED, + + // Rely on static state + NEW_MUTABLE_INSTANCE, NEW_BUILDER, GET_DEFAULT_INSTANCE, GET_PARSER; @@ -170,24 +295,30 @@ public abstract class GeneratedMessageLite< * Theses different kinds of operations are required to implement message-level operations for * builders in the runtime. This method bundles those operations to reduce the generated methods * count. + * * <ul> - * <li>{@code PARSE_PARTIAL_FROM} is parameterized with an {@link CodedInputStream} and - * {@link ExtensionRegistryLite}. It consumes the input stream, parsing the contents into the - * returned protocol buffer. If parsing throws an {@link InvalidProtocolBufferException}, the - * implementation wraps it in a RuntimeException - * <li>{@code NEW_INSTANCE} returns a new instance of the protocol buffer - * <li>{@code IS_INITIALIZED} is parameterized with a {@code Boolean} detailing whether to - * memoize. It returns {@code null} for false and the default instance for true. We optionally - * memoize to support the Builder case, where memoization is not desired. - * <li>{@code NEW_BUILDER} returns a {@code BuilderType} instance. - * <li>{@code MERGE_FROM} is parameterized with a {@code MessageType} and merges the fields from - * that instance into this instance. - * <li>{@code MAKE_IMMUTABLE} sets all internal fields to an immutable state. + * <li>{@code MERGE_FROM_STREAM} is parameterized with an {@link CodedInputStream} and {@link + * ExtensionRegistryLite}. It consumes the input stream, parsing the contents into the + * returned protocol buffer. If parsing throws an {@link InvalidProtocolBufferException}, + * the implementation wraps it in a RuntimeException. + * <li>{@code NEW_INSTANCE} returns a new instance of the protocol buffer that has not yet been + * made immutable. See {@code MAKE_IMMUTABLE}. + * <li>{@code IS_INITIALIZED} returns {@code null} for false and the default instance for true. + * It doesn't use or modify any memoized value. + * <li>{@code GET_MEMOIZED_IS_INITIALIZED} returns the memoized {@code isInitialized} byte + * value. + * <li>{@code SET_MEMOIZED_IS_INITIALIZED} sets the memoized {@code isInitilaized} byte value to + * 1 if the first parameter is not null, or to 0 if the first parameter is null. + * <li>{@code NEW_BUILDER} returns a {@code BuilderType} instance. + * <li>{@code VISIT} is parameterized with a {@code Visitor} and a {@code MessageType} and + * recursively iterates through the fields side by side between this and the instance. + * <li>{@code MAKE_IMMUTABLE} sets all internal fields to an immutable state. * </ul> + * * This method, plus the implementation of the Builder, enables the Builder class to be proguarded * away entirely on Android. - * <p> - * For use by generated code only. + * + * <p>For use by generated code only. */ protected abstract Object dynamicMethod(MethodToInvoke method, Object arg0, Object arg1); @@ -205,9 +336,27 @@ public abstract class GeneratedMessageLite< return dynamicMethod(method, null, null); } + // BEGIN REGULAR + void visit(Visitor visitor, MessageType other) { + dynamicMethod(MethodToInvoke.VISIT, visitor, other); + unknownFields = visitor.visitUnknownFields(unknownFields, other.unknownFields); + } + // END REGULAR + + @Override + int getMemoizedSerializedSize() { + return memoizedSerializedSize; + } + + @Override + void setMemoizedSerializedSize(int size) { + memoizedSerializedSize = size; + } + + + /** - * Merge some unknown fields into the {@link UnknownFieldSetLite} for this - * message. + * Merge some unknown fields into the {@link UnknownFieldSetLite} for this message. * * <p>For use by generated code only. */ @@ -219,7 +368,7 @@ public abstract class GeneratedMessageLite< public abstract static class Builder< MessageType extends GeneratedMessageLite<MessageType, BuilderType>, BuilderType extends Builder<MessageType, BuilderType>> - extends AbstractMessageLite.Builder<BuilderType> { + extends AbstractMessageLite.Builder<MessageType, BuilderType> { private final MessageType defaultInstance; protected MessageType instance; @@ -227,7 +376,8 @@ public abstract class GeneratedMessageLite< protected Builder(MessageType defaultInstance) { this.defaultInstance = defaultInstance; - this.instance = (MessageType) defaultInstance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); + this.instance = + (MessageType) defaultInstance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); isBuilt = false; } @@ -237,26 +387,27 @@ public abstract class GeneratedMessageLite< */ protected void copyOnWrite() { if (isBuilt) { - MessageType newInstance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); - newInstance.dynamicMethod(MethodToInvoke.MERGE_FROM, instance); + MessageType newInstance = + (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); + mergeFromInstance(newInstance, instance); instance = newInstance; isBuilt = false; } } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final boolean isInitialized() { return GeneratedMessageLite.isInitialized(instance, false /* shouldMemoize */); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final BuilderType clear() { // No need to copy on write since we're dropping the instance anyways. - instance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); + instance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public BuilderType clone() { BuilderType builder = (BuilderType) getDefaultInstanceForType().newBuilderForType(); @@ -264,20 +415,19 @@ public abstract class GeneratedMessageLite< return builder; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public MessageType buildPartial() { if (isBuilt) { return instance; } - - instance.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); - instance.unknownFields.makeImmutable(); - + + instance.makeImmutable(); + isBuilt = true; return instance; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final MessageType build() { MessageType result = buildPartial(); if (!result.isInitialized()) { @@ -285,34 +435,74 @@ public abstract class GeneratedMessageLite< } return result; } - + + @Override + protected BuilderType internalMergeFrom(MessageType message) { + return mergeFrom(message); + } + /** All subclasses implement this. */ public BuilderType mergeFrom(MessageType message) { copyOnWrite(); - instance.dynamicMethod(MethodToInvoke.MERGE_FROM, message); + mergeFromInstance(instance, message); return (BuilderType) this; } - + + private void mergeFromInstance(MessageType dest, MessageType src) { + // BEGIN EXPERIMENTAL + // Protobuf.getInstance().schemaFor(dest).mergeFrom(dest, src); + // END EXPERIMENTAL + // BEGIN REGULAR + dest.visit(MergeFromVisitor.INSTANCE, src); + // END REGULAR + } + + @Override public MessageType getDefaultInstanceForType() { return defaultInstance; } - + + @Override + public BuilderType mergeFrom(byte[] input, int offset, int length) + throws InvalidProtocolBufferException { + // BEGIN REGULAR + return super.mergeFrom(input, offset, length); + // END REGULAR + // BEGIN EXPERIMENTAL + // copyOnWrite(); + // try { + // Protobuf.getInstance().schemaFor(instance).mergeFrom( + // instance, input, offset, offset + length, new ArrayDecoders.Registers()); + // } catch (InvalidProtocolBufferException e) { + // throw e; + // } catch (IndexOutOfBoundsException e) { + // throw InvalidProtocolBufferException.truncatedMessage(); + // } catch (IOException e) { + // throw new RuntimeException("Reading from byte array should not throw IOException.", e); + // } + // return (BuilderType) this; + // END EXPERIMENTAL + } + + @Override public BuilderType mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - MessageType parsedMessage = null; + throws IOException { + copyOnWrite(); try { - parsedMessage = - (MessageType) getDefaultInstanceForType().getParserForType().parsePartialFrom( - input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (MessageType) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); + // BEGIN REGULAR + instance.dynamicMethod(MethodToInvoke.MERGE_FROM_STREAM, input, extensionRegistry); + // END REGULAR + // BEGIN EXPERIMENTAL + // Protobuf.getInstance().schemaFor(instance).mergeFrom( + // instance, CodedInputStreamReader.forCodedInput(input), extensionRegistry); + // END EXPERIMENTAL + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); } + throw e; } return (BuilderType) this; } @@ -356,32 +546,38 @@ public abstract class GeneratedMessageLite< extends GeneratedMessageLite<MessageType, BuilderType> implements ExtendableMessageOrBuilder<MessageType, BuilderType> { - /** - * Represents the set of extensions on this message. For use by generated - * code only. - */ - protected FieldSet<ExtensionDescriptor> extensions = FieldSet.newFieldSet(); + /** Represents the set of extensions on this message. For use by generated code only. */ + protected FieldSet<ExtensionDescriptor> extensions = FieldSet.emptySet(); + @SuppressWarnings("unchecked") protected final void mergeExtensionFields(final MessageType other) { if (extensions.isImmutable()) { extensions = extensions.clone(); } extensions.mergeFrom(((ExtendableMessage) other).extensions); } - + + // BEGIN REGULAR + @Override + final void visit(Visitor visitor, MessageType other) { + super.visit(visitor, other); + extensions = visitor.visitExtensions(extensions, other.extensions); + } + // END REGULAR + /** * Parse an unknown field or an extension. For use by generated code only. - * + * * <p>For use by generated code only. - * + * * @return {@code true} unless the tag is an end-group tag. */ protected <MessageType extends MessageLite> boolean parseUnknownField( MessageType defaultInstance, CodedInputStream input, ExtensionRegistryLite extensionRegistry, - int tag) throws IOException { - int wireType = WireFormat.getTagWireType(tag); + int tag) + throws IOException { int fieldNumber = WireFormat.getTagFieldNumber(tag); // TODO(dweis): How much bytecode would be saved by not requiring the generated code to @@ -389,6 +585,17 @@ public abstract class GeneratedMessageLite< GeneratedExtension<MessageType, ?> extension = extensionRegistry.findLiteExtensionByNumber( defaultInstance, fieldNumber); + return parseExtension(input, extensionRegistry, extension, tag, fieldNumber); + } + + private boolean parseExtension( + CodedInputStream input, + ExtensionRegistryLite extensionRegistry, + GeneratedExtension<?, ?> extension, + int tag, + int fieldNumber) + throws IOException { + int wireType = WireFormat.getTagWireType(tag); boolean unknown = false; boolean packed = false; if (extension == null) { @@ -411,6 +618,8 @@ public abstract class GeneratedMessageLite< return parseUnknownField(tag, input); } + ensureExtensionsAreMutable(); + if (packed) { int length = input.readRawVarint32(); int limit = input.pushLimit(length); @@ -489,10 +698,156 @@ public abstract class GeneratedMessageLite< extension.singularToFieldSetType(value)); } } - return true; } + /** + * Parse an unknown field or an extension. For use by generated code only. + * + * <p>For use by generated code only. + * + * @return {@code true} unless the tag is an end-group tag. + */ + protected <MessageType extends MessageLite> boolean parseUnknownFieldAsMessageSet( + MessageType defaultInstance, + CodedInputStream input, + ExtensionRegistryLite extensionRegistry, + int tag) + throws IOException { + + if (tag == WireFormat.MESSAGE_SET_ITEM_TAG) { + mergeMessageSetExtensionFromCodedStream(defaultInstance, input, extensionRegistry); + return true; + } + + // TODO(dweis): Do we really want to support non message set wire format in message sets? + // Full runtime does... So we do for now. + int wireType = WireFormat.getTagWireType(tag); + if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) { + return parseUnknownField(defaultInstance, input, extensionRegistry, tag); + } else { + // TODO(dweis): Should we throw on invalid input? Full runtime does not... + return input.skipField(tag); + } + } + + /** + * Merges the message set from the input stream; requires message set wire format. + * + * @param defaultInstance the default instance of the containing message we are parsing in + * @param input the stream to parse from + * @param extensionRegistry the registry to use when parsing + */ + private <MessageType extends MessageLite> void mergeMessageSetExtensionFromCodedStream( + MessageType defaultInstance, + CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws IOException { + // The wire format for MessageSet is: + // message MessageSet { + // repeated group Item = 1 { + // required int32 typeId = 2; + // required bytes message = 3; + // } + // } + // "typeId" is the extension's field number. The extension can only be + // a message type, where "message" contains the encoded bytes of that + // message. + // + // In practice, we will probably never see a MessageSet item in which + // the message appears before the type ID, or where either field does not + // appear exactly once. However, in theory such cases are valid, so we + // should be prepared to accept them. + + int typeId = 0; + ByteString rawBytes = null; // If we encounter "message" before "typeId" + GeneratedExtension<?, ?> extension = null; + + // Read bytes from input, if we get it's type first then parse it eagerly, + // otherwise we store the raw bytes in a local variable. + while (true) { + final int tag = input.readTag(); + if (tag == 0) { + break; + } + + if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { + typeId = input.readUInt32(); + if (typeId != 0) { + extension = extensionRegistry.findLiteExtensionByNumber(defaultInstance, typeId); + } + + } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { + if (typeId != 0) { + if (extension != null) { + // We already know the type, so we can parse directly from the + // input with no copying. Hooray! + eagerlyMergeMessageSetExtension(input, extension, extensionRegistry, typeId); + rawBytes = null; + continue; + } + } + // We haven't seen a type ID yet or we want parse message lazily. + rawBytes = input.readBytes(); + + } else { // Unknown tag. Skip it. + if (!input.skipField(tag)) { + break; // End of group + } + } + } + input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); + + // Process the raw bytes. + if (rawBytes != null && typeId != 0) { // Zero is not a valid type ID. + if (extension != null) { // We known the type + mergeMessageSetExtensionFromBytes(rawBytes, extensionRegistry, extension); + } else { // We don't know how to parse this. Ignore it. + if (rawBytes != null) { + mergeLengthDelimitedField(typeId, rawBytes); + } + } + } + } + + private void eagerlyMergeMessageSetExtension( + CodedInputStream input, + GeneratedExtension<?, ?> extension, + ExtensionRegistryLite extensionRegistry, + int typeId) + throws IOException { + int fieldNumber = typeId; + int tag = WireFormat.makeTag(typeId, WireFormat.WIRETYPE_LENGTH_DELIMITED); + parseExtension(input, extensionRegistry, extension, tag, fieldNumber); + } + + private void mergeMessageSetExtensionFromBytes( + ByteString rawBytes, + ExtensionRegistryLite extensionRegistry, + GeneratedExtension<?, ?> extension) + throws IOException { + MessageLite.Builder subBuilder = null; + MessageLite existingValue = (MessageLite) extensions.getField(extension.descriptor); + if (existingValue != null) { + subBuilder = existingValue.toBuilder(); + } + if (subBuilder == null) { + subBuilder = extension.getMessageDefaultInstance().newBuilderForType(); + } + subBuilder.mergeFrom(rawBytes, extensionRegistry); + MessageLite value = subBuilder.build(); + + ensureExtensionsAreMutable().setField( + extension.descriptor, extension.singularToFieldSetType(value)); + } + + private FieldSet<ExtensionDescriptor> ensureExtensionsAreMutable() { + if (extensions.isImmutable()) { + extensions = extensions.clone(); + } + return extensions; + } + private void verifyExtensionContainingType( final GeneratedExtension<MessageType, ?> extension) { if (extension.getContainingTypeDefaultInstance() != @@ -505,35 +860,33 @@ public abstract class GeneratedMessageLite< } /** Check if a singular extension is present. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) - public final <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extension) { + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extension) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); return extensions.hasField(extensionLite.descriptor); } /** Get the number of elements in a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extension) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); return extensions.getRepeatedFieldCount(extensionLite.descriptor); } /** Get the value of an extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") - public final <Type> Type getExtension( - final ExtensionLite<MessageType, Type> extension) { + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extension) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); final Object value = extensions.getField(extensionLite.descriptor); if (value == null) { @@ -544,11 +897,10 @@ public abstract class GeneratedMessageLite< } /** Get one element of a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extension, - final int index) { + final ExtensionLite<MessageType, List<Type>> extension, final int index) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); @@ -562,14 +914,15 @@ public abstract class GeneratedMessageLite< return extensions.isInitialized(); } - @Override - protected final void doneParsing() { - super.doneParsing(); - + protected final void makeImmutable() { + super.makeImmutable(); + // BEGIN REGULAR extensions.makeImmutable(); + // END REGULAR } + /** * Used by subclasses to serialize extensions. Extension ranges may be * interleaved with field numbers, but we must write them in canonical @@ -640,12 +993,6 @@ public abstract class GeneratedMessageLite< implements ExtendableMessageOrBuilder<MessageType, BuilderType> { protected ExtendableBuilder(MessageType defaultInstance) { super(defaultInstance); - - // TODO(dweis): This is kind of an unnecessary clone since we construct a - // new instance in the parent constructor which makes the extensions - // immutable. This extra allocation shouldn't matter in practice - // though. - instance.extensions = instance.extensions.clone(); } // For immutable message conversion. @@ -654,17 +1001,26 @@ public abstract class GeneratedMessageLite< instance.extensions = extensions; } - // @Override (Java 1.6 override semantics, but we must support 1.5) + @Override protected void copyOnWrite() { if (!isBuilt) { return; } - + super.copyOnWrite(); instance.extensions = instance.extensions.clone(); } - // @Override (Java 1.6 override semantics, but we must support 1.5) + private FieldSet<ExtensionDescriptor> ensureExtensionsAreMutable() { + FieldSet<ExtensionDescriptor> extensions = instance.extensions; + if (extensions.isImmutable()) { + extensions = extensions.clone(); + instance.extensions = extensions; + } + return extensions; + } + + @Override public final MessageType buildPartial() { if (isBuilt) { return instance; @@ -686,54 +1042,44 @@ public abstract class GeneratedMessageLite< } /** Check if a singular extension is present. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) - public final <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extension) { + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extension) { return instance.hasExtension(extension); } /** Get the number of elements in a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extension) { return instance.getExtensionCount(extension); } /** Get the value of an extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") - public final <Type> Type getExtension( - final ExtensionLite<MessageType, Type> extension) { + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extension) { return instance.getExtension(extension); } /** Get one element of a repeated extension. */ + @Override @SuppressWarnings("unchecked") - //@Override (Java 1.6 override semantics, but we must support 1.5) public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extension, - final int index) { + final ExtensionLite<MessageType, List<Type>> extension, final int index) { return instance.getExtension(extension, index); } - // This is implemented here only to work around an apparent bug in the - // Java compiler and/or build system. See bug #1898463. The mere presence - // of this dummy clone() implementation makes it go away. - @Override - public BuilderType clone() { - return super.clone(); - } - /** Set the value of an extension. */ public final <Type> BuilderType setExtension( final ExtensionLite<MessageType, Type> extension, final Type value) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.setField(extensionLite.descriptor, extensionLite.toFieldSetType(value)); + ensureExtensionsAreMutable() + .setField(extensionLite.descriptor, extensionLite.toFieldSetType(value)); return (BuilderType) this; } @@ -743,11 +1089,12 @@ public abstract class GeneratedMessageLite< final int index, final Type value) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.setRepeatedField( - extensionLite.descriptor, index, extensionLite.singularToFieldSetType(value)); + ensureExtensionsAreMutable() + .setRepeatedField( + extensionLite.descriptor, index, extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } @@ -757,11 +1104,11 @@ public abstract class GeneratedMessageLite< final Type value) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.addRepeatedField( - extensionLite.descriptor, extensionLite.singularToFieldSetType(value)); + ensureExtensionsAreMutable() + .addRepeatedField(extensionLite.descriptor, extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } @@ -769,10 +1116,10 @@ public abstract class GeneratedMessageLite< public final <Type> BuilderType clearExtension( final ExtensionLite<MessageType, ?> extension) { GeneratedExtension<MessageType, ?> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.clearField(extensionLite.descriptor); + ensureExtensionsAreMutable().clearField(extensionLite.descriptor); return (BuilderType) this; } } @@ -844,37 +1191,44 @@ public abstract class GeneratedMessageLite< final boolean isRepeated; final boolean isPacked; + @Override public int getNumber() { return number; } + @Override public WireFormat.FieldType getLiteType() { return type; } + @Override public WireFormat.JavaType getLiteJavaType() { return type.getJavaType(); } + @Override public boolean isRepeated() { return isRepeated; } + @Override public boolean isPacked() { return isPacked; } + @Override public Internal.EnumLiteMap<?> getEnumType() { return enumTypeMap; } + @Override @SuppressWarnings("unchecked") - public MessageLite.Builder internalMergeFrom( - MessageLite.Builder to, MessageLite from) { + public MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from) { return ((Builder) to).mergeFrom((GeneratedMessageLite) from); } + @Override public int compareTo(ExtensionDescriptor other) { return number - other.number; } @@ -915,6 +1269,7 @@ public abstract class GeneratedMessageLite< } } + /** * Lite equivalent to {@link GeneratedMessage.GeneratedExtension}. * @@ -969,6 +1324,7 @@ public abstract class GeneratedMessageLite< } /** Get the field number. */ + @Override public int getNumber() { return descriptor.getNumber(); } @@ -978,6 +1334,7 @@ public abstract class GeneratedMessageLite< * If the extension is an embedded message or group, returns the default * instance of the message. */ + @Override public MessageLite getMessageDefaultInstance() { return messageDefaultInstance; } @@ -1032,14 +1389,17 @@ public abstract class GeneratedMessageLite< } } + @Override public FieldType getLiteType() { return descriptor.getLiteType(); } + @Override public boolean isRepeated() { return descriptor.isRepeated; } + @Override public Type getDefaultValue() { return defaultValue; } @@ -1049,7 +1409,12 @@ public abstract class GeneratedMessageLite< * A serialized (serializable) form of the generated message. Stores the * message as a class name and a byte array. */ - static final class SerializedForm implements Serializable { + protected static final class SerializedForm implements Serializable { + + public static SerializedForm of(MessageLite message) { + return new SerializedForm(message); + } + private static final long serialVersionUID = 0L; private final String messageClassName; @@ -1083,7 +1448,7 @@ public abstract class GeneratedMessageLite< } catch (ClassNotFoundException e) { throw new RuntimeException("Unable to find proto buffer class: " + messageClassName, e); } catch (NoSuchFieldException e) { - throw new RuntimeException("Unable to find DEFAULT_INSTANCE in " + messageClassName, e); + return readResolveFallback(); } catch (SecurityException e) { throw new RuntimeException("Unable to call DEFAULT_INSTANCE in " + messageClassName, e); } catch (IllegalAccessException e) { @@ -1092,18 +1457,35 @@ public abstract class GeneratedMessageLite< throw new RuntimeException("Unable to understand proto buffer", e); } } - } - /** - * Replaces this object in the output stream with a serialized form. - * Part of Java's serialization magic. Generated sub-classes must override - * this method by calling {@code return super.writeReplace();} - * @return a SerializedForm of this message - */ - protected Object writeReplace() throws ObjectStreamException { - return new SerializedForm(this); + /** + * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 generated code. + */ + @Deprecated + private Object readResolveFallback() throws ObjectStreamException { + try { + Class<?> messageClass = Class.forName(messageClassName); + java.lang.reflect.Field defaultInstanceField = + messageClass.getDeclaredField("defaultInstance"); + defaultInstanceField.setAccessible(true); + MessageLite defaultInstance = (MessageLite) defaultInstanceField.get(null); + return defaultInstance.newBuilderForType() + .mergeFrom(asBytes) + .buildPartial(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to find proto buffer class: " + messageClassName, e); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find defaultInstance in " + messageClassName, e); + } catch (SecurityException e) { + throw new RuntimeException("Unable to call defaultInstance in " + messageClassName, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to call parsePartialFrom", e); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException("Unable to understand proto buffer", e); + } + } } - + /** * Checks that the {@link Extension} is Lite and returns it as a * {@link GeneratedExtension}. @@ -1117,7 +1499,7 @@ public abstract class GeneratedMessageLite< if (!extension.isLite()) { throw new IllegalArgumentException("Expected a lite extension."); } - + return (GeneratedExtension<MessageType, T>) extension; } @@ -1128,31 +1510,88 @@ public abstract class GeneratedMessageLite< */ protected static final <T extends GeneratedMessageLite<T, ?>> boolean isInitialized( T message, boolean shouldMemoize) { - return message.dynamicMethod(MethodToInvoke.IS_INITIALIZED, shouldMemoize) != null; + byte memoizedIsInitialized = + (Byte) message.dynamicMethod(MethodToInvoke.GET_MEMOIZED_IS_INITIALIZED); + if (memoizedIsInitialized == 1) { + return true; + } + if (memoizedIsInitialized == 0) { + return false; + } + // BEGIN EXPERIMENTAL + // boolean isInitialized = Protobuf.getInstance().schemaFor(message).isInitialized(message); + // END EXPERIMENTAL + // BEGIN REGULAR + boolean isInitialized = + message.dynamicMethod(MethodToInvoke.IS_INITIALIZED, Boolean.FALSE) != null; + // END REGULAR + if (shouldMemoize) { + message.dynamicMethod( + MethodToInvoke.SET_MEMOIZED_IS_INITIALIZED, isInitialized ? message : null); + } + return isInitialized; } - - protected static final <T extends GeneratedMessageLite<T, ?>> void makeImmutable(T message) { - message.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); + + protected static IntList emptyIntList() { + return IntArrayList.emptyList(); } - - /** - * A static helper method for parsing a partial from input using the extension registry and the - * instance. - */ - static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( - T instance, CodedInputStream input, ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { - try { - return (T) instance.dynamicMethod( - MethodToInvoke.PARSE_PARTIAL_FROM, input, extensionRegistry); - } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidProtocolBufferException) { - throw (InvalidProtocolBufferException) e.getCause(); - } - throw e; - } + + protected static IntList mutableCopy(IntList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static LongList emptyLongList() { + return LongArrayList.emptyList(); } - + + protected static LongList mutableCopy(LongList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static FloatList emptyFloatList() { + return FloatArrayList.emptyList(); + } + + protected static FloatList mutableCopy(FloatList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static DoubleList emptyDoubleList() { + return DoubleArrayList.emptyList(); + } + + protected static DoubleList mutableCopy(DoubleList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static BooleanList emptyBooleanList() { + return BooleanArrayList.emptyList(); + } + + protected static BooleanList mutableCopy(BooleanList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static <E> ProtobufList<E> emptyProtobufList() { + return ProtobufArrayList.emptyList(); + } + + protected static <E> ProtobufList<E> mutableCopy(ProtobufList<E> list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + /** * A {@link Parser} implementation that delegates to the default instance. * <p> @@ -1160,117 +1599,954 @@ public abstract class GeneratedMessageLite< */ protected static class DefaultInstanceBasedParser<T extends GeneratedMessageLite<T, ?>> extends AbstractParser<T> { - + private T defaultInstance; - + public DefaultInstanceBasedParser(T defaultInstance) { this.defaultInstance = defaultInstance; } - + @Override public T parsePartialFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return GeneratedMessageLite.parsePartialFrom(defaultInstance, input, extensionRegistry); } + + @Override + public T parsePartialFrom(byte[] input) throws InvalidProtocolBufferException { + return GeneratedMessageLite.parsePartialFrom(defaultInstance, input); + } } - - protected static IntList newIntList() { - return new IntArrayList(); - } - - protected static IntList newIntListWithCapacity(int capacity) { - return new IntArrayList(capacity); - } - - protected static IntList newIntList(List<Integer> toCopy) { - return new IntArrayList(toCopy); + + /** + * A static helper method for parsing a partial from input using the extension registry and the + * instance. + */ + // TODO(dweis): Should this verify that the last tag was 0? + static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T instance, CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + @SuppressWarnings("unchecked") // Guaranteed by protoc + T result = (T) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); + try { + // BEGIN REGULAR + result.dynamicMethod(MethodToInvoke.MERGE_FROM_STREAM, input, extensionRegistry); + // END REGULAR + // BEGIN EXPERIMENTAL + // Protobuf.getInstance().schemaFor(result).mergeFrom( + // result, CodedInputStreamReader.forCodedInput(input), extensionRegistry); + // END EXPERIMENTAL + result.makeImmutable(); + // BEGIN EXPERIMENTAL + // } catch (IOException e) { + // if (e.getCause() instanceof InvalidProtocolBufferException) { + // throw (InvalidProtocolBufferException) e.getCause(); + // } + // throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(result); + // END EXPERIMENTAL + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidProtocolBufferException) { + throw (InvalidProtocolBufferException) e.getCause(); + } + throw e; + } + return result; } - - protected static IntList emptyIntList() { - return IntArrayList.emptyList(); + + /** A static helper method for parsing a partial from byte array. */ + static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(T instance, byte[] input) + throws InvalidProtocolBufferException { + // BEGIN REGULAR + return parsePartialFrom(instance, input, ExtensionRegistryLite.getEmptyRegistry()); + // END REGULAR + // BEGIN EXPERIMENTAL + // @SuppressWarnings("unchecked") // Guaranteed by protoc + // T result = (T) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); + // try { + // Protobuf.getInstance().schemaFor(result).mergeFrom( + // result, input, 0, input.length, new ArrayDecoders.Registers()); + // result.makeImmutable(); + // if (result.memoizedHashCode != 0) { + // throw new RuntimeException(); + // } + // } catch (IOException e) { + // if (e.getCause() instanceof InvalidProtocolBufferException) { + // throw (InvalidProtocolBufferException) e.getCause(); + // } + // throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(result); + // } catch (IndexOutOfBoundsException e) { + // throw InvalidProtocolBufferException.truncatedMessage().setUnfinishedMessage(result); + // } + // return result; + // END EXPERIMENTAL } - protected static LongList newLongList() { - return new LongArrayList(); + protected static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T defaultInstance, + CodedInputStream input) + throws InvalidProtocolBufferException { + return parsePartialFrom(defaultInstance, input, ExtensionRegistryLite.getEmptyRegistry()); } - protected static LongList newLongListWithCapacity(int capacity) { - return new LongArrayList(capacity); + /** + * Helper method to check if message is initialized. + * + * @throws InvalidProtocolBufferException if it is not initialized. + * @return The message to check. + */ + private static <T extends GeneratedMessageLite<T, ?>> T checkMessageInitialized(T message) + throws InvalidProtocolBufferException { + if (message != null && !message.isInitialized()) { + throw message.newUninitializedMessageException() + .asInvalidProtocolBufferException() + .setUnfinishedMessage(message); + } + return message; } - - protected static LongList newLongList(List<Long> toCopy) { - return new LongArrayList(toCopy); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, ByteBuffer data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parseFrom(defaultInstance, CodedInputStream.newInstance(data), extensionRegistry)); } - - protected static LongList emptyLongList() { - return LongArrayList.emptyList(); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, ByteBuffer data) throws InvalidProtocolBufferException { + return parseFrom(defaultInstance, data, ExtensionRegistryLite.getEmptyRegistry()); } - - protected static FloatList newFloatList() { - return new FloatArrayList(); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, ByteString data) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parseFrom(defaultInstance, data, ExtensionRegistryLite.getEmptyRegistry())); } - - protected static FloatList newFloatListWithCapacity(int capacity) { - return new FloatArrayList(capacity); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized(parsePartialFrom(defaultInstance, data, extensionRegistry)); } - - protected static FloatList newFloatList(List<Float> toCopy) { - return new FloatArrayList(toCopy); + + // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the + // ByteString. + private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T defaultInstance, ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + T message; + try { + CodedInputStream input = data.newCodedInput(); + message = parsePartialFrom(defaultInstance, input, extensionRegistry); + try { + input.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; + } catch (InvalidProtocolBufferException e) { + throw e; + } } - - protected static FloatList emptyFloatList() { - return FloatArrayList.emptyList(); + + // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the + // ByteString. + private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T defaultInstance, byte[] data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + T message; + try { + CodedInputStream input = CodedInputStream.newInstance(data); + message = parsePartialFrom(defaultInstance, input, extensionRegistry); + try { + input.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; + } catch (InvalidProtocolBufferException e) { + throw e; + } } - - protected static DoubleList newDoubleList() { - return new DoubleArrayList(); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, byte[] data) + throws InvalidProtocolBufferException { + return checkMessageInitialized(parsePartialFrom(defaultInstance, data)); } - - protected static DoubleList newDoubleListWithCapacity(int capacity) { - return new DoubleArrayList(capacity); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, byte[] data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized(parsePartialFrom(defaultInstance, data, extensionRegistry)); } - - protected static DoubleList newDoubleList(List<Double> toCopy) { - return new DoubleArrayList(toCopy); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, InputStream input) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(defaultInstance, CodedInputStream.newInstance(input), + ExtensionRegistryLite.getEmptyRegistry())); } - - protected static DoubleList emptyDoubleList() { - return DoubleArrayList.emptyList(); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, InputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(defaultInstance, CodedInputStream.newInstance(input), extensionRegistry)); } - - protected static BooleanList newBooleanList() { - return new BooleanArrayList(); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, CodedInputStream input) + throws InvalidProtocolBufferException { + return parseFrom(defaultInstance, input, ExtensionRegistryLite.getEmptyRegistry()); } - - protected static BooleanList newBooleanListWithCapacity(int capacity) { - return new BooleanArrayList(capacity); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(defaultInstance, input, extensionRegistry)); } - - protected static BooleanList newBooleanList(List<Boolean> toCopy) { - return new BooleanArrayList(toCopy); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseDelimitedFrom( + T defaultInstance, InputStream input) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialDelimitedFrom(defaultInstance, input, + ExtensionRegistryLite.getEmptyRegistry())); } - - protected static BooleanList emptyBooleanList() { - return BooleanArrayList.emptyList(); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseDelimitedFrom( + T defaultInstance, InputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialDelimitedFrom(defaultInstance, input, extensionRegistry)); } - - protected static <E> ProtobufList<E> newProtobufList() { - return new ProtobufArrayList<E>(); + + private static <T extends GeneratedMessageLite<T, ?>> T parsePartialDelimitedFrom( + T defaultInstance, + InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + int size; + try { + int firstByte = input.read(); + if (firstByte == -1) { + return null; + } + size = CodedInputStream.readRawVarint32(firstByte, input); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e.getMessage()); + } + InputStream limitedInput = new LimitedInputStream(input, size); + CodedInputStream codedInput = CodedInputStream.newInstance(limitedInput); + T message = parsePartialFrom(defaultInstance, codedInput, extensionRegistry); + try { + codedInput.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; } - - protected static <E> ProtobufList<E> newProtobufList(List<E> toCopy) { - return new ProtobufArrayList<E>(toCopy); + + // BEGIN REGULAR + /** + * An abstract visitor that the generated code calls into that we use to implement various + * features. Fields that are not members of oneofs are always visited. Members of a oneof are only + * visited when they are the set oneof case value on the "other" proto. The visitOneofNotSet + * method is invoked if other's oneof case is not set. + */ + protected interface Visitor { + boolean visitBoolean(boolean minePresent, boolean mine, boolean otherPresent, boolean other); + int visitInt(boolean minePresent, int mine, boolean otherPresent, int other); + double visitDouble(boolean minePresent, double mine, boolean otherPresent, double other); + float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other); + long visitLong(boolean minePresent, long mine, boolean otherPresent, long other); + String visitString(boolean minePresent, String mine, boolean otherPresent, String other); + ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other); + + Object visitOneofBoolean(boolean minePresent, Object mine, Object other); + Object visitOneofInt(boolean minePresent, Object mine, Object other); + Object visitOneofDouble(boolean minePresent, Object mine, Object other); + Object visitOneofFloat(boolean minePresent, Object mine, Object other); + Object visitOneofLong(boolean minePresent, Object mine, Object other); + Object visitOneofString(boolean minePresent, Object mine, Object other); + Object visitOneofByteString(boolean minePresent, Object mine, Object other); + Object visitOneofMessage(boolean minePresent, Object mine, Object other); + void visitOneofNotSet(boolean minePresent); + + /** + * Message fields use null sentinals. + */ + <T extends MessageLite> T visitMessage(T mine, T other); + + <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other); + BooleanList visitBooleanList(BooleanList mine, BooleanList other); + IntList visitIntList(IntList mine, IntList other); + DoubleList visitDoubleList(DoubleList mine, DoubleList other); + FloatList visitFloatList(FloatList mine, FloatList other); + LongList visitLongList(LongList mine, LongList other); + FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, FieldSet<ExtensionDescriptor> other); + UnknownFieldSetLite visitUnknownFields(UnknownFieldSetLite mine, UnknownFieldSetLite other); + <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other); } - - protected static <E> ProtobufList<E> newProtobufListWithCapacity(int capacity) { - return new ProtobufArrayList<E>(capacity); + + /** + * Implements equals. Throws a {@link NotEqualsException} when not equal. + */ + static class EqualsVisitor implements Visitor { + + static final class NotEqualsException extends RuntimeException {} + + static final EqualsVisitor INSTANCE = new EqualsVisitor(); + + static final NotEqualsException NOT_EQUALS = new NotEqualsException(); + + private EqualsVisitor() {} + + @Override + public boolean visitBoolean( + boolean minePresent, boolean mine, boolean otherPresent, boolean other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public double visitDouble( + boolean minePresent, double mine, boolean otherPresent, double other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public String visitString( + boolean minePresent, String mine, boolean otherPresent, String other) { + if (minePresent != otherPresent || !mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) { + if (minePresent != otherPresent || !mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofInt(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofDouble(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofFloat(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofLong(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofString(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofByteString(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { + if (minePresent && ((GeneratedMessageLite<?, ?>) mine).equals(this, (MessageLite) other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public void visitOneofNotSet(boolean minePresent) { + if (minePresent) { + throw NOT_EQUALS; + } + } + + @Override + public <T extends MessageLite> T visitMessage(T mine, T other) { + if (mine == null && other == null) { + return null; + } + + if (mine == null || other == null) { + throw NOT_EQUALS; + } + + ((GeneratedMessageLite<?, ?>) mine).equals(this, other); + + return mine; + } + + @Override + public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public BooleanList visitBooleanList(BooleanList mine, BooleanList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public IntList visitIntList(IntList mine, IntList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public DoubleList visitDoubleList(DoubleList mine, DoubleList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public FloatList visitFloatList(FloatList mine, FloatList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public LongList visitLongList(LongList mine, LongList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, + FieldSet<ExtensionDescriptor> other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public UnknownFieldSetLite visitUnknownFields( + UnknownFieldSetLite mine, + UnknownFieldSetLite other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } } - - protected static <E> ProtobufList<E> emptyProtobufList() { - return ProtobufArrayList.emptyList(); + + /** + * Implements hashCode by accumulating state. + */ + static class HashCodeVisitor implements Visitor { + + // The caller must ensure that the visitor is invoked parameterized with this and this such that + // other is this. This is required due to how oneof cases are handled. See the class comment + // on Visitor for more information. + + int hashCode = 0; + + @Override + public boolean visitBoolean( + boolean minePresent, boolean mine, boolean otherPresent, boolean other) { + hashCode = (53 * hashCode) + Internal.hashBoolean(mine); + return mine; + } + + @Override + public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) { + hashCode = (53 * hashCode) + mine; + return mine; + } + + @Override + public double visitDouble( + boolean minePresent, double mine, boolean otherPresent, double other) { + hashCode = (53 * hashCode) + Internal.hashLong(Double.doubleToLongBits(mine)); + return mine; + } + + @Override + public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) { + hashCode = (53 * hashCode) + Float.floatToIntBits(mine); + return mine; + } + + @Override + public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) { + hashCode = (53 * hashCode) + Internal.hashLong(mine); + return mine; + } + + @Override + public String visitString( + boolean minePresent, String mine, boolean otherPresent, String other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Internal.hashBoolean(((Boolean) mine)); + return mine; + } + + @Override + public Object visitOneofInt(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + (Integer) mine; + return mine; + } + + @Override + public Object visitOneofDouble(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Internal.hashLong(Double.doubleToLongBits((Double) mine)); + return mine; + } + + @Override + public Object visitOneofFloat(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Float.floatToIntBits((Float) mine); + return mine; + } + + @Override + public Object visitOneofLong(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Internal.hashLong((Long) mine); + return mine; + } + + @Override + public Object visitOneofString(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public Object visitOneofByteString(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { + return visitMessage((MessageLite) mine, (MessageLite) other); + } + + @Override + public void visitOneofNotSet(boolean minePresent) { + if (minePresent) { + throw new IllegalStateException(); // Can't happen if other == this. + } + } + + @Override + public <T extends MessageLite> T visitMessage(T mine, T other) { + final int protoHash; + if (mine != null) { + if (mine instanceof GeneratedMessageLite) { + protoHash = ((GeneratedMessageLite) mine).hashCode(this); + } else { + protoHash = mine.hashCode(); + } + } else { + protoHash = 37; + } + hashCode = (53 * hashCode) + protoHash; + return mine; + } + + @Override + public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public BooleanList visitBooleanList(BooleanList mine, BooleanList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public IntList visitIntList(IntList mine, IntList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public DoubleList visitDoubleList(DoubleList mine, DoubleList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public FloatList visitFloatList(FloatList mine, FloatList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public LongList visitLongList(LongList mine, LongList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, + FieldSet<ExtensionDescriptor> other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public UnknownFieldSetLite visitUnknownFields( + UnknownFieldSetLite mine, + UnknownFieldSetLite other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } } - - protected static LazyStringArrayList emptyLazyStringArrayList() { - return LazyStringArrayList.emptyList(); + + /** + * Implements field merging semantics over the visitor interface. + */ + protected static class MergeFromVisitor implements Visitor { + + public static final MergeFromVisitor INSTANCE = new MergeFromVisitor(); + + private MergeFromVisitor() {} + + @Override + public boolean visitBoolean( + boolean minePresent, boolean mine, boolean otherPresent, boolean other) { + return otherPresent ? other : mine; + } + + @Override + public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) { + return otherPresent ? other : mine; + } + + @Override + public double visitDouble( + boolean minePresent, double mine, boolean otherPresent, double other) { + return otherPresent ? other : mine; + } + + @Override + public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) { + return otherPresent ? other : mine; + } + + @Override + public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) { + return otherPresent ? other : mine; + } + + @Override + public String visitString( + boolean minePresent, String mine, boolean otherPresent, String other) { + return otherPresent ? other : mine; + } + + @Override + public ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) { + return otherPresent ? other : mine; + } + + @Override + public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofInt(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofDouble(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofFloat(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofLong(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofString(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofByteString(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { + if (minePresent) { + return visitMessage((MessageLite) mine, (MessageLite) other); + } + return other; + } + + @Override + public void visitOneofNotSet(boolean minePresent) { + return; + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + @Override + public <T extends MessageLite> T visitMessage(T mine, T other) { + if (mine != null && other != null) { + return (T) mine.toBuilder().mergeFrom(other).build(); + } + + return mine != null ? mine : other; + } + + @Override + public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public BooleanList visitBooleanList(BooleanList mine, BooleanList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public IntList visitIntList(IntList mine, IntList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public DoubleList visitDoubleList(DoubleList mine, DoubleList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public FloatList visitFloatList(FloatList mine, FloatList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public LongList visitLongList(LongList mine, LongList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, + FieldSet<ExtensionDescriptor> other) { + if (mine.isImmutable()) { + mine = mine.clone(); + } + mine.mergeFrom(other); + return mine; + } + + @Override + public UnknownFieldSetLite visitUnknownFields( + UnknownFieldSetLite mine, + UnknownFieldSetLite other) { + return other == UnknownFieldSetLite.getDefaultInstance() + ? mine : UnknownFieldSetLite.mutableCopyOf(mine, other); + } + + @Override + public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { + if (!other.isEmpty()) { + if (!mine.isMutable()) { + mine = mine.mutableCopy(); + } + mine.mergeFrom(other); + } + return mine; + } } + // END REGULAR } diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java new file mode 100644 index 00000000..4acd8f2f --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java @@ -0,0 +1,2861 @@ +// 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 static com.google.protobuf.Internal.checkNotNull; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; +// In opensource protobuf, we have versioned this GeneratedMessageV3 class to GeneratedMessageV3V3 and +// in the future may have GeneratedMessageV3V4 etc. This allows us to change some aspects of this +// class without breaking binary compatibility with old generated code that still subclasses +// the old GeneratedMessageV3 class. To allow these different GeneratedMessageV3V? classes to +// interoperate (e.g., a GeneratedMessageV3V3 object has a message extension field whose class +// type is GeneratedMessageV3V4), these classes still share a common parent class AbstractMessage +// and are using the same GeneratedMessage.GeneratedExtension class for extension definitions. +// Since this class becomes GeneratedMessageV3V? in opensource, we have to add an import here +// to be able to use GeneratedMessage.GeneratedExtension. The GeneratedExtension definition in +// this file is also excluded from opensource to avoid conflict. +import com.google.protobuf.GeneratedMessage.GeneratedExtension; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * All generated protocol message classes extend this class. This class + * implements most of the Message and Builder interfaces using Java reflection. + * Users can ignore this class and pretend that generated messages implement + * the Message interface directly. + * + * @author kenton@google.com Kenton Varda + */ +public abstract class GeneratedMessageV3 extends AbstractMessage + implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * For testing. Allows a test to disable the optimization that avoids using + * field builders for nested messages until they are requested. By disabling + * this optimization, existing tests can be reused to test the field builders. + */ + protected static boolean alwaysUseFieldBuilders = false; + + /** For use by generated code only. */ + protected UnknownFieldSet unknownFields; + + protected GeneratedMessageV3() { + unknownFields = UnknownFieldSet.getDefaultInstance(); + } + + protected GeneratedMessageV3(Builder<?> builder) { + unknownFields = builder.getUnknownFields(); + } + + @Override + public Parser<? extends GeneratedMessageV3> getParserForType() { + throw new UnsupportedOperationException( + "This is supposed to be overridden by subclasses."); + } + + /** + * For testing. Allows a test to disable the optimization that avoids using + * field builders for nested messages until they are requested. By disabling + * this optimization, existing tests can be reused to test the field builders. + * See {@link RepeatedFieldBuilder} and {@link SingleFieldBuilder}. + */ + static void enableAlwaysUseFieldBuildersForTesting() { + alwaysUseFieldBuilders = true; + } + + /** + * Get the FieldAccessorTable for this type. We can't have the message + * class pass this in to the constructor because of bootstrapping trouble + * with DescriptorProtos. + */ + protected abstract FieldAccessorTable internalGetFieldAccessorTable(); + + @Override + public Descriptor getDescriptorForType() { + return internalGetFieldAccessorTable().descriptor; + } + + /** + * Internal helper to return a modifiable map containing all the fields. + * The returned Map is modifialbe so that the caller can add additional + * extension fields to implement {@link #getAllFields()}. + * + * @param getBytesForString whether to generate ByteString for string fields + */ + private Map<FieldDescriptor, Object> getAllFieldsMutable( + boolean getBytesForString) { + final TreeMap<FieldDescriptor, Object> result = + new TreeMap<FieldDescriptor, Object>(); + final Descriptor descriptor = internalGetFieldAccessorTable().descriptor; + final List<FieldDescriptor> fields = descriptor.getFields(); + + for (int i = 0; i < fields.size(); i++) { + FieldDescriptor field = fields.get(i); + final OneofDescriptor oneofDescriptor = field.getContainingOneof(); + + /* + * If the field is part of a Oneof, then at maximum one field in the Oneof is set + * and it is not repeated. There is no need to iterate through the others. + */ + if (oneofDescriptor != null) { + // Skip other fields in the Oneof we know are not set + i += oneofDescriptor.getFieldCount() - 1; + if (!hasOneof(oneofDescriptor)) { + // If no field is set in the Oneof, skip all the fields in the Oneof + continue; + } + // Get the pointer to the only field which is set in the Oneof + field = getOneofFieldDescriptor(oneofDescriptor); + } else { + // If we are not in a Oneof, we need to check if the field is set and if it is repeated + if (field.isRepeated()) { + final List<?> value = (List<?>) getField(field); + if (!value.isEmpty()) { + result.put(field, value); + } + continue; + } + if (!hasField(field)) { + continue; + } + } + // Add the field to the map + if (getBytesForString && field.getJavaType() == FieldDescriptor.JavaType.STRING) { + result.put(field, getFieldRaw(field)); + } else { + result.put(field, getField(field)); + } + } + return result; + } + + @Override + public boolean isInitialized() { + for (final FieldDescriptor field : getDescriptorForType().getFields()) { + // Check that all required fields are present. + if (field.isRequired()) { + if (!hasField(field)) { + return false; + } + } + // Check that embedded messages are initialized. + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + @SuppressWarnings("unchecked") final + List<Message> messageList = (List<Message>) getField(field); + for (final Message element : messageList) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (hasField(field) && !((Message) getField(field)).isInitialized()) { + return false; + } + } + } + } + + return true; + } + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + return Collections.unmodifiableMap( + getAllFieldsMutable(/* getBytesForString = */ false)); + } + + /** + * Returns a collection of all the fields in this message which are set + * and their corresponding values. A singular ("required" or "optional") + * field is set iff hasField() returns true for that field. A "repeated" + * field is set iff getRepeatedFieldCount() is greater than zero. The + * values are exactly what would be returned by calling + * {@link #getFieldRaw(Descriptors.FieldDescriptor)} for each field. The map + * is guaranteed to be a sorted map, so iterating over it will return fields + * in order by field number. + */ + Map<FieldDescriptor, Object> getAllFieldsRaw() { + return Collections.unmodifiableMap( + getAllFieldsMutable(/* getBytesForString = */ true)); + } + + @Override + public boolean hasOneof(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).has(this); + } + + @Override + public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).get(this); + } + + @Override + public boolean hasField(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).has(this); + } + + @Override + public Object getField(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).get(this); + } + + /** + * Obtains the value of the given field, or the default value if it is + * not set. For primitive fields, the boxed primitive value is returned. + * For enum fields, the EnumValueDescriptor for the value is returned. For + * embedded message fields, the sub-message is returned. For repeated + * fields, a java.util.List is returned. For present string fields, a + * ByteString is returned representing the bytes that the field contains. + */ + Object getFieldRaw(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).getRaw(this); + } + + @Override + public int getRepeatedFieldCount(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field) + .getRepeatedCount(this); + } + + @Override + public Object getRepeatedField(final FieldDescriptor field, final int index) { + return internalGetFieldAccessorTable().getField(field) + .getRepeated(this, index); + } + + @Override + public UnknownFieldSet getUnknownFields() { + throw new UnsupportedOperationException( + "This is supposed to be overridden by subclasses."); + } + + /** + * Called by subclasses to parse an unknown field. + * + * @return {@code true} unless the tag is an end-group tag. + */ + protected boolean parseUnknownField( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + int tag) + throws IOException { + if (input.shouldDiscardUnknownFields()) { + return input.skipField(tag); + } + return unknownFields.mergeFieldFrom(tag, input); + } + + protected boolean parseUnknownFieldProto3( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + int tag) + throws IOException { + if (input.shouldDiscardUnknownFieldsProto3()) { + return input.skipField(tag); + } + return unknownFields.mergeFieldFrom(tag, input); + } + + protected static <M extends Message> M parseWithIOException(Parser<M> parser, InputStream input) + throws IOException { + try { + return parser.parseFrom(input); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseWithIOException(Parser<M> parser, InputStream input, + ExtensionRegistryLite extensions) throws IOException { + try { + return parser.parseFrom(input, extensions); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseWithIOException(Parser<M> parser, + CodedInputStream input) throws IOException { + try { + return parser.parseFrom(input); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseWithIOException(Parser<M> parser, + CodedInputStream input, ExtensionRegistryLite extensions) throws IOException { + try { + return parser.parseFrom(input, extensions); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseDelimitedWithIOException(Parser<M> parser, + InputStream input) throws IOException { + try { + return parser.parseDelimitedFrom(input); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseDelimitedWithIOException(Parser<M> parser, + InputStream input, ExtensionRegistryLite extensions) throws IOException { + try { + return parser.parseDelimitedFrom(input, extensions); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static boolean canUseUnsafe() { + return UnsafeUtil.hasUnsafeArrayOperations() && UnsafeUtil.hasUnsafeByteBufferOperations(); + } + + @Override + public void writeTo(final CodedOutputStream output) throws IOException { + MessageReflection.writeMessageTo(this, getAllFieldsRaw(), output, false); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + memoizedSize = MessageReflection.getSerializedSize( + this, getAllFieldsRaw()); + return memoizedSize; + } + + + + /** + * Used by parsing constructors in generated classes. + */ + protected void makeExtensionsImmutable() { + // Noop for messages without extensions. + } + + /** + * TODO(xiaofeng): remove this after b/29368482 is fixed. We need to move this + * interface to AbstractMessage in order to versioning GeneratedMessageV3 but + * this move breaks binary compatibility for AppEngine. After AppEngine is + * fixed we can exlude this from google3. + */ + protected interface BuilderParent extends AbstractMessage.BuilderParent {} + + /** + * TODO(xiaofeng): remove this together with GeneratedMessageV3.BuilderParent. + */ + protected abstract Message.Builder newBuilderForType(BuilderParent parent); + + @Override + protected Message.Builder newBuilderForType(final AbstractMessage.BuilderParent parent) { + return newBuilderForType(new BuilderParent() { + @Override + public void markDirty() { + parent.markDirty(); + } + }); + } + + + @SuppressWarnings("unchecked") + public abstract static class Builder <BuilderType extends Builder<BuilderType>> + extends AbstractMessage.Builder<BuilderType> { + + private BuilderParent builderParent; + + private BuilderParentImpl meAsParent; + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See GeneratedMessageV3.BuilderListener. + private boolean isClean; + + private UnknownFieldSet unknownFields = + UnknownFieldSet.getDefaultInstance(); + + protected Builder() { + this(null); + } + + protected Builder(BuilderParent builderParent) { + this.builderParent = builderParent; + } + + @Override + void dispose() { + builderParent = null; + } + + /** + * Called by the subclass when a message is built. + */ + protected void onBuilt() { + if (builderParent != null) { + markClean(); + } + } + + /** + * Called by the subclass or a builder to notify us that a message was + * built and may be cached and therefore invalidations are needed. + */ + @Override + protected void markClean() { + this.isClean = true; + } + + /** + * Gets whether invalidations are needed + * + * @return whether invalidations are needed + */ + protected boolean isClean() { + return isClean; + } + + @Override + public BuilderType clone() { + BuilderType builder = + (BuilderType) getDefaultInstanceForType().newBuilderForType(); + builder.mergeFrom(buildPartial()); + return builder; + } + + /** + * Called by the initialization and clear code paths to allow subclasses to + * reset any of their builtin fields back to the initial values. + */ + @Override + public BuilderType clear() { + unknownFields = UnknownFieldSet.getDefaultInstance(); + onChanged(); + return (BuilderType) this; + } + + /** + * Get the FieldAccessorTable for this type. We can't have the message + * class pass this in to the constructor because of bootstrapping trouble + * with DescriptorProtos. + */ + protected abstract FieldAccessorTable internalGetFieldAccessorTable(); + + @Override + public Descriptor getDescriptorForType() { + return internalGetFieldAccessorTable().descriptor; + } + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + return Collections.unmodifiableMap(getAllFieldsMutable()); + } + + /** Internal helper which returns a mutable map. */ + private Map<FieldDescriptor, Object> getAllFieldsMutable() { + final TreeMap<FieldDescriptor, Object> result = + new TreeMap<FieldDescriptor, Object>(); + final Descriptor descriptor = internalGetFieldAccessorTable().descriptor; + final List<FieldDescriptor> fields = descriptor.getFields(); + + for (int i = 0; i < fields.size(); i++) { + FieldDescriptor field = fields.get(i); + final OneofDescriptor oneofDescriptor = field.getContainingOneof(); + + /* + * If the field is part of a Oneof, then at maximum one field in the Oneof is set + * and it is not repeated. There is no need to iterate through the others. + */ + if (oneofDescriptor != null) { + // Skip other fields in the Oneof we know are not set + i += oneofDescriptor.getFieldCount() - 1; + if (!hasOneof(oneofDescriptor)) { + // If no field is set in the Oneof, skip all the fields in the Oneof + continue; + } + // Get the pointer to the only field which is set in the Oneof + field = getOneofFieldDescriptor(oneofDescriptor); + } else { + // If we are not in a Oneof, we need to check if the field is set and if it is repeated + if (field.isRepeated()) { + final List<?> value = (List<?>) getField(field); + if (!value.isEmpty()) { + result.put(field, value); + } + continue; + } + if (!hasField(field)) { + continue; + } + } + // Add the field to the map + result.put(field, getField(field)); + } + return result; + } + + @Override + public Message.Builder newBuilderForField(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).newBuilder(); + } + + @Override + public Message.Builder getFieldBuilder(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).getBuilder(this); + } + + @Override + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, int index) { + return internalGetFieldAccessorTable().getField(field).getRepeatedBuilder( + this, index); + } + + @Override + public boolean hasOneof(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).has(this); + } + + @Override + public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).get(this); + } + + @Override + public boolean hasField(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).has(this); + } + + @Override + public Object getField(final FieldDescriptor field) { + Object object = internalGetFieldAccessorTable().getField(field).get(this); + if (field.isRepeated()) { + // The underlying list object is still modifiable at this point. + // Make sure not to expose the modifiable list to the caller. + return Collections.unmodifiableList((List) object); + } else { + return object; + } + } + + @Override + public BuilderType setField(final FieldDescriptor field, final Object value) { + internalGetFieldAccessorTable().getField(field).set(this, value); + return (BuilderType) this; + } + + @Override + public BuilderType clearField(final FieldDescriptor field) { + internalGetFieldAccessorTable().getField(field).clear(this); + return (BuilderType) this; + } + + @Override + public BuilderType clearOneof(final OneofDescriptor oneof) { + internalGetFieldAccessorTable().getOneof(oneof).clear(this); + return (BuilderType) this; + } + + @Override + public int getRepeatedFieldCount(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field) + .getRepeatedCount(this); + } + + @Override + public Object getRepeatedField(final FieldDescriptor field, final int index) { + return internalGetFieldAccessorTable().getField(field) + .getRepeated(this, index); + } + + @Override + public BuilderType setRepeatedField( + final FieldDescriptor field, final int index, final Object value) { + internalGetFieldAccessorTable().getField(field) + .setRepeated(this, index, value); + return (BuilderType) this; + } + + @Override + public BuilderType addRepeatedField(final FieldDescriptor field, final Object value) { + internalGetFieldAccessorTable().getField(field).addRepeated(this, value); + return (BuilderType) this; + } + + @Override + public BuilderType setUnknownFields(final UnknownFieldSet unknownFields) { + this.unknownFields = unknownFields; + onChanged(); + return (BuilderType) this; + } + + protected BuilderType setUnknownFieldsProto3(final UnknownFieldSet unknownFields) { + if (CodedInputStream.getProto3DiscardUnknownFieldsDefault()) { + return (BuilderType) this; + } + this.unknownFields = unknownFields; + onChanged(); + return (BuilderType) this; + } + + @Override + public BuilderType mergeUnknownFields( + final UnknownFieldSet unknownFields) { + return setUnknownFields( + UnknownFieldSet.newBuilder(this.unknownFields) + .mergeFrom(unknownFields) + .build()); + } + + + @Override + public boolean isInitialized() { + for (final FieldDescriptor field : getDescriptorForType().getFields()) { + // Check that all required fields are present. + if (field.isRequired()) { + if (!hasField(field)) { + return false; + } + } + // Check that embedded messages are initialized. + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + @SuppressWarnings("unchecked") final + List<Message> messageList = (List<Message>) getField(field); + for (final Message element : messageList) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (hasField(field) && + !((Message) getField(field)).isInitialized()) { + return false; + } + } + } + } + return true; + } + + @Override + public final UnknownFieldSet getUnknownFields() { + return unknownFields; + } + + /** + * Implementation of {@link BuilderParent} for giving to our children. This + * small inner class makes it so we don't publicly expose the BuilderParent + * methods. + */ + private class BuilderParentImpl implements BuilderParent { + + @Override + public void markDirty() { + onChanged(); + } + } + + /** + * Gets the {@link BuilderParent} for giving to our children. + * @return The builder parent for our children. + */ + protected BuilderParent getParentForChildren() { + if (meAsParent == null) { + meAsParent = new BuilderParentImpl(); + } + return meAsParent; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + protected final void onChanged() { + if (isClean && builderParent != null) { + builderParent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + isClean = false; + } + } + + /** + * Gets the map field with the given field number. This method should be + * overridden in the generated message class if the message contains map + * fields. + * + * Unlike other field types, reflection support for map fields can't be + * implemented based on generated public API because we need to access a + * map field as a list in reflection API but the generated API only allows + * us to access it as a map. This method returns the underlying map field + * directly and thus enables us to access the map field as a list. + */ + @SuppressWarnings({"unused", "rawtypes"}) + protected MapField internalGetMapField(int fieldNumber) { + // Note that we can't use descriptor names here because this method will + // be called when descriptor is being initialized. + throw new RuntimeException( + "No map fields found in " + getClass().getName()); + } + + /** Like {@link #internalGetMapField} but return a mutable version. */ + @SuppressWarnings({"unused", "rawtypes"}) + protected MapField internalGetMutableMapField(int fieldNumber) { + // Note that we can't use descriptor names here because this method will + // be called when descriptor is being initialized. + throw new RuntimeException( + "No map fields found in " + getClass().getName()); + } + } + + // ================================================================= + // Extensions-related stuff + + public interface ExtendableMessageOrBuilder< + MessageType extends ExtendableMessage> extends MessageOrBuilder { + // Re-define for return type covariance. + @Override + Message getDefaultInstanceForType(); + + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + ExtensionLite<MessageType, Type> extension); + + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + ExtensionLite<MessageType, List<Type>> extension); + + /** Get the value of an extension. */ + <Type> Type getExtension( + ExtensionLite<MessageType, Type> extension); + + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + ExtensionLite<MessageType, List<Type>> extension, + int index); + + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + Extension<MessageType, Type> extension); + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + GeneratedExtension<MessageType, Type> extension); + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + Extension<MessageType, List<Type>> extension); + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + GeneratedExtension<MessageType, List<Type>> extension); + /** Get the value of an extension. */ + <Type> Type getExtension( + Extension<MessageType, Type> extension); + /** Get the value of an extension. */ + <Type> Type getExtension( + GeneratedExtension<MessageType, Type> extension); + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + Extension<MessageType, List<Type>> extension, + int index); + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + GeneratedExtension<MessageType, List<Type>> extension, + int index); + } + + /** + * Generated message classes for message types that contain extension ranges + * subclass this. + * + * <p>This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "has", "get", and "getCount" -- but for extensions. The extensions + * are identified using instances of the class {@link GeneratedExtension}; + * the protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then you might write code like: + * + * <pre> + * MyProto.Foo foo = getFoo(); + * int i = foo.getExtension(MyProto.bar); + * </pre> + * + * <p>See also {@link ExtendableBuilder}. + */ + public abstract static class ExtendableMessage< + MessageType extends ExtendableMessage> + extends GeneratedMessageV3 + implements ExtendableMessageOrBuilder<MessageType> { + + private static final long serialVersionUID = 1L; + + private final FieldSet<FieldDescriptor> extensions; + + protected ExtendableMessage() { + this.extensions = FieldSet.newFieldSet(); + } + + protected ExtendableMessage( + ExtendableBuilder<MessageType, ?> builder) { + super(builder); + this.extensions = builder.buildExtensions(); + } + + private void verifyExtensionContainingType( + final Extension<MessageType, ?> extension) { + if (extension.getDescriptor().getContainingType() != + getDescriptorForType()) { + // This can only happen if someone uses unchecked operations. + throw new IllegalArgumentException( + "Extension is for type \"" + + extension.getDescriptor().getContainingType().getFullName() + + "\" which does not match message type \"" + + getDescriptorForType().getFullName() + "\"."); + } + } + + /** Check if a singular extension is present. */ + @Override + @SuppressWarnings("unchecked") + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + return extensions.hasField(extension.getDescriptor()); + } + + /** Get the number of elements in a repeated extension. */ + @Override + @SuppressWarnings("unchecked") + public final <Type> int getExtensionCount( + final ExtensionLite<MessageType, List<Type>> extensionLite) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + final FieldDescriptor descriptor = extension.getDescriptor(); + return extensions.getRepeatedFieldCount(descriptor); + } + + /** Get the value of an extension. */ + @Override + @SuppressWarnings("unchecked") + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + final Object value = extensions.getField(descriptor); + if (value == null) { + if (descriptor.isRepeated()) { + return (Type) Collections.emptyList(); + } else if (descriptor.getJavaType() == + FieldDescriptor.JavaType.MESSAGE) { + return (Type) extension.getMessageDefaultInstance(); + } else { + return (Type) extension.fromReflectionType( + descriptor.getDefaultValue()); + } + } else { + return (Type) extension.fromReflectionType(value); + } + } + + /** Get one element of a repeated extension. */ + @Override + @SuppressWarnings("unchecked") + public final <Type> Type getExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, final int index) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + return (Type) extension.singularFromReflectionType( + extensions.getRepeatedField(descriptor, index)); + } + + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension( + final GeneratedExtension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final Extension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final GeneratedExtension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension(final Extension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get one element of a repeated extension. */ + @Override + public final <Type> Type getExtension( + final Extension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Get one element of a repeated extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + + /** Called by subclasses to check if all extensions are initialized. */ + protected boolean extensionsAreInitialized() { + return extensions.isInitialized(); + } + + @Override + public boolean isInitialized() { + return super.isInitialized() && extensionsAreInitialized(); + } + + @Override + protected boolean parseUnknownField( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + int tag) throws IOException { + return MessageReflection.mergeFieldFrom( + input, input.shouldDiscardUnknownFields() ? null : unknownFields, extensionRegistry, + getDescriptorForType(), new MessageReflection.ExtensionAdapter(extensions), tag); + } + + @Override + protected boolean parseUnknownFieldProto3( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + int tag) throws IOException { + return MessageReflection.mergeFieldFrom( + input, + input.shouldDiscardUnknownFieldsProto3() ? null : unknownFields, + extensionRegistry, + getDescriptorForType(), + new MessageReflection.ExtensionAdapter(extensions), + tag); + } + + + /** + * Used by parsing constructors in generated classes. + */ + @Override + protected void makeExtensionsImmutable() { + extensions.makeImmutable(); + } + + /** + * Used by subclasses to serialize extensions. Extension ranges may be + * interleaved with field numbers, but we must write them in canonical + * (sorted by field number) order. ExtensionWriter helps us write + * individual ranges of extensions at once. + */ + protected class ExtensionWriter { + // Imagine how much simpler this code would be if Java iterators had + // a way to get the next element without advancing the iterator. + + private final Iterator<Map.Entry<FieldDescriptor, Object>> iter = + extensions.iterator(); + private Map.Entry<FieldDescriptor, Object> next; + private final boolean messageSetWireFormat; + + private ExtensionWriter(final boolean messageSetWireFormat) { + if (iter.hasNext()) { + next = iter.next(); + } + this.messageSetWireFormat = messageSetWireFormat; + } + + public void writeUntil(final int end, final CodedOutputStream output) + throws IOException { + while (next != null && next.getKey().getNumber() < end) { + FieldDescriptor descriptor = next.getKey(); + if (messageSetWireFormat && descriptor.getLiteJavaType() == + WireFormat.JavaType.MESSAGE && + !descriptor.isRepeated()) { + if (next instanceof LazyField.LazyEntry<?>) { + output.writeRawMessageSetExtension(descriptor.getNumber(), + ((LazyField.LazyEntry<?>) next).getField().toByteString()); + } else { + output.writeMessageSetExtension(descriptor.getNumber(), + (Message) next.getValue()); + } + } else { + // TODO(xiangl): Taken care of following code, it may cause + // problem when we use LazyField for normal fields/extensions. + // Due to the optional field can be duplicated at the end of + // serialized bytes, which will make the serialized size change + // after lazy field parsed. So when we use LazyField globally, + // we need to change the following write method to write cached + // bytes directly rather than write the parsed message. + FieldSet.writeField(descriptor, next.getValue(), output); + } + if (iter.hasNext()) { + next = iter.next(); + } else { + next = null; + } + } + } + } + + protected ExtensionWriter newExtensionWriter() { + return new ExtensionWriter(false); + } + protected ExtensionWriter newMessageSetExtensionWriter() { + return new ExtensionWriter(true); + } + + /** Called by subclasses to compute the size of extensions. */ + protected int extensionsSerializedSize() { + return extensions.getSerializedSize(); + } + protected int extensionsSerializedSizeAsMessageSet() { + return extensions.getMessageSetSerializedSize(); + } + + // --------------------------------------------------------------- + // Reflection + + protected Map<FieldDescriptor, Object> getExtensionFields() { + return extensions.getAllFields(); + } + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + final Map<FieldDescriptor, Object> result = + super.getAllFieldsMutable(/* getBytesForString = */ false); + result.putAll(getExtensionFields()); + return Collections.unmodifiableMap(result); + } + + @Override + public Map<FieldDescriptor, Object> getAllFieldsRaw() { + final Map<FieldDescriptor, Object> result = + super.getAllFieldsMutable(/* getBytesForString = */ false); + result.putAll(getExtensionFields()); + return Collections.unmodifiableMap(result); + } + + @Override + public boolean hasField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.hasField(field); + } else { + return super.hasField(field); + } + } + + @Override + public Object getField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + final Object value = extensions.getField(field); + if (value == null) { + if (field.isRepeated()) { + return Collections.emptyList(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + // Lacking an ExtensionRegistry, we have no way to determine the + // extension's real type, so we return a DynamicMessage. + return DynamicMessage.getDefaultInstance(field.getMessageType()); + } else { + return field.getDefaultValue(); + } + } else { + return value; + } + } else { + return super.getField(field); + } + } + + @Override + public int getRepeatedFieldCount(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedFieldCount(field); + } else { + return super.getRepeatedFieldCount(field); + } + } + + @Override + public Object getRepeatedField(final FieldDescriptor field, + final int index) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedField(field, index); + } else { + return super.getRepeatedField(field, index); + } + } + + private void verifyContainingType(final FieldDescriptor field) { + if (field.getContainingType() != getDescriptorForType()) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + } + + /** + * Generated message builders for message types that contain extension ranges + * subclass this. + * + * <p>This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "get", "set", and "add" -- but for extensions. The extensions are + * identified using instances of the class {@link GeneratedExtension}; the + * protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then you might write code like: + * + * <pre> + * MyProto.Foo foo = + * MyProto.Foo.newBuilder() + * .setExtension(MyProto.bar, 123) + * .build(); + * </pre> + * + * <p>See also {@link ExtendableMessage}. + */ + @SuppressWarnings("unchecked") + public abstract static class ExtendableBuilder< + MessageType extends ExtendableMessage, + BuilderType extends ExtendableBuilder<MessageType, BuilderType>> + extends Builder<BuilderType> + implements ExtendableMessageOrBuilder<MessageType> { + + private FieldSet<FieldDescriptor> extensions = FieldSet.emptySet(); + + protected ExtendableBuilder() {} + + protected ExtendableBuilder( + BuilderParent parent) { + super(parent); + } + + // For immutable message conversion. + void internalSetExtensionSet(FieldSet<FieldDescriptor> extensions) { + this.extensions = extensions; + } + + @Override + public BuilderType clear() { + extensions = FieldSet.emptySet(); + return super.clear(); + } + + private void ensureExtensionsIsMutable() { + if (extensions.isImmutable()) { + extensions = extensions.clone(); + } + } + + private void verifyExtensionContainingType( + final Extension<MessageType, ?> extension) { + if (extension.getDescriptor().getContainingType() != + getDescriptorForType()) { + // This can only happen if someone uses unchecked operations. + throw new IllegalArgumentException( + "Extension is for type \"" + + extension.getDescriptor().getContainingType().getFullName() + + "\" which does not match message type \"" + + getDescriptorForType().getFullName() + "\"."); + } + } + + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + return extensions.hasField(extension.getDescriptor()); + } + + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final ExtensionLite<MessageType, List<Type>> extensionLite) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + final FieldDescriptor descriptor = extension.getDescriptor(); + return extensions.getRepeatedFieldCount(descriptor); + } + + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + final Object value = extensions.getField(descriptor); + if (value == null) { + if (descriptor.isRepeated()) { + return (Type) Collections.emptyList(); + } else if (descriptor.getJavaType() == + FieldDescriptor.JavaType.MESSAGE) { + return (Type) extension.getMessageDefaultInstance(); + } else { + return (Type) extension.fromReflectionType( + descriptor.getDefaultValue()); + } + } else { + return (Type) extension.fromReflectionType(value); + } + } + + /** Get one element of a repeated extension. */ + @Override + public final <Type> Type getExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, final int index) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + return (Type) extension.singularFromReflectionType( + extensions.getRepeatedField(descriptor, index)); + } + + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + final ExtensionLite<MessageType, Type> extensionLite, + final Type value) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + final FieldDescriptor descriptor = extension.getDescriptor(); + extensions.setField(descriptor, extension.toReflectionType(value)); + onChanged(); + return (BuilderType) this; + } + + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, + final int index, final Type value) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + final FieldDescriptor descriptor = extension.getDescriptor(); + extensions.setRepeatedField( + descriptor, index, + extension.singularToReflectionType(value)); + onChanged(); + return (BuilderType) this; + } + + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, + final Type value) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + final FieldDescriptor descriptor = extension.getDescriptor(); + extensions.addRepeatedField( + descriptor, extension.singularToReflectionType(value)); + onChanged(); + return (BuilderType) this; + } + + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + final ExtensionLite<MessageType, ?> extensionLite) { + Extension<MessageType, ?> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + extensions.clearField(extension.getDescriptor()); + onChanged(); + return (BuilderType) this; + } + + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension( + final GeneratedExtension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final Extension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final GeneratedExtension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension(final Extension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final Extension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + final Extension<MessageType, Type> extension, final Type value) { + return setExtension((ExtensionLite<MessageType, Type>) extension, value); + } + /** Set the value of an extension. */ + public <Type> BuilderType setExtension( + final GeneratedExtension<MessageType, Type> extension, final Type value) { + return setExtension((ExtensionLite<MessageType, Type>) extension, value); + } + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + final Extension<MessageType, List<Type>> extension, + final int index, final Type value) { + return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value); + } + /** Set the value of one element of a repeated extension. */ + public <Type> BuilderType setExtension( + final GeneratedExtension<MessageType, List<Type>> extension, + final int index, final Type value) { + return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value); + } + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + final Extension<MessageType, List<Type>> extension, final Type value) { + return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value); + } + /** Append a value to a repeated extension. */ + public <Type> BuilderType addExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final Type value) { + return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value); + } + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + final Extension<MessageType, ?> extension) { + return clearExtension((ExtensionLite<MessageType, ?>) extension); + } + /** Clear an extension. */ + public <Type> BuilderType clearExtension( + final GeneratedExtension<MessageType, ?> extension) { + return clearExtension((ExtensionLite<MessageType, ?>) extension); + } + + /** Called by subclasses to check if all extensions are initialized. */ + protected boolean extensionsAreInitialized() { + return extensions.isInitialized(); + } + + /** + * Called by the build code path to create a copy of the extensions for + * building the message. + */ + private FieldSet<FieldDescriptor> buildExtensions() { + extensions.makeImmutable(); + return extensions; + } + + @Override + public boolean isInitialized() { + return super.isInitialized() && extensionsAreInitialized(); + } + + // --------------------------------------------------------------- + // Reflection + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable(); + result.putAll(extensions.getAllFields()); + return Collections.unmodifiableMap(result); + } + + @Override + public Object getField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + final Object value = extensions.getField(field); + if (value == null) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + // Lacking an ExtensionRegistry, we have no way to determine the + // extension's real type, so we return a DynamicMessage. + return DynamicMessage.getDefaultInstance(field.getMessageType()); + } else { + return field.getDefaultValue(); + } + } else { + return value; + } + } else { + return super.getField(field); + } + } + + @Override + public int getRepeatedFieldCount(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedFieldCount(field); + } else { + return super.getRepeatedFieldCount(field); + } + } + + @Override + public Object getRepeatedField(final FieldDescriptor field, + final int index) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedField(field, index); + } else { + return super.getRepeatedField(field, index); + } + } + + @Override + public boolean hasField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.hasField(field); + } else { + return super.hasField(field); + } + } + + @Override + public BuilderType setField(final FieldDescriptor field, + final Object value) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.setField(field, value); + onChanged(); + return (BuilderType) this; + } else { + return super.setField(field, value); + } + } + + @Override + public BuilderType clearField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.clearField(field); + onChanged(); + return (BuilderType) this; + } else { + return super.clearField(field); + } + } + + @Override + public BuilderType setRepeatedField(final FieldDescriptor field, + final int index, final Object value) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.setRepeatedField(field, index, value); + onChanged(); + return (BuilderType) this; + } else { + return super.setRepeatedField(field, index, value); + } + } + + @Override + public BuilderType addRepeatedField(final FieldDescriptor field, + final Object value) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.addRepeatedField(field, value); + onChanged(); + return (BuilderType) this; + } else { + return super.addRepeatedField(field, value); + } + } + + protected final void mergeExtensionFields(final ExtendableMessage other) { + ensureExtensionsIsMutable(); + extensions.mergeFrom(other.extensions); + onChanged(); + } + + private void verifyContainingType(final FieldDescriptor field) { + if (field.getContainingType() != getDescriptorForType()) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + } + + // ----------------------------------------------------------------- + + /** + * Gets the descriptor for an extension. The implementation depends on whether + * the extension is scoped in the top level of a file or scoped in a Message. + */ + static interface ExtensionDescriptorRetriever { + FieldDescriptor getDescriptor(); + } + + + // ================================================================= + + /** Calls Class.getMethod and throws a RuntimeException if it fails. */ + @SuppressWarnings("unchecked") + private static Method getMethodOrDie( + final Class clazz, final String name, final Class... params) { + try { + return clazz.getMethod(name, params); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Generated message class \"" + clazz.getName() + + "\" missing method \"" + name + "\".", e); + } + } + + /** Calls invoke and throws a RuntimeException if it fails. */ + private static Object invokeOrDie( + final Method method, final Object object, final Object... params) { + try { + return method.invoke(object, params); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Couldn't use Java reflection to implement protocol message " + + "reflection.", e); + } catch (InvocationTargetException e) { + final Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else { + throw new RuntimeException( + "Unexpected exception thrown by generated accessor method.", cause); + } + } + } + + /** + * Gets the map field with the given field number. This method should be + * overridden in the generated message class if the message contains map + * fields. + * + * Unlike other field types, reflection support for map fields can't be + * implemented based on generated public API because we need to access a + * map field as a list in reflection API but the generated API only allows + * us to access it as a map. This method returns the underlying map field + * directly and thus enables us to access the map field as a list. + */ + @SuppressWarnings({"rawtypes", "unused"}) + protected MapField internalGetMapField(int fieldNumber) { + // Note that we can't use descriptor names here because this method will + // be called when descriptor is being initialized. + throw new RuntimeException( + "No map fields found in " + getClass().getName()); + } + + /** + * Users should ignore this class. This class provides the implementation + * with access to the fields of a message object using Java reflection. + */ + public static final class FieldAccessorTable { + + /** + * Construct a FieldAccessorTable for a particular message class. Only + * one FieldAccessorTable should ever be constructed per class. + * + * @param descriptor The type's descriptor. + * @param camelCaseNames The camelcase names of all fields in the message. + * These are used to derive the accessor method names. + * @param messageClass The message type. + * @param builderClass The builder type. + */ + public FieldAccessorTable( + final Descriptor descriptor, + final String[] camelCaseNames, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass) { + this(descriptor, camelCaseNames); + ensureFieldAccessorsInitialized(messageClass, builderClass); + } + + /** + * Construct a FieldAccessorTable for a particular message class without + * initializing FieldAccessors. + */ + public FieldAccessorTable( + final Descriptor descriptor, + final String[] camelCaseNames) { + this.descriptor = descriptor; + this.camelCaseNames = camelCaseNames; + fields = new FieldAccessor[descriptor.getFields().size()]; + oneofs = new OneofAccessor[descriptor.getOneofs().size()]; + initialized = false; + } + + /** + * Ensures the field accessors are initialized. This method is thread-safe. + * + * @param messageClass The message type. + * @param builderClass The builder type. + * @return this + */ + public FieldAccessorTable ensureFieldAccessorsInitialized( + Class<? extends GeneratedMessageV3> messageClass, + Class<? extends Builder> builderClass) { + if (initialized) { return this; } + synchronized (this) { + if (initialized) { return this; } + int fieldsSize = fields.length; + for (int i = 0; i < fieldsSize; i++) { + FieldDescriptor field = descriptor.getFields().get(i); + String containingOneofCamelCaseName = null; + if (field.getContainingOneof() != null) { + containingOneofCamelCaseName = + camelCaseNames[fieldsSize + field.getContainingOneof().getIndex()]; + } + if (field.isRepeated()) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isMapField()) { + fields[i] = new MapFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } else { + fields[i] = new RepeatedMessageFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } + } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM) { + fields[i] = new RepeatedEnumFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } else { + fields[i] = new RepeatedFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } + } else { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + fields[i] = new SingularMessageFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM) { + fields[i] = new SingularEnumFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } else if (field.getJavaType() == FieldDescriptor.JavaType.STRING) { + fields[i] = new SingularStringFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } else { + fields[i] = new SingularFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } + } + } + + int oneofsSize = oneofs.length; + for (int i = 0; i < oneofsSize; i++) { + oneofs[i] = new OneofAccessor( + descriptor, camelCaseNames[i + fieldsSize], + messageClass, builderClass); + } + initialized = true; + camelCaseNames = null; + return this; + } + } + + private final Descriptor descriptor; + private final FieldAccessor[] fields; + private String[] camelCaseNames; + private final OneofAccessor[] oneofs; + private volatile boolean initialized; + + /** Get the FieldAccessor for a particular field. */ + private FieldAccessor getField(final FieldDescriptor field) { + if (field.getContainingType() != descriptor) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } else if (field.isExtension()) { + // If this type had extensions, it would subclass ExtendableMessage, + // which overrides the reflection interface to handle extensions. + throw new IllegalArgumentException( + "This type does not have extensions."); + } + return fields[field.getIndex()]; + } + + /** Get the OneofAccessor for a particular oneof. */ + private OneofAccessor getOneof(final OneofDescriptor oneof) { + if (oneof.getContainingType() != descriptor) { + throw new IllegalArgumentException( + "OneofDescriptor does not match message type."); + } + return oneofs[oneof.getIndex()]; + } + + /** + * Abstract interface that provides access to a single field. This is + * implemented differently depending on the field type and cardinality. + */ + private interface FieldAccessor { + Object get(GeneratedMessageV3 message); + Object get(GeneratedMessageV3.Builder builder); + Object getRaw(GeneratedMessageV3 message); + Object getRaw(GeneratedMessageV3.Builder builder); + void set(Builder builder, Object value); + Object getRepeated(GeneratedMessageV3 message, int index); + Object getRepeated(GeneratedMessageV3.Builder builder, int index); + Object getRepeatedRaw(GeneratedMessageV3 message, int index); + Object getRepeatedRaw(GeneratedMessageV3.Builder builder, int index); + void setRepeated(Builder builder, + int index, Object value); + void addRepeated(Builder builder, Object value); + boolean has(GeneratedMessageV3 message); + boolean has(GeneratedMessageV3.Builder builder); + int getRepeatedCount(GeneratedMessageV3 message); + int getRepeatedCount(GeneratedMessageV3.Builder builder); + void clear(Builder builder); + Message.Builder newBuilder(); + Message.Builder getBuilder(GeneratedMessageV3.Builder builder); + Message.Builder getRepeatedBuilder(GeneratedMessageV3.Builder builder, + int index); + } + + /** OneofAccessor provides access to a single oneof. */ + private static class OneofAccessor { + OneofAccessor( + final Descriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass) { + this.descriptor = descriptor; + caseMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Case"); + caseMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Case"); + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + } + + private final Descriptor descriptor; + private final Method caseMethod; + private final Method caseMethodBuilder; + private final Method clearMethod; + + public boolean has(final GeneratedMessageV3 message) { + if (((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber() == 0) { + return false; + } + return true; + } + + public boolean has(GeneratedMessageV3.Builder builder) { + if (((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber() == 0) { + return false; + } + return true; + } + + public FieldDescriptor get(final GeneratedMessageV3 message) { + int fieldNumber = ((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber(); + if (fieldNumber > 0) { + return descriptor.findFieldByNumber(fieldNumber); + } + return null; + } + + public FieldDescriptor get(GeneratedMessageV3.Builder builder) { + int fieldNumber = ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); + if (fieldNumber > 0) { + return descriptor.findFieldByNumber(fieldNumber); + } + return null; + } + + public void clear(final Builder builder) { + invokeOrDie(clearMethod, builder); + } + } + + private static boolean supportFieldPresence(FileDescriptor file) { + return file.getSyntax() == FileDescriptor.Syntax.PROTO2; + } + + // --------------------------------------------------------------- + + private static class SingularFieldAccessor implements FieldAccessor { + SingularFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass, + final String containingOneofCamelCaseName) { + field = descriptor; + isOneofField = descriptor.getContainingOneof() != null; + hasHasMethod = supportFieldPresence(descriptor.getFile()) + || (!isOneofField && descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE); + getMethod = getMethodOrDie(messageClass, "get" + camelCaseName); + getMethodBuilder = getMethodOrDie(builderClass, "get" + camelCaseName); + type = getMethod.getReturnType(); + setMethod = getMethodOrDie(builderClass, "set" + camelCaseName, type); + hasMethod = + hasHasMethod ? getMethodOrDie(messageClass, "has" + camelCaseName) : null; + hasMethodBuilder = + hasHasMethod ? getMethodOrDie(builderClass, "has" + camelCaseName) : null; + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + caseMethod = isOneofField ? getMethodOrDie( + messageClass, "get" + containingOneofCamelCaseName + "Case") : null; + caseMethodBuilder = isOneofField ? getMethodOrDie( + builderClass, "get" + containingOneofCamelCaseName + "Case") : null; + } + + // Note: We use Java reflection to call public methods rather than + // access private fields directly as this avoids runtime security + // checks. + protected final Class<?> type; + protected final Method getMethod; + protected final Method getMethodBuilder; + protected final Method setMethod; + protected final Method hasMethod; + protected final Method hasMethodBuilder; + protected final Method clearMethod; + protected final Method caseMethod; + protected final Method caseMethodBuilder; + protected final FieldDescriptor field; + protected final boolean isOneofField; + protected final boolean hasHasMethod; + + private int getOneofFieldNumber(final GeneratedMessageV3 message) { + return ((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber(); + } + + private int getOneofFieldNumber(final GeneratedMessageV3.Builder builder) { + return ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); + } + + @Override + public Object get(final GeneratedMessageV3 message) { + return invokeOrDie(getMethod, message); + } + @Override + public Object get(GeneratedMessageV3.Builder builder) { + return invokeOrDie(getMethodBuilder, builder); + } + @Override + public Object getRaw(final GeneratedMessageV3 message) { + return get(message); + } + @Override + public Object getRaw(GeneratedMessageV3.Builder builder) { + return get(builder); + } + @Override + public void set(final Builder builder, final Object value) { + invokeOrDie(setMethod, builder, value); + } + @Override + public Object getRepeated(final GeneratedMessageV3 message, final int index) { + throw new UnsupportedOperationException( + "getRepeatedField() called on a singular field."); + } + @Override + public Object getRepeatedRaw(final GeneratedMessageV3 message, final int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldRaw() called on a singular field."); + } + @Override + public Object getRepeated(GeneratedMessageV3.Builder builder, int index) { + throw new UnsupportedOperationException( + "getRepeatedField() called on a singular field."); + } + @Override + public Object getRepeatedRaw(GeneratedMessageV3.Builder builder, int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldRaw() called on a singular field."); + } + @Override + public void setRepeated(final Builder builder, final int index, final Object value) { + throw new UnsupportedOperationException( + "setRepeatedField() called on a singular field."); + } + @Override + public void addRepeated(final Builder builder, final Object value) { + throw new UnsupportedOperationException( + "addRepeatedField() called on a singular field."); + } + @Override + public boolean has(final GeneratedMessageV3 message) { + if (!hasHasMethod) { + if (isOneofField) { + return getOneofFieldNumber(message) == field.getNumber(); + } + return !get(message).equals(field.getDefaultValue()); + } + return (Boolean) invokeOrDie(hasMethod, message); + } + @Override + public boolean has(GeneratedMessageV3.Builder builder) { + if (!hasHasMethod) { + if (isOneofField) { + return getOneofFieldNumber(builder) == field.getNumber(); + } + return !get(builder).equals(field.getDefaultValue()); + } + return (Boolean) invokeOrDie(hasMethodBuilder, builder); + } + @Override + public int getRepeatedCount(final GeneratedMessageV3 message) { + throw new UnsupportedOperationException( + "getRepeatedFieldSize() called on a singular field."); + } + @Override + public int getRepeatedCount(GeneratedMessageV3.Builder builder) { + throw new UnsupportedOperationException( + "getRepeatedFieldSize() called on a singular field."); + } + @Override + public void clear(final Builder builder) { + invokeOrDie(clearMethod, builder); + } + @Override + public Message.Builder newBuilder() { + throw new UnsupportedOperationException( + "newBuilderForField() called on a non-Message type."); + } + @Override + public Message.Builder getBuilder(GeneratedMessageV3.Builder builder) { + throw new UnsupportedOperationException( + "getFieldBuilder() called on a non-Message type."); + } + @Override + public Message.Builder getRepeatedBuilder(GeneratedMessageV3.Builder builder, int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a non-Message type."); + } + } + + private static class RepeatedFieldAccessor implements FieldAccessor { + protected final Class type; + protected final Method getMethod; + protected final Method getMethodBuilder; + protected final Method getRepeatedMethod; + protected final Method getRepeatedMethodBuilder; + protected final Method setRepeatedMethod; + protected final Method addRepeatedMethod; + protected final Method getCountMethod; + protected final Method getCountMethodBuilder; + protected final Method clearMethod; + + RepeatedFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass) { + getMethod = getMethodOrDie(messageClass, + "get" + camelCaseName + "List"); + getMethodBuilder = getMethodOrDie(builderClass, + "get" + camelCaseName + "List"); + getRepeatedMethod = + getMethodOrDie(messageClass, "get" + camelCaseName, Integer.TYPE); + getRepeatedMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName, Integer.TYPE); + type = getRepeatedMethod.getReturnType(); + setRepeatedMethod = + getMethodOrDie(builderClass, "set" + camelCaseName, + Integer.TYPE, type); + addRepeatedMethod = + getMethodOrDie(builderClass, "add" + camelCaseName, type); + getCountMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Count"); + getCountMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Count"); + + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + } + + @Override + public Object get(final GeneratedMessageV3 message) { + return invokeOrDie(getMethod, message); + } + @Override + public Object get(GeneratedMessageV3.Builder builder) { + return invokeOrDie(getMethodBuilder, builder); + } + @Override + public Object getRaw(final GeneratedMessageV3 message) { + return get(message); + } + @Override + public Object getRaw(GeneratedMessageV3.Builder builder) { + return get(builder); + } + @Override + public void set(final Builder builder, final Object value) { + // Add all the elements individually. This serves two purposes: + // 1) Verifies that each element has the correct type. + // 2) Insures that the caller cannot modify the list later on and + // have the modifications be reflected in the message. + clear(builder); + for (final Object element : (List<?>) value) { + addRepeated(builder, element); + } + } + @Override + public Object getRepeated(final GeneratedMessageV3 message, final int index) { + return invokeOrDie(getRepeatedMethod, message, index); + } + @Override + public Object getRepeated(GeneratedMessageV3.Builder builder, int index) { + return invokeOrDie(getRepeatedMethodBuilder, builder, index); + } + @Override + public Object getRepeatedRaw(GeneratedMessageV3 message, int index) { + return getRepeated(message, index); + } + @Override + public Object getRepeatedRaw(GeneratedMessageV3.Builder builder, int index) { + return getRepeated(builder, index); + } + @Override + public void setRepeated(final Builder builder, final int index, final Object value) { + invokeOrDie(setRepeatedMethod, builder, index, value); + } + @Override + public void addRepeated(final Builder builder, final Object value) { + invokeOrDie(addRepeatedMethod, builder, value); + } + @Override + public boolean has(final GeneratedMessageV3 message) { + throw new UnsupportedOperationException( + "hasField() called on a repeated field."); + } + @Override + public boolean has(GeneratedMessageV3.Builder builder) { + throw new UnsupportedOperationException( + "hasField() called on a repeated field."); + } + @Override + public int getRepeatedCount(final GeneratedMessageV3 message) { + return (Integer) invokeOrDie(getCountMethod, message); + } + @Override + public int getRepeatedCount(GeneratedMessageV3.Builder builder) { + return (Integer) invokeOrDie(getCountMethodBuilder, builder); + } + @Override + public void clear(final Builder builder) { + invokeOrDie(clearMethod, builder); + } + @Override + public Message.Builder newBuilder() { + throw new UnsupportedOperationException( + "newBuilderForField() called on a non-Message type."); + } + @Override + public Message.Builder getBuilder(GeneratedMessageV3.Builder builder) { + throw new UnsupportedOperationException( + "getFieldBuilder() called on a non-Message type."); + } + @Override + public Message.Builder getRepeatedBuilder(GeneratedMessageV3.Builder builder, int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a non-Message type."); + } + } + + private static class MapFieldAccessor implements FieldAccessor { + MapFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass) { + field = descriptor; + Method getDefaultInstanceMethod = + getMethodOrDie(messageClass, "getDefaultInstance"); + MapField defaultMapField = getMapField( + (GeneratedMessageV3) invokeOrDie(getDefaultInstanceMethod, null)); + mapEntryMessageDefaultInstance = + defaultMapField.getMapEntryMessageDefaultInstance(); + } + + private final FieldDescriptor field; + private final Message mapEntryMessageDefaultInstance; + + private MapField<?, ?> getMapField(GeneratedMessageV3 message) { + return (MapField<?, ?>) message.internalGetMapField(field.getNumber()); + } + + private MapField<?, ?> getMapField(GeneratedMessageV3.Builder builder) { + return (MapField<?, ?>) builder.internalGetMapField(field.getNumber()); + } + + private MapField<?, ?> getMutableMapField( + GeneratedMessageV3.Builder builder) { + return (MapField<?, ?>) builder.internalGetMutableMapField( + field.getNumber()); + } + + private Message coerceType(Message value) { + if (value == null) { + return null; + } + if (mapEntryMessageDefaultInstance.getClass().isInstance(value)) { + return value; + } + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + return mapEntryMessageDefaultInstance.toBuilder().mergeFrom(value).build(); + } + + @Override + @SuppressWarnings("unchecked") + public Object get(GeneratedMessageV3 message) { + List result = new ArrayList(); + for (int i = 0; i < getRepeatedCount(message); i++) { + result.add(getRepeated(message, i)); + } + return Collections.unmodifiableList(result); + } + + @Override + @SuppressWarnings("unchecked") + public Object get(Builder builder) { + List result = new ArrayList(); + for (int i = 0; i < getRepeatedCount(builder); i++) { + result.add(getRepeated(builder, i)); + } + return Collections.unmodifiableList(result); + } + + @Override + public Object getRaw(GeneratedMessageV3 message) { + return get(message); + } + + @Override + public Object getRaw(GeneratedMessageV3.Builder builder) { + return get(builder); + } + + @Override + public void set(Builder builder, Object value) { + clear(builder); + for (Object entry : (List) value) { + addRepeated(builder, entry); + } + } + + @Override + public Object getRepeated(GeneratedMessageV3 message, int index) { + return getMapField(message).getList().get(index); + } + + @Override + public Object getRepeated(Builder builder, int index) { + return getMapField(builder).getList().get(index); + } + + @Override + public Object getRepeatedRaw(GeneratedMessageV3 message, int index) { + return getRepeated(message, index); + } + + @Override + public Object getRepeatedRaw(Builder builder, int index) { + return getRepeated(builder, index); + } + + @Override + public void setRepeated(Builder builder, int index, Object value) { + getMutableMapField(builder).getMutableList().set(index, coerceType((Message) value)); + } + + @Override + public void addRepeated(Builder builder, Object value) { + getMutableMapField(builder).getMutableList().add(coerceType((Message) value)); + } + + @Override + public boolean has(GeneratedMessageV3 message) { + throw new UnsupportedOperationException( + "hasField() is not supported for repeated fields."); + } + + @Override + public boolean has(Builder builder) { + throw new UnsupportedOperationException( + "hasField() is not supported for repeated fields."); + } + + @Override + public int getRepeatedCount(GeneratedMessageV3 message) { + return getMapField(message).getList().size(); + } + + @Override + public int getRepeatedCount(Builder builder) { + return getMapField(builder).getList().size(); + } + + @Override + public void clear(Builder builder) { + getMutableMapField(builder).getMutableList().clear(); + } + + @Override + public com.google.protobuf.Message.Builder newBuilder() { + return mapEntryMessageDefaultInstance.newBuilderForType(); + } + + @Override + public com.google.protobuf.Message.Builder getBuilder(Builder builder) { + throw new UnsupportedOperationException( + "Nested builder not supported for map fields."); + } + + @Override + public com.google.protobuf.Message.Builder getRepeatedBuilder(Builder builder, int index) { + throw new UnsupportedOperationException( + "Nested builder not supported for map fields."); + } + } + + // --------------------------------------------------------------- + + private static final class SingularEnumFieldAccessor + extends SingularFieldAccessor { + SingularEnumFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, containingOneofCamelCaseName); + + enumDescriptor = descriptor.getEnumType(); + + valueOfMethod = getMethodOrDie(type, "valueOf", + EnumValueDescriptor.class); + getValueDescriptorMethod = + getMethodOrDie(type, "getValueDescriptor"); + + supportUnknownEnumValue = descriptor.getFile().supportsUnknownEnumValue(); + if (supportUnknownEnumValue) { + getValueMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Value"); + getValueMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Value"); + setValueMethod = + getMethodOrDie(builderClass, "set" + camelCaseName + "Value", int.class); + } + } + + private EnumDescriptor enumDescriptor; + + private Method valueOfMethod; + private Method getValueDescriptorMethod; + + private boolean supportUnknownEnumValue; + private Method getValueMethod; + private Method getValueMethodBuilder; + private Method setValueMethod; + + @Override + public Object get(final GeneratedMessageV3 message) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getValueMethod, message); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } + return invokeOrDie(getValueDescriptorMethod, super.get(message)); + } + + @Override + public Object get(final GeneratedMessageV3.Builder builder) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getValueMethodBuilder, builder); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } + return invokeOrDie(getValueDescriptorMethod, super.get(builder)); + } + + @Override + public void set(final Builder builder, final Object value) { + if (supportUnknownEnumValue) { + invokeOrDie(setValueMethod, builder, + ((EnumValueDescriptor) value).getNumber()); + return; + } + super.set(builder, invokeOrDie(valueOfMethod, null, value)); + } + } + + private static final class RepeatedEnumFieldAccessor + extends RepeatedFieldAccessor { + RepeatedEnumFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass) { + super(descriptor, camelCaseName, messageClass, builderClass); + + enumDescriptor = descriptor.getEnumType(); + + valueOfMethod = getMethodOrDie(type, "valueOf", + EnumValueDescriptor.class); + getValueDescriptorMethod = + getMethodOrDie(type, "getValueDescriptor"); + + supportUnknownEnumValue = descriptor.getFile().supportsUnknownEnumValue(); + if (supportUnknownEnumValue) { + getRepeatedValueMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Value", int.class); + getRepeatedValueMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Value", int.class); + setRepeatedValueMethod = + getMethodOrDie(builderClass, "set" + camelCaseName + "Value", int.class, int.class); + addRepeatedValueMethod = + getMethodOrDie(builderClass, "add" + camelCaseName + "Value", int.class); + } + } + private EnumDescriptor enumDescriptor; + + private final Method valueOfMethod; + private final Method getValueDescriptorMethod; + + private boolean supportUnknownEnumValue; + private Method getRepeatedValueMethod; + private Method getRepeatedValueMethodBuilder; + private Method setRepeatedValueMethod; + private Method addRepeatedValueMethod; + + @Override + @SuppressWarnings("unchecked") + public Object get(final GeneratedMessageV3 message) { + final List newList = new ArrayList(); + final int size = getRepeatedCount(message); + for (int i = 0; i < size; i++) { + newList.add(getRepeated(message, i)); + } + return Collections.unmodifiableList(newList); + } + + @Override + @SuppressWarnings("unchecked") + public Object get(final GeneratedMessageV3.Builder builder) { + final List newList = new ArrayList(); + final int size = getRepeatedCount(builder); + for (int i = 0; i < size; i++) { + newList.add(getRepeated(builder, i)); + } + return Collections.unmodifiableList(newList); + } + + @Override + public Object getRepeated(final GeneratedMessageV3 message, + final int index) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getRepeatedValueMethod, message, index); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } + return invokeOrDie(getValueDescriptorMethod, + super.getRepeated(message, index)); + } + @Override + public Object getRepeated(final GeneratedMessageV3.Builder builder, + final int index) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getRepeatedValueMethodBuilder, builder, index); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } + return invokeOrDie(getValueDescriptorMethod, + super.getRepeated(builder, index)); + } + @Override + public void setRepeated(final Builder builder, + final int index, final Object value) { + if (supportUnknownEnumValue) { + invokeOrDie(setRepeatedValueMethod, builder, index, + ((EnumValueDescriptor) value).getNumber()); + return; + } + super.setRepeated(builder, index, invokeOrDie(valueOfMethod, null, + value)); + } + @Override + public void addRepeated(final Builder builder, final Object value) { + if (supportUnknownEnumValue) { + invokeOrDie(addRepeatedValueMethod, builder, + ((EnumValueDescriptor) value).getNumber()); + return; + } + super.addRepeated(builder, invokeOrDie(valueOfMethod, null, value)); + } + } + + // --------------------------------------------------------------- + + /** + * Field accessor for string fields. + * + * <p>This class makes getFooBytes() and setFooBytes() available for + * reflection API so that reflection based serialize/parse functions can + * access the raw bytes of the field to preserve non-UTF8 bytes in the + * string. + * + * <p>This ensures the serialize/parse round-trip safety, which is important + * for servers which forward messages. + */ + private static final class SingularStringFieldAccessor + extends SingularFieldAccessor { + SingularStringFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, + containingOneofCamelCaseName); + getBytesMethod = getMethodOrDie(messageClass, + "get" + camelCaseName + "Bytes"); + getBytesMethodBuilder = getMethodOrDie(builderClass, + "get" + camelCaseName + "Bytes"); + setBytesMethodBuilder = getMethodOrDie(builderClass, + "set" + camelCaseName + "Bytes", ByteString.class); + } + + private final Method getBytesMethod; + private final Method getBytesMethodBuilder; + private final Method setBytesMethodBuilder; + + @Override + public Object getRaw(final GeneratedMessageV3 message) { + return invokeOrDie(getBytesMethod, message); + } + + @Override + public Object getRaw(GeneratedMessageV3.Builder builder) { + return invokeOrDie(getBytesMethodBuilder, builder); + } + + @Override + public void set(GeneratedMessageV3.Builder builder, Object value) { + if (value instanceof ByteString) { + invokeOrDie(setBytesMethodBuilder, builder, value); + } else { + super.set(builder, value); + } + } + } + + // --------------------------------------------------------------- + + private static final class SingularMessageFieldAccessor + extends SingularFieldAccessor { + SingularMessageFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, + containingOneofCamelCaseName); + + newBuilderMethod = getMethodOrDie(type, "newBuilder"); + getBuilderMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Builder"); + } + + private final Method newBuilderMethod; + private final Method getBuilderMethodBuilder; + + private Object coerceType(final Object value) { + if (type.isInstance(value)) { + return value; + } else { + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + return ((Message.Builder) invokeOrDie(newBuilderMethod, null)) + .mergeFrom((Message) value).buildPartial(); + } + } + + @Override + public void set(final Builder builder, final Object value) { + super.set(builder, coerceType(value)); + } + @Override + public Message.Builder newBuilder() { + return (Message.Builder) invokeOrDie(newBuilderMethod, null); + } + @Override + public Message.Builder getBuilder(GeneratedMessageV3.Builder builder) { + return (Message.Builder) invokeOrDie(getBuilderMethodBuilder, builder); + } + } + + private static final class RepeatedMessageFieldAccessor + extends RepeatedFieldAccessor { + RepeatedMessageFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessageV3> messageClass, + final Class<? extends Builder> builderClass) { + super(descriptor, camelCaseName, messageClass, builderClass); + + newBuilderMethod = getMethodOrDie(type, "newBuilder"); + getBuilderMethodBuilder = getMethodOrDie(builderClass, + "get" + camelCaseName + "Builder", Integer.TYPE); + } + + private final Method newBuilderMethod; + private final Method getBuilderMethodBuilder; + + private Object coerceType(final Object value) { + if (type.isInstance(value)) { + return value; + } else { + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + return ((Message.Builder) invokeOrDie(newBuilderMethod, null)) + .mergeFrom((Message) value).build(); + } + } + + @Override + public void setRepeated(final Builder builder, + final int index, final Object value) { + super.setRepeated(builder, index, coerceType(value)); + } + @Override + public void addRepeated(final Builder builder, final Object value) { + super.addRepeated(builder, coerceType(value)); + } + @Override + public Message.Builder newBuilder() { + return (Message.Builder) invokeOrDie(newBuilderMethod, null); + } + @Override + public Message.Builder getRepeatedBuilder( + final GeneratedMessageV3.Builder builder, final int index) { + return (Message.Builder) invokeOrDie( + getBuilderMethodBuilder, builder, index); + } + } + } + + /** + * Replaces this object in the output stream with a serialized form. + * Part of Java's serialization magic. Generated sub-classes must override + * this method by calling {@code return super.writeReplace();} + * @return a SerializedForm of this message + */ + protected Object writeReplace() throws ObjectStreamException { + return new GeneratedMessageLite.SerializedForm(this); + } + + /** + * Checks that the {@link Extension} is non-Lite and returns it as a + * {@link GeneratedExtension}. + */ + private static <MessageType extends ExtendableMessage<MessageType>, T> + Extension<MessageType, T> checkNotLite( + ExtensionLite<MessageType, T> extension) { + if (extension.isLite()) { + throw new IllegalArgumentException("Expected non-lite extension."); + } + + return (Extension<MessageType, T>) extension; + } + + protected static int computeStringSize(final int fieldNumber, final Object value) { + if (value instanceof String) { + return CodedOutputStream.computeStringSize(fieldNumber, (String) value); + } else { + return CodedOutputStream.computeBytesSize(fieldNumber, (ByteString) value); + } + } + + protected static int computeStringSizeNoTag(final Object value) { + if (value instanceof String) { + return CodedOutputStream.computeStringSizeNoTag((String) value); + } else { + return CodedOutputStream.computeBytesSizeNoTag((ByteString) value); + } + } + + protected static void writeString( + CodedOutputStream output, final int fieldNumber, final Object value) throws IOException { + if (value instanceof String) { + output.writeString(fieldNumber, (String) value); + } else { + output.writeBytes(fieldNumber, (ByteString) value); + } + } + + protected static void writeStringNoTag( + CodedOutputStream output, final Object value) throws IOException { + if (value instanceof String) { + output.writeStringNoTag((String) value); + } else { + output.writeBytesNoTag((ByteString) value); + } + } + + protected static <V> void serializeIntegerMapTo( + CodedOutputStream out, + MapField<Integer, V> field, + MapEntry<Integer, V> defaultEntry, + int fieldNumber) throws IOException { + Map<Integer, V> m = field.getMap(); + if (!out.isSerializationDeterministic()) { + serializeMapTo(out, m, defaultEntry, fieldNumber); + return; + } + // Sorting the unboxed keys and then look up the values during serialziation is 2x faster + // than sorting map entries with a custom comparator directly. + int[] keys = new int[m.size()]; + int index = 0; + for (int k : m.keySet()) { + keys[index++] = k; + } + Arrays.sort(keys); + for (int key : keys) { + out.writeMessage(fieldNumber, + defaultEntry.newBuilderForType() + .setKey(key) + .setValue(m.get(key)) + .build()); + } + } + + protected static <V> void serializeLongMapTo( + CodedOutputStream out, + MapField<Long, V> field, + MapEntry<Long, V> defaultEntry, + int fieldNumber) + throws IOException { + Map<Long, V> m = field.getMap(); + if (!out.isSerializationDeterministic()) { + serializeMapTo(out, m, defaultEntry, fieldNumber); + return; + } + + long[] keys = new long[m.size()]; + int index = 0; + for (long k : m.keySet()) { + keys[index++] = k; + } + Arrays.sort(keys); + for (long key : keys) { + out.writeMessage(fieldNumber, + defaultEntry.newBuilderForType() + .setKey(key) + .setValue(m.get(key)) + .build()); + } + } + + protected static <V> void serializeStringMapTo( + CodedOutputStream out, + MapField<String, V> field, + MapEntry<String, V> defaultEntry, + int fieldNumber) + throws IOException { + Map<String, V> m = field.getMap(); + if (!out.isSerializationDeterministic()) { + serializeMapTo(out, m, defaultEntry, fieldNumber); + return; + } + + // Sorting the String keys and then look up the values during serialziation is 25% faster than + // sorting map entries with a custom comparator directly. + String[] keys = new String[m.size()]; + keys = m.keySet().toArray(keys); + Arrays.sort(keys); + for (String key : keys) { + out.writeMessage(fieldNumber, + defaultEntry.newBuilderForType() + .setKey(key) + .setValue(m.get(key)) + .build()); + } + } + + protected static <V> void serializeBooleanMapTo( + CodedOutputStream out, + MapField<Boolean, V> field, + MapEntry<Boolean, V> defaultEntry, + int fieldNumber) + throws IOException { + Map<Boolean, V> m = field.getMap(); + if (!out.isSerializationDeterministic()) { + serializeMapTo(out, m, defaultEntry, fieldNumber); + return; + } + maybeSerializeBooleanEntryTo(out, m, defaultEntry, fieldNumber, false); + maybeSerializeBooleanEntryTo(out, m, defaultEntry, fieldNumber, true); + } + + private static <V> void maybeSerializeBooleanEntryTo( + CodedOutputStream out, + Map<Boolean, V> m, + MapEntry<Boolean, V> defaultEntry, + int fieldNumber, + boolean key) + throws IOException { + if (m.containsKey(key)) { + out.writeMessage(fieldNumber, + defaultEntry.newBuilderForType() + .setKey(key) + .setValue(m.get(key)) + .build()); + } + } + + /** Serialize the map using the iteration order. */ + private static <K, V> void serializeMapTo( + CodedOutputStream out, + Map<K, V> m, + MapEntry<K, V> defaultEntry, + int fieldNumber) + throws IOException { + for (Map.Entry<K, V> entry : m.entrySet()) { + out.writeMessage(fieldNumber, + defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build()); + } + } +} + diff --git a/java/core/src/main/java/com/google/protobuf/IntArrayList.java b/java/core/src/main/java/com/google/protobuf/IntArrayList.java index f4e68ed8..aacd71e1 100644 --- a/java/core/src/main/java/com/google/protobuf/IntArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/IntArrayList.java @@ -30,36 +30,35 @@ package com.google.protobuf; -import com.google.protobuf.Internal.IntList; +import static com.google.protobuf.Internal.checkNotNull; +import com.google.protobuf.Internal.IntList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** * An implementation of {@link IntList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class IntArrayList extends AbstractProtobufList<Integer> implements IntList, RandomAccess { - - private static final int DEFAULT_CAPACITY = 10; - +final class IntArrayList extends AbstractProtobufList<Integer> + implements IntList, RandomAccess, PrimitiveNonBoxingCollection { + private static final IntArrayList EMPTY_LIST = new IntArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static IntArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private int[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -70,34 +69,70 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis * Constructs a new mutable {@code IntArrayList} with default capacity. */ IntArrayList() { - this(DEFAULT_CAPACITY); + this(new int[DEFAULT_CAPACITY], 0); } /** - * Constructs a new mutable {@code IntArrayList} with the provided capacity. + * Constructs a new mutable {@code IntArrayList} + * containing the same elements as {@code other}. */ - IntArrayList(int capacity) { - array = new int[capacity]; - size = 0; + private IntArrayList(int[] other, int size) { + array = other; + this.size = size; } - /** - * Constructs a new mutable {@code IntArrayList} containing the same elements as {@code other}. - */ - IntArrayList(List<Integer> other) { - if (other instanceof IntArrayList) { - IntArrayList list = (IntArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new int[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + @Override + protected void removeRange(int fromIndex, int toIndex) { + ensureIsMutable(); + if (toIndex < fromIndex) { + throw new IndexOutOfBoundsException("toIndex < fromIndex"); + } + + System.arraycopy(array, toIndex, array, fromIndex, size - toIndex); + size -= (toIndex - fromIndex); + modCount++; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof IntArrayList)) { + return super.equals(o); + } + IntArrayList other = (IntArrayList) o; + if (size != other.size) { + return false; + } + + final int[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + array[i]; + } + return result; } - + + @Override + public IntList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new IntArrayList(Arrays.copyOf(array, capacity), size); + } + @Override public Integer get(int index) { return getInt(index); @@ -149,7 +184,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -157,10 +192,10 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; int[] newArray = new int[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -174,38 +209,36 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis @Override public boolean addAll(Collection<? extends Integer> collection) { ensureIsMutable(); - - if (collection == null) { - throw new NullPointerException(); - } - + + checkNotNull(collection); + // We specialize when adding another IntArrayList to avoid boxing elements. if (!(collection instanceof IntArrayList)) { return super.addAll(collection); } - + IntArrayList list = (IntArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -225,7 +258,9 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis ensureIsMutable(); ensureIndexInRange(index); int value = array[index]; - System.arraycopy(array, index + 1, array, index, size - index); + if (index < size - 1) { + System.arraycopy(array, index + 1, array, index, size - index); + } size--; modCount++; return value; @@ -234,7 +269,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/Internal.java b/java/core/src/main/java/com/google/protobuf/Internal.java index e19b6dca..848cad03 100644 --- a/java/core/src/main/java/com/google/protobuf/Internal.java +++ b/java/core/src/main/java/com/google/protobuf/Internal.java @@ -41,6 +41,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.RandomAccess; import java.util.Set; /** @@ -51,10 +52,32 @@ import java.util.Set; * * @author kenton@google.com (Kenton Varda) */ -public class Internal { +public final class Internal { - protected static final Charset UTF_8 = Charset.forName("UTF-8"); - protected static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + private Internal() {} + + static final Charset UTF_8 = Charset.forName("UTF-8"); + static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + /** + * Throws an appropriate {@link NullPointerException} if the given objects is {@code null}. + */ + static <T> T checkNotNull(T obj) { + if (obj == null) { + throw new NullPointerException(); + } + return obj; + } + + /** + * Throws an appropriate {@link NullPointerException} if the given objects is {@code null}. + */ + static <T> T checkNotNull(T obj, String message) { + if (obj == null) { + throw new NullPointerException(message); + } + return obj; + } /** * Helper called by generated code to construct default values for string @@ -391,9 +414,8 @@ public class Internal { } } - /** - * An empty byte array constant used in generated code. - */ + + /** An empty byte array constant used in generated code. */ public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; /** @@ -406,6 +428,12 @@ public class Internal { public static final CodedInputStream EMPTY_CODED_INPUT_STREAM = CodedInputStream.newInstance(EMPTY_BYTE_ARRAY); + + /** Helper method to merge two MessageLite instances. */ + static Object mergeMessage(Object destination, Object source) { + return ((MessageLite) destination).toBuilder().mergeFrom((MessageLite) source).buildPartial(); + } + /** * Provides an immutable view of {@code List<T>} around a {@code List<F>}. * @@ -454,10 +482,13 @@ public class Internal { public static <T extends EnumLite> Converter<Integer, T> newEnumConverter( final EnumLiteMap<T> enumMap, final T unrecognizedValue) { return new Converter<Integer, T>() { + @Override public T doForward(Integer value) { T result = enumMap.findValueByNumber(value); return result == null ? unrecognizedValue : result; } + + @Override public Integer doBackward(T value) { return value.getNumber(); } @@ -570,8 +601,10 @@ public class Internal { /** * Extends {@link List} to add the capability to make the list immutable and inspect if it is * modifiable. + * <p> + * All implementations must support efficient random access. */ - public static interface ProtobufList<E> extends List<E> { + public static interface ProtobufList<E> extends List<E>, RandomAccess { /** * Makes this list immutable. All subsequent modifications will throw an @@ -583,6 +616,11 @@ public class Internal { * Returns whether this list can be modified via the publicly accessible {@link List} methods. */ boolean isModifiable(); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + ProtobufList<E> mutableCopyWithCapacity(int capacity); } /** @@ -605,6 +643,12 @@ public class Internal { * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. */ int setInt(int index, int element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + IntList mutableCopyWithCapacity(int capacity); } /** @@ -627,6 +671,12 @@ public class Internal { * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. */ boolean setBoolean(int index, boolean element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + BooleanList mutableCopyWithCapacity(int capacity); } /** @@ -649,6 +699,12 @@ public class Internal { * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. */ long setLong(int index, long element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + LongList mutableCopyWithCapacity(int capacity); } /** @@ -671,6 +727,12 @@ public class Internal { * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. */ double setDouble(int index, double element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + DoubleList mutableCopyWithCapacity(int capacity); } /** @@ -693,5 +755,11 @@ public class Internal { * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. */ float setFloat(int index, float element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + FloatList mutableCopyWithCapacity(int capacity); } } diff --git a/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java index 85ce7b24..510c6aac 100644 --- a/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java +++ b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java @@ -50,6 +50,10 @@ public class InvalidProtocolBufferException extends IOException { super(e.getMessage(), e); } + public InvalidProtocolBufferException(final String description, IOException e) { + super(description, e); + } + /** * Attaches an unfinished message to the exception to support best-effort * parsing in {@code Parser} interface. @@ -107,11 +111,23 @@ public class InvalidProtocolBufferException extends IOException { "Protocol message end-group tag did not match expected tag."); } - static InvalidProtocolBufferException invalidWireType() { - return new InvalidProtocolBufferException( + static InvalidWireTypeException invalidWireType() { + return new InvalidWireTypeException( "Protocol message tag had invalid wire type."); } + /** + * Exception indicating that and unexpected wire type was encountered for a field. + */ + @ExperimentalApi + public static class InvalidWireTypeException extends InvalidProtocolBufferException { + private static final long serialVersionUID = 3283890091615336259L; + + public InvalidWireTypeException(String description) { + super(description); + } + } + static InvalidProtocolBufferException recursionLimitExceeded() { return new InvalidProtocolBufferException( "Protocol message had too many levels of nesting. May be malicious. " + diff --git a/java/core/src/main/java/com/google/protobuf/IterableByteBufferInputStream.java b/java/core/src/main/java/com/google/protobuf/IterableByteBufferInputStream.java new file mode 100644 index 00000000..713e8064 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/IterableByteBufferInputStream.java @@ -0,0 +1,150 @@ +// 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 static com.google.protobuf.Internal.EMPTY_BYTE_BUFFER; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Iterator; + +class IterableByteBufferInputStream extends InputStream { + /** The {@link Iterator} with type {@link ByteBuffer} of {@code input} */ + private Iterator<ByteBuffer> iterator; + /** The current ByteBuffer; */ + private ByteBuffer currentByteBuffer; + /** The number of ByteBuffers in the input data. */ + private int dataSize; + /** + * Current {@code ByteBuffer}'s index + * + * <p>If index equals dataSize, then all the data in the InputStream has been consumed + */ + private int currentIndex; + /** The current position for current ByteBuffer */ + private int currentByteBufferPos; + /** Whether current ByteBuffer has an array */ + private boolean hasArray; + /** + * If the current ByteBuffer is unsafe-direct based, currentArray is null; otherwise should be the + * array inside ByteBuffer. + */ + private byte[] currentArray; + /** Current ByteBuffer's array offset */ + private int currentArrayOffset; + /** + * If the current ByteBuffer is unsafe-direct based, currentAddress is the start address of this + * ByteBuffer; otherwise should be zero. + */ + private long currentAddress; + + IterableByteBufferInputStream(Iterable<ByteBuffer> data) { + iterator = data.iterator(); + dataSize = 0; + for (ByteBuffer unused : data) { + dataSize++; + } + currentIndex = -1; + + if (!getNextByteBuffer()) { + currentByteBuffer = EMPTY_BYTE_BUFFER; + currentIndex = 0; + currentByteBufferPos = 0; + currentAddress = 0; + } + } + + private boolean getNextByteBuffer() { + currentIndex++; + if (!iterator.hasNext()) { + return false; + } + currentByteBuffer = iterator.next(); + currentByteBufferPos = currentByteBuffer.position(); + if (currentByteBuffer.hasArray()) { + hasArray = true; + currentArray = currentByteBuffer.array(); + currentArrayOffset = currentByteBuffer.arrayOffset(); + } else { + hasArray = false; + currentAddress = UnsafeUtil.addressOffset(currentByteBuffer); + currentArray = null; + } + return true; + } + + private void updateCurrentByteBufferPos(int numberOfBytesRead) { + currentByteBufferPos += numberOfBytesRead; + if (currentByteBufferPos == currentByteBuffer.limit()) { + getNextByteBuffer(); + } + } + + @Override + public int read() throws IOException { + if (currentIndex == dataSize) { + return -1; + } + if (hasArray) { + int result = currentArray[currentByteBufferPos + currentArrayOffset] & 0xFF; + updateCurrentByteBufferPos(1); + return result; + } else { + int result = UnsafeUtil.getByte(currentByteBufferPos + currentAddress) & 0xFF; + updateCurrentByteBufferPos(1); + return result; + } + } + + @Override + public int read(byte[] output, int offset, int length) throws IOException { + if (currentIndex == dataSize) { + return -1; + } + int remaining = currentByteBuffer.limit() - currentByteBufferPos; + if (length > remaining) { + length = remaining; + } + if (hasArray) { + System.arraycopy( + currentArray, currentByteBufferPos + currentArrayOffset, output, offset, length); + updateCurrentByteBufferPos(length); + } else { + int prevPos = currentByteBuffer.position(); + currentByteBuffer.position(currentByteBufferPos); + currentByteBuffer.get(output, offset, length); + currentByteBuffer.position(prevPos); + updateCurrentByteBufferPos(length); + } + return length; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/LazyField.java b/java/core/src/main/java/com/google/protobuf/LazyField.java index 5e0a485c..98e13ca1 100644 --- a/java/core/src/main/java/com/google/protobuf/LazyField.java +++ b/java/core/src/main/java/com/google/protobuf/LazyField.java @@ -39,14 +39,14 @@ import java.util.Map.Entry; * * Most of key methods are implemented in {@link LazyFieldLite} but this class * can contain default instance of the message to provide {@code hashCode()}, - * {@code equals()} and {@code toString()}. + * {@code euqals()} and {@code toString()}. * * @author xiangl@google.com (Xiang Li) */ public class LazyField extends LazyFieldLite { /** - * Carry a message's default instance which is used by {@code hashCode()}, {@code equals()} and + * Carry a message's default instance which is used by {@code hashCode()}, {@code euqals()} and * {@code toString()}. */ private final MessageLite defaultInstance; @@ -95,12 +95,12 @@ public class LazyField extends LazyFieldLite { this.entry = entry; } - // @Override + @Override public K getKey() { return entry.getKey(); } - // @Override + @Override public Object getValue() { LazyField field = entry.getValue(); if (field == null) { @@ -113,7 +113,7 @@ public class LazyField extends LazyFieldLite { return entry.getValue(); } - // @Override + @Override public Object setValue(Object value) { if (!(value instanceof MessageLite)) { throw new IllegalArgumentException( @@ -131,13 +131,13 @@ public class LazyField extends LazyFieldLite { this.iterator = iterator; } - // @Override + @Override public boolean hasNext() { return iterator.hasNext(); } + @Override @SuppressWarnings("unchecked") - // @Override public Entry<K, Object> next() { Entry<K, ?> entry = iterator.next(); if (entry.getValue() instanceof LazyField) { @@ -146,7 +146,7 @@ public class LazyField extends LazyFieldLite { return (Entry<K, Object>) entry; } - // @Override + @Override public void remove() { iterator.remove(); } diff --git a/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java index eea1fe3c..49ecfc0b 100644 --- a/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java +++ b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java @@ -30,14 +30,26 @@ 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 parse it on-demand. + * 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. * - * LazyField is thread-compatible e.g. concurrent read are safe, however, - * synchronizations are 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, so you don't need to use it directly. + * 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) */ @@ -46,8 +58,34 @@ public class LazyFieldLite { ExtensionRegistryLite.getEmptyRegistry(); /** - * A delayed-parsed version of the bytes. When this is non-null then {@code extensionRegistry } is - * also non-null and {@code value} and {@code memoizedBytes} are null. + * 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; @@ -60,12 +98,15 @@ public class LazyFieldLite { private ExtensionRegistryLite extensionRegistry; /** - * The parsed value. When this is non-null then {@code delayedBytes} will be null. + * 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}. Will be null when {@code value} is null. + * 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; @@ -94,6 +135,43 @@ public class LazyFieldLite { 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. */ @@ -206,29 +284,48 @@ public class LazyFieldLite { 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)); + // At this point we have two fully parsed messages. + setValue(this.value.toBuilder().mergeFrom(other.value).build()); + } + + /** + * 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; - } else { - // All extensions from the other message will be dropped because we have no registry. - setValue(mergeValueAndBytes(this.value, other.toByteString(), EMPTY_REGISTRY)); + } + + // 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( @@ -259,10 +356,12 @@ public class LazyFieldLite { * parsed. Be careful when using this method. */ public int getSerializedSize() { - if (delayedBytes != null) { - return delayedBytes.size(); - } else if (memoizedBytes != null) { + // 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 { @@ -274,12 +373,14 @@ public class LazyFieldLite { * Returns a BytesString for this field in a thread-safe way. */ public ByteString toByteString() { - if (delayedBytes != null) { - return delayedBytes; - } 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; @@ -293,6 +394,7 @@ public class LazyFieldLite { } } + /** * Might lazily parse the bytes that were previously passed in. Is thread-safe. */ @@ -311,18 +413,15 @@ public class LazyFieldLite { .parseFrom(delayedBytes, extensionRegistry); this.value = parsedValue; this.memoizedBytes = delayedBytes; - this.delayedBytes = null; } else { this.value = defaultInstance; this.memoizedBytes = ByteString.EMPTY; - this.delayedBytes = null; } } 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; - this.delayedBytes = null; } } } diff --git a/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java b/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java index c3be3cca..d474c51e 100644 --- a/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java @@ -30,12 +30,12 @@ package com.google.protobuf; -import java.util.Arrays; -import java.util.List; import java.util.AbstractList; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.RandomAccess; /** @@ -64,7 +64,7 @@ import java.util.RandomAccess; */ public class LazyStringArrayList extends AbstractProtobufList<String> implements LazyStringList, RandomAccess { - + private static final LazyStringArrayList EMPTY_LIST = new LazyStringArrayList(); static { EMPTY_LIST.makeImmutable(); @@ -80,11 +80,11 @@ public class LazyStringArrayList extends AbstractProtobufList<String> private final List<Object> list; public LazyStringArrayList() { - list = new ArrayList<Object>(); + this(DEFAULT_CAPACITY); } public LazyStringArrayList(int intialCapacity) { - list = new ArrayList<Object>(intialCapacity); + this(new ArrayList<Object>(intialCapacity)); } public LazyStringArrayList(LazyStringList from) { @@ -93,7 +93,21 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } public LazyStringArrayList(List<String> from) { - list = new ArrayList<Object>(from); + this(new ArrayList<Object>(from)); + } + + private LazyStringArrayList(ArrayList<Object> list) { + this.list = list; + } + + @Override + public LazyStringArrayList mutableCopyWithCapacity(int capacity) { + if (capacity < size()) { + throw new IllegalArgumentException(); + } + ArrayList<Object> newList = new ArrayList<Object>(capacity); + newList.addAll(list); + return new LazyStringArrayList(newList); } @Override @@ -170,7 +184,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return ret; } - // @Override + @Override public boolean addAllByteString(Collection<? extends ByteString> values) { ensureIsMutable(); boolean ret = list.addAll(values); @@ -178,7 +192,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return ret; } - // @Override + @Override public boolean addAllByteArray(Collection<byte[]> c) { ensureIsMutable(); boolean ret = list.addAll(c); @@ -201,14 +215,14 @@ public class LazyStringArrayList extends AbstractProtobufList<String> modCount++; } - // @Override + @Override public void add(ByteString element) { ensureIsMutable(); list.add(element); modCount++; } - // @Override + @Override public void add(byte[] element) { ensureIsMutable(); list.add(element); @@ -220,7 +234,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return list.get(index); } - // @Override + @Override public ByteString getByteString(int index) { Object o = list.get(index); ByteString b = asByteString(o); @@ -230,7 +244,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return b; } - // @Override + @Override public byte[] getByteArray(int index) { Object o = list.get(index); byte[] b = asByteArray(o); @@ -240,7 +254,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return b; } - // @Override + @Override public void set(int index, ByteString s) { setAndReturn(index, s); } @@ -250,7 +264,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return list.set(index, s); } - // @Override + @Override public void set(int index, byte[] s) { setAndReturn(index, s); } @@ -290,12 +304,12 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } } - // @Override + @Override public List<?> getUnderlyingElements() { return Collections.unmodifiableList(list); } - // @Override + @Override public void mergeFrom(LazyStringList other) { ensureIsMutable(); for (Object o : other.getUnderlyingElements()) { @@ -349,7 +363,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } } - // @Override + @Override public List<byte[]> asByteArrayList() { return new ByteArrayListView(this); } @@ -393,12 +407,12 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } } - // @Override + @Override public List<ByteString> asByteStringList() { return new ByteStringListView(this); } - // @Override + @Override public LazyStringList getUnmodifiableView() { if (isModifiable()) { return new UnmodifiableLazyStringList(this); diff --git a/java/core/src/main/java/com/google/protobuf/LongArrayList.java b/java/core/src/main/java/com/google/protobuf/LongArrayList.java index ebe62029..95945cb7 100644 --- a/java/core/src/main/java/com/google/protobuf/LongArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/LongArrayList.java @@ -30,36 +30,35 @@ package com.google.protobuf; -import com.google.protobuf.Internal.LongList; +import static com.google.protobuf.Internal.checkNotNull; +import com.google.protobuf.Internal.LongList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** * An implementation of {@link LongList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class LongArrayList extends AbstractProtobufList<Long> implements LongList, RandomAccess { - - private static final int DEFAULT_CAPACITY = 10; - +final class LongArrayList extends AbstractProtobufList<Long> + implements LongList, RandomAccess, PrimitiveNonBoxingCollection { + private static final LongArrayList EMPTY_LIST = new LongArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static LongArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private long[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -70,34 +69,70 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList * Constructs a new mutable {@code LongArrayList} with default capacity. */ LongArrayList() { - this(DEFAULT_CAPACITY); + this(new long[DEFAULT_CAPACITY], 0); } /** - * Constructs a new mutable {@code LongArrayList} with the provided capacity. + * Constructs a new mutable {@code LongArrayList} + * containing the same elements as {@code other}. */ - LongArrayList(int capacity) { - array = new long[capacity]; - size = 0; + private LongArrayList(long[] other, int size) { + array = other; + this.size = size; } - /** - * Constructs a new mutable {@code LongArrayList} containing the same elements as {@code other}. - */ - LongArrayList(List<Long> other) { - if (other instanceof LongArrayList) { - LongArrayList list = (LongArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new long[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + @Override + protected void removeRange(int fromIndex, int toIndex) { + ensureIsMutable(); + if (toIndex < fromIndex) { + throw new IndexOutOfBoundsException("toIndex < fromIndex"); + } + + System.arraycopy(array, toIndex, array, fromIndex, size - toIndex); + size -= (toIndex - fromIndex); + modCount++; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof LongArrayList)) { + return super.equals(o); + } + LongArrayList other = (LongArrayList) o; + if (size != other.size) { + return false; + } + + final long[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + Internal.hashLong(array[i]); + } + return result; } - + + @Override + public LongList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new LongArrayList(Arrays.copyOf(array, capacity), size); + } + @Override public Long get(int index) { return getLong(index); @@ -149,7 +184,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -157,10 +192,10 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; long[] newArray = new long[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -174,38 +209,36 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList @Override public boolean addAll(Collection<? extends Long> collection) { ensureIsMutable(); - - if (collection == null) { - throw new NullPointerException(); - } - + + checkNotNull(collection); + // We specialize when adding another LongArrayList to avoid boxing elements. if (!(collection instanceof LongArrayList)) { return super.addAll(collection); } - + LongArrayList list = (LongArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -225,7 +258,9 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList ensureIsMutable(); ensureIndexInRange(index); long value = array[index]; - System.arraycopy(array, index + 1, array, index, size - index); + if (index < size - 1) { + System.arraycopy(array, index + 1, array, index, size - index); + } size--; modCount++; return value; @@ -234,7 +269,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/MapEntry.java b/java/core/src/main/java/com/google/protobuf/MapEntry.java index 31414bb4..0849b821 100644 --- a/java/core/src/main/java/com/google/protobuf/MapEntry.java +++ b/java/core/src/main/java/com/google/protobuf/MapEntry.java @@ -33,7 +33,6 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; - import java.io.IOException; import java.util.Collections; import java.util.Map; @@ -41,63 +40,84 @@ import java.util.TreeMap; /** * Implements MapEntry messages. - * + * * In reflection API, map fields will be treated as repeated message fields and * each map entry is accessed as a message. This MapEntry class is used to * represent these map entry messages in reflection API. - * + * * Protobuf internal. Users shouldn't use this class. */ public final class MapEntry<K, V> extends AbstractMessage { - private static class Metadata<K, V> { - public final Descriptor descriptor; - public final MapEntry<K, V> defaultInstance; - public final AbstractParser<MapEntry<K, V>> parser; - + + private static final class Metadata<K, V> extends MapEntryLite.Metadata<K, V> { + + public final Descriptor descriptor; + public final Parser<MapEntry<K, V>> parser; + public Metadata( - final Descriptor descriptor, final MapEntry<K, V> defaultInstance) { + Descriptor descriptor, + MapEntry<K, V> defaultInstance, + WireFormat.FieldType keyType, + WireFormat.FieldType valueType) { + super(keyType, defaultInstance.key, valueType, defaultInstance.value); this.descriptor = descriptor; - this.defaultInstance = defaultInstance; - final Metadata<K, V> thisMetadata = this; this.parser = new AbstractParser<MapEntry<K, V>>() { - private final Parser<MapEntryLite<K, V>> dataParser = - defaultInstance.data.getParserForType(); + @Override public MapEntry<K, V> parsePartialFrom( CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - MapEntryLite<K, V> data = - dataParser.parsePartialFrom(input, extensionRegistry); - return new MapEntry<K, V>(thisMetadata, data); + return new MapEntry<K, V>(Metadata.this, input, extensionRegistry); } - }; } } - + + private final K key; + private final V value; private final Metadata<K, V> metadata; - private final MapEntryLite<K, V> data; - + /** Create a default MapEntry instance. */ - private MapEntry(Descriptor descriptor, + private MapEntry( + Descriptor descriptor, WireFormat.FieldType keyType, K defaultKey, WireFormat.FieldType valueType, V defaultValue) { - this.data = MapEntryLite.newDefaultInstance( - keyType, defaultKey, valueType, defaultValue); - this.metadata = new Metadata<K, V>(descriptor, this); + this.key = defaultKey; + this.value = defaultValue; + this.metadata = new Metadata<K, V>(descriptor, this, keyType, valueType); } - - /** Create a new MapEntry message. */ - private MapEntry(Metadata<K, V> metadata, MapEntryLite<K, V> data) { + + /** Create a MapEntry with the provided key and value. */ + @SuppressWarnings("unchecked") + private MapEntry(Metadata metadata, K key, V value) { + this.key = key; + this.value = value; this.metadata = metadata; - this.data = data; } - + + /** Parsing constructor. */ + private MapEntry( + Metadata<K, V> metadata, + CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + this.metadata = metadata; + Map.Entry<K, V> entry = MapEntryLite.parseEntry(input, metadata, extensionRegistry); + this.key = entry.getKey(); + this.value = entry.getValue(); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e).setUnfinishedMessage(this); + } + } + /** * Create a default MapEntry instance. A default MapEntry instance should be * created only once for each map entry message type. Generated code should * store the created default instance and use it later to create new MapEntry - * messages of the same type. + * messages of the same type. */ public static <K, V> MapEntry<K, V> newDefaultInstance( Descriptor descriptor, @@ -106,30 +126,38 @@ public final class MapEntry<K, V> extends AbstractMessage { return new MapEntry<K, V>( descriptor, keyType, defaultKey, valueType, defaultValue); } - + public K getKey() { - return data.getKey(); + return key; } - + public V getValue() { - return data.getValue(); + return value; } - + + private volatile int cachedSerializedSize = -1; + @Override public int getSerializedSize() { - return data.getSerializedSize(); + if (cachedSerializedSize != -1) { + return cachedSerializedSize; + } + + int size = MapEntryLite.computeSerializedSize(metadata, key, value); + cachedSerializedSize = size; + return size; } - + @Override public void writeTo(CodedOutputStream output) throws IOException { - data.writeTo(output); + MapEntryLite.writeTo(output, metadata, key, value); } - + @Override public boolean isInitialized() { - return data.isInitialized(); + return isInitialized(metadata, value); } - + @Override public Parser<MapEntry<K, V>> getParserForType() { return metadata.parser; @@ -139,15 +167,15 @@ public final class MapEntry<K, V> extends AbstractMessage { public Builder<K, V> newBuilderForType() { return new Builder<K, V>(metadata); } - + @Override public Builder<K, V> toBuilder() { - return new Builder<K, V>(metadata, data); + return new Builder<K, V>(metadata, key, value, true, true); } @Override public MapEntry<K, V> getDefaultInstanceForType() { - return metadata.defaultInstance; + return new MapEntry<K, V>(metadata, metadata.defaultKey, metadata.defaultValue); } @Override @@ -157,8 +185,7 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public Map<FieldDescriptor, Object> getAllFields() { - final TreeMap<FieldDescriptor, Object> result = - new TreeMap<FieldDescriptor, Object>(); + TreeMap<FieldDescriptor, Object> result = new TreeMap<FieldDescriptor, Object>(); for (final FieldDescriptor field : metadata.descriptor.getFields()) { if (hasField(field)) { result.put(field, getField(field)); @@ -166,12 +193,12 @@ public final class MapEntry<K, V> extends AbstractMessage { } return Collections.unmodifiableMap(result); } - + private void checkFieldDescriptor(FieldDescriptor field) { if (field.getContainingType() != metadata.descriptor) { throw new RuntimeException( "Wrong FieldDescriptor \"" + field.getFullName() - + "\" used in message \"" + metadata.descriptor.getFullName()); + + "\" used in message \"" + metadata.descriptor.getFullName()); } } @@ -217,56 +244,52 @@ public final class MapEntry<K, V> extends AbstractMessage { public static class Builder<K, V> extends AbstractMessage.Builder<Builder<K, V>> { private final Metadata<K, V> metadata; - private MapEntryLite<K, V> data; - private MapEntryLite.Builder<K, V> dataBuilder; - + private K key; + private V value; + private boolean hasKey; + private boolean hasValue; + private Builder(Metadata<K, V> metadata) { - this.metadata = metadata; - this.data = metadata.defaultInstance.data; - this.dataBuilder = null; + this(metadata, metadata.defaultKey, metadata.defaultValue, false, false); } - - private Builder(Metadata<K, V> metadata, MapEntryLite<K, V> data) { + + private Builder(Metadata<K, V> metadata, K key, V value, boolean hasKey, boolean hasValue) { this.metadata = metadata; - this.data = data; - this.dataBuilder = null; + this.key = key; + this.value = value; + this.hasKey = hasKey; + this.hasValue = hasValue; } - + public K getKey() { - return dataBuilder == null ? data.getKey() : dataBuilder.getKey(); + return key; } - + public V getValue() { - return dataBuilder == null ? data.getValue() : dataBuilder.getValue(); - } - - private void ensureMutable() { - if (dataBuilder == null) { - dataBuilder = data.toBuilder(); - } + return value; } - + public Builder<K, V> setKey(K key) { - ensureMutable(); - dataBuilder.setKey(key); + this.key = key; + this.hasKey = true; return this; } - + public Builder<K, V> clearKey() { - ensureMutable(); - dataBuilder.clearKey(); + this.key = metadata.defaultKey; + this.hasKey = false; return this; } - + public Builder<K, V> setValue(V value) { - ensureMutable(); - dataBuilder.setValue(value); + this.value = value; + this.hasValue = true; return this; } - + public Builder<K, V> clearValue() { - ensureMutable(); - dataBuilder.clearValue(); + this.value = metadata.defaultValue; + this.hasValue = false; return this; } @@ -281,29 +304,24 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public MapEntry<K, V> buildPartial() { - if (dataBuilder != null) { - data = dataBuilder.buildPartial(); - dataBuilder = null; - } - return new MapEntry<K, V>(metadata, data); + return new MapEntry<K, V>(metadata, key, value); } @Override public Descriptor getDescriptorForType() { return metadata.descriptor; } - + private void checkFieldDescriptor(FieldDescriptor field) { if (field.getContainingType() != metadata.descriptor) { throw new RuntimeException( "Wrong FieldDescriptor \"" + field.getFullName() - + "\" used in message \"" + metadata.descriptor.getFullName()); + + "\" used in message \"" + metadata.descriptor.getFullName()); } } @Override - public com.google.protobuf.Message.Builder newBuilderForField( - FieldDescriptor field) { + public Message.Builder newBuilderForField(FieldDescriptor field) { checkFieldDescriptor(field);; // This method should be called for message fields and in a MapEntry // message only the value field can possibly be a message field. @@ -312,7 +330,7 @@ public final class MapEntry<K, V> extends AbstractMessage { throw new RuntimeException( "\"" + field.getFullName() + "\" is not a message value field."); } - return ((Message) data.getValue()).newBuilderForType(); + return ((Message) value).newBuilderForType(); } @SuppressWarnings("unchecked") @@ -324,6 +342,15 @@ public final class MapEntry<K, V> extends AbstractMessage { } else { if (field.getType() == FieldDescriptor.Type.ENUM) { value = ((EnumValueDescriptor) value).getNumber(); + } else if (field.getType() == FieldDescriptor.Type.MESSAGE) { + if (value != null && !metadata.defaultValue.getClass().isInstance(value)) { + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + value = + ((Message) metadata.defaultValue).toBuilder().mergeFrom((Message) value).build(); + } } setValue((V) value); } @@ -362,22 +389,17 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public MapEntry<K, V> getDefaultInstanceForType() { - return metadata.defaultInstance; + return new MapEntry<K, V>(metadata, metadata.defaultKey, metadata.defaultValue); } @Override public boolean isInitialized() { - if (dataBuilder != null) { - return dataBuilder.isInitialized(); - } else { - return data.isInitialized(); - } + return MapEntry.isInitialized(metadata, value); } @Override public Map<FieldDescriptor, Object> getAllFields() { - final TreeMap<FieldDescriptor, Object> result = - new TreeMap<FieldDescriptor, Object>(); + final TreeMap<FieldDescriptor, Object> result = new TreeMap<FieldDescriptor, Object>(); for (final FieldDescriptor field : metadata.descriptor.getFields()) { if (hasField(field)) { result.put(field, getField(field)); @@ -389,7 +411,7 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public boolean hasField(FieldDescriptor field) { checkFieldDescriptor(field); - return true; + return field.getNumber() == 1 ? hasKey : hasValue; } @Override @@ -398,8 +420,7 @@ public final class MapEntry<K, V> extends AbstractMessage { Object result = field.getNumber() == 1 ? getKey() : getValue(); // Convert enums to EnumValueDescriptor. if (field.getType() == FieldDescriptor.Type.ENUM) { - result = field.getEnumType().findValueByNumberCreatingIfUnknown( - (java.lang.Integer) result); + result = field.getEnumType().findValueByNumberCreatingIfUnknown((Integer) result); } return result; } @@ -409,25 +430,34 @@ public final class MapEntry<K, V> extends AbstractMessage { throw new RuntimeException( "There is no repeated field in a map entry message."); } - + @Override public Object getRepeatedField(FieldDescriptor field, int index) { throw new RuntimeException( "There is no repeated field in a map entry message."); } - + @Override public UnknownFieldSet getUnknownFields() { return UnknownFieldSet.getDefaultInstance(); } @Override + @SuppressWarnings("unchecked") public Builder<K, V> clone() { - if (dataBuilder == null) { - return new Builder<K, V>(metadata, data); - } else { - return new Builder<K, V>(metadata, dataBuilder.build()); - } + return new Builder(metadata, key, value, hasKey, hasValue); + } + } + + private static <V> boolean isInitialized(Metadata metadata, V value) { + if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { + return ((MessageLite) value).isInitialized(); } + return true; + } + + /** Returns the metadata only for experimental runtime. */ + final Metadata<K, V> getMetadata() { + return metadata; } } diff --git a/java/core/src/main/java/com/google/protobuf/MapEntryLite.java b/java/core/src/main/java/com/google/protobuf/MapEntryLite.java index bcffa946..dcb5dfad 100644 --- a/java/core/src/main/java/com/google/protobuf/MapEntryLite.java +++ b/java/core/src/main/java/com/google/protobuf/MapEntryLite.java @@ -31,79 +31,74 @@ package com.google.protobuf; import java.io.IOException; +import java.util.AbstractMap; +import java.util.Map; /** * Implements the lite version of map entry messages. - * + * * This class serves as an utility class to help do serialization/parsing of * map entries. It's used in generated code and also in the full version * MapEntry message. - * + * * Protobuf internal. Users shouldn't use. */ -public class MapEntryLite<K, V> extends AbstractMessageLite { - private static class Metadata<K, V> { - public final MapEntryLite<K, V> defaultInstance; +public class MapEntryLite<K, V> { + + static class Metadata<K, V> { public final WireFormat.FieldType keyType; + public final K defaultKey; public final WireFormat.FieldType valueType; - public final Parser<MapEntryLite<K, V>> parser; + public final V defaultValue; + public Metadata( - MapEntryLite<K, V> defaultInstance, - WireFormat.FieldType keyType, - WireFormat.FieldType valueType) { - this.defaultInstance = defaultInstance; + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { this.keyType = keyType; + this.defaultKey = defaultKey; this.valueType = valueType; - final Metadata<K, V> finalThis = this; - this.parser = new AbstractParser<MapEntryLite<K, V>>() { - @Override - public MapEntryLite<K, V> parsePartialFrom( - CodedInputStream input, ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { - return new MapEntryLite<K, V>(finalThis, input, extensionRegistry); - } - }; + this.defaultValue = defaultValue; } } - + private static final int KEY_FIELD_NUMBER = 1; private static final int VALUE_FIELD_NUMBER = 2; - + private final Metadata<K, V> metadata; private final K key; private final V value; - + /** Creates a default MapEntryLite message instance. */ private MapEntryLite( WireFormat.FieldType keyType, K defaultKey, WireFormat.FieldType valueType, V defaultValue) { - this.metadata = new Metadata<K, V>(this, keyType, valueType); + this.metadata = new Metadata<K, V>(keyType, defaultKey, valueType, defaultValue); this.key = defaultKey; this.value = defaultValue; } - + /** Creates a new MapEntryLite message. */ private MapEntryLite(Metadata<K, V> metadata, K key, V value) { this.metadata = metadata; this.key = key; this.value = value; } - + public K getKey() { return key; } - + public V getValue() { return value; } /** * Creates a default MapEntryLite message instance. - * + * * This method is used by generated code to create the default instance for * a map entry message. The created default instance should be used to create * new map entry messages of the same type. For each map entry message, only - * one default instance should be created. + * one default instance should be created. */ public static <K, V> MapEntryLite<K, V> newDefaultInstance( WireFormat.FieldType keyType, K defaultKey, @@ -111,80 +106,20 @@ public class MapEntryLite<K, V> extends AbstractMessageLite { return new MapEntryLite<K, V>( keyType, defaultKey, valueType, defaultValue); } - - @Override - public void writeTo(CodedOutputStream output) throws IOException { - writeField(KEY_FIELD_NUMBER, metadata.keyType, key, output); - writeField(VALUE_FIELD_NUMBER, metadata.valueType, value, output); - } - private void writeField( - int number, WireFormat.FieldType type, Object value, - CodedOutputStream output) throws IOException { - output.writeTag(number, type.getWireType()); - FieldSet.writeElementNoTag(output, type, value); + static <K, V> void writeTo(CodedOutputStream output, Metadata<K, V> metadata, K key, V value) + throws IOException { + FieldSet.writeElement(output, metadata.keyType, KEY_FIELD_NUMBER, key); + FieldSet.writeElement(output, metadata.valueType, VALUE_FIELD_NUMBER, value); } - private volatile int cachedSerializedSize = -1; - @Override - public int getSerializedSize() { - if (cachedSerializedSize != -1) { - return cachedSerializedSize; - } - int size = 0; - size += getFieldSize(KEY_FIELD_NUMBER, metadata.keyType, key); - size += getFieldSize(VALUE_FIELD_NUMBER, metadata.valueType, value); - cachedSerializedSize = size; - return size; + static <K, V> int computeSerializedSize(Metadata<K, V> metadata, K key, V value) { + return FieldSet.computeElementSize(metadata.keyType, KEY_FIELD_NUMBER, key) + + FieldSet.computeElementSize(metadata.valueType, VALUE_FIELD_NUMBER, value); } - private int getFieldSize( - int number, WireFormat.FieldType type, Object value) { - return CodedOutputStream.computeTagSize(number) - + FieldSet.computeElementSizeNoTag(type, value); - } - - /** Parsing constructor. */ - private MapEntryLite( - Metadata<K, V> metadata, - CodedInputStream input, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { - try { - K key = metadata.defaultInstance.key; - V value = metadata.defaultInstance.value; - while (true) { - int tag = input.readTag(); - if (tag == 0) { - break; - } - if (tag == WireFormat.makeTag( - KEY_FIELD_NUMBER, metadata.keyType.getWireType())) { - key = mergeField( - input, extensionRegistry, metadata.keyType, key); - } else if (tag == WireFormat.makeTag( - VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) { - value = mergeField( - input, extensionRegistry, metadata.valueType, value); - } else { - if (!input.skipField(tag)) { - break; - } - } - } - this.metadata = metadata; - this.key = key; - this.value = value; - } catch (InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (IOException e) { - throw new InvalidProtocolBufferException(e.getMessage()) - .setUnfinishedMessage(this); - } - } - @SuppressWarnings("unchecked") - private <T> T mergeField( + static <T> T parseField( CodedInputStream input, ExtensionRegistryLite extensionRegistry, WireFormat.FieldType type, T value) throws IOException { switch (type) { @@ -201,131 +136,96 @@ public class MapEntryLite<K, V> extends AbstractMessageLite { } } - @Override - public Parser<MapEntryLite<K, V>> getParserForType() { - return metadata.parser; - } - - @Override - public Builder<K, V> newBuilderForType() { - return new Builder<K, V>(metadata); - } - - @Override - public Builder<K, V> toBuilder() { - return new Builder<K, V>(metadata, key, value); + /** + * Serializes the provided key and value as though they were wrapped by a {@link MapEntryLite} + * to the output stream. This helper method avoids allocation of a {@link MapEntryLite} + * built with a key and value and is called from generated code directly. + */ + public void serializeTo(CodedOutputStream output, int fieldNumber, K key, V value) + throws IOException { + output.writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeUInt32NoTag(computeSerializedSize(metadata, key, value)); + writeTo(output, metadata, key, value); } - @Override - public MapEntryLite<K, V> getDefaultInstanceForType() { - return metadata.defaultInstance; + /** + * Computes the message size for the provided key and value as though they were wrapped + * by a {@link MapEntryLite}. This helper method avoids allocation of a {@link MapEntryLite} + * built with a key and value and is called from generated code directly. + */ + public int computeMessageSize(int fieldNumber, K key, V value) { + return CodedOutputStream.computeTagSize(fieldNumber) + + CodedOutputStream.computeLengthDelimitedFieldSize( + computeSerializedSize(metadata, key, value)); } - @Override - public boolean isInitialized() { - if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { - return ((MessageLite) value).isInitialized(); + /** + * Parses an entry off of the input as a {@link Map.Entry}. This helper requires an allocation + * so using {@link #parseInto} is preferred if possible. + */ + public Map.Entry<K, V> parseEntry(ByteString bytes, ExtensionRegistryLite extensionRegistry) + throws IOException { + return parseEntry(bytes.newCodedInput(), metadata, extensionRegistry); + } + + static <K, V> Map.Entry<K, V> parseEntry( + CodedInputStream input, Metadata<K, V> metadata, ExtensionRegistryLite extensionRegistry) + throws IOException{ + K key = metadata.defaultKey; + V value = metadata.defaultValue; + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) { + key = parseField(input, extensionRegistry, metadata.keyType, key); + } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) { + value = parseField(input, extensionRegistry, metadata.valueType, value); + } else { + if (!input.skipField(tag)) { + break; + } + } } - return true; + return new AbstractMap.SimpleImmutableEntry<K, V>(key, value); } /** - * Builder used to create {@link MapEntryLite} messages. + * Parses an entry off of the input into the map. This helper avoids allocaton of a + * {@link MapEntryLite} by parsing directly into the provided {@link MapFieldLite}. */ - public static class Builder<K, V> - extends AbstractMessageLite.Builder<Builder<K, V>> { - private final Metadata<K, V> metadata; - private K key; - private V value; - - private Builder(Metadata<K, V> metadata) { - this.metadata = metadata; - this.key = metadata.defaultInstance.key; - this.value = metadata.defaultInstance.value; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - public Builder<K, V> setKey(K key) { - this.key = key; - return this; - } - - public Builder<K, V> setValue(V value) { - this.value = value; - return this; - } - - public Builder<K, V> clearKey() { - this.key = metadata.defaultInstance.key; - return this; - } - - public Builder<K, V> clearValue() { - this.value = metadata.defaultInstance.value; - return this; - } - - @Override - public Builder<K, V> clear() { - this.key = metadata.defaultInstance.key; - this.value = metadata.defaultInstance.value; - return this; - } - - @Override - public MapEntryLite<K, V> build() { - MapEntryLite<K, V> result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); + public void parseInto( + MapFieldLite<K, V> map, CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { + int length = input.readRawVarint32(); + final int oldLimit = input.pushLimit(length); + K key = metadata.defaultKey; + V value = metadata.defaultValue; + + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; } - return result; - } - - @Override - public MapEntryLite<K, V> buildPartial() { - return new MapEntryLite<K, V>(metadata, key, value); - } - - @Override - public MessageLite getDefaultInstanceForType() { - return metadata.defaultInstance; - } - - @Override - public boolean isInitialized() { - if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { - return ((MessageLite) value).isInitialized(); + if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) { + key = parseField(input, extensionRegistry, metadata.keyType, key); + } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) { + value = parseField(input, extensionRegistry, metadata.valueType, value); + } else { + if (!input.skipField(tag)) { + break; + } } - return true; } - private Builder(Metadata<K, V> metadata, K key, V value) { - this.metadata = metadata; - this.key = key; - this.value = value; - } - - @Override - public Builder<K, V> clone() { - return new Builder<K, V>(metadata, key, value); - } + input.checkLastTagWas(0); + input.popLimit(oldLimit); + map.put(key, value); + } - @Override - public Builder<K, V> mergeFrom( - CodedInputStream input, ExtensionRegistryLite extensionRegistry) - throws IOException { - MapEntryLite<K, V> entry = - new MapEntryLite<K, V>(metadata, input, extensionRegistry); - this.key = entry.key; - this.value = entry.value; - return this; - } + /** For experimental runtime internal use only. */ + Metadata<K, V> getMetadata() { + return metadata; } } diff --git a/java/core/src/main/java/com/google/protobuf/MapField.java b/java/core/src/main/java/com/google/protobuf/MapField.java index b290993c..ad8ceb02 100644 --- a/java/core/src/main/java/com/google/protobuf/MapField.java +++ b/java/core/src/main/java/com/google/protobuf/MapField.java @@ -30,25 +30,28 @@ package com.google.protobuf; -import com.google.protobuf.MapFieldLite.MutatabilityAwareMap; +import static com.google.protobuf.Internal.checkNotNull; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * Internal representation of map fields in generated messages. - * + * * This class supports accessing the map field as a {@link Map} to be used in * generated API and also supports accessing the field as a {@link List} to be * used in reflection API. It keeps track of where the data is currently stored - * and do necessary conversions between map and list. - * + * and do necessary conversions between map and list. + * * This class is a protobuf implementation detail. Users shouldn't use this * class directly. - * + * * THREAD-SAFETY NOTE: Read-only access is thread-safe. Users can call getMap() * and getList() concurrently in multiple threads. If write-access is needed, * all access must be synchronized. @@ -56,21 +59,21 @@ import java.util.Map; public class MapField<K, V> implements MutabilityOracle { /** * Indicates where the data of this map field is currently stored. - * + * * MAP: Data is stored in mapData. * LIST: Data is stored in listData. * BOTH: mapData and listData have the same data. * * When the map field is accessed (through generated API or reflection API), * it will shift between these 3 modes: - * + * * getMap() getList() getMutableMap() getMutableList() * MAP MAP BOTH MAP LIST * LIST BOTH LIST MAP LIST * BOTH BOTH BOTH MAP LIST - * + * * As the map field changes its mode, the list/map reference returned in a - * previous method call may be invalidated. + * previous method call may be invalidated. */ private enum StorageMode {MAP, LIST, BOTH} @@ -78,38 +81,42 @@ public class MapField<K, V> implements MutabilityOracle { private volatile StorageMode mode; private MutatabilityAwareMap<K, V> mapData; private List<Message> listData; - + // Convert between a map entry Message and a key-value pair. private static interface Converter<K, V> { Message convertKeyAndValueToMessage(K key, V value); void convertMessageToKeyAndValue(Message message, Map<K, V> map); - + Message getMessageDefaultInstance(); } - + private static class ImmutableMessageConverter<K, V> implements Converter<K, V> { private final MapEntry<K, V> defaultEntry; public ImmutableMessageConverter(MapEntry<K, V> defaultEntry) { this.defaultEntry = defaultEntry; } - + + @Override public Message convertKeyAndValueToMessage(K key, V value) { return defaultEntry.newBuilderForType().setKey(key).setValue(value).buildPartial(); } - + + @Override + @SuppressWarnings("unchecked") public void convertMessageToKeyAndValue(Message message, Map<K, V> map) { MapEntry<K, V> entry = (MapEntry<K, V>) message; map.put(entry.getKey(), entry.getValue()); } + @Override public Message getMessageDefaultInstance() { return defaultEntry; } } - + private final Converter<K, V> converter; - + private MapField( Converter<K, V> converter, StorageMode mode, @@ -120,34 +127,34 @@ public class MapField<K, V> implements MutabilityOracle { this.mapData = new MutatabilityAwareMap<K, V>(this, mapData); this.listData = null; } - + private MapField( MapEntry<K, V> defaultEntry, StorageMode mode, Map<K, V> mapData) { this(new ImmutableMessageConverter<K, V>(defaultEntry), mode, mapData); } - - + + /** Returns an immutable empty MapField. */ public static <K, V> MapField<K, V> emptyMapField( MapEntry<K, V> defaultEntry) { return new MapField<K, V>( defaultEntry, StorageMode.MAP, Collections.<K, V>emptyMap()); } - - + + /** Creates a new mutable empty MapField. */ public static <K, V> MapField<K, V> newMapField(MapEntry<K, V> defaultEntry) { return new MapField<K, V>( defaultEntry, StorageMode.MAP, new LinkedHashMap<K, V>()); } - - + + private Message convertKeyAndValueToMessage(K key, V value) { return converter.convertKeyAndValueToMessage(key, value); } - + @SuppressWarnings("unchecked") private void convertMessageToKeyAndValue(Message message, Map<K, V> map) { converter.convertMessageToKeyAndValue(message, map); @@ -170,7 +177,7 @@ public class MapField<K, V> implements MutabilityOracle { } return new MutatabilityAwareMap<K, V>(this, mapData); } - + /** Returns the content of this MapField as a read-only Map. */ public Map<K, V> getMap() { if (mode == StorageMode.LIST) { @@ -183,7 +190,7 @@ public class MapField<K, V> implements MutabilityOracle { } return Collections.unmodifiableMap(mapData); } - + /** Gets a mutable Map view of this MapField. */ public Map<K, V> getMutableMap() { if (mode != StorageMode.MAP) { @@ -191,20 +198,20 @@ public class MapField<K, V> implements MutabilityOracle { mapData = convertListToMap(listData); } listData = null; - mode = StorageMode.MAP; + mode = StorageMode.MAP; } return mapData; } - + public void mergeFrom(MapField<K, V> other) { getMutableMap().putAll(MapFieldLite.copy(other.getMap())); } - + public void clear() { mapData = new MutatabilityAwareMap<K, V>(this, new LinkedHashMap<K, V>()); mode = StorageMode.MAP; } - + @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { @@ -214,18 +221,18 @@ public class MapField<K, V> implements MutabilityOracle { MapField<K, V> other = (MapField<K, V>) object; return MapFieldLite.<K, V>equals(getMap(), other.getMap()); } - + @Override public int hashCode() { return MapFieldLite.<K, V>calculateHashCodeForMap(getMap()); } - + /** Returns a deep copy of this MapField. */ public MapField<K, V> copy() { return new MapField<K, V>( converter, StorageMode.MAP, MapFieldLite.copy(getMap())); } - + /** Gets the content of this MapField as a read-only List. */ List<Message> getList() { if (mode == StorageMode.MAP) { @@ -238,7 +245,7 @@ public class MapField<K, V> implements MutabilityOracle { } return Collections.unmodifiableList(listData); } - + /** Gets a mutable List view of this MapField. */ List<Message> getMutableList() { if (mode != StorageMode.LIST) { @@ -250,7 +257,7 @@ public class MapField<K, V> implements MutabilityOracle { } return listData; } - + /** * Gets the default instance of the message stored in the list view of this * map field. @@ -258,7 +265,7 @@ public class MapField<K, V> implements MutabilityOracle { Message getMapEntryMessageDefaultInstance() { return converter.getMessageDefaultInstance(); } - + /** * Makes this list immutable. All subsequent modifications will throw an * {@link UnsupportedOperationException}. @@ -266,14 +273,14 @@ public class MapField<K, V> implements MutabilityOracle { public void makeImmutable() { isMutable = false; } - + /** * Returns whether this field can be modified. */ public boolean isMutable() { return isMutable; } - + /* (non-Javadoc) * @see com.google.protobuf.MutabilityOracle#ensureMutable() */ @@ -283,4 +290,344 @@ public class MapField<K, V> implements MutabilityOracle { throw new UnsupportedOperationException(); } } + + /** + * An internal map that checks for mutability before delegating. + */ + private static class MutatabilityAwareMap<K, V> implements Map<K, V> { + private final MutabilityOracle mutabilityOracle; + private final Map<K, V> delegate; + + MutatabilityAwareMap(MutabilityOracle mutabilityOracle, Map<K, V> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public V get(Object key) { + return delegate.get(key); + } + + @Override + public V put(K key, V value) { + mutabilityOracle.ensureMutable(); + checkNotNull(key); + checkNotNull(value); + return delegate.put(key, value); + } + + @Override + public V remove(Object key) { + mutabilityOracle.ensureMutable(); + return delegate.remove(key); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + mutabilityOracle.ensureMutable(); + for (K key : m.keySet()) { + checkNotNull(key); + checkNotNull(m.get(key)); + } + delegate.putAll(m); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public Set<K> keySet() { + return new MutatabilityAwareSet<K>(mutabilityOracle, delegate.keySet()); + } + + @Override + public Collection<V> values() { + return new MutatabilityAwareCollection<V>(mutabilityOracle, delegate.values()); + } + + @Override + public Set<java.util.Map.Entry<K, V>> entrySet() { + return new MutatabilityAwareSet<Entry<K, V>>(mutabilityOracle, delegate.entrySet()); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + /** + * An internal collection that checks for mutability before delegating. + */ + private static class MutatabilityAwareCollection<E> implements Collection<E> { + private final MutabilityOracle mutabilityOracle; + private final Collection<E> delegate; + + MutatabilityAwareCollection(MutabilityOracle mutabilityOracle, Collection<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public Iterator<E> iterator() { + return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); + } + + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public boolean add(E e) { + // Unsupported operation in the delegate. + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + mutabilityOracle.ensureMutable(); + return delegate.remove(o); + } + + @Override + public boolean containsAll(Collection<?> c) { + return delegate.containsAll(c); + } + + @Override + public boolean addAll(Collection<? extends E> c) { + // Unsupported operation in the delegate. + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.removeAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.retainAll(c); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + + /** + * An internal set that checks for mutability before delegating. + */ + private static class MutatabilityAwareSet<E> implements Set<E> { + private final MutabilityOracle mutabilityOracle; + private final Set<E> delegate; + + MutatabilityAwareSet(MutabilityOracle mutabilityOracle, Set<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public Iterator<E> iterator() { + return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); + } + + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public boolean add(E e) { + mutabilityOracle.ensureMutable(); + return delegate.add(e); + } + + @Override + public boolean remove(Object o) { + mutabilityOracle.ensureMutable(); + return delegate.remove(o); + } + + @Override + public boolean containsAll(Collection<?> c) { + return delegate.containsAll(c); + } + + @Override + public boolean addAll(Collection<? extends E> c) { + mutabilityOracle.ensureMutable(); + return delegate.addAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.retainAll(c); + } + + @Override + public boolean removeAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.removeAll(c); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + + /** + * An internal iterator that checks for mutability before delegating. + */ + private static class MutatabilityAwareIterator<E> implements Iterator<E> { + private final MutabilityOracle mutabilityOracle; + private final Iterator<E> delegate; + + MutatabilityAwareIterator(MutabilityOracle mutabilityOracle, Iterator<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public E next() { + return delegate.next(); + } + + @Override + public void remove() { + mutabilityOracle.ensureMutable(); + delegate.remove(); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + } } diff --git a/java/core/src/main/java/com/google/protobuf/MapFieldLite.java b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java index 960b6339..a8b3dd88 100644 --- a/java/core/src/main/java/com/google/protobuf/MapFieldLite.java +++ b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java @@ -30,74 +30,100 @@ package com.google.protobuf; -import com.google.protobuf.Internal.EnumLite; +import static com.google.protobuf.Internal.checkNotNull; +import com.google.protobuf.Internal.EnumLite; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * Internal representation of map fields in generated lite-runtime messages. - * + * * This class is a protobuf implementation detail. Users shouldn't use this * class directly. */ -public final class MapFieldLite<K, V> implements MutabilityOracle { - private MutatabilityAwareMap<K, V> mapData; +public final class MapFieldLite<K, V> extends LinkedHashMap<K, V> { + private boolean isMutable; - + + private MapFieldLite() { + this.isMutable = true; + } + private MapFieldLite(Map<K, V> mapData) { - this.mapData = new MutatabilityAwareMap<K, V>(this, mapData); + super(mapData); this.isMutable = true; } - + @SuppressWarnings({"rawtypes", "unchecked"}) - private static final MapFieldLite EMPTY_MAP_FIELD = - new MapFieldLite(Collections.emptyMap()); + private static final MapFieldLite EMPTY_MAP_FIELD = new MapFieldLite(); static { EMPTY_MAP_FIELD.makeImmutable(); } - + /** Returns an singleton immutable empty MapFieldLite instance. */ @SuppressWarnings({"unchecked", "cast"}) public static <K, V> MapFieldLite<K, V> emptyMapField() { return (MapFieldLite<K, V>) EMPTY_MAP_FIELD; } - - /** Creates a new MapFieldLite instance. */ - public static <K, V> MapFieldLite<K, V> newMapField() { - return new MapFieldLite<K, V>(new LinkedHashMap<K, V>()); + + public void mergeFrom(MapFieldLite<K, V> other) { + ensureMutable(); + if (!other.isEmpty()) { + putAll(other); + } } - - /** Gets the content of this MapField as a read-only Map. */ - public Map<K, V> getMap() { - return Collections.unmodifiableMap(mapData); + + @SuppressWarnings({"unchecked", "cast"}) + @Override public Set<Map.Entry<K, V>> entrySet() { + return isEmpty() ? Collections.<Map.Entry<K, V>>emptySet() : super.entrySet(); } - - /** Gets a mutable Map view of this MapField. */ - public Map<K, V> getMutableMap() { - return mapData; + + @Override public void clear() { + ensureMutable(); + super.clear(); } - - public void mergeFrom(MapFieldLite<K, V> other) { - mapData.putAll(copy(other.mapData)); + + @Override public V put(K key, V value) { + ensureMutable(); + checkNotNull(key); + + checkNotNull(value); + return super.put(key, value); + } + + public V put(Map.Entry<K, V> entry) { + return put(entry.getKey(), entry.getValue()); + } + + @Override public void putAll(Map<? extends K, ? extends V> m) { + ensureMutable(); + checkForNullKeysAndValues(m); + super.putAll(m); + } + + @Override public V remove(Object key) { + ensureMutable(); + return super.remove(key); } - - public void clear() { - mapData.clear(); + + private static void checkForNullKeysAndValues(Map<?, ?> m) { + for (Object key : m.keySet()) { + checkNotNull(key); + checkNotNull(m.get(key)); + } } - + private static boolean equals(Object a, Object b) { if (a instanceof byte[] && b instanceof byte[]) { return Arrays.equals((byte[]) a, (byte[]) b); } return a.equals(b); } - + /** * Checks whether two {@link Map}s are equal. We don't use the default equals * method of {@link Map} because it compares by identity not by content for @@ -120,20 +146,16 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { } return true; } - + /** * Checks whether two map fields are equal. */ @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { - if (!(object instanceof MapFieldLite)) { - return false; - } - MapFieldLite<K, V> other = (MapFieldLite<K, V>) object; - return equals(mapData, other.mapData); + return (object instanceof Map) && equals(this, (Map<K, V>) object); } - + private static int calculateHashCodeForObject(Object a) { if (a instanceof byte[]) { return Internal.hashCode((byte[]) a); @@ -156,14 +178,14 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { result += calculateHashCodeForObject(entry.getKey()) ^ calculateHashCodeForObject(entry.getValue()); } - return result; + return result; } - + @Override public int hashCode() { - return calculateHashCodeForMap(mapData); + return calculateHashCodeForMap(this); } - + private static Object copy(Object object) { if (object instanceof byte[]) { byte[] data = (byte[]) object; @@ -171,7 +193,7 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { } return object; } - + /** * Makes a deep copy of a {@link Map}. Immutable objects in the map will be * shared (e.g., integers, strings, immutable messages) and mutable ones will @@ -185,12 +207,12 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { } return result; } - + /** Returns a deep copy of this map field. */ - public MapFieldLite<K, V> copy() { - return new MapFieldLite<K, V>(copy(mapData)); + public MapFieldLite<K, V> mutableCopy() { + return isEmpty() ? new MapFieldLite<K, V>() : new MapFieldLite<K, V>(this); } - + /** * Makes this field immutable. All subsequent modifications will throw an * {@link UnsupportedOperationException}. @@ -198,352 +220,17 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { public void makeImmutable() { isMutable = false; } - + /** * Returns whether this field can be modified. */ public boolean isMutable() { return isMutable; } - - @Override - public void ensureMutable() { - if (!isMutable()) { - throw new UnsupportedOperationException(); - } - } - - /** - * An internal map that checks for mutability before delegating. - */ - static class MutatabilityAwareMap<K, V> implements Map<K, V> { - private final MutabilityOracle mutabilityOracle; - private final Map<K, V> delegate; - - MutatabilityAwareMap(MutabilityOracle mutabilityOracle, Map<K, V> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return delegate.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return delegate.containsValue(value); - } - - @Override - public V get(Object key) { - return delegate.get(key); - } - - @Override - public V put(K key, V value) { - mutabilityOracle.ensureMutable(); - return delegate.put(key, value); - } - - @Override - public V remove(Object key) { - mutabilityOracle.ensureMutable(); - return delegate.remove(key); - } - - @Override - public void putAll(Map<? extends K, ? extends V> m) { - mutabilityOracle.ensureMutable(); - delegate.putAll(m); - } - - @Override - public void clear() { - mutabilityOracle.ensureMutable(); - delegate.clear(); - } - - @Override - public Set<K> keySet() { - return new MutatabilityAwareSet<K>(mutabilityOracle, delegate.keySet()); - } - - @Override - public Collection<V> values() { - return new MutatabilityAwareCollection<V>(mutabilityOracle, delegate.values()); - } - - @Override - public Set<java.util.Map.Entry<K, V>> entrySet() { - return new MutatabilityAwareSet<Entry<K, V>>(mutabilityOracle, delegate.entrySet()); - } - - @Override - public boolean equals(Object o) { - return delegate.equals(o); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } - } - - /** - * An internal collection that checks for mutability before delegating. - */ - private static class MutatabilityAwareCollection<E> implements Collection<E> { - private final MutabilityOracle mutabilityOracle; - private final Collection<E> delegate; - - MutatabilityAwareCollection(MutabilityOracle mutabilityOracle, Collection<E> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator<E> iterator() { - return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public <T> T[] toArray(T[] a) { - return delegate.toArray(a); - } - - @Override - public boolean add(E e) { - // Unsupported operation in the delegate. - throw new UnsupportedOperationException(); - } - - @Override - public boolean remove(Object o) { - mutabilityOracle.ensureMutable(); - return delegate.remove(o); - } - - @Override - public boolean containsAll(Collection<?> c) { - return delegate.containsAll(c); - } - - @Override - public boolean addAll(Collection<? extends E> c) { - // Unsupported operation in the delegate. + private void ensureMutable() { + if (!isMutable()) { throw new UnsupportedOperationException(); } - - @Override - public boolean removeAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.removeAll(c); - } - - @Override - public boolean retainAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.retainAll(c); - } - - @Override - public void clear() { - mutabilityOracle.ensureMutable(); - delegate.clear(); - } - - @Override - public boolean equals(Object o) { - return delegate.equals(o); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } - } - - /** - * An internal set that checks for mutability before delegating. - */ - private static class MutatabilityAwareSet<E> implements Set<E> { - private final MutabilityOracle mutabilityOracle; - private final Set<E> delegate; - - MutatabilityAwareSet(MutabilityOracle mutabilityOracle, Set<E> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator<E> iterator() { - return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public <T> T[] toArray(T[] a) { - return delegate.toArray(a); - } - - @Override - public boolean add(E e) { - mutabilityOracle.ensureMutable(); - return delegate.add(e); - } - - @Override - public boolean remove(Object o) { - mutabilityOracle.ensureMutable(); - return delegate.remove(o); - } - - @Override - public boolean containsAll(Collection<?> c) { - return delegate.containsAll(c); - } - - @Override - public boolean addAll(Collection<? extends E> c) { - mutabilityOracle.ensureMutable(); - return delegate.addAll(c); - } - - @Override - public boolean retainAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.retainAll(c); - } - - @Override - public boolean removeAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.removeAll(c); - } - - @Override - public void clear() { - mutabilityOracle.ensureMutable(); - delegate.clear(); - } - - @Override - public boolean equals(Object o) { - return delegate.equals(o); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } - } - - /** - * An internal iterator that checks for mutability before delegating. - */ - private static class MutatabilityAwareIterator<E> implements Iterator<E> { - private final MutabilityOracle mutabilityOracle; - private final Iterator<E> delegate; - - MutatabilityAwareIterator(MutabilityOracle mutabilityOracle, Iterator<E> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - - @Override - public boolean hasNext() { - return delegate.hasNext(); - } - - @Override - public E next() { - return delegate.next(); - } - - @Override - public void remove() { - mutabilityOracle.ensureMutable(); - delegate.remove(); - } - - @Override - public boolean equals(Object obj) { - return delegate.equals(obj); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } } } diff --git a/java/core/src/main/java/com/google/protobuf/Message.java b/java/core/src/main/java/com/google/protobuf/Message.java index 9516d71f..0770d417 100644 --- a/java/core/src/main/java/com/google/protobuf/Message.java +++ b/java/core/src/main/java/com/google/protobuf/Message.java @@ -51,6 +51,7 @@ import java.util.Map; public interface Message extends MessageLite, MessageOrBuilder { // (From MessageLite, re-declared here only for return type covariance.) + @Override Parser<? extends Message> getParserForType(); @@ -97,7 +98,10 @@ public interface Message extends MessageLite, MessageOrBuilder { // Builders // (From MessageLite, re-declared here only for return type covariance.) + @Override Builder newBuilderForType(); + + @Override Builder toBuilder(); /** @@ -106,6 +110,7 @@ public interface Message extends MessageLite, MessageOrBuilder { interface Builder extends MessageLite.Builder, MessageOrBuilder { // (From MessageLite.Builder, re-declared here only for return type // covariance.) + @Override Builder clear(); /** @@ -120,7 +125,7 @@ public interface Message extends MessageLite, MessageOrBuilder { * it is merged into the corresponding sub-message of this message * using the same merging rules.<br> * * For repeated fields, the elements in {@code other} are concatenated - * with the elements in this message. + * with the elements in this message.<br> * * For oneof groups, if the other message has one of the fields set, * the group of this message is cleared and replaced by the field * of the other message, so that the oneof constraint is preserved. @@ -131,18 +136,27 @@ public interface Message extends MessageLite, MessageOrBuilder { // (From MessageLite.Builder, re-declared here only for return type // covariance.) + @Override Message build(); + + @Override Message buildPartial(); + + @Override Builder clone(); + + @Override Builder mergeFrom(CodedInputStream input) throws IOException; - Builder mergeFrom(CodedInputStream input, - ExtensionRegistryLite extensionRegistry) - throws IOException; + + @Override + Builder mergeFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException; /** * Get the message's type's descriptor. * See {@link Message#getDescriptorForType()}. */ + @Override Descriptors.Descriptor getDescriptorForType(); /** @@ -240,27 +254,39 @@ public interface Message extends MessageLite, MessageOrBuilder { // (From MessageLite.Builder, re-declared here only for return type // covariance.) + @Override Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException; - Builder mergeFrom(ByteString data, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + @Override Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException; - Builder mergeFrom(byte[] data, int off, int len) - throws InvalidProtocolBufferException; - Builder mergeFrom(byte[] data, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException; - Builder mergeFrom(byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(byte[] data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + @Override Builder mergeFrom(InputStream input) throws IOException; - Builder mergeFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) - throws IOException; - boolean mergeDelimitedFrom(InputStream input) - throws IOException; - boolean mergeDelimitedFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) - throws IOException; + + @Override + Builder mergeFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException; + + @Override + boolean mergeDelimitedFrom(InputStream input) throws IOException; + + @Override + boolean mergeDelimitedFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException; } } diff --git a/java/core/src/main/java/com/google/protobuf/MessageLite.java b/java/core/src/main/java/com/google/protobuf/MessageLite.java index 798b7943..88f531df 100644 --- a/java/core/src/main/java/com/google/protobuf/MessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/MessageLite.java @@ -295,6 +295,27 @@ public interface MessageLite extends MessageLiteOrBuilder { Builder mergeFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws IOException; + + /** + * Merge {@code other} into the message being built. {@code other} must + * have the exact same type as {@code this} (i.e. + * {@code getClass().equals(getDefaultInstanceForType().getClass())}). + * + * Merging occurs as follows. For each field:<br> + * * For singular primitive fields, if the field is set in {@code other}, + * then {@code other}'s value overwrites the value in this message.<br> + * * For singular message fields, if the field is set in {@code other}, + * it is merged into the corresponding sub-message of this message + * using the same merging rules.<br> + * * For repeated fields, the elements in {@code other} are concatenated + * with the elements in this message. + * * For oneof groups, if the other message has one of the fields set, + * the group of this message is cleared and replaced by the field + * of the other message, so that the oneof constraint is preserved. + * + * This is equivalent to the {@code Message::MergeFrom} method in C++. + */ + Builder mergeFrom(MessageLite other); /** * Like {@link #mergeFrom(InputStream)}, but does not read until EOF. diff --git a/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java b/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java index e69de29b..8e265935 100644 --- a/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java +++ b/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java @@ -0,0 +1,281 @@ +// 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.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** Helps generate {@link String} representations of {@link MessageLite} protos. */ +final class MessageLiteToString { + + private static final String LIST_SUFFIX = "List"; + private static final String BUILDER_LIST_SUFFIX = "OrBuilderList"; + private static final String MAP_SUFFIX = "Map"; + private static final String BYTES_SUFFIX = "Bytes"; + + /** + * Returns a {@link String} representation of the {@link MessageLite} object. The first line of + * the {@code String} representation representation includes a comment string to uniquely identify + * the object instance. This acts as an indicator that this should not be relied on for + * comparisons. + * + * <p>For use by generated code only. + */ + static String toString(MessageLite messageLite, String commentString) { + StringBuilder buffer = new StringBuilder(); + buffer.append("# ").append(commentString); + reflectivePrintWithIndent(messageLite, buffer, 0); + return buffer.toString(); + } + + /** + * Reflectively prints the {@link MessageLite} to the buffer at given {@code indent} level. + * + * @param buffer the buffer to write to + * @param indent the number of spaces to indent the proto by + */ + private static void reflectivePrintWithIndent( + MessageLite messageLite, StringBuilder buffer, int indent) { + // Build a map of method name to method. We're looking for methods like getFoo(), hasFoo(), + // getFooList() and getFooMap() which might be useful for building an object's string + // representation. + Map<String, Method> nameToNoArgMethod = new HashMap<String, Method>(); + Map<String, Method> nameToMethod = new HashMap<String, Method>(); + Set<String> getters = new TreeSet<String>(); + for (Method method : messageLite.getClass().getDeclaredMethods()) { + nameToMethod.put(method.getName(), method); + if (method.getParameterTypes().length == 0) { + nameToNoArgMethod.put(method.getName(), method); + + if (method.getName().startsWith("get")) { + getters.add(method.getName()); + } + } + } + + for (String getter : getters) { + String suffix = getter.replaceFirst("get", ""); + if (suffix.endsWith(LIST_SUFFIX) + && !suffix.endsWith(BUILDER_LIST_SUFFIX) + // Sometimes people have fields named 'list' that aren't repeated. + && !suffix.equals(LIST_SUFFIX)) { + String camelCase = + suffix.substring(0, 1).toLowerCase() + + suffix.substring(1, suffix.length() - LIST_SUFFIX.length()); + // Try to reflectively get the value and toString() the field as if it were repeated. This + // only works if the method names have not been proguarded out or renamed. + Method listMethod = nameToNoArgMethod.get(getter); + if (listMethod != null && listMethod.getReturnType().equals(List.class)) { + printField( + buffer, + indent, + camelCaseToSnakeCase(camelCase), + GeneratedMessageLite.invokeOrDie(listMethod, messageLite)); + continue; + } + } + if (suffix.endsWith(MAP_SUFFIX) + // Sometimes people have fields named 'map' that aren't maps. + && !suffix.equals(MAP_SUFFIX)) { + String camelCase = + suffix.substring(0, 1).toLowerCase() + + suffix.substring(1, suffix.length() - MAP_SUFFIX.length()); + // Try to reflectively get the value and toString() the field as if it were a map. This only + // works if the method names have not been proguarded out or renamed. + Method mapMethod = nameToNoArgMethod.get(getter); + if (mapMethod != null + && mapMethod.getReturnType().equals(Map.class) + // Skip the deprecated getter method with no prefix "Map" when the field name ends with + // "map". + && !mapMethod.isAnnotationPresent(Deprecated.class) + // Skip the internal mutable getter method. + && Modifier.isPublic(mapMethod.getModifiers())) { + printField( + buffer, + indent, + camelCaseToSnakeCase(camelCase), + GeneratedMessageLite.invokeOrDie(mapMethod, messageLite)); + continue; + } + } + + Method setter = nameToMethod.get("set" + suffix); + if (setter == null) { + continue; + } + if (suffix.endsWith(BYTES_SUFFIX) + && nameToNoArgMethod.containsKey( + "get" + suffix.substring(0, suffix.length() - "Bytes".length()))) { + // Heuristic to skip bytes based accessors for string fields. + continue; + } + + String camelCase = suffix.substring(0, 1).toLowerCase() + suffix.substring(1); + + // Try to reflectively get the value and toString() the field as if it were optional. This + // only works if the method names have not been proguarded out or renamed. + Method getMethod = nameToNoArgMethod.get("get" + suffix); + Method hasMethod = nameToNoArgMethod.get("has" + suffix); + // TODO(dweis): Fix proto3 semantics. + if (getMethod != null) { + Object value = GeneratedMessageLite.invokeOrDie(getMethod, messageLite); + final boolean hasValue = + hasMethod == null + ? !isDefaultValue(value) + : (Boolean) GeneratedMessageLite.invokeOrDie(hasMethod, messageLite); + // TODO(dweis): This doesn't stop printing oneof case twice: value and enum style. + if (hasValue) { + printField(buffer, indent, camelCaseToSnakeCase(camelCase), value); + } + continue; + } + } + + if (messageLite instanceof GeneratedMessageLite.ExtendableMessage) { + Iterator<Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object>> iter = + ((GeneratedMessageLite.ExtendableMessage<?, ?>) messageLite).extensions.iterator(); + while (iter.hasNext()) { + Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object> entry = iter.next(); + printField(buffer, indent, "[" + entry.getKey().getNumber() + "]", entry.getValue()); + } + } + + if (((GeneratedMessageLite<?, ?>) messageLite).unknownFields != null) { + ((GeneratedMessageLite<?, ?>) messageLite).unknownFields.printWithIndent(buffer, indent); + } + } + + private static boolean isDefaultValue(Object o) { + if (o instanceof Boolean) { + return !((Boolean) o); + } + if (o instanceof Integer) { + return ((Integer) o) == 0; + } + if (o instanceof Float) { + return ((Float) o) == 0f; + } + if (o instanceof Double) { + return ((Double) o) == 0d; + } + if (o instanceof String) { + return o.equals(""); + } + if (o instanceof ByteString) { + return o.equals(ByteString.EMPTY); + } + if (o instanceof MessageLite) { // Can happen in oneofs. + return o == ((MessageLite) o).getDefaultInstanceForType(); + } + if (o instanceof java.lang.Enum<?>) { // Catches oneof enums. + return ((java.lang.Enum<?>) o).ordinal() == 0; + } + + return false; + } + + /** + * Formats a text proto field. + * + * <p>For use by generated code only. + * + * @param buffer the buffer to write to + * @param indent the number of spaces the proto should be indented by + * @param name the field name (in lower underscore case) + * @param object the object value of the field + */ + static final void printField(StringBuilder buffer, int indent, String name, Object object) { + if (object instanceof List<?>) { + List<?> list = (List<?>) object; + for (Object entry : list) { + printField(buffer, indent, name, entry); + } + return; + } + if (object instanceof Map<?, ?>) { + Map<?, ?> map = (Map<?, ?>) object; + for (Map.Entry<?, ?> entry : map.entrySet()) { + printField(buffer, indent, name, entry); + } + return; + } + + buffer.append('\n'); + for (int i = 0; i < indent; i++) { + buffer.append(' '); + } + buffer.append(name); + + if (object instanceof String) { + buffer.append(": \"").append(TextFormatEscaper.escapeText((String) object)).append('"'); + } else if (object instanceof ByteString) { + buffer.append(": \"").append(TextFormatEscaper.escapeBytes((ByteString) object)).append('"'); + } else if (object instanceof GeneratedMessageLite) { + buffer.append(" {"); + reflectivePrintWithIndent((GeneratedMessageLite<?, ?>) object, buffer, indent + 2); + buffer.append("\n"); + for (int i = 0; i < indent; i++) { + buffer.append(' '); + } + buffer.append("}"); + } else if (object instanceof Map.Entry<?, ?>) { + buffer.append(" {"); + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object; + printField(buffer, indent + 2, "key", entry.getKey()); + printField(buffer, indent + 2, "value", entry.getValue()); + buffer.append("\n"); + for (int i = 0; i < indent; i++) { + buffer.append(' '); + } + buffer.append("}"); + } else { + buffer.append(": ").append(object.toString()); + } + } + + private static final String camelCaseToSnakeCase(String camelCase) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < camelCase.length(); i++) { + char ch = camelCase.charAt(i); + if (Character.isUpperCase(ch)) { + builder.append("_"); + } + builder.append(Character.toLowerCase(ch)); + } + return builder.toString(); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java index f0fc4859..5e7d7821 100644 --- a/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java +++ b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java @@ -42,7 +42,7 @@ import java.util.Map; public interface MessageOrBuilder extends MessageLiteOrBuilder { // (From MessageLite, re-declared here only for return type covariance.) - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override Message getDefaultInstanceForType(); /** diff --git a/java/core/src/main/java/com/google/protobuf/MessageReflection.java b/java/core/src/main/java/com/google/protobuf/MessageReflection.java index de4bfd3e..69ad7ddf 100644 --- a/java/core/src/main/java/com/google/protobuf/MessageReflection.java +++ b/java/core/src/main/java/com/google/protobuf/MessageReflection.java @@ -31,7 +31,6 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.FieldDescriptor; - import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -370,6 +369,7 @@ class MessageReflection { private final Message.Builder builder; + @Override public Descriptors.Descriptor getDescriptorForType() { return builder.getDescriptorForType(); } @@ -378,6 +378,7 @@ class MessageReflection { this.builder = builder; } + @Override public Object getField(Descriptors.FieldDescriptor field) { return builder.getField(field); } @@ -387,25 +388,27 @@ class MessageReflection { return builder.hasField(field); } - public MergeTarget setField(Descriptors.FieldDescriptor field, - Object value) { + @Override + public MergeTarget setField(Descriptors.FieldDescriptor field, Object value) { builder.setField(field, value); return this; } + @Override public MergeTarget clearField(Descriptors.FieldDescriptor field) { builder.clearField(field); return this; } + @Override public MergeTarget setRepeatedField( Descriptors.FieldDescriptor field, int index, Object value) { builder.setRepeatedField(field, index, value); return this; } - public MergeTarget addRepeatedField( - Descriptors.FieldDescriptor field, Object value) { + @Override + public MergeTarget addRepeatedField(Descriptors.FieldDescriptor field, Object value) { builder.addRepeatedField(field, value); return this; } @@ -426,25 +429,30 @@ class MessageReflection { return builder.getOneofFieldDescriptor(oneof); } + @Override public ContainerType getContainerType() { return ContainerType.MESSAGE; } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByName( ExtensionRegistry registry, String name) { return registry.findImmutableExtensionByName(name); } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByNumber( - ExtensionRegistry registry, Descriptors.Descriptor containingType, - int fieldNumber) { + ExtensionRegistry registry, Descriptors.Descriptor containingType, int fieldNumber) { return registry.findImmutableExtensionByNumber(containingType, fieldNumber); } - public Object parseGroup(CodedInputStream input, + @Override + public Object parseGroup( + CodedInputStream input, ExtensionRegistryLite extensionRegistry, - Descriptors.FieldDescriptor field, Message defaultInstance) + Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { Message.Builder subBuilder; // When default instance is not null. The field is an extension field. @@ -463,9 +471,12 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessage(CodedInputStream input, + @Override + public Object parseMessage( + CodedInputStream input, ExtensionRegistryLite extensionRegistry, - Descriptors.FieldDescriptor field, Message defaultInstance) + Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { Message.Builder subBuilder; // When default instance is not null. The field is an extension field. @@ -484,9 +495,12 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessageFromBytes(ByteString bytes, + @Override + public Object parseMessageFromBytes( + ByteString bytes, ExtensionRegistryLite extensionRegistry, - Descriptors.FieldDescriptor field, Message defaultInstance) + Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { Message.Builder subBuilder; // When default instance is not null. The field is an extension field. @@ -505,8 +519,9 @@ class MessageReflection { return subBuilder.buildPartial(); } - public MergeTarget newMergeTargetForField(Descriptors.FieldDescriptor field, - Message defaultInstance) { + @Override + public MergeTarget newMergeTargetForField( + Descriptors.FieldDescriptor field, Message defaultInstance) { if (defaultInstance != null) { return new BuilderAdapter( defaultInstance.newBuilderForType()); @@ -515,8 +530,8 @@ class MessageReflection { } } - public WireFormat.Utf8Validation - getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + @Override + public WireFormat.Utf8Validation getUtf8Validation(Descriptors.FieldDescriptor descriptor) { if (descriptor.needsUtf8Check()) { return WireFormat.Utf8Validation.STRICT; } @@ -528,6 +543,7 @@ class MessageReflection { return WireFormat.Utf8Validation.LOOSE; } + @Override public Object finish() { return builder.buildPartial(); } @@ -542,38 +558,43 @@ class MessageReflection { this.extensions = extensions; } + @Override public Descriptors.Descriptor getDescriptorForType() { throw new UnsupportedOperationException( "getDescriptorForType() called on FieldSet object"); } + @Override public Object getField(Descriptors.FieldDescriptor field) { return extensions.getField(field); } + @Override public boolean hasField(Descriptors.FieldDescriptor field) { return extensions.hasField(field); } - public MergeTarget setField(Descriptors.FieldDescriptor field, - Object value) { + @Override + public MergeTarget setField(Descriptors.FieldDescriptor field, Object value) { extensions.setField(field, value); return this; } + @Override public MergeTarget clearField(Descriptors.FieldDescriptor field) { extensions.clearField(field); return this; } + @Override public MergeTarget setRepeatedField( Descriptors.FieldDescriptor field, int index, Object value) { extensions.setRepeatedField(field, index, value); return this; } - public MergeTarget addRepeatedField( - Descriptors.FieldDescriptor field, Object value) { + @Override + public MergeTarget addRepeatedField(Descriptors.FieldDescriptor field, Object value) { extensions.addRepeatedField(field, value); return this; } @@ -594,25 +615,31 @@ class MessageReflection { return null; } + @Override public ContainerType getContainerType() { return ContainerType.EXTENSION_SET; } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByName( ExtensionRegistry registry, String name) { return registry.findImmutableExtensionByName(name); } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByNumber( - ExtensionRegistry registry, Descriptors.Descriptor containingType, - int fieldNumber) { + ExtensionRegistry registry, Descriptors.Descriptor containingType, int fieldNumber) { return registry.findImmutableExtensionByNumber(containingType, fieldNumber); } - public Object parseGroup(CodedInputStream input, - ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, - Message defaultInstance) throws IOException { + @Override + public Object parseGroup( + CodedInputStream input, + ExtensionRegistryLite registry, + Descriptors.FieldDescriptor field, + Message defaultInstance) + throws IOException { Message.Builder subBuilder = defaultInstance.newBuilderForType(); if (!field.isRepeated()) { @@ -625,9 +652,13 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessage(CodedInputStream input, - ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, - Message defaultInstance) throws IOException { + @Override + public Object parseMessage( + CodedInputStream input, + ExtensionRegistryLite registry, + Descriptors.FieldDescriptor field, + Message defaultInstance) + throws IOException { Message.Builder subBuilder = defaultInstance.newBuilderForType(); if (!field.isRepeated()) { @@ -640,9 +671,13 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessageFromBytes(ByteString bytes, - ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, - Message defaultInstance) throws IOException { + @Override + public Object parseMessageFromBytes( + ByteString bytes, + ExtensionRegistryLite registry, + Descriptors.FieldDescriptor field, + Message defaultInstance) + throws IOException { Message.Builder subBuilder = defaultInstance.newBuilderForType(); if (!field.isRepeated()) { Message originalMessage = (Message) getField(field); @@ -654,14 +689,15 @@ class MessageReflection { return subBuilder.buildPartial(); } + @Override public MergeTarget newMergeTargetForField( Descriptors.FieldDescriptor descriptor, Message defaultInstance) { throw new UnsupportedOperationException( "newMergeTargetForField() called on FieldSet object"); } - public WireFormat.Utf8Validation - getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + @Override + public WireFormat.Utf8Validation getUtf8Validation(Descriptors.FieldDescriptor descriptor) { if (descriptor.needsUtf8Check()) { return WireFormat.Utf8Validation.STRICT; } @@ -669,6 +705,7 @@ class MessageReflection { return WireFormat.Utf8Validation.LOOSE; } + @Override public Object finish() { throw new UnsupportedOperationException( "finish() called on FieldSet object"); @@ -676,12 +713,14 @@ class MessageReflection { } /** - * Parses a single field into MergeTarget. The target can be Message.Builder, - * FieldSet or MutableMessage. + * Parses a single field into MergeTarget. The target can be Message.Builder, FieldSet or + * MutableMessage. * - * Package-private because it is used by GeneratedMessage.ExtendableMessage. + * <p>Package-private because it is used by GeneratedMessage.ExtendableMessage. * * @param tag The tag, which should have already been read. + * @param unknownFields If not null, unknown fields will be merged to this {@link + * UnknownFieldSet}, otherwise unknown fields will be discarded. * @return {@code true} unless the tag is an end-group tag. */ static boolean mergeFieldFrom( @@ -690,7 +729,8 @@ class MessageReflection { ExtensionRegistryLite extensionRegistry, Descriptors.Descriptor type, MergeTarget target, - int tag) throws IOException { + int tag) + throws IOException { if (type.getOptions().getMessageSetWireFormat() && tag == WireFormat.MESSAGE_SET_ITEM_TAG) { mergeMessageSetExtensionFromCodedStream( @@ -754,7 +794,11 @@ class MessageReflection { } if (unknown) { // Unknown field or wrong wire type. Skip. - return unknownFields.mergeFieldFrom(tag, input); + if (unknownFields != null) { + return unknownFields.mergeFieldFrom(tag, input); + } else { + return input.skipField(tag); + } } if (packed) { @@ -806,7 +850,9 @@ class MessageReflection { // If the number isn't recognized as a valid value for this enum, // drop it. if (value == null) { - unknownFields.mergeVarintField(fieldNumber, rawValue); + if (unknownFields != null) { + unknownFields.mergeVarintField(fieldNumber, rawValue); + } return true; } } @@ -909,7 +955,7 @@ class MessageReflection { mergeMessageSetExtensionFromBytes( rawBytes, extension, extensionRegistry, target); } else { // We don't know how to parse this. Ignore it. - if (rawBytes != null) { + if (rawBytes != null && unknownFields != null) { unknownFields.mergeField(typeId, UnknownFieldSet.Field.newBuilder() .addLengthDelimited(rawBytes).build()); } diff --git a/java/core/src/main/java/com/google/protobuf/NioByteString.java b/java/core/src/main/java/com/google/protobuf/NioByteString.java index f71e41b2..76594809 100644 --- a/java/core/src/main/java/com/google/protobuf/NioByteString.java +++ b/java/core/src/main/java/com/google/protobuf/NioByteString.java @@ -30,15 +30,16 @@ package com.google.protobuf; -import java.io.FileOutputStream; +import static com.google.protobuf.Internal.checkNotNull; + import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.InvalidMarkException; -import java.nio.channels.Channels; import java.nio.charset.Charset; import java.util.Collections; import java.util.List; @@ -50,11 +51,10 @@ final class NioByteString extends ByteString.LeafByteString { private final ByteBuffer buffer; NioByteString(ByteBuffer buffer) { - if (buffer == null) { - throw new NullPointerException("buffer"); - } + checkNotNull(buffer, "buffer"); - this.buffer = buffer.slice(); + // Use native byte order for fast fixed32/64 operations. + this.buffer = buffer.slice().order(ByteOrder.nativeOrder()); } // ================================================================= @@ -119,7 +119,7 @@ final class NioByteString extends ByteString.LeafByteString { @Override public void writeTo(OutputStream out) throws IOException { - writeToInternal(out, buffer.position(), buffer.remaining()); + out.write(toByteArray()); } @Override @@ -137,14 +137,12 @@ final class NioByteString extends ByteString.LeafByteString { return; } - // Slow path - if (out instanceof FileOutputStream || numberToWrite >= 8192) { - // Use a channel to write out the ByteBuffer. - Channels.newChannel(out).write(slice(sourceOffset, sourceOffset + numberToWrite)); - } else { - // Just copy the data to an array and write it. - out.write(toByteArray()); - } + ByteBufferWriter.write(slice(sourceOffset, sourceOffset + numberToWrite), out); + } + + @Override + void writeTo(ByteOutput output) throws IOException { + output.writeLazy(buffer.slice()); } @Override @@ -159,46 +157,30 @@ final class NioByteString extends ByteString.LeafByteString { @Override protected String toStringInternal(Charset charset) { - byte[] bytes; - int offset; + final byte[] bytes; + final int offset; + final int length; if (buffer.hasArray()) { bytes = buffer.array(); offset = buffer.arrayOffset() + buffer.position(); + length = buffer.remaining(); } else { + // TODO(nathanmittler): Can we optimize this? bytes = toByteArray(); offset = 0; + length = bytes.length; } - return new String(bytes, offset, size(), charset); + return new String(bytes, offset, length, charset); } @Override public boolean isValidUtf8() { - // TODO(nathanmittler): add a ByteBuffer fork for Utf8.isValidUtf8 to avoid the copy - byte[] bytes; - int startIndex; - if (buffer.hasArray()) { - bytes = buffer.array(); - startIndex = buffer.arrayOffset() + buffer.position(); - } else { - bytes = toByteArray(); - startIndex = 0; - } - return Utf8.isValidUtf8(bytes, startIndex, startIndex + size()); + return Utf8.isValidUtf8(buffer); } @Override protected int partialIsValidUtf8(int state, int offset, int length) { - // TODO(nathanmittler): TODO add a ByteBuffer fork for Utf8.partialIsValidUtf8 to avoid the copy - byte[] bytes; - int startIndex; - if (buffer.hasArray()) { - bytes = buffer.array(); - startIndex = buffer.arrayOffset() + buffer.position(); - } else { - bytes = toByteArray(); - startIndex = 0; - } - return Utf8.partialIsValidUtf8(state, bytes, startIndex, startIndex + size()); + return Utf8.partialIsValidUtf8(state, buffer, offset, offset + length); } @Override @@ -285,7 +267,7 @@ final class NioByteString extends ByteString.LeafByteString { @Override public CodedInputStream newCodedInput() { - return CodedInputStream.newInstance(buffer); + return CodedInputStream.newInstance(buffer, true); } /** diff --git a/java/core/src/main/java/com/google/protobuf/Parser.java b/java/core/src/main/java/com/google/protobuf/Parser.java index 3fa11c3b..e07c6895 100644 --- a/java/core/src/main/java/com/google/protobuf/Parser.java +++ b/java/core/src/main/java/com/google/protobuf/Parser.java @@ -30,8 +30,8 @@ package com.google.protobuf; -import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; /** * Abstract interface for parsing Protocol Messages. @@ -40,7 +40,7 @@ import java.io.InputStream; * * <p>All methods may throw {@link InvalidProtocolBufferException}. In the event of invalid data, * like an encoding error, the cause of the thrown exception will be {@code null}. However, if an - * I/O problem occurs, an exception is thrown with an {@link IOException} cause. + * I/O problem occurs, an exception is thrown with an {@link java.io.IOException} cause. * * @author liujisi@google.com (Pherl Liu) */ @@ -94,6 +94,18 @@ public interface Parser<MessageType> { // Convenience methods. /** + * Parses {@code data} as a message of {@code MessageType}. This is just a small wrapper around + * {@link #parseFrom(CodedInputStream)}. + */ + public MessageType parseFrom(ByteBuffer data) throws InvalidProtocolBufferException; + + /** + * Parses {@code data} as a message of {@code MessageType}. This is just a small wrapper around + * {@link #parseFrom(CodedInputStream, ExtensionRegistryLite)}. + */ + public MessageType parseFrom(ByteBuffer data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + /** * Parses {@code data} as a message of {@code MessageType}. * This is just a small wrapper around {@link #parseFrom(CodedInputStream)}. */ diff --git a/java/core/src/main/java/com/google/protobuf/PrimitiveNonBoxingCollection.java b/java/core/src/main/java/com/google/protobuf/PrimitiveNonBoxingCollection.java new file mode 100644 index 00000000..79b5769d --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/PrimitiveNonBoxingCollection.java @@ -0,0 +1,34 @@ +// 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; + +/** A marker interface indicating that the collection supports primitives and is non-boxing. */ +interface PrimitiveNonBoxingCollection {} diff --git a/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java b/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java index d2f82ac5..81255ec2 100644 --- a/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java @@ -38,7 +38,7 @@ import java.util.List; /** * Implements {@link ProtobufList} for non-primitive and {@link String} types. */ -class ProtobufArrayList<E> extends AbstractProtobufList<E> { +final class ProtobufArrayList<E> extends AbstractProtobufList<E> { private static final ProtobufArrayList<Object> EMPTY_LIST = new ProtobufArrayList<Object>(); static { @@ -51,17 +51,23 @@ class ProtobufArrayList<E> extends AbstractProtobufList<E> { } private final List<E> list; - + ProtobufArrayList() { - list = new ArrayList<E>(); + this(new ArrayList<E>(DEFAULT_CAPACITY)); } - ProtobufArrayList(List<E> toCopy) { - list = new ArrayList<E>(toCopy); + private ProtobufArrayList(List<E> list) { + this.list = list; } - - ProtobufArrayList(int capacity) { - list = new ArrayList<E>(capacity); + + @Override + public ProtobufArrayList<E> mutableCopyWithCapacity(int capacity) { + if (capacity < size()) { + throw new IllegalArgumentException(); + } + List<E> newList = new ArrayList<E>(capacity); + newList.addAll(list); + return new ProtobufArrayList<E>(newList); } @Override diff --git a/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java b/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java index 0c8df989..a596d301 100644 --- a/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java +++ b/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java @@ -42,6 +42,7 @@ public interface ProtocolMessageEnum extends Internal.EnumLite { /** * Return the value's numeric value as defined in the .proto file. */ + @Override int getNumber(); /** diff --git a/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java index f91cdbce..29f567dc 100644 --- a/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java +++ b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java @@ -579,7 +579,7 @@ public class RepeatedFieldBuilder } } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void markDirty() { onChanged(); } @@ -621,10 +621,12 @@ public class RepeatedFieldBuilder this.builder = builder; } + @Override public int size() { return this.builder.getCount(); } + @Override public MType get(int index) { return builder.getMessage(index); } @@ -654,10 +656,12 @@ public class RepeatedFieldBuilder this.builder = builder; } + @Override public int size() { return this.builder.getCount(); } + @Override public BType get(int index) { return builder.getBuilder(index); } @@ -687,10 +691,12 @@ public class RepeatedFieldBuilder this.builder = builder; } + @Override public int size() { return this.builder.getCount(); } + @Override public IType get(int index) { return builder.getMessageOrBuilder(index); } diff --git a/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java new file mode 100644 index 00000000..30c991d4 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java @@ -0,0 +1,702 @@ +// 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 static com.google.protobuf.Internal.checkNotNull; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * {@code RepeatedFieldBuilderV3} implements a structure that a protocol + * message uses to hold a repeated field of other protocol messages. It supports + * the classical use case of adding immutable {@link Message}'s to the + * repeated field and is highly optimized around this (no extra memory + * allocations and sharing of immutable arrays). + * <br> + * It also supports the additional use case of adding a {@link Message.Builder} + * to the repeated field and deferring conversion of that {@code Builder} + * to an immutable {@code Message}. In this way, it's possible to maintain + * a tree of {@code Builder}'s that acts as a fully read/write data + * structure. + * <br> + * Logically, one can think of a tree of builders as converting the entire tree + * to messages when build is called on the root or when any method is called + * that desires a Message instead of a Builder. In terms of the implementation, + * the {@code SingleFieldBuilderV3} and {@code RepeatedFieldBuilderV3} + * classes cache messages that were created so that messages only need to be + * created when some change occurred in its builder or a builder for one of its + * descendants. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + * + * @author jonp@google.com (Jon Perlow) + */ +public class RepeatedFieldBuilderV3 + <MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + implements AbstractMessage.BuilderParent { + + // Parent to send changes to. + private AbstractMessage.BuilderParent parent; + + // List of messages. Never null. It may be immutable, in which case + // isMessagesListMutable will be false. See note below. + private List<MType> messages; + + // Whether messages is an mutable array that can be modified. + private boolean isMessagesListMutable; + + // List of builders. May be null, in which case, no nested builders were + // created. If not null, entries represent the builder for that index. + private List<SingleFieldBuilderV3<MType, BType, IType>> builders; + + // Here are the invariants for messages and builders: + // 1. messages is never null and its count corresponds to the number of items + // in the repeated field. + // 2. If builders is non-null, messages and builders MUST always + // contain the same number of items. + // 3. Entries in either array can be null, but for any index, there MUST be + // either a Message in messages or a builder in builders. + // 4. If the builder at an index is non-null, the builder is + // authoritative. This is the case where a Builder was set on the index. + // Any message in the messages array MUST be ignored. + // t. If the builder at an index is null, the message in the messages + // list is authoritative. This is the case where a Message (not a Builder) + // was set directly for an index. + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See AbstractMessage.BuilderListener. + private boolean isClean; + + // A view of this builder that exposes a List interface of messages. This is + // initialized on demand. This is fully backed by this object and all changes + // are reflected in it. Access to any item converts it to a message if it + // was a builder. + private MessageExternalList<MType, BType, IType> externalMessageList; + + // A view of this builder that exposes a List interface of builders. This is + // initialized on demand. This is fully backed by this object and all changes + // are reflected in it. Access to any item converts it to a builder if it + // was a message. + private BuilderExternalList<MType, BType, IType> externalBuilderList; + + // A view of this builder that exposes a List interface of the interface + // implemented by messages and builders. This is initialized on demand. This + // is fully backed by this object and all changes are reflected in it. + // Access to any item returns either a builder or message depending on + // what is most efficient. + private MessageOrBuilderExternalList<MType, BType, IType> + externalMessageOrBuilderList; + + /** + * Constructs a new builder with an empty list of messages. + * + * @param messages the current list of messages + * @param isMessagesListMutable Whether the messages list is mutable + * @param parent a listener to notify of changes + * @param isClean whether the builder is initially marked clean + */ + public RepeatedFieldBuilderV3( + List<MType> messages, + boolean isMessagesListMutable, + AbstractMessage.BuilderParent parent, + boolean isClean) { + this.messages = messages; + this.isMessagesListMutable = isMessagesListMutable; + this.parent = parent; + this.isClean = isClean; + } + + public void dispose() { + // Null out parent so we stop sending it invalidations. + parent = null; + } + + /** + * Ensures that the list of messages is mutable so it can be updated. If it's + * immutable, a copy is made. + */ + private void ensureMutableMessageList() { + if (!isMessagesListMutable) { + messages = new ArrayList<MType>(messages); + isMessagesListMutable = true; + } + } + + /** + * Ensures that the list of builders is not null. If it's null, the list is + * created and initialized to be the same size as the messages list with + * null entries. + */ + private void ensureBuilders() { + if (this.builders == null) { + this.builders = + new ArrayList<SingleFieldBuilderV3<MType, BType, IType>>( + messages.size()); + for (int i = 0; i < messages.size(); i++) { + builders.add(null); + } + } + } + + /** + * Gets the count of items in the list. + * + * @return the count of items in the list. + */ + public int getCount() { + return messages.size(); + } + + /** + * Gets whether the list is empty. + * + * @return whether the list is empty + */ + public boolean isEmpty() { + return messages.isEmpty(); + } + + /** + * Get the message at the specified index. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. + * + * @param index the index of the message to get + * @return the message for the specified index + */ + public MType getMessage(int index) { + return getMessage(index, false); + } + + /** + * Get the message at the specified index. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. + * + * @param index the index of the message to get + * @param forBuild this is being called for build so we want to make sure + * we SingleFieldBuilderV3.build to send dirty invalidations + * @return the message for the specified index + */ + private MType getMessage(int index, boolean forBuild) { + if (this.builders == null) { + // We don't have any builders -- return the current Message. + // This is the case where no builder was created, so we MUST have a + // Message. + return messages.get(index); + } + + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + // We don't have a builder -- return the current message. + // This is the case where no builder was created for the entry at index, + // so we MUST have a message. + return messages.get(index); + + } else { + return forBuild ? builder.build() : builder.getMessage(); + } + } + + /** + * Gets a builder for the specified index. If no builder has been created for + * that index, a builder is created on demand by calling + * {@link Message#toBuilder}. + * + * @param index the index of the message to get + * @return The builder for that index + */ + public BType getBuilder(int index) { + ensureBuilders(); + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + MType message = messages.get(index); + builder = new SingleFieldBuilderV3<MType, BType, IType>( + message, this, isClean); + builders.set(index, builder); + } + return builder.getBuilder(); + } + + /** + * Gets the base class interface for the specified index. This may either be + * a builder or a message. It will return whatever is more efficient. + * + * @param index the index of the message to get + * @return the message or builder for the index as the base class interface + */ + @SuppressWarnings("unchecked") + public IType getMessageOrBuilder(int index) { + if (this.builders == null) { + // We don't have any builders -- return the current Message. + // This is the case where no builder was created, so we MUST have a + // Message. + return (IType) messages.get(index); + } + + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + // We don't have a builder -- return the current message. + // This is the case where no builder was created for the entry at index, + // so we MUST have a message. + return (IType) messages.get(index); + + } else { + return builder.getMessageOrBuilder(); + } + } + + /** + * Sets a message at the specified index replacing the existing item at + * that index. + * + * @param index the index to set. + * @param message the message to set + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> setMessage( + int index, MType message) { + checkNotNull(message); + ensureMutableMessageList(); + messages.set(index, message); + if (builders != null) { + SingleFieldBuilderV3<MType, BType, IType> entry = + builders.set(index, null); + if (entry != null) { + entry.dispose(); + } + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends the specified element to the end of this list. + * + * @param message the message to add + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> addMessage( + MType message) { + checkNotNull(message); + ensureMutableMessageList(); + messages.add(message); + if (builders != null) { + builders.add(null); + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Inserts the specified message at the specified position in this list. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices). + * + * @param index the index at which to insert the message + * @param message the message to add + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> addMessage( + int index, MType message) { + checkNotNull(message); + ensureMutableMessageList(); + messages.add(index, message); + if (builders != null) { + builders.add(index, null); + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends all of the messages in the specified collection to the end of + * this list, in the order that they are returned by the specified + * collection's iterator. + * + * @param values the messages to add + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> addAllMessages( + Iterable<? extends MType> values) { + for (final MType value : values) { + checkNotNull(value); + } + + // If we can inspect the size, we can more efficiently add messages. + int size = -1; + if (values instanceof Collection) { + @SuppressWarnings("unchecked") final + Collection<MType> collection = (Collection<MType>) values; + if (collection.size() == 0) { + return this; + } + size = collection.size(); + } + ensureMutableMessageList(); + + if (size >= 0 && messages instanceof ArrayList) { + ((ArrayList<MType>) messages) + .ensureCapacity(messages.size() + size); + } + + for (MType value : values) { + addMessage(value); + } + + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends a new builder to the end of this list and returns the builder. + * + * @param message the message to add which is the basis of the builder + * @return the new builder + */ + public BType addBuilder(MType message) { + ensureMutableMessageList(); + ensureBuilders(); + SingleFieldBuilderV3<MType, BType, IType> builder = + new SingleFieldBuilderV3<MType, BType, IType>( + message, this, isClean); + messages.add(null); + builders.add(builder); + onChanged(); + incrementModCounts(); + return builder.getBuilder(); + } + + /** + * Inserts a new builder at the specified position in this list. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices). + * + * @param index the index at which to insert the builder + * @param message the message to add which is the basis of the builder + * @return the builder + */ + public BType addBuilder(int index, MType message) { + ensureMutableMessageList(); + ensureBuilders(); + SingleFieldBuilderV3<MType, BType, IType> builder = + new SingleFieldBuilderV3<MType, BType, IType>( + message, this, isClean); + messages.add(index, null); + builders.add(index, builder); + onChanged(); + incrementModCounts(); + return builder.getBuilder(); + } + + /** + * Removes the element at the specified position in this list. Shifts any + * subsequent elements to the left (subtracts one from their indices). + * Returns the element that was removed from the list. + * + * @param index the index at which to remove the message + */ + public void remove(int index) { + ensureMutableMessageList(); + messages.remove(index); + if (builders != null) { + SingleFieldBuilderV3<MType, BType, IType> entry = + builders.remove(index); + if (entry != null) { + entry.dispose(); + } + } + onChanged(); + incrementModCounts(); + } + + /** + * Removes all of the elements from this list. + * The list will be empty after this call returns. + */ + public void clear() { + messages = Collections.emptyList(); + isMessagesListMutable = false; + if (builders != null) { + for (SingleFieldBuilderV3<MType, BType, IType> entry : + builders) { + if (entry != null) { + entry.dispose(); + } + } + builders = null; + } + onChanged(); + incrementModCounts(); + } + + /** + * Builds the list of messages from the builder and returns them. + * + * @return an immutable list of messages + */ + public List<MType> build() { + // Now that build has been called, we are required to dispatch + // invalidations. + isClean = true; + + if (!isMessagesListMutable && builders == null) { + // We still have an immutable list and we never created a builder. + return messages; + } + + boolean allMessagesInSync = true; + if (!isMessagesListMutable) { + // We still have an immutable list. Let's see if any of them are out + // of sync with their builders. + for (int i = 0; i < messages.size(); i++) { + Message message = messages.get(i); + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(i); + if (builder != null) { + if (builder.build() != message) { + allMessagesInSync = false; + break; + } + } + } + if (allMessagesInSync) { + // Immutable list is still in sync. + return messages; + } + } + + // Need to make sure messages is up to date + ensureMutableMessageList(); + for (int i = 0; i < messages.size(); i++) { + messages.set(i, getMessage(i, true)); + } + + // We're going to return our list as immutable so we mark that we can + // no longer update it. + messages = Collections.unmodifiableList(messages); + isMessagesListMutable = false; + return messages; + } + + /** + * Gets a view of the builder as a list of messages. The returned list is live + * and will reflect any changes to the underlying builder. + * + * @return the messages in the list + */ + public List<MType> getMessageList() { + if (externalMessageList == null) { + externalMessageList = + new MessageExternalList<MType, BType, IType>(this); + } + return externalMessageList; + } + + /** + * Gets a view of the builder as a list of builders. This returned list is + * live and will reflect any changes to the underlying builder. + * + * @return the builders in the list + */ + public List<BType> getBuilderList() { + if (externalBuilderList == null) { + externalBuilderList = + new BuilderExternalList<MType, BType, IType>(this); + } + return externalBuilderList; + } + + /** + * Gets a view of the builder as a list of MessageOrBuilders. This returned + * list is live and will reflect any changes to the underlying builder. + * + * @return the builders in the list + */ + public List<IType> getMessageOrBuilderList() { + if (externalMessageOrBuilderList == null) { + externalMessageOrBuilderList = + new MessageOrBuilderExternalList<MType, BType, IType>(this); + } + return externalMessageOrBuilderList; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + private void onChanged() { + if (isClean && parent != null) { + parent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + isClean = false; + } + } + + @Override + public void markDirty() { + onChanged(); + } + + /** + * Increments the mod counts so that an ConcurrentModificationException can + * be thrown if calling code tries to modify the builder while its iterating + * the list. + */ + private void incrementModCounts() { + if (externalMessageList != null) { + externalMessageList.incrementModCount(); + } + if (externalBuilderList != null) { + externalBuilderList.incrementModCount(); + } + if (externalMessageOrBuilderList != null) { + externalMessageOrBuilderList.incrementModCount(); + } + } + + /** + * Provides a live view of the builder as a list of messages. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class MessageExternalList< + MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<MType> implements List<MType> { + + RepeatedFieldBuilderV3<MType, BType, IType> builder; + + MessageExternalList( + RepeatedFieldBuilderV3<MType, BType, IType> builder) { + this.builder = builder; + } + + @Override + public int size() { + return this.builder.getCount(); + } + + @Override + public MType get(int index) { + return builder.getMessage(index); + } + + void incrementModCount() { + modCount++; + } + } + + /** + * Provides a live view of the builder as a list of builders. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class BuilderExternalList< + MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<BType> implements List<BType> { + + RepeatedFieldBuilderV3<MType, BType, IType> builder; + + BuilderExternalList( + RepeatedFieldBuilderV3<MType, BType, IType> builder) { + this.builder = builder; + } + + @Override + public int size() { + return this.builder.getCount(); + } + + @Override + public BType get(int index) { + return builder.getBuilder(index); + } + + void incrementModCount() { + modCount++; + } + } + + /** + * Provides a live view of the builder as a list of builders. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class MessageOrBuilderExternalList< + MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<IType> implements List<IType> { + + RepeatedFieldBuilderV3<MType, BType, IType> builder; + + MessageOrBuilderExternalList( + RepeatedFieldBuilderV3<MType, BType, IType> builder) { + this.builder = builder; + } + + @Override + public int size() { + return this.builder.getCount(); + } + + @Override + public IType get(int index) { + return builder.getMessageOrBuilder(index); + } + + void incrementModCount() { + modCount++; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/RopeByteString.java b/java/core/src/main/java/com/google/protobuf/RopeByteString.java index 8badfabd..6fa555df 100644 --- a/java/core/src/main/java/com/google/protobuf/RopeByteString.java +++ b/java/core/src/main/java/com/google/protobuf/RopeByteString.java @@ -48,10 +48,11 @@ import java.util.Stack; /** * Class to represent {@code ByteStrings} formed by concatenation of other * ByteStrings, without copying the data in the pieces. The concatenation is - * represented as a tree whose leaf nodes are each a {@link LiteralByteString}. + * represented as a tree whose leaf nodes are each a + * {@link com.google.protobuf.ByteString.LeafByteString}. * * <p>Most of the operation here is inspired by the now-famous paper <a - * href="http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol25/issue12/spe986.pdf"> + * href="https://web.archive.org/web/20060202015456/http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol25/issue12/spe986.pdf"> * BAP95 </a> Ropes: an Alternative to Strings hans-j. boehm, russ atkinson and * michael plass * @@ -139,8 +140,9 @@ final class RopeByteString extends ByteString { /** * Concatenate the given strings while performing various optimizations to * slow the growth rate of tree depth and tree node count. The result is - * either a {@link LiteralByteString} or a {@link RopeByteString} - * depending on which optimizations, if any, were applied. + * either a {@link com.google.protobuf.ByteString.LeafByteString} or a + * {@link RopeByteString} depending on which optimizations, if any, were + * applied. * * <p>Small pieces of length less than {@link * ByteString#CONCATENATE_BY_COPY_SIZE} may be copied by value here, as in @@ -294,8 +296,7 @@ final class RopeByteString extends ByteString { * * <p>Substrings of {@code length < 2} should result in at most a single * recursive call chain, terminating at a leaf node. Thus the result will be a - * {@link LiteralByteString}. {@link #RopeByteString(ByteString, - * ByteString)}. + * {@link com.google.protobuf.ByteString.LeafByteString}. * * @param beginIndex start at this index * @param endIndex the last character is the one before this index @@ -368,7 +369,7 @@ final class RopeByteString extends ByteString { @Override public List<ByteBuffer> asReadOnlyByteBufferList() { - // Walk through the list of LiteralByteString's that make up this + // Walk through the list of LeafByteString's that make up this // rope, and add each one as a read-only ByteBuffer. List<ByteBuffer> result = new ArrayList<ByteBuffer>(); PieceIterator pieces = new PieceIterator(this); @@ -400,6 +401,13 @@ final class RopeByteString extends ByteString { } @Override + void writeTo(ByteOutput output) throws IOException { + left.writeTo(output); + right.writeTo(output); + } + + + @Override protected String toStringInternal(Charset charset) { return new String(toByteArray(), charset); } @@ -709,9 +717,10 @@ final class RopeByteString extends ByteString { } /** - * Returns the next item and advances one {@code LiteralByteString}. + * Returns the next item and advances one + * {@link com.google.protobuf.ByteString.LeafByteString}. * - * @return next non-empty LiteralByteString or {@code null} + * @return next non-empty LeafByteString or {@code null} */ @Override public LeafByteString next() { diff --git a/java/core/src/main/java/com/google/protobuf/RpcUtil.java b/java/core/src/main/java/com/google/protobuf/RpcUtil.java index 694b8d13..f7d555ae 100644 --- a/java/core/src/main/java/com/google/protobuf/RpcUtil.java +++ b/java/core/src/main/java/com/google/protobuf/RpcUtil.java @@ -71,6 +71,7 @@ public final class RpcUtil { final Class<Type> originalClass, final Type defaultInstance) { return new RpcCallback<Message>() { + @Override public void run(final Message parameter) { Type typedParameter; try { @@ -107,8 +108,9 @@ public final class RpcUtil { return new RpcCallback<ParameterType>() { private boolean alreadyCalled = false; + @Override public void run(final ParameterType parameter) { - synchronized(this) { + synchronized (this) { if (alreadyCalled) { throw new AlreadyCalledException(); } diff --git a/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java index aba65e32..941b5def 100644 --- a/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java +++ b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java @@ -234,7 +234,7 @@ public class SingleFieldBuilder } } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void markDirty() { onChanged(); } diff --git a/java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java new file mode 100644 index 00000000..8ab0f26d --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java @@ -0,0 +1,237 @@ +// 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 static com.google.protobuf.Internal.checkNotNull; + +/** + * {@code SingleFieldBuilderV3} implements a structure that a protocol + * message uses to hold a single field of another protocol message. It supports + * the classical use case of setting an immutable {@link Message} as the value + * of the field and is highly optimized around this. + * <br> + * It also supports the additional use case of setting a {@link Message.Builder} + * as the field and deferring conversion of that {@code Builder} + * to an immutable {@code Message}. In this way, it's possible to maintain + * a tree of {@code Builder}'s that acts as a fully read/write data + * structure. + * <br> + * Logically, one can think of a tree of builders as converting the entire tree + * to messages when build is called on the root or when any method is called + * that desires a Message instead of a Builder. In terms of the implementation, + * the {@code SingleFieldBuilderV3} and {@code RepeatedFieldBuilderV3} + * classes cache messages that were created so that messages only need to be + * created when some change occurred in its builder or a builder for one of its + * descendants. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + * + * @author jonp@google.com (Jon Perlow) + */ +public class SingleFieldBuilderV3 + <MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + implements AbstractMessage.BuilderParent { + + // Parent to send changes to. + private AbstractMessage.BuilderParent parent; + + // Invariant: one of builder or message fields must be non-null. + + // If set, this is the case where we are backed by a builder. In this case, + // message field represents a cached message for the builder (or null if + // there is no cached message). + private BType builder; + + // If builder is non-null, this represents a cached message from the builder. + // If builder is null, this is the authoritative message for the field. + private MType message; + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See AbstractMessage.BuilderListener. + private boolean isClean; + + public SingleFieldBuilderV3( + MType message, + AbstractMessage.BuilderParent parent, + boolean isClean) { + this.message = checkNotNull(message); + this.parent = parent; + this.isClean = isClean; + } + + public void dispose() { + // Null out parent so we stop sending it invalidations. + parent = null; + } + + /** + * Get the message for the field. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. If no message has + * been set, returns the default instance of the message. + * + * @return the message for the field + */ + @SuppressWarnings("unchecked") + public MType getMessage() { + if (message == null) { + // If message is null, the invariant is that we must be have a builder. + message = (MType) builder.buildPartial(); + } + return message; + } + + /** + * Builds the message and returns it. + * + * @return the message + */ + public MType build() { + // Now that build has been called, we are required to dispatch + // invalidations. + isClean = true; + return getMessage(); + } + + /** + * Gets a builder for the field. If no builder has been created yet, a + * builder is created on demand by calling {@link Message#toBuilder}. + * + * @return The builder for the field + */ + @SuppressWarnings("unchecked") + public BType getBuilder() { + if (builder == null) { + // builder.mergeFrom() on a fresh builder + // does not create any sub-objects with independent clean/dirty states, + // therefore setting the builder itself to clean without actually calling + // build() cannot break any invariants. + builder = (BType) message.newBuilderForType(this); + builder.mergeFrom(message); // no-op if message is the default message + builder.markClean(); + } + return builder; + } + + /** + * Gets the base class interface for the field. This may either be a builder + * or a message. It will return whatever is more efficient. + * + * @return the message or builder for the field as the base class interface + */ + @SuppressWarnings("unchecked") + public IType getMessageOrBuilder() { + if (builder != null) { + return (IType) builder; + } else { + return (IType) message; + } + } + + /** + * Sets a message for the field replacing any existing value. + * + * @param message the message to set + * @return the builder + */ + public SingleFieldBuilderV3<MType, BType, IType> setMessage( + MType message) { + this.message = checkNotNull(message); + if (builder != null) { + builder.dispose(); + builder = null; + } + onChanged(); + return this; + } + + /** + * Merges the field from another field. + * + * @param value the value to merge from + * @return the builder + */ + public SingleFieldBuilderV3<MType, BType, IType> mergeFrom( + MType value) { + if (builder == null && message == message.getDefaultInstanceForType()) { + message = value; + } else { + getBuilder().mergeFrom(value); + } + onChanged(); + return this; + } + + /** + * Clears the value of the field. + * + * @return the builder + */ + @SuppressWarnings("unchecked") + public SingleFieldBuilderV3<MType, BType, IType> clear() { + message = (MType) (message != null ? + message.getDefaultInstanceForType() : + builder.getDefaultInstanceForType()); + if (builder != null) { + builder.dispose(); + builder = null; + } + onChanged(); + return this; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + private void onChanged() { + // If builder is null, this is the case where onChanged is being called + // from setMessage or clear. + if (builder != null) { + message = null; + } + if (isClean && parent != null) { + parent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + isClean = false; + } + } + + @Override + public void markDirty() { + onChanged(); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java index 0674d2e2..279edc4d 100644 --- a/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java +++ b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java @@ -35,12 +35,12 @@ import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; -import java.util.TreeMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedMap; +import java.util.TreeMap; /** * A custom map implementation from FieldDescriptor to Object optimized to @@ -197,6 +197,7 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { overflowEntries.entrySet(); } + @Override public int size() { return entryList.size() + overflowEntries.size(); @@ -356,6 +357,7 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { return lazyEntrySet; } + /** * @throws UnsupportedOperationException if {@link #makeImmutable()} has * has been called. @@ -411,22 +413,22 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { this.value = value; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public K getKey() { return key; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public V getValue() { return value; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int compareTo(Entry other) { return getKey().compareTo(other.getKey()); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public V setValue(V newValue) { checkMutable(); final V oldValue = this.value; @@ -525,6 +527,7 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { } } + /** * Iterator implementation that switches from the entry array to the overflow * entries appropriately. @@ -535,13 +538,13 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { private boolean nextCalledBeforeRemove; private Iterator<Map.Entry<K, V>> lazyOverflowIterator; - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasNext() { - return (pos + 1) < entryList.size() || - getOverflowIterator().hasNext(); + return (pos + 1) < entryList.size() + || (!overflowEntries.isEmpty() && getOverflowIterator().hasNext()); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Map.Entry<K, V> next() { nextCalledBeforeRemove = true; // Always increment pos so that we know whether the last returned value @@ -552,7 +555,7 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { return getOverflowIterator().next(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void remove() { if (!nextCalledBeforeRemove) { throw new IllegalStateException("remove() was called before next()"); @@ -588,31 +591,83 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { */ private static class EmptySet { - private static final Iterator<Object> ITERATOR = new Iterator<Object>() { - //@Override (Java 1.6 override semantics, but we must support 1.5) - public boolean hasNext() { - return false; - } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Object next() { - throw new NoSuchElementException(); - } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public void remove() { - throw new UnsupportedOperationException(); - } - }; + private static final Iterator<Object> ITERATOR = + new Iterator<Object>() { + @Override + public boolean hasNext() { + return false; + } + @Override + public Object next() { + throw new NoSuchElementException(); + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; - private static final Iterable<Object> ITERABLE = new Iterable<Object>() { - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Iterator<Object> iterator() { - return ITERATOR; - } - }; + private static final Iterable<Object> ITERABLE = + new Iterable<Object>() { + @Override + public Iterator<Object> iterator() { + return ITERATOR; + } + }; @SuppressWarnings("unchecked") static <T> Iterable<T> iterable() { return (Iterable<T>) ITERABLE; } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof SmallSortedMap)) { + return super.equals(o); + } + + SmallSortedMap<?, ?> other = (SmallSortedMap<?, ?>) o; + final int size = size(); + if (size != other.size()) { + return false; + } + + // Best effort try to avoid allocating an entry set. + final int numArrayEntries = getNumArrayEntries(); + if (numArrayEntries != other.getNumArrayEntries()) { + return entrySet().equals(other.entrySet()); + } + + for (int i = 0; i < numArrayEntries; i++) { + if (!getArrayEntryAt(i).equals(other.getArrayEntryAt(i))) { + return false; + } + } + + if (numArrayEntries != size) { + return overflowEntries.equals(other.overflowEntries); + } + + + return true; + } + + @Override + public int hashCode() { + int h = 0; + final int listSize = getNumArrayEntries(); + for (int i = 0; i < listSize; i++) { + h += entryList.get(i).hashCode(); + } + // Avoid the iterator allocation if possible. + if (getNumOverflowEntries() > 0) { + h += overflowEntries.hashCode(); + } + return h; + } } diff --git a/java/core/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java index c99b5285..25c3474f 100644 --- a/java/core/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -34,7 +34,6 @@ import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; - import java.io.IOException; import java.math.BigInteger; import java.nio.CharBuffer; @@ -56,14 +55,7 @@ import java.util.regex.Pattern; public final class TextFormat { private TextFormat() {} - private static final Logger logger = - Logger.getLogger(TextFormat.class.getName()); - - private static final Printer DEFAULT_PRINTER = new Printer(); - private static final Printer SINGLE_LINE_PRINTER = - (new Printer()).setSingleLineMode(true); - private static final Printer UNICODE_PRINTER = - (new Printer()).setEscapeNonAscii(false); + private static final Logger logger = Logger.getLogger(TextFormat.class.getName()); /** * Outputs a textual representation of the Protocol Message supplied into @@ -73,14 +65,14 @@ public final class TextFormat { public static void print( final MessageOrBuilder message, final Appendable output) throws IOException { - DEFAULT_PRINTER.print(message, new TextGenerator(output)); + Printer.DEFAULT.print(message, multiLineOutput(output)); } /** Outputs a textual representation of {@code fields} to {@code output}. */ public static void print(final UnknownFieldSet fields, final Appendable output) throws IOException { - DEFAULT_PRINTER.printUnknownFields(fields, new TextGenerator(output)); + Printer.DEFAULT.printUnknownFields(fields, multiLineOutput(output)); } /** @@ -90,7 +82,7 @@ public final class TextFormat { public static void printUnicode( final MessageOrBuilder message, final Appendable output) throws IOException { - UNICODE_PRINTER.print(message, new TextGenerator(output)); + Printer.UNICODE.print(message, multiLineOutput(output)); } /** @@ -100,7 +92,7 @@ public final class TextFormat { public static void printUnicode(final UnknownFieldSet fields, final Appendable output) throws IOException { - UNICODE_PRINTER.printUnknownFields(fields, new TextGenerator(output)); + Printer.UNICODE.printUnknownFields(fields, multiLineOutput(output)); } /** @@ -109,10 +101,9 @@ public final class TextFormat { */ public static String shortDebugString(final MessageOrBuilder message) { try { - final StringBuilder sb = new StringBuilder(); - SINGLE_LINE_PRINTER.print(message, new TextGenerator(sb)); - // Single line mode currently might have an extra space at the end. - return sb.toString().trim(); + final StringBuilder text = new StringBuilder(); + Printer.DEFAULT.print(message, singleLineOutput(text)); + return text.toString(); } catch (IOException e) { throw new IllegalStateException(e); } @@ -125,11 +116,11 @@ public final class TextFormat { public static String shortDebugString(final FieldDescriptor field, final Object value) { try { - final StringBuilder sb = new StringBuilder(); - SINGLE_LINE_PRINTER.printField(field, value, new TextGenerator(sb)); - return sb.toString().trim(); + final StringBuilder text = new StringBuilder(); + Printer.DEFAULT.printField(field, value, singleLineOutput(text)); + return text.toString(); } catch (IOException e) { - throw new IllegalStateException(e); + throw new IllegalStateException(e); } } @@ -139,10 +130,9 @@ public final class TextFormat { */ public static String shortDebugString(final UnknownFieldSet fields) { try { - final StringBuilder sb = new StringBuilder(); - SINGLE_LINE_PRINTER.printUnknownFields(fields, new TextGenerator(sb)); - // Single line mode currently might have an extra space at the end. - return sb.toString().trim(); + final StringBuilder text = new StringBuilder(); + Printer.DEFAULT.printUnknownFields(fields, singleLineOutput(text)); + return text.toString(); } catch (IOException e) { throw new IllegalStateException(e); } @@ -183,7 +173,7 @@ public final class TextFormat { public static String printToUnicodeString(final MessageOrBuilder message) { try { final StringBuilder text = new StringBuilder(); - UNICODE_PRINTER.print(message, new TextGenerator(text)); + Printer.UNICODE.print(message, multiLineOutput(text)); return text.toString(); } catch (IOException e) { throw new IllegalStateException(e); @@ -197,7 +187,7 @@ public final class TextFormat { public static String printToUnicodeString(final UnknownFieldSet fields) { try { final StringBuilder text = new StringBuilder(); - UNICODE_PRINTER.printUnknownFields(fields, new TextGenerator(text)); + Printer.UNICODE.printUnknownFields(fields, multiLineOutput(text)); return text.toString(); } catch (IOException e) { throw new IllegalStateException(e); @@ -208,7 +198,7 @@ public final class TextFormat { final Object value, final Appendable output) throws IOException { - DEFAULT_PRINTER.printField(field, value, new TextGenerator(output)); + Printer.DEFAULT.printField(field, value, multiLineOutput(output)); } public static String printFieldToString(final FieldDescriptor field, @@ -223,6 +213,23 @@ public final class TextFormat { } /** + * Outputs a unicode textual representation of the value of given field value. + * + * <p>Same as {@code printFieldValue()}, except that non-ASCII characters in string type fields + * are not escaped in backslash+octals. + * + * @param field the descriptor of the field + * @param value the value of the field + * @param output the output to which to append the formatted value + * @throws ClassCastException if the value is not appropriate for the given field descriptor + * @throws IOException if there is an exception writing to the output + */ + public static void printUnicodeFieldValue( + final FieldDescriptor field, final Object value, final Appendable output) throws IOException { + Printer.UNICODE.printFieldValue(field, value, multiLineOutput(output)); + } + + /** * Outputs a textual representation of the value of given field value. * * @param field the descriptor of the field @@ -236,7 +243,7 @@ public final class TextFormat { final Object value, final Appendable output) throws IOException { - DEFAULT_PRINTER.printFieldValue(field, value, new TextGenerator(output)); + Printer.DEFAULT.printFieldValue(field, value, multiLineOutput(output)); } /** @@ -253,7 +260,7 @@ public final class TextFormat { final Object value, final Appendable output) throws IOException { - printUnknownFieldValue(tag, value, new TextGenerator(output)); + printUnknownFieldValue(tag, value, multiLineOutput(output)); } private static void printUnknownFieldValue(final int tag, @@ -272,12 +279,24 @@ public final class TextFormat { generator.print(String.format((Locale) null, "0x%016x", (Long) value)); break; case WireFormat.WIRETYPE_LENGTH_DELIMITED: - generator.print("\""); - generator.print(escapeBytes((ByteString) value)); - generator.print("\""); + try { + // Try to parse and print the field as an embedded message + UnknownFieldSet message = UnknownFieldSet.parseFrom((ByteString) value); + generator.print("{"); + generator.eol(); + generator.indent(); + Printer.DEFAULT.printUnknownFields(message, generator); + generator.outdent(); + generator.print("}"); + } catch (InvalidProtocolBufferException e) { + // If not parseable as a message, print as a String + generator.print("\""); + generator.print(escapeBytes((ByteString) value)); + generator.print("\""); + } break; case WireFormat.WIRETYPE_START_GROUP: - DEFAULT_PRINTER.printUnknownFields((UnknownFieldSet) value, generator); + Printer.DEFAULT.printUnknownFields((UnknownFieldSet) value, generator); break; default: throw new IllegalArgumentException("Bad tag: " + tag); @@ -286,24 +305,16 @@ public final class TextFormat { /** Helper class for converting protobufs to text. */ private static final class Printer { - /** Whether to omit newlines from the output. */ - boolean singleLineMode = false; + // Printer instance which escapes non-ASCII characters. + static final Printer DEFAULT = new Printer(true); + // Printer instance which emits Unicode (it still escapes newlines and quotes in strings). + static final Printer UNICODE = new Printer(false); /** Whether to escape non ASCII characters with backslash and octal. */ - boolean escapeNonAscii = true; - - private Printer() {} - - /** Setter of singleLineMode */ - private Printer setSingleLineMode(boolean singleLineMode) { - this.singleLineMode = singleLineMode; - return this; - } + private final boolean escapeNonAscii; - /** Setter of escapeNonAscii */ - private Printer setEscapeNonAscii(boolean escapeNonAscii) { + private Printer(boolean escapeNonAscii) { this.escapeNonAscii = escapeNonAscii; - return this; } private void print( @@ -355,12 +366,9 @@ public final class TextFormat { } if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - if (singleLineMode) { - generator.print(" { "); - } else { - generator.print(" {\n"); - generator.indent(); - } + generator.print(" {"); + generator.eol(); + generator.indent(); } else { generator.print(": "); } @@ -368,19 +376,10 @@ public final class TextFormat { printFieldValue(field, value, generator); if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - if (singleLineMode) { - generator.print("} "); - } else { - generator.outdent(); - generator.print("}\n"); - } - } else { - if (singleLineMode) { - generator.print(" "); - } else { - generator.print("\n"); - } + generator.outdent(); + generator.print("}"); } + generator.eol(); } private void printFieldValue(final FieldDescriptor field, @@ -425,7 +424,7 @@ public final class TextFormat { case STRING: generator.print("\""); generator.print(escapeNonAscii - ? escapeText((String) value) + ? TextFormatEscaper.escapeText((String) value) : escapeDoubleQuotesAndBackslashes((String) value) .replace("\n", "\\n")); generator.print("\""); @@ -469,19 +468,13 @@ public final class TextFormat { field.getLengthDelimitedList(), generator); for (final UnknownFieldSet value : field.getGroupList()) { generator.print(entry.getKey().toString()); - if (singleLineMode) { - generator.print(" { "); - } else { - generator.print(" {\n"); - generator.indent(); - } + generator.print(" {"); + generator.eol(); + generator.indent(); printUnknownFields(value, generator); - if (singleLineMode) { - generator.print("} "); - } else { - generator.outdent(); - generator.print("}\n"); - } + generator.outdent(); + generator.print("}"); + generator.eol(); } } } @@ -495,7 +488,7 @@ public final class TextFormat { generator.print(String.valueOf(number)); generator.print(": "); printUnknownFieldValue(wireType, value, generator); - generator.print(singleLineMode ? " " : "\n"); + generator.eol(); } } } @@ -521,16 +514,29 @@ public final class TextFormat { } } - /** + private static TextGenerator multiLineOutput(Appendable output) { + return new TextGenerator(output, false); + } + + private static TextGenerator singleLineOutput(Appendable output) { + return new TextGenerator(output, true); + } + + /** * An inner class for writing text to the output stream. */ private static final class TextGenerator { private final Appendable output; private final StringBuilder indent = new StringBuilder(); - private boolean atStartOfLine = true; + private final boolean singleLineMode; + // While technically we are "at the start of a line" at the very beginning of the output, all + // we would do in response to this is emit the (zero length) indentation, so it has no effect. + // Setting it false here does however suppress an unwanted leading space in single-line mode. + private boolean atStartOfLine = false; - private TextGenerator(final Appendable output) { + private TextGenerator(final Appendable output, boolean singleLineMode) { this.output = output; + this.singleLineMode = singleLineMode; } /** @@ -552,35 +558,31 @@ public final class TextFormat { throw new IllegalArgumentException( " Outdent() without matching Indent()."); } - indent.delete(length - 2, length); + indent.setLength(length - 2); } /** - * Print text to the output stream. + * Print text to the output stream. Bare newlines are never expected to be passed to this + * method; to indicate the end of a line, call "eol()". */ public void print(final CharSequence text) throws IOException { - final int size = text.length(); - int pos = 0; - - for (int i = 0; i < size; i++) { - if (text.charAt(i) == '\n') { - write(text.subSequence(pos, i + 1)); - pos = i + 1; - atStartOfLine = true; - } + if (atStartOfLine) { + atStartOfLine = false; + output.append(singleLineMode ? " " : indent); } - write(text.subSequence(pos, size)); + output.append(text); } - private void write(final CharSequence data) throws IOException { - if (data.length() == 0) { - return; - } - if (atStartOfLine) { - atStartOfLine = false; - output.append(indent); + /** + * Signifies reaching the "end of the current line" in the output. In single-line mode, this + * does not result in a newline being emitted, but ensures that a separating space is written + * before the next output. + */ + public void eol() throws IOException { + if (!singleLineMode) { + output.append("\n"); } - output.append(data); + atStartOfLine = true; } } @@ -661,6 +663,22 @@ public final class TextFormat { nextToken(); } + int getPreviousLine() { + return previousLine; + } + + int getPreviousColumn() { + return previousColumn; + } + + int getLine() { + return line; + } + + int getColumn() { + return column; + } + /** Are we at the end of the input? */ public boolean atEnd() { return currentToken.length() == 0; @@ -957,17 +975,19 @@ public final class TextFormat { */ public boolean consumeBoolean() throws ParseException { if (currentToken.equals("true") + || currentToken.equals("True") || currentToken.equals("t") || currentToken.equals("1")) { nextToken(); return true; } else if (currentToken.equals("false") + || currentToken.equals("False") || currentToken.equals("f") || currentToken.equals("0")) { nextToken(); return false; } else { - throw parseException("Expected \"true\" or \"false\"."); + throw parseException("Expected \"true\" or \"false\". Found \"" + currentToken + "\"."); } } @@ -1074,7 +1094,7 @@ public final class TextFormat { private ParseException floatParseException(final NumberFormatException e) { return parseException("Couldn't parse number: " + e.getMessage()); } - + /** * Returns a {@link UnknownFieldParseException} with the line and column * numbers of the previous token in the description, and the unknown field @@ -1133,7 +1153,7 @@ public final class TextFormat { return column; } } - + /** * Thrown when encountering an unknown field while parsing * a text format message. @@ -1204,6 +1224,22 @@ public final class TextFormat { } /** + * Parse a text-format message from {@code input}. + * + * @return the parsed message, guaranteed initialized + */ + public static <T extends Message> T parse(final CharSequence input, + final Class<T> protoClass) + throws ParseException { + Message.Builder builder = + Internal.getDefaultInstance(protoClass).newBuilderForType(); + merge(input, builder); + @SuppressWarnings("unchecked") + T output = (T) builder.build(); + return output; + } + + /** * Parse a text-format message from {@code input} and merge the contents * into {@code builder}. Extensions will be recognized if they are * registered in {@code extensionRegistry}. @@ -1228,6 +1264,25 @@ public final class TextFormat { PARSER.merge(input, extensionRegistry, builder); } + /** + * Parse a text-format message from {@code input}. Extensions will be + * recognized if they are registered in {@code extensionRegistry}. + * + * @return the parsed message, guaranteed initialized + */ + public static <T extends Message> T parse( + final CharSequence input, + final ExtensionRegistry extensionRegistry, + final Class<T> protoClass) + throws ParseException { + Message.Builder builder = + Internal.getDefaultInstance(protoClass).newBuilderForType(); + merge(input, extensionRegistry, builder); + @SuppressWarnings("unchecked") + T output = (T) builder.build(); + return output; + } + /** * Parser for text-format proto2 instances. This class is thread-safe. @@ -1256,12 +1311,19 @@ public final class TextFormat { } private final boolean allowUnknownFields; + private final boolean allowUnknownEnumValues; private final SingularOverwritePolicy singularOverwritePolicy; + private TextFormatParseInfoTree.Builder parseInfoTreeBuilder; - private Parser(boolean allowUnknownFields, - SingularOverwritePolicy singularOverwritePolicy) { + private Parser( + boolean allowUnknownFields, + boolean allowUnknownEnumValues, + SingularOverwritePolicy singularOverwritePolicy, + TextFormatParseInfoTree.Builder parseInfoTreeBuilder) { this.allowUnknownFields = allowUnknownFields; + this.allowUnknownEnumValues = allowUnknownEnumValues; this.singularOverwritePolicy = singularOverwritePolicy; + this.parseInfoTreeBuilder = parseInfoTreeBuilder; } /** @@ -1276,8 +1338,10 @@ public final class TextFormat { */ public static class Builder { private boolean allowUnknownFields = false; + private boolean allowUnknownEnumValues = false; private SingularOverwritePolicy singularOverwritePolicy = SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES; + private TextFormatParseInfoTree.Builder parseInfoTreeBuilder = null; /** @@ -1288,8 +1352,18 @@ public final class TextFormat { return this; } + public Builder setParseInfoTreeBuilder( + TextFormatParseInfoTree.Builder parseInfoTreeBuilder) { + this.parseInfoTreeBuilder = parseInfoTreeBuilder; + return this; + } + public Parser build() { - return new Parser(allowUnknownFields, singularOverwritePolicy); + return new Parser( + allowUnknownFields, + allowUnknownEnumValues, + singularOverwritePolicy, + parseInfoTreeBuilder); } } @@ -1353,6 +1427,28 @@ public final class TextFormat { return text; } + // Check both unknown fields and unknown extensions and log warning messages + // or throw exceptions according to the flag. + private void checkUnknownFields(final List<String> unknownFields) + throws ParseException { + if (unknownFields.isEmpty()) { + return; + } + + StringBuilder msg = new StringBuilder("Input contains unknown fields and/or extensions:"); + for (String field : unknownFields) { + msg.append('\n').append(field); + } + + if (allowUnknownFields) { + logger.warning(msg.toString()); + } else { + String[] lineColumn = unknownFields.get(0).split(":"); + throw new ParseException(Integer.valueOf(lineColumn[0]), + Integer.valueOf(lineColumn[1]), msg.toString()); + } + } + /** * Parse a text-format message from {@code input} and merge the contents * into {@code builder}. Extensions will be recognized if they are @@ -1366,9 +1462,13 @@ public final class TextFormat { MessageReflection.BuilderAdapter target = new MessageReflection.BuilderAdapter(builder); + List<String> unknownFields = new ArrayList<String>(); + while (!tokenizer.atEnd()) { - mergeField(tokenizer, extensionRegistry, target); + mergeField(tokenizer, extensionRegistry, target, unknownFields); } + + checkUnknownFields(unknownFields); } @@ -1378,9 +1478,26 @@ public final class TextFormat { */ private void mergeField(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, - final MessageReflection.MergeTarget target) + final MessageReflection.MergeTarget target, + List<String> unknownFields) + throws ParseException { + mergeField(tokenizer, extensionRegistry, target, parseInfoTreeBuilder, + unknownFields); + } + + /** + * Parse a single field from {@code tokenizer} and merge it into + * {@code target}. + */ + private void mergeField(final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final MessageReflection.MergeTarget target, + TextFormatParseInfoTree.Builder parseTreeBuilder, + List<String> unknownFields) throws ParseException { FieldDescriptor field = null; + int startLine = tokenizer.getLine(); + int startColumn = tokenizer.getColumn(); final Descriptor type = target.getDescriptorForType(); ExtensionRegistry.ExtensionInfo extension = null; @@ -1397,13 +1514,15 @@ public final class TextFormat { extensionRegistry, name.toString()); if (extension == null) { - if (!allowUnknownFields) { - throw tokenizer.parseExceptionPreviousToken( - "Extension \"" + name + "\" not found in the ExtensionRegistry."); - } else { - logger.warning( - "Extension \"" + name + "\" not found in the ExtensionRegistry."); - } + unknownFields.add( + (tokenizer.getPreviousLine() + 1) + + ":" + + (tokenizer.getPreviousColumn() + 1) + + ":\t" + + type.getFullName() + + ".[" + + name + + "]"); } else { if (extension.descriptor.getContainingType() != type) { throw tokenizer.parseExceptionPreviousToken( @@ -1438,16 +1557,14 @@ public final class TextFormat { } if (field == null) { - if (!allowUnknownFields) { - throw tokenizer.unknownFieldParseExceptionPreviousToken( - name, - "Message type \"" + type.getFullName() - + "\" has no field named \"" + name + "\"."); - } else { - logger.warning( - "Message type \"" + type.getFullName() - + "\" has no field named \"" + name + "\"."); - } + unknownFields.add( + (tokenizer.getPreviousLine() + 1) + + ":" + + (tokenizer.getPreviousColumn() + 1) + + ":\t" + + type.getFullName() + + "." + + name); } } @@ -1472,22 +1589,24 @@ public final class TextFormat { // Handle potential ':'. if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { tokenizer.tryConsume(":"); // optional + if (parseTreeBuilder != null) { + TextFormatParseInfoTree.Builder childParseTreeBuilder = + parseTreeBuilder.getBuilderForSubMessageField(field); + consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, + childParseTreeBuilder, unknownFields); + } else { + consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, + parseTreeBuilder, unknownFields); + } } else { tokenizer.consume(":"); // required + consumeFieldValues(tokenizer, extensionRegistry, target, field, + extension, parseTreeBuilder, unknownFields); } - // Support specifying repeated field values as a comma-separated list. - // Ex."foo: [1, 2, 3]" - if (field.isRepeated() && tokenizer.tryConsume("[")) { - while (true) { - consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); - if (tokenizer.tryConsume("]")) { - // End of list. - break; - } - tokenizer.consume(","); - } - } else { - consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); + + if (parseTreeBuilder != null) { + parseTreeBuilder.setLocation( + field, TextFormatParseLocation.create(startLine, startColumn)); } // For historical reasons, fields may optionally be separated by commas or @@ -1498,6 +1617,45 @@ public final class TextFormat { } /** + * Parse a one or more field values from {@code tokenizer} and merge it into + * {@code builder}. + */ + private void consumeFieldValues( + final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final MessageReflection.MergeTarget target, + final FieldDescriptor field, + final ExtensionRegistry.ExtensionInfo extension, + final TextFormatParseInfoTree.Builder parseTreeBuilder, + List<String> unknownFields) + throws ParseException { + // Support specifying repeated field values as a comma-separated list. + // Ex."foo: [1, 2, 3]" + if (field.isRepeated() && tokenizer.tryConsume("[")) { + if (!tokenizer.tryConsume("]")) { // Allow "foo: []" to be treated as empty. + while (true) { + consumeFieldValue( + tokenizer, + extensionRegistry, + target, + field, + extension, + parseTreeBuilder, + unknownFields); + if (tokenizer.tryConsume("]")) { + // End of list. + break; + } + tokenizer.consume(","); + } + } + } else { + consumeFieldValue(tokenizer, extensionRegistry, target, field, + extension, parseTreeBuilder, unknownFields); + } + } + + /** * Parse a single field value from {@code tokenizer} and merge it into * {@code builder}. */ @@ -1506,7 +1664,9 @@ public final class TextFormat { final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, final FieldDescriptor field, - final ExtensionRegistry.ExtensionInfo extension) + final ExtensionRegistry.ExtensionInfo extension, + final TextFormatParseInfoTree.Builder parseTreeBuilder, + List<String> unknownFields) throws ParseException { Object value = null; @@ -1528,7 +1688,8 @@ public final class TextFormat { throw tokenizer.parseException( "Expected \"" + endToken + "\"."); } - mergeField(tokenizer, extensionRegistry, subField); + mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder, + unknownFields); } value = subField.finish(); @@ -1584,17 +1745,40 @@ public final class TextFormat { final int number = tokenizer.consumeInt32(); value = enumType.findValueByNumber(number); if (value == null) { - throw tokenizer.parseExceptionPreviousToken( - "Enum type \"" + enumType.getFullName() - + "\" has no value with number " + number + '.'); + String unknownValueMsg = + "Enum type \"" + + enumType.getFullName() + + "\" has no value with number " + + number + + '.'; + if (allowUnknownEnumValues) { + logger.warning(unknownValueMsg); + return; + } else { + throw tokenizer.parseExceptionPreviousToken( + "Enum type \"" + + enumType.getFullName() + + "\" has no value with number " + + number + + '.'); + } } } else { final String id = tokenizer.consumeIdentifier(); value = enumType.findValueByName(id); if (value == null) { - throw tokenizer.parseExceptionPreviousToken( - "Enum type \"" + enumType.getFullName() - + "\" has no value named \"" + id + "\"."); + String unknownValueMsg = + "Enum type \"" + + enumType.getFullName() + + "\" has no value named \"" + + id + + "\"."; + if (allowUnknownEnumValues) { + logger.warning(unknownValueMsg); + return; + } else { + throw tokenizer.parseExceptionPreviousToken(unknownValueMsg); + } } } @@ -1607,6 +1791,8 @@ public final class TextFormat { } if (field.isRepeated()) { + // TODO(b/29122459): If field.isMapField() and FORBID_SINGULAR_OVERWRITES mode, + // check for duplicate map keys here. target.addRepeatedField(field, value); } else if ((singularOverwritePolicy == SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES) @@ -1704,11 +1890,6 @@ public final class TextFormat { // Some of these methods are package-private because Descriptors.java uses // them. - private interface ByteSequence { - int size(); - byte byteAt(int offset); - } - /** * Escapes bytes in the format used in protocol buffer text format, which * is the same as the format used for C string literals. All bytes @@ -1717,74 +1898,15 @@ public final class TextFormat { * which no defined short-hand escape sequence is defined will be escaped * using 3-digit octal sequences. */ - public static String escapeBytes(final ByteSequence input) { - final StringBuilder builder = new StringBuilder(input.size()); - for (int i = 0; i < input.size(); i++) { - final byte b = input.byteAt(i); - switch (b) { - // Java does not recognize \a or \v, apparently. - case 0x07: builder.append("\\a"); break; - case '\b': builder.append("\\b"); break; - case '\f': builder.append("\\f"); break; - case '\n': builder.append("\\n"); break; - case '\r': builder.append("\\r"); break; - case '\t': builder.append("\\t"); break; - case 0x0b: builder.append("\\v"); break; - case '\\': builder.append("\\\\"); break; - case '\'': builder.append("\\\'"); break; - case '"' : builder.append("\\\""); break; - default: - // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are - // printable. Other byte values must be escaped. - if (b >= 0x20 && b <= 0x7e) { - builder.append((char) b); - } else { - builder.append('\\'); - builder.append((char) ('0' + ((b >>> 6) & 3))); - builder.append((char) ('0' + ((b >>> 3) & 7))); - builder.append((char) ('0' + (b & 7))); - } - break; - } - } - return builder.toString(); - } - - /** - * Escapes bytes in the format used in protocol buffer text format, which - * is the same as the format used for C string literals. All bytes - * that are not printable 7-bit ASCII characters are escaped, as well as - * backslash, single-quote, and double-quote characters. Characters for - * which no defined short-hand escape sequence is defined will be escaped - * using 3-digit octal sequences. - */ - public static String escapeBytes(final ByteString input) { - return escapeBytes(new ByteSequence() { - @Override - public int size() { - return input.size(); - } - @Override - public byte byteAt(int offset) { - return input.byteAt(offset); - } - }); + public static String escapeBytes(ByteString input) { + return TextFormatEscaper.escapeBytes(input); } /** * Like {@link #escapeBytes(ByteString)}, but used for byte array. */ - public static String escapeBytes(final byte[] input) { - return escapeBytes(new ByteSequence() { - @Override - public int size() { - return input.length; - } - @Override - public byte byteAt(int offset) { - return input[offset]; - } - }); + public static String escapeBytes(byte[] input) { + return TextFormatEscaper.escapeBytes(input); } /** @@ -1868,7 +1990,9 @@ public final class TextFormat { } } - return ByteString.copyFrom(result, 0, pos); + return result.length == pos + ? ByteString.wrap(result) // This reference has not been out of our control. + : ByteString.copyFrom(result, 0, pos); } /** @@ -1896,7 +2020,7 @@ public final class TextFormat { * Escape double quotes and backslashes in a String for unicode output of a message. */ public static String escapeDoubleQuotesAndBackslashes(final String input) { - return input.replace("\\", "\\\\").replace("\"", "\\\""); + return TextFormatEscaper.escapeDoubleQuotesAndBackslashes(input); } /** diff --git a/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java b/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java index e69de29b..da9ceadd 100644 --- a/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java @@ -0,0 +1,137 @@ +// 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; + +/** + * Provide text format escaping support for proto2 instances. + */ +final class TextFormatEscaper { + private TextFormatEscaper() {} + + private interface ByteSequence { + int size(); + byte byteAt(int offset); + } + + /** + * Escapes bytes in the format used in protocol buffer text format, which + * is the same as the format used for C string literals. All bytes + * that are not printable 7-bit ASCII characters are escaped, as well as + * backslash, single-quote, and double-quote characters. Characters for + * which no defined short-hand escape sequence is defined will be escaped + * using 3-digit octal sequences. + */ + static String escapeBytes(final ByteSequence input) { + final StringBuilder builder = new StringBuilder(input.size()); + for (int i = 0; i < input.size(); i++) { + final byte b = input.byteAt(i); + switch (b) { + // Java does not recognize \a or \v, apparently. + case 0x07: builder.append("\\a"); break; + case '\b': builder.append("\\b"); break; + case '\f': builder.append("\\f"); break; + case '\n': builder.append("\\n"); break; + case '\r': builder.append("\\r"); break; + case '\t': builder.append("\\t"); break; + case 0x0b: builder.append("\\v"); break; + case '\\': builder.append("\\\\"); break; + case '\'': builder.append("\\\'"); break; + case '"' : builder.append("\\\""); break; + default: + // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are + // printable. Other byte values must be escaped. + if (b >= 0x20 && b <= 0x7e) { + builder.append((char) b); + } else { + builder.append('\\'); + builder.append((char) ('0' + ((b >>> 6) & 3))); + builder.append((char) ('0' + ((b >>> 3) & 7))); + builder.append((char) ('0' + (b & 7))); + } + break; + } + } + return builder.toString(); + } + + /** + * Escapes bytes in the format used in protocol buffer text format, which + * is the same as the format used for C string literals. All bytes + * that are not printable 7-bit ASCII characters are escaped, as well as + * backslash, single-quote, and double-quote characters. Characters for + * which no defined short-hand escape sequence is defined will be escaped + * using 3-digit octal sequences. + */ + static String escapeBytes(final ByteString input) { + return escapeBytes(new ByteSequence() { + @Override + public int size() { + return input.size(); + } + @Override + public byte byteAt(int offset) { + return input.byteAt(offset); + } + }); + } + + /** + * Like {@link #escapeBytes(ByteString)}, but used for byte array. + */ + static String escapeBytes(final byte[] input) { + return escapeBytes(new ByteSequence() { + @Override + public int size() { + return input.length; + } + @Override + public byte byteAt(int offset) { + return input[offset]; + } + }); + } + + /** + * Like {@link #escapeBytes(ByteString)}, but escapes a text string. + * Non-ASCII characters are first encoded as UTF-8, then each byte is escaped + * individually as a 3-digit octal escape. Yes, it's weird. + */ + static String escapeText(final String input) { + return escapeBytes(ByteString.copyFromUtf8(input)); + } + + /** + * Escape double quotes and backslashes in a String for unicode output of a message. + */ + static String escapeDoubleQuotesAndBackslashes(final String input) { + return input.replace("\\", "\\\\").replace("\"", "\\\""); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java b/java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java new file mode 100644 index 00000000..0127ce92 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java @@ -0,0 +1,226 @@ +// 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 com.google.protobuf.Descriptors.FieldDescriptor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * Data structure which is populated with the locations of each field value parsed from the text. + * + * <p>The locations of primary fields values are retrieved by {@code getLocation} or + * {@code getLocations}. The locations of sub message values are within nested + * {@code TextFormatParseInfoTree}s and are retrieve by {@code getNestedTree} or + * {@code getNestedTrees}. + * + * <p>The {@code TextFormatParseInfoTree} is created by a Builder. + */ +public class TextFormatParseInfoTree { + + // Defines a mapping between each field's descriptor to the list of locations where + // its value(s) were was encountered. + private Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField; + + // Defines a mapping between a field's descriptor to a list of TextFormatParseInfoTrees for + // sub message location information. + Map<FieldDescriptor, List<TextFormatParseInfoTree>> subtreesFromField; + + /** + * Construct a {@code TextFormatParseInfoTree}. + * + * @param locationsFromField a map of fields to location in the source code + * @param subtreeBuildersFromField a map of fields to parse tree location information builders + */ + private TextFormatParseInfoTree( + Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField, + Map<FieldDescriptor, List<TextFormatParseInfoTree.Builder>> subtreeBuildersFromField) { + + // The maps are unmodifiable. The values in the maps are unmodifiable. + Map<FieldDescriptor, List<TextFormatParseLocation>> locs = + new HashMap<FieldDescriptor, List<TextFormatParseLocation>>(); + for (Entry<FieldDescriptor, List<TextFormatParseLocation>> kv : locationsFromField.entrySet()) { + locs.put(kv.getKey(), Collections.unmodifiableList(kv.getValue())); + } + this.locationsFromField = Collections.unmodifiableMap(locs); + + Map<FieldDescriptor, List<TextFormatParseInfoTree>> subs = + new HashMap<FieldDescriptor, List<TextFormatParseInfoTree>>(); + for (Entry<FieldDescriptor, List<Builder>> kv : subtreeBuildersFromField.entrySet()) { + List<TextFormatParseInfoTree> submessagesOfField = new ArrayList<TextFormatParseInfoTree>(); + for (Builder subBuilder : kv.getValue()) { + submessagesOfField.add(subBuilder.build()); + } + subs.put(kv.getKey(), Collections.unmodifiableList(submessagesOfField)); + } + this.subtreesFromField = Collections.unmodifiableMap(subs); + } + + /** + * Retrieve all the locations of a field. + * + * @param fieldDescriptor the the @{link FieldDescriptor} of the desired field + * @return a list of the locations of values of the field. If there are not values + * or the field doesn't exist, an empty list is returned. + */ + public List<TextFormatParseLocation> getLocations(final FieldDescriptor fieldDescriptor) { + List<TextFormatParseLocation> result = locationsFromField.get(fieldDescriptor); + return (result == null) ? Collections.<TextFormatParseLocation>emptyList() : result; + } + + /** + * Get the location in the source of a field's value. + * + * <p>Returns the {@link TextFormatParseLocation} for index-th value of the field in the parsed + * text. + * + * @param fieldDescriptor the @{link FieldDescriptor} of the desired field + * @param index the index of the value. + * @return the {@link TextFormatParseLocation} of the value + * @throws IllegalArgumentException index is out of range + */ + public TextFormatParseLocation getLocation(final FieldDescriptor fieldDescriptor, int index) { + return getFromList(getLocations(fieldDescriptor), index, fieldDescriptor); + } + + /** + * Retrieve a list of all the location information trees for a sub message field. + * + * @param fieldDescriptor the @{link FieldDescriptor} of the desired field + * @return A list of {@link TextFormatParseInfoTree} + */ + public List<TextFormatParseInfoTree> getNestedTrees(final FieldDescriptor fieldDescriptor) { + List<TextFormatParseInfoTree> result = subtreesFromField.get(fieldDescriptor); + return result == null ? Collections.<TextFormatParseInfoTree>emptyList() : result; + } + + /** + * Returns the parse info tree for the given field, which must be a message type. + * + * @param fieldDescriptor the @{link FieldDescriptor} of the desired sub message + * @param index the index of message value. + * @return the {@code ParseInfoTree} of the message value. {@code null} is returned if the field + * doesn't exist or the index is out of range. + * @throws IllegalArgumentException if index is out of range + */ + public TextFormatParseInfoTree getNestedTree(final FieldDescriptor fieldDescriptor, int index) { + return getFromList(getNestedTrees(fieldDescriptor), index, fieldDescriptor); + } + + /** + * Create a builder for a {@code ParseInfoTree}. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + private static <T> T getFromList(List<T> list, int index, FieldDescriptor fieldDescriptor) { + if (index >= list.size() || index < 0) { + throw new IllegalArgumentException(String.format("Illegal index field: %s, index %d", + fieldDescriptor == null ? "<null>" : fieldDescriptor.getName(), index)); + } + return list.get(index); + } + + /** + * Builder for a {@link TextFormatParseInfoTree}. + */ + public static class Builder { + + private Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField; + + // Defines a mapping between a field's descriptor to a list of ParseInfoTrees builders for + // sub message location information. + private Map<FieldDescriptor, List<Builder>> subtreeBuildersFromField; + + /** + * Create a root level {@ParseInfoTree} builder. + */ + private Builder() { + locationsFromField = new HashMap<FieldDescriptor, List<TextFormatParseLocation>>(); + subtreeBuildersFromField = new HashMap<FieldDescriptor, List<Builder>>(); + } + + /** + * Record the starting location of a single value for a field. + * + * @param fieldDescriptor the field + * @param location source code location information + */ + public Builder setLocation( + final FieldDescriptor fieldDescriptor, TextFormatParseLocation location) { + List<TextFormatParseLocation> fieldLocations = locationsFromField.get(fieldDescriptor); + if (fieldLocations == null) { + fieldLocations = new ArrayList<TextFormatParseLocation>(); + locationsFromField.put(fieldDescriptor, fieldLocations); + } + fieldLocations.add(location); + return this; + } + + /** + * Set for a sub message. + * + * <p>A new builder is created for a sub message. The builder that is returned is a new builder. + * The return is <em>not</em> the invoked {@code builder.getBuilderForSubMessageField}. + * + * @param fieldDescriptor the field whose value is the submessage + * @return a new Builder for the sub message + */ + public Builder getBuilderForSubMessageField(final FieldDescriptor fieldDescriptor) { + List<Builder> submessageBuilders = subtreeBuildersFromField.get(fieldDescriptor); + if (submessageBuilders == null) { + submessageBuilders = new ArrayList<Builder>(); + subtreeBuildersFromField.put(fieldDescriptor, submessageBuilders); + } + Builder subtreeBuilder = new Builder(); + submessageBuilders.add(subtreeBuilder); + return subtreeBuilder; + } + + /** + * Build the {@code TextFormatParseInfoTree}. + * + * @return the {@code TextFormatParseInfoTree} + */ + public TextFormatParseInfoTree build() { + return new TextFormatParseInfoTree(locationsFromField, subtreeBuildersFromField); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java b/java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java new file mode 100644 index 00000000..cce286e1 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java @@ -0,0 +1,104 @@ +// 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.util.Arrays; + +/** + * A location in the source code. + * + * <p>A location is the starting line number and starting column number. + */ +public final class TextFormatParseLocation { + + /** + * The empty location. + */ + public static final TextFormatParseLocation EMPTY = new TextFormatParseLocation(-1, -1); + + /** + * Create a location. + * + * @param line the starting line number + * @param column the starting column number + * @return a {@code ParseLocation} + */ + static TextFormatParseLocation create(int line, int column) { + if (line == -1 && column == -1) { + return EMPTY; + } + if (line < 0 || column < 0) { + throw new IllegalArgumentException( + String.format("line and column values must be >= 0: line %d, column: %d", line, column)); + } + return new TextFormatParseLocation(line, column); + } + + private final int line; + private final int column; + + private TextFormatParseLocation(int line, int column) { + this.line = line; + this.column = column; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + @Override + public String toString() { + return String.format("ParseLocation{line=%d, column=%d}", line, column); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof TextFormatParseLocation)) { + return false; + } + TextFormatParseLocation that = (TextFormatParseLocation) o; + return (this.line == that.getLine()) + && (this.column == that.getColumn()); + } + + @Override + public int hashCode() { + int[] values = {line, column}; + return Arrays.hashCode(values); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java index 7cd2250e..37d64633 100644 --- a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -31,7 +31,6 @@ package com.google.protobuf; import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -39,6 +38,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.TreeMap; @@ -57,7 +57,10 @@ import java.util.TreeMap; * @author kenton@google.com Kenton Varda */ public final class UnknownFieldSet implements MessageLite { - private UnknownFieldSet() {} + + private UnknownFieldSet() { + fields = null; + } /** Create a new {@link Builder}. */ public static Builder newBuilder() { @@ -76,20 +79,23 @@ public final class UnknownFieldSet implements MessageLite { public static UnknownFieldSet getDefaultInstance() { return defaultInstance; } + @Override public UnknownFieldSet getDefaultInstanceForType() { return defaultInstance; } private static final UnknownFieldSet defaultInstance = - new UnknownFieldSet(Collections.<Integer, Field>emptyMap()); + new UnknownFieldSet(Collections.<Integer, Field>emptyMap(), + Collections.<Integer, Field>emptyMap()); /** * Construct an {@code UnknownFieldSet} around the given map. The map is * expected to be immutable. */ - private UnknownFieldSet(final Map<Integer, Field> fields) { + UnknownFieldSet(final Map<Integer, Field> fields, + final Map<Integer, Field> fieldsDescending) { this.fields = fields; } - private Map<Integer, Field> fields; + private final Map<Integer, Field> fields; @Override @@ -126,9 +132,11 @@ public final class UnknownFieldSet implements MessageLite { } /** Serializes the set and writes it to {@code output}. */ + @Override public void writeTo(final CodedOutputStream output) throws IOException { for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { - entry.getValue().writeTo(entry.getKey(), output); + Field field = entry.getValue(); + field.writeTo(entry.getKey(), output); } } @@ -146,6 +154,7 @@ public final class UnknownFieldSet implements MessageLite { * Serializes the message to a {@code ByteString} and returns it. This is * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. */ + @Override public ByteString toByteString() { try { final ByteString.CodedBuilder out = @@ -163,6 +172,7 @@ public final class UnknownFieldSet implements MessageLite { * Serializes the message to a {@code byte} array and returns it. This is * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. */ + @Override public byte[] toByteArray() { try { final byte[] result = new byte[getSerializedSize()]; @@ -181,12 +191,14 @@ public final class UnknownFieldSet implements MessageLite { * Serializes the message and writes it to {@code output}. This is just a * trivial wrapper around {@link #writeTo(CodedOutputStream)}. */ + @Override public void writeTo(final OutputStream output) throws IOException { final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); writeTo(codedOutput); codedOutput.flush(); } + @Override public void writeDelimitedTo(OutputStream output) throws IOException { final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); codedOutput.writeRawVarint32(getSerializedSize()); @@ -195,6 +207,7 @@ public final class UnknownFieldSet implements MessageLite { } /** Get the number of bytes required to encode this set. */ + @Override public int getSerializedSize() { int result = 0; for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { @@ -215,10 +228,8 @@ public final class UnknownFieldSet implements MessageLite { } } - /** - * Get the number of bytes required to encode this set using - * {@code MessageSet} wire format. - */ + + /** Get the number of bytes required to encode this set using {@code MessageSet} wire format. */ public int getSerializedSizeAsMessageSet() { int result = 0; for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { @@ -228,6 +239,7 @@ public final class UnknownFieldSet implements MessageLite { return result; } + @Override public boolean isInitialized() { // UnknownFieldSets do not have required fields, so they are always // initialized. @@ -258,10 +270,12 @@ public final class UnknownFieldSet implements MessageLite { return newBuilder().mergeFrom(input).build(); } + @Override public Builder newBuilderForType() { return newBuilder(); } + @Override public Builder toBuilder() { return newBuilder().mergeFrom(this); } @@ -329,18 +343,21 @@ public final class UnknownFieldSet implements MessageLite { * in undefined behavior and can cause a {@code NullPointerException} to be * thrown. */ + @Override public UnknownFieldSet build() { - getFieldBuilder(0); // Force lastField to be built. + getFieldBuilder(0); // Force lastField to be built. final UnknownFieldSet result; if (fields.isEmpty()) { result = getDefaultInstance(); } else { - result = new UnknownFieldSet(Collections.unmodifiableMap(fields)); + Map<Integer, Field> descendingFields = null; + result = new UnknownFieldSet(Collections.unmodifiableMap(fields), descendingFields); } fields = null; return result; } + @Override public UnknownFieldSet buildPartial() { // No required fields, so this is the same as build(). return build(); @@ -349,10 +366,12 @@ public final class UnknownFieldSet implements MessageLite { @Override public Builder clone() { getFieldBuilder(0); // Force lastField to be built. + Map<Integer, Field> descendingFields = null; return UnknownFieldSet.newBuilder().mergeFrom( - new UnknownFieldSet(fields)); + new UnknownFieldSet(fields, descendingFields)); } + @Override public UnknownFieldSet getDefaultInstanceForType() { return UnknownFieldSet.getDefaultInstance(); } @@ -364,6 +383,7 @@ public final class UnknownFieldSet implements MessageLite { } /** Reset the builder to an empty set. */ + @Override public Builder clear() { reinitialize(); return this; @@ -487,6 +507,7 @@ public final class UnknownFieldSet implements MessageLite { * Parse an entire message from {@code input} and merge its fields into * this set. */ + @Override public Builder mergeFrom(final CodedInputStream input) throws IOException { while (true) { final int tag = input.readTag(); @@ -536,8 +557,8 @@ public final class UnknownFieldSet implements MessageLite { * set being built. This is just a small wrapper around * {@link #mergeFrom(CodedInputStream)}. */ - public Builder mergeFrom(final ByteString data) - throws InvalidProtocolBufferException { + @Override + public Builder mergeFrom(final ByteString data) throws InvalidProtocolBufferException { try { final CodedInputStream input = data.newCodedInput(); mergeFrom(input); @@ -557,8 +578,8 @@ public final class UnknownFieldSet implements MessageLite { * set being built. This is just a small wrapper around * {@link #mergeFrom(CodedInputStream)}. */ - public Builder mergeFrom(final byte[] data) - throws InvalidProtocolBufferException { + @Override + public Builder mergeFrom(final byte[] data) throws InvalidProtocolBufferException { try { final CodedInputStream input = CodedInputStream.newInstance(data); mergeFrom(input); @@ -578,6 +599,7 @@ public final class UnknownFieldSet implements MessageLite { * set being built. This is just a small wrapper around * {@link #mergeFrom(CodedInputStream)}. */ + @Override public Builder mergeFrom(final InputStream input) throws IOException { final CodedInputStream codedInput = CodedInputStream.newInstance(input); mergeFrom(codedInput); @@ -585,8 +607,8 @@ public final class UnknownFieldSet implements MessageLite { return this; } - public boolean mergeDelimitedFrom(InputStream input) - throws IOException { + @Override + public boolean mergeDelimitedFrom(InputStream input) throws IOException { final int firstByte = input.read(); if (firstByte == -1) { return false; @@ -597,30 +619,29 @@ public final class UnknownFieldSet implements MessageLite { return true; } - public boolean mergeDelimitedFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) throws IOException { + @Override + public boolean mergeDelimitedFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { // UnknownFieldSet has no extensions. return mergeDelimitedFrom(input); } - public Builder mergeFrom( - CodedInputStream input, - ExtensionRegistryLite extensionRegistry) throws IOException { + @Override + public Builder mergeFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { // UnknownFieldSet has no extensions. return mergeFrom(input); } - public Builder mergeFrom( - ByteString data, - ExtensionRegistryLite extensionRegistry) + @Override + public Builder mergeFrom(ByteString data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { // UnknownFieldSet has no extensions. return mergeFrom(data); } - public Builder mergeFrom(byte[] data, int off, int len) - throws InvalidProtocolBufferException { + @Override + public Builder mergeFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException { try { final CodedInputStream input = CodedInputStream.newInstance(data, off, len); @@ -636,29 +657,37 @@ public final class UnknownFieldSet implements MessageLite { } } - public Builder mergeFrom( - byte[] data, - ExtensionRegistryLite extensionRegistry) + @Override + public Builder mergeFrom(byte[] data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { // UnknownFieldSet has no extensions. return mergeFrom(data); } - public Builder mergeFrom( - byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) + @Override + public Builder mergeFrom(byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { // UnknownFieldSet has no extensions. return mergeFrom(data, off, len); } - public Builder mergeFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) throws IOException { + @Override + public Builder mergeFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { // UnknownFieldSet has no extensions. return mergeFrom(input); } + @Override + public Builder mergeFrom(MessageLite m) { + if (m instanceof UnknownFieldSet) { + return mergeFrom((UnknownFieldSet) m); + } + throw new IllegalArgumentException( + "mergeFrom(MessageLite) can only merge messages of the same type."); + } + + @Override public boolean isInitialized() { // UnknownFieldSets do not have required fields, so they are always // initialized. @@ -816,9 +845,10 @@ public final class UnknownFieldSet implements MessageLite { } } + /** - * Get the number of bytes required to encode this field, including field - * number, using {@code MessageSet} wire format. + * Get the number of bytes required to encode this field, including field number, using {@code + * MessageSet} wire format. */ public int getSerializedSizeAsMessageSetExtension(final int fieldNumber) { int result = 0; @@ -987,6 +1017,7 @@ public final class UnknownFieldSet implements MessageLite { * Parser to implement MessageLite interface. */ public static final class Parser extends AbstractParser<UnknownFieldSet> { + @Override public UnknownFieldSet parsePartialFrom( CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { @@ -996,7 +1027,7 @@ public final class UnknownFieldSet implements MessageLite { } catch (InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(builder.buildPartial()); } catch (IOException e) { - throw new InvalidProtocolBufferException(e.getMessage()) + throw new InvalidProtocolBufferException(e) .setUnfinishedMessage(builder.buildPartial()); } return builder.buildPartial(); @@ -1004,6 +1035,7 @@ public final class UnknownFieldSet implements MessageLite { } private static final Parser PARSER = new Parser(); + @Override public final Parser getParserForType() { return PARSER; } diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java index 435ad4d4..f0b919ad 100644 --- a/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java @@ -61,15 +61,6 @@ public final class UnknownFieldSetLite { public static UnknownFieldSetLite getDefaultInstance() { return DEFAULT_INSTANCE; } - - /** - * Returns an empty {@code UnknownFieldSetLite.Builder}. - * - * <p>For use by generated code only. - */ - public static Builder newBuilder() { - return new Builder(); - } /** * Returns a new mutable instance. @@ -90,7 +81,7 @@ public final class UnknownFieldSetLite { System.arraycopy(second.objects, 0, objects, first.count, second.count); return new UnknownFieldSetLite(count, tags, objects, true /* isMutable */); } - + /** * The number of elements in the set. */ @@ -185,6 +176,42 @@ public final class UnknownFieldSetLite { } /** + * Serializes the set and writes it to {@code output} using {@code MessageSet} wire format. + * + * <p>For use by generated code only. + */ + public void writeAsMessageSetTo(CodedOutputStream output) throws IOException { + for (int i = 0; i < count; i++) { + int fieldNumber = WireFormat.getTagFieldNumber(tags[i]); + output.writeRawMessageSetExtension(fieldNumber, (ByteString) objects[i]); + } + } + + + /** + * Get the number of bytes required to encode this field, including field number, using {@code + * MessageSet} wire format. + */ + public int getSerializedSizeAsMessageSet() { + int size = memoizedSerializedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < count; i++) { + int tag = tags[i]; + int fieldNumber = WireFormat.getTagFieldNumber(tag); + size += CodedOutputStream.computeRawMessageSetExtensionSize( + fieldNumber, (ByteString) objects[i]); + } + + memoizedSerializedSize = size; + + return size; + } + + /** * Get the number of bytes required to encode this set. * * <p>For use by generated code only. @@ -225,6 +252,24 @@ public final class UnknownFieldSetLite { return size; } + + private static boolean equals(int[] tags1, int[] tags2, int count) { + for (int i = 0; i < count; ++i) { + if (tags1[i] != tags2[i]) { + return false; + } + } + return true; + } + + private static boolean equals(Object[] objects1, Object[] objects2, int count) { + for (int i = 0; i < count; ++i) { + if (!objects1[i].equals(objects2[i])) { + return false; + } + } + return true; + } @Override public boolean equals(Object obj) { @@ -242,27 +287,59 @@ public final class UnknownFieldSetLite { UnknownFieldSetLite other = (UnknownFieldSetLite) obj; if (count != other.count - // TODO(dweis): Only have to compare up to count but at worst 2x worse than we need to do. - || !Arrays.equals(tags, other.tags) - || !Arrays.deepEquals(objects, other.objects)) { + || !equals(tags, other.tags, count) + || !equals(objects, other.objects, count)) { return false; } return true; } + private static int hashCode(int[] tags, int count) { + int hashCode = 17; + for (int i = 0; i < count; ++i) { + hashCode = 31 * hashCode + tags[i]; + } + return hashCode; + } + + private static int hashCode(Object[] objects, int count) { + int hashCode = 17; + for (int i = 0; i < count; ++i) { + hashCode = 31 * hashCode + objects[i].hashCode(); + } + return hashCode; + } + @Override public int hashCode() { int hashCode = 17; - + hashCode = 31 * hashCode + count; - hashCode = 31 * hashCode + Arrays.hashCode(tags); - hashCode = 31 * hashCode + Arrays.deepHashCode(objects); - + hashCode = 31 * hashCode + hashCode(tags, count); + hashCode = 31 * hashCode + hashCode(objects, count); + return hashCode; } - private void storeField(int tag, Object value) { + /** + * Prints a String representation of the unknown field set. + * + * <p>For use by generated code only. + * + * @param buffer the buffer to write to + * @param indent the number of spaces the fields should be indented by + */ + final void printWithIndent(StringBuilder buffer, int indent) { + for (int i = 0; i < count; i++) { + int fieldNumber = WireFormat.getTagFieldNumber(tags[i]); + MessageLiteToString.printField(buffer, indent, String.valueOf(fieldNumber), objects[i]); + } + } + + // Package private for unsafe experimental runtime. + void storeField(int tag, Object value) { + checkMutable(); ensureCapacity(); tags[count] = tag; @@ -369,90 +446,4 @@ public final class UnknownFieldSetLite { } return this; } - - /** - * Builder for {@link UnknownFieldSetLite}s. - * - * <p>Use {@link UnknownFieldSet#newBuilder()} to construct a {@code Builder}. - * - * <p>For use by generated code only. - */ - // TODO(dweis): Update the mutable API to no longer need this builder and delete. - public static final class Builder { - - private UnknownFieldSetLite set; - - private Builder() { - this.set = null; - } - - /** - * Ensures internal state is initialized for use. - */ - private void ensureNotBuilt() { - if (set == null) { - set = new UnknownFieldSetLite(); - } - - set.checkMutable(); - } - - /** - * Parse a single field from {@code input} and merge it into this set. - * - * <p>For use by generated code only. - * - * @param tag The field's tag number, which was already parsed. - * @return {@code false} if the tag is an end group tag. - */ - boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException { - ensureNotBuilt(); - return set.mergeFieldFrom(tag, input); - } - - /** - * Convenience method for merging a new field containing a single varint - * value. This is used in particular when an unknown enum value is - * encountered. - * - * <p>For use by generated code only. - */ - Builder mergeVarintField(int fieldNumber, int value) { - ensureNotBuilt(); - set.mergeVarintField(fieldNumber, value); - return this; - } - - /** - * Convenience method for merging a length-delimited field. - * - * <p>For use by generated code only. - */ - public Builder mergeLengthDelimitedField(final int fieldNumber, final ByteString value) { - ensureNotBuilt(); - set.mergeLengthDelimitedField(fieldNumber, value); - return this; - } - - /** - * Build the {@link UnknownFieldSetLite} and return it. - * - * <p>Once {@code build()} has been called, the {@code Builder} will no - * longer be usable. Calling any method after {@code build()} will result - * in undefined behavior and can cause an - * {@code UnsupportedOperationException} to be thrown. - * - * <p>For use by generated code only. - */ - public UnknownFieldSetLite build() { - if (set == null) { - return DEFAULT_INSTANCE; - } - - set.checkMutable(); - set.makeImmutable(); - - return set; - } - } } diff --git a/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java b/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java index 5257c5a2..30e87911 100644 --- a/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java +++ b/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java @@ -68,42 +68,42 @@ public class UnmodifiableLazyStringList extends AbstractList<String> return list.size(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public ByteString getByteString(int index) { return list.getByteString(index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void add(ByteString element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void set(int index, ByteString element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean addAllByteString(Collection<? extends ByteString> element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public byte[] getByteArray(int index) { return list.getByteArray(index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void add(byte[] element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void set(int index, byte[] element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean addAllByteArray(Collection<byte[]> element) { throw new UnsupportedOperationException(); } @@ -113,47 +113,47 @@ public class UnmodifiableLazyStringList extends AbstractList<String> return new ListIterator<String>() { ListIterator<String> iter = list.listIterator(index); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasNext() { return iter.hasNext(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public String next() { return iter.next(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasPrevious() { return iter.hasPrevious(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public String previous() { return iter.previous(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int nextIndex() { return iter.nextIndex(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int previousIndex() { return iter.previousIndex(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void remove() { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void set(String o) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void add(String o) { throw new UnsupportedOperationException(); } @@ -165,45 +165,45 @@ public class UnmodifiableLazyStringList extends AbstractList<String> return new Iterator<String>() { Iterator<String> iter = list.iterator(); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasNext() { return iter.hasNext(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public String next() { return iter.next(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public List<?> getUnderlyingElements() { // The returned value is already unmodifiable. return list.getUnderlyingElements(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void mergeFrom(LazyStringList other) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public List<byte[]> asByteArrayList() { return Collections.unmodifiableList(list.asByteArrayList()); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public List<ByteString> asByteStringList() { return Collections.unmodifiableList(list.asByteStringList()); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public LazyStringList getUnmodifiableView() { return this; } diff --git a/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java b/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java index f443ee39..878c7758 100644 --- a/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java +++ b/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java @@ -30,6 +30,7 @@ package com.google.protobuf; +import java.io.IOException; import java.nio.ByteBuffer; /** @@ -41,6 +42,23 @@ import java.nio.ByteBuffer; * guaranteed that the buffer backing the {@link ByteString} will never change! Mutation of a * {@link ByteString} can lead to unexpected and undesirable consequences in your application, * and will likely be difficult to debug. Proceed with caution! + * + * <p>This can have a number of significant side affects that have + * spooky-action-at-a-distance-like behavior. In particular, if the bytes value changes out from + * under a Protocol Buffer: + * <ul> + * <li>serialization may throw + * <li>serialization may succeed but the wrong bytes may be written out + * <li>messages are no longer threadsafe + * <li>hashCode may be incorrect + * <ul> + * <li>can result in a permanent memory leak when used as a key in a long-lived HashMap + * <li> the semantics of many programs may be violated if this is the case + * </ul> + * </ul> + * Each of these issues will occur in parts of the code base that are entirely distinct from the + * parts of the code base modifying the buffer. In fact, both parts of the code base may be correct + * - it is the bridging with the unsafe operations that was in error! */ @ExperimentalApi public final class UnsafeByteOperations { @@ -49,15 +67,54 @@ public final class UnsafeByteOperations { /** * An unsafe operation that returns a {@link ByteString} that is backed by the provided buffer. * - * @param buffer the Java NIO buffer to be wrapped. - * @return a {@link ByteString} backed by the provided buffer. + * @param buffer the buffer to be wrapped + * @return a {@link ByteString} backed by the provided buffer + */ + public static ByteString unsafeWrap(byte[] buffer) { + return ByteString.wrap(buffer); + } + + /** + * An unsafe operation that returns a {@link ByteString} that is backed by a subregion of the + * provided buffer. + * + * @param buffer the buffer to be wrapped + * @param offset the offset of the wrapped region + * @param length the number of bytes of the wrapped region + * @return a {@link ByteString} backed by the provided buffer + */ + public static ByteString unsafeWrap(byte[] buffer, int offset, int length) { + return ByteString.wrap(buffer, offset, length); + } + + /** + * An unsafe operation that returns a {@link ByteString} that is backed by the provided buffer. + * + * @param buffer the Java NIO buffer to be wrapped + * @return a {@link ByteString} backed by the provided buffer */ public static ByteString unsafeWrap(ByteBuffer buffer) { - if (buffer.hasArray()) { - final int offset = buffer.arrayOffset(); - return ByteString.wrap(buffer.array(), offset + buffer.position(), buffer.remaining()); - } else { - return new NioByteString(buffer); - } + return ByteString.wrap(buffer); + } + + /** + * Writes the given {@link ByteString} to the provided {@link ByteOutput}. Calling this method may + * result in multiple operations on the target {@link ByteOutput} + * (i.e. for roped {@link ByteString}s). + * + * <p>This method exposes the internal backing buffer(s) of the {@link ByteString} to the {@link + * ByteOutput} in order to avoid additional copying overhead. It would be possible for a malicious + * {@link ByteOutput} to corrupt the {@link ByteString}. Use with caution! + * + * <p> NOTE: The {@link ByteOutput} <strong>MUST NOT</strong> modify the provided buffers. Doing + * so may result in corrupted data, which would be difficult to debug. + * + * @param bytes the {@link ByteString} to be written + * @param output the output to receive the bytes + * @throws IOException if an I/O error occurs + */ + public static void unsafeWriteTo(ByteString bytes, ByteOutput output) throws IOException { + bytes.writeTo(output); } + } diff --git a/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java b/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java new file mode 100644 index 00000000..d84ef3c5 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java @@ -0,0 +1,574 @@ +// 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.lang.reflect.Field; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** Utility class for working with unsafe operations. */ +final class UnsafeUtil { + private static final Logger logger = Logger.getLogger(UnsafeUtil.class.getName()); + private static final sun.misc.Unsafe UNSAFE = getUnsafe(); + private static final MemoryAccessor MEMORY_ACCESSOR = getMemoryAccessor(); + private static final boolean HAS_UNSAFE_BYTEBUFFER_OPERATIONS = + supportsUnsafeByteBufferOperations(); + private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = supportsUnsafeArrayOperations(); + + private static final long BYTE_ARRAY_BASE_OFFSET = arrayBaseOffset(byte[].class); + // Micro-optimization: we can assume a scale of 1 and skip the multiply + // private static final long BYTE_ARRAY_INDEX_SCALE = 1; + + private static final long BOOLEAN_ARRAY_BASE_OFFSET = arrayBaseOffset(boolean[].class); + private static final long BOOLEAN_ARRAY_INDEX_SCALE = arrayIndexScale(boolean[].class); + + private static final long INT_ARRAY_BASE_OFFSET = arrayBaseOffset(int[].class); + private static final long INT_ARRAY_INDEX_SCALE = arrayIndexScale(int[].class); + + private static final long LONG_ARRAY_BASE_OFFSET = arrayBaseOffset(long[].class); + private static final long LONG_ARRAY_INDEX_SCALE = arrayIndexScale(long[].class); + + private static final long FLOAT_ARRAY_BASE_OFFSET = arrayBaseOffset(float[].class); + private static final long FLOAT_ARRAY_INDEX_SCALE = arrayIndexScale(float[].class); + + private static final long DOUBLE_ARRAY_BASE_OFFSET = arrayBaseOffset(double[].class); + private static final long DOUBLE_ARRAY_INDEX_SCALE = arrayIndexScale(double[].class); + + private static final long OBJECT_ARRAY_BASE_OFFSET = arrayBaseOffset(Object[].class); + private static final long OBJECT_ARRAY_INDEX_SCALE = arrayIndexScale(Object[].class); + + private static final long BUFFER_ADDRESS_OFFSET = fieldOffset(bufferAddressField()); + + private UnsafeUtil() {} + + static boolean hasUnsafeArrayOperations() { + return HAS_UNSAFE_ARRAY_OPERATIONS; + } + + static boolean hasUnsafeByteBufferOperations() { + return HAS_UNSAFE_BYTEBUFFER_OPERATIONS; + } + + + static long objectFieldOffset(Field field) { + return MEMORY_ACCESSOR.objectFieldOffset(field); + } + + private static int arrayBaseOffset(Class<?> clazz) { + return HAS_UNSAFE_ARRAY_OPERATIONS ? MEMORY_ACCESSOR.arrayBaseOffset(clazz) : -1; + } + + private static int arrayIndexScale(Class<?> clazz) { + return HAS_UNSAFE_ARRAY_OPERATIONS ? MEMORY_ACCESSOR.arrayIndexScale(clazz) : -1; + } + + static byte getByte(Object target, long offset) { + return MEMORY_ACCESSOR.getByte(target, offset); + } + + static void putByte(Object target, long offset, byte value) { + MEMORY_ACCESSOR.putByte(target, offset, value); + } + + static int getInt(Object target, long offset) { + return MEMORY_ACCESSOR.getInt(target, offset); + } + + static void putInt(Object target, long offset, int value) { + MEMORY_ACCESSOR.putInt(target, offset, value); + } + + static long getLong(Object target, long offset) { + return MEMORY_ACCESSOR.getLong(target, offset); + } + + static void putLong(Object target, long offset, long value) { + MEMORY_ACCESSOR.putLong(target, offset, value); + } + + static boolean getBoolean(Object target, long offset) { + return MEMORY_ACCESSOR.getBoolean(target, offset); + } + + static void putBoolean(Object target, long offset, boolean value) { + MEMORY_ACCESSOR.putBoolean(target, offset, value); + } + + static float getFloat(Object target, long offset) { + return MEMORY_ACCESSOR.getFloat(target, offset); + } + + static void putFloat(Object target, long offset, float value) { + MEMORY_ACCESSOR.putFloat(target, offset, value); + } + + static double getDouble(Object target, long offset) { + return MEMORY_ACCESSOR.getDouble(target, offset); + } + + static void putDouble(Object target, long offset, double value) { + MEMORY_ACCESSOR.putDouble(target, offset, value); + } + + static Object getObject(Object target, long offset) { + return MEMORY_ACCESSOR.getObject(target, offset); + } + + static byte getByte(byte[] target, long index) { + return MEMORY_ACCESSOR.getByte(target, BYTE_ARRAY_BASE_OFFSET + index); + } + + static void putByte(byte[] target, long index, byte value) { + MEMORY_ACCESSOR.putByte(target, BYTE_ARRAY_BASE_OFFSET + index, value); + } + + static int getInt(int[] target, long index) { + return MEMORY_ACCESSOR.getInt(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE)); + } + + static void putInt(int[] target, long index, int value) { + MEMORY_ACCESSOR.putInt(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE), value); + } + + static long getLong(long[] target, long index) { + return MEMORY_ACCESSOR.getLong( + target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE)); + } + + static void putLong(long[] target, long index, long value) { + MEMORY_ACCESSOR.putLong( + target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE), value); + } + + static boolean getBoolean(boolean[] target, long index) { + return MEMORY_ACCESSOR.getBoolean( + target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE)); + } + + static void putBoolean(boolean[] target, long index, boolean value) { + MEMORY_ACCESSOR.putBoolean( + target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE), value); + } + + static float getFloat(float[] target, long index) { + return MEMORY_ACCESSOR.getFloat( + target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE)); + } + + static void putFloat(float[] target, long index, float value) { + MEMORY_ACCESSOR.putFloat( + target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE), value); + } + + static double getDouble(double[] target, long index) { + return MEMORY_ACCESSOR.getDouble( + target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE)); + } + + static void putDouble(double[] target, long index, double value) { + MEMORY_ACCESSOR.putDouble( + target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE), value); + } + + static Object getObject(Object[] target, long index) { + return MEMORY_ACCESSOR.getObject( + target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE)); + } + + static void putObject(Object[] target, long index, Object value) { + MEMORY_ACCESSOR.putObject( + target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE), value); + } + + static void copyMemory(byte[] src, long srcIndex, long targetOffset, long length) { + MEMORY_ACCESSOR.copyMemory(src, srcIndex, targetOffset, length); + } + + static void copyMemory(long srcOffset, byte[] target, long targetIndex, long length) { + MEMORY_ACCESSOR.copyMemory(srcOffset, target, targetIndex, length); + } + + static void copyMemory(byte[] src, long srcIndex, byte[] target, long targetIndex, long length) { + System.arraycopy(src, (int) srcIndex, target, (int) targetIndex, (int) length); + } + + static byte getByte(long address) { + return MEMORY_ACCESSOR.getByte(address); + } + + static void putByte(long address, byte value) { + MEMORY_ACCESSOR.putByte(address, value); + } + + static int getInt(long address) { + return MEMORY_ACCESSOR.getInt(address); + } + + static void putInt(long address, int value) { + MEMORY_ACCESSOR.putInt(address, value); + } + + static long getLong(long address) { + return MEMORY_ACCESSOR.getLong(address); + } + + static void putLong(long address, long value) { + MEMORY_ACCESSOR.putLong(address, value); + } + + /** + * Gets the offset of the {@code address} field of the given direct {@link ByteBuffer}. + */ + static long addressOffset(ByteBuffer buffer) { + return MEMORY_ACCESSOR.getLong(buffer, BUFFER_ADDRESS_OFFSET); + } + + static Object getStaticObject(Field field) { + return MEMORY_ACCESSOR.getStaticObject(field); + } + + /** + * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this platform. + */ + static sun.misc.Unsafe getUnsafe() { + sun.misc.Unsafe unsafe = null; + try { + unsafe = + AccessController.doPrivileged( + new PrivilegedExceptionAction<sun.misc.Unsafe>() { + @Override + public sun.misc.Unsafe run() throws Exception { + Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class; + + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } + } + // The sun.misc.Unsafe field does not exist. + return null; + } + }); + } catch (Throwable e) { + // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError + // for Unsafe. + } + return unsafe; + } + + /** Get a {@link MemoryAccessor} appropriate for the platform, or null if not supported. */ + private static MemoryAccessor getMemoryAccessor() { + if (UNSAFE == null) { + return null; + } + return new JvmMemoryAccessor(UNSAFE); + } + + /** Indicates whether or not unsafe array operations are supported on this platform. */ + private static boolean supportsUnsafeArrayOperations() { + if (UNSAFE == null) { + return false; + } + try { + Class<?> clazz = UNSAFE.getClass(); + clazz.getMethod("objectFieldOffset", Field.class); + clazz.getMethod("arrayBaseOffset", Class.class); + clazz.getMethod("arrayIndexScale", Class.class); + clazz.getMethod("getInt", Object.class, long.class); + clazz.getMethod("putInt", Object.class, long.class, int.class); + clazz.getMethod("getLong", Object.class, long.class); + clazz.getMethod("putLong", Object.class, long.class, long.class); + clazz.getMethod("getObject", Object.class, long.class); + clazz.getMethod("putObject", Object.class, long.class, Object.class); + clazz.getMethod("getByte", Object.class, long.class); + clazz.getMethod("putByte", Object.class, long.class, byte.class); + clazz.getMethod("getBoolean", Object.class, long.class); + clazz.getMethod("putBoolean", Object.class, long.class, boolean.class); + clazz.getMethod("getFloat", Object.class, long.class); + clazz.getMethod("putFloat", Object.class, long.class, float.class); + clazz.getMethod("getDouble", Object.class, long.class); + clazz.getMethod("putDouble", Object.class, long.class, double.class); + + return true; + } catch (Throwable e) { + logger.log( + Level.WARNING, + "platform method missing - proto runtime falling back to safer methods: " + e); + } + return false; + } + + private static boolean supportsUnsafeByteBufferOperations() { + if (UNSAFE == null) { + return false; + } + try { + Class<?> clazz = UNSAFE.getClass(); + // Methods for getting direct buffer address. + clazz.getMethod("objectFieldOffset", Field.class); + clazz.getMethod("getLong", Object.class, long.class); + + if (bufferAddressField() == null) { + return false; + } + + clazz.getMethod("getByte", long.class); + clazz.getMethod("putByte", long.class, byte.class); + clazz.getMethod("getInt", long.class); + clazz.getMethod("putInt", long.class, int.class); + clazz.getMethod("getLong", long.class); + clazz.getMethod("putLong", long.class, long.class); + clazz.getMethod("copyMemory", long.class, long.class, long.class); + clazz.getMethod("copyMemory", Object.class, long.class, Object.class, long.class, long.class); + return true; + } catch (Throwable e) { + logger.log( + Level.WARNING, + "platform method missing - proto runtime falling back to safer methods: " + e); + } + return false; + } + + + /** Finds the address field within a direct {@link Buffer}. */ + private static Field bufferAddressField() { + Field field = field(Buffer.class, "address"); + return field != null && field.getType() == long.class ? field : null; + } + + /** Finds the value field within a {@link String}. */ + private static Field stringValueField() { + Field field = field(String.class, "value"); + return field != null && field.getType() == char[].class ? field : null; + } + + /** + * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not + * available. + */ + private static long fieldOffset(Field field) { + return field == null || MEMORY_ACCESSOR == null ? -1 : MEMORY_ACCESSOR.objectFieldOffset(field); + } + + /** + * Gets the field with the given name within the class, or {@code null} if not found. If found, + * the field is made accessible. + */ + private static Field field(Class<?> clazz, String fieldName) { + Field field; + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (Throwable t) { + // Failed to access the fields. + field = null; + } + return field; + } + + private abstract static class MemoryAccessor { + + sun.misc.Unsafe unsafe; + + MemoryAccessor(sun.misc.Unsafe unsafe) { + this.unsafe = unsafe; + } + + public final long objectFieldOffset(Field field) { + return unsafe.objectFieldOffset(field); + } + + public abstract byte getByte(Object target, long offset); + + public abstract void putByte(Object target, long offset, byte value); + + public final int getInt(Object target, long offset) { + return unsafe.getInt(target, offset); + } + + public final void putInt(Object target, long offset, int value) { + unsafe.putInt(target, offset, value); + } + + public final long getLong(Object target, long offset) { + return unsafe.getLong(target, offset); + } + + public final void putLong(Object target, long offset, long value) { + unsafe.putLong(target, offset, value); + } + + public abstract boolean getBoolean(Object target, long offset); + + public abstract void putBoolean(Object target, long offset, boolean value); + + public abstract float getFloat(Object target, long offset); + + public abstract void putFloat(Object target, long offset, float value); + + public abstract double getDouble(Object target, long offset); + + public abstract void putDouble(Object target, long offset, double value); + + public final Object getObject(Object target, long offset) { + return unsafe.getObject(target, offset); + } + + public final void putObject(Object target, long offset, Object value) { + unsafe.putObject(target, offset, value); + } + + public final int arrayBaseOffset(Class<?> clazz) { + return unsafe.arrayBaseOffset(clazz); + } + + public final int arrayIndexScale(Class<?> clazz) { + return unsafe.arrayIndexScale(clazz); + } + + public abstract byte getByte(long address); + + public abstract void putByte(long address, byte value); + + public abstract int getInt(long address); + + public abstract void putInt(long address, int value); + + public abstract long getLong(long address); + + public abstract void putLong(long address, long value); + + public abstract Object getStaticObject(Field field); + + public abstract void copyMemory(long srcOffset, byte[] target, long targetIndex, long length); + + public abstract void copyMemory(byte[] src, long srcIndex, long targetOffset, long length); + } + + private static final class JvmMemoryAccessor extends MemoryAccessor { + + JvmMemoryAccessor(sun.misc.Unsafe unsafe) { + super(unsafe); + } + + @Override + public byte getByte(long address) { + return unsafe.getByte(address); + } + + @Override + public void putByte(long address, byte value) { + unsafe.putByte(address, value); + } + + @Override + public int getInt(long address) { + return unsafe.getInt(address); + } + + @Override + public void putInt(long address, int value) { + unsafe.putInt(address, value); + } + + @Override + public long getLong(long address) { + return unsafe.getLong(address); + } + + @Override + public void putLong(long address, long value) { + unsafe.putLong(address, value); + } + + @Override + public byte getByte(Object target, long offset) { + return unsafe.getByte(target, offset); + } + + @Override + public void putByte(Object target, long offset, byte value) { + unsafe.putByte(target, offset, value); + } + + @Override + public boolean getBoolean(Object target, long offset) { + return unsafe.getBoolean(target, offset); + } + + @Override + public void putBoolean(Object target, long offset, boolean value) { + unsafe.putBoolean(target, offset, value); + } + + @Override + public float getFloat(Object target, long offset) { + return unsafe.getFloat(target, offset); + } + + @Override + public void putFloat(Object target, long offset, float value) { + unsafe.putFloat(target, offset, value); + } + + @Override + public double getDouble(Object target, long offset) { + return unsafe.getDouble(target, offset); + } + + @Override + public void putDouble(Object target, long offset, double value) { + unsafe.putDouble(target, offset, value); + } + + @Override + public void copyMemory(long srcOffset, byte[] target, long targetIndex, long length) { + unsafe.copyMemory(null, srcOffset, target, BYTE_ARRAY_BASE_OFFSET + targetIndex, length); + } + + @Override + public void copyMemory(byte[] src, long srcIndex, long targetOffset, long length) { + unsafe.copyMemory(src, BYTE_ARRAY_BASE_OFFSET + srcIndex, null, targetOffset, length); + } + + @Override + public Object getStaticObject(Field field) { + return getObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field)); + } + } + +} diff --git a/java/core/src/main/java/com/google/protobuf/Utf8.java b/java/core/src/main/java/com/google/protobuf/Utf8.java index 48c7e9e6..de75fe6b 100644 --- a/java/core/src/main/java/com/google/protobuf/Utf8.java +++ b/java/core/src/main/java/com/google/protobuf/Utf8.java @@ -30,6 +30,20 @@ package com.google.protobuf; +import static com.google.protobuf.UnsafeUtil.addressOffset; +import static com.google.protobuf.UnsafeUtil.hasUnsafeArrayOperations; +import static com.google.protobuf.UnsafeUtil.hasUnsafeByteBufferOperations; +import static java.lang.Character.MAX_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; +import static java.lang.Character.MIN_LOW_SURROGATE; +import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT; +import static java.lang.Character.MIN_SURROGATE; +import static java.lang.Character.isSurrogatePair; +import static java.lang.Character.toCodePoint; + +import java.nio.ByteBuffer; +import java.util.Arrays; + /** * A set of low-level, high-performance static utility methods related * to the UTF-8 character encoding. This class has no dependencies @@ -64,9 +78,23 @@ package com.google.protobuf; * * @author martinrb@google.com (Martin Buchholz) */ +// TODO(nathanmittler): Copy changes in this class back to Guava final class Utf8 { - private Utf8() {} - + + /** + * UTF-8 is a runtime hot spot so we attempt to provide heavily optimized implementations + * depending on what is available on the platform. The processor is the platform-optimized + * delegate for which all methods are delegated directly to. + */ + private static final Processor processor = + UnsafeProcessor.isAvailable() ? new UnsafeProcessor() : new SafeProcessor(); + + /** + * A mask used when performing unsafe reads to determine if a long value contains any non-ASCII + * characters (i.e. any byte >= 0x80). + */ + private static final long ASCII_MASK_LONG = 0x8080808080808080L; + /** * Maximum number of bytes per Java UTF-16 char in UTF-8. * @see java.nio.charset.CharsetEncoder#maxBytesPerChar() @@ -85,6 +113,18 @@ final class Utf8 { */ public static final int MALFORMED = -1; + /** + * Used by {@code Unsafe} UTF-8 string validation logic to determine the minimum string length + * above which to employ an optimized algorithm for counting ASCII characters. The reason for this + * threshold is that for small strings, the optimization may not be beneficial or may even + * negatively impact performance since it requires additional logic to avoid unaligned reads + * (when calling {@code Unsafe.getLong}). This threshold guarantees that even if the initial + * offset is unaligned, we're guaranteed to make at least one call to {@code Unsafe.getLong()} + * which provides a performance improvement that entirely subsumes the cost of the additional + * logic. + */ + private static final int UNSAFE_COUNT_ASCII_THRESHOLD = 16; + // Other state values include the partial bytes of the incomplete // character to be decoded in the simplest way: we pack the bytes // into the state int in little-endian order. For example: @@ -112,7 +152,7 @@ final class Utf8 { * isValidUtf8(bytes, 0, bytes.length)}. */ public static boolean isValidUtf8(byte[] bytes) { - return isValidUtf8(bytes, 0, bytes.length); + return processor.isValidUtf8(bytes, 0, bytes.length); } /** @@ -125,7 +165,7 @@ final class Utf8 { * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. */ public static boolean isValidUtf8(byte[] bytes, int index, int limit) { - return partialIsValidUtf8(bytes, index, limit) == COMPLETE; + return processor.isValidUtf8(bytes, index, limit); } /** @@ -146,183 +186,8 @@ final class Utf8 { * decode the character when passed to a subsequent invocation of a * partial decoding method. */ - public static int partialIsValidUtf8( - int state, byte[] bytes, int index, int limit) { - if (state != COMPLETE) { - // The previous decoding operation was incomplete (or malformed). - // We look for a well-formed sequence consisting of bytes from - // the previous decoding operation (stored in state) together - // with bytes from the array slice. - // - // We expect such "straddler characters" to be rare. - - if (index >= limit) { // No bytes? No progress. - return state; - } - int byte1 = (byte) state; - // byte1 is never ASCII. - if (byte1 < (byte) 0xE0) { - // two-byte form - - // Simultaneously checks for illegal trailing-byte in - // leading position and overlong 2-byte form. - if (byte1 < (byte) 0xC2 || - // byte2 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else if (byte1 < (byte) 0xF0) { - // three-byte form - - // Get byte2 from saved state or array - int byte2 = (byte) ~(state >> 8); - if (byte2 == 0) { - byte2 = bytes[index++]; - if (index >= limit) { - return incompleteStateFor(byte1, byte2); - } - } - if (byte2 > (byte) 0xBF || - // overlong? 5 most significant bits must not all be zero - (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) || - // illegal surrogate codepoint? - (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || - // byte3 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else { - // four-byte form - - // Get byte2 and byte3 from saved state or array - int byte2 = (byte) ~(state >> 8); - int byte3 = 0; - if (byte2 == 0) { - byte2 = bytes[index++]; - if (index >= limit) { - return incompleteStateFor(byte1, byte2); - } - } else { - byte3 = (byte) (state >> 16); - } - if (byte3 == 0) { - byte3 = bytes[index++]; - if (index >= limit) { - return incompleteStateFor(byte1, byte2, byte3); - } - } - - // If we were called with state == MALFORMED, then byte1 is 0xFF, - // which never occurs in well-formed UTF-8, and so we will return - // MALFORMED again below. - - if (byte2 > (byte) 0xBF || - // Check that 1 <= plane <= 16. Tricky optimized form of: - // if (byte1 > (byte) 0xF4 || - // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || - // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) - (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || - // byte3 trailing-byte test - byte3 > (byte) 0xBF || - // byte4 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } - } - - return partialIsValidUtf8(bytes, index, limit); - } - - /** - * Tells whether the given byte array slice is a well-formed, - * malformed, or incomplete UTF-8 byte sequence. The range of bytes - * to be checked extends from index {@code index}, inclusive, to - * {@code limit}, exclusive. - * - * <p>This is a convenience method, equivalent to a call to {@code - * partialIsValidUtf8(Utf8.COMPLETE, bytes, index, limit)}. - * - * @return {@link #MALFORMED} if the partial byte sequence is - * definitely not well-formed, {@link #COMPLETE} if it is well-formed - * (no additional input needed), or if the byte sequence is - * "incomplete", i.e. apparently terminated in the middle of a character, - * an opaque integer "state" value containing enough information to - * decode the character when passed to a subsequent invocation of a - * partial decoding method. - */ - public static int partialIsValidUtf8( - byte[] bytes, int index, int limit) { - // Optimize for 100% ASCII. - // Hotspot loves small simple top-level loops like this. - while (index < limit && bytes[index] >= 0) { - index++; - } - - return (index >= limit) ? COMPLETE : - partialIsValidUtf8NonAscii(bytes, index, limit); - } - - private static int partialIsValidUtf8NonAscii( - byte[] bytes, int index, int limit) { - for (;;) { - int byte1, byte2; - - // Optimize for interior runs of ASCII bytes. - do { - if (index >= limit) { - return COMPLETE; - } - } while ((byte1 = bytes[index++]) >= 0); - - if (byte1 < (byte) 0xE0) { - // two-byte form - - if (index >= limit) { - return byte1; - } - - // Simultaneously checks for illegal trailing-byte in - // leading position and overlong 2-byte form. - if (byte1 < (byte) 0xC2 || - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else if (byte1 < (byte) 0xF0) { - // three-byte form - - if (index >= limit - 1) { // incomplete sequence - return incompleteStateFor(bytes, index, limit); - } - if ((byte2 = bytes[index++]) > (byte) 0xBF || - // overlong? 5 most significant bits must not all be zero - (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) || - // check for illegal surrogate codepoints - (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || - // byte3 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else { - // four-byte form - - if (index >= limit - 2) { // incomplete sequence - return incompleteStateFor(bytes, index, limit); - } - if ((byte2 = bytes[index++]) > (byte) 0xBF || - // Check that 1 <= plane <= 16. Tricky optimized form of: - // if (byte1 > (byte) 0xF4 || - // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || - // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) - (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || - // byte3 trailing-byte test - bytes[index++] > (byte) 0xBF || - // byte4 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } - } + public static int partialIsValidUtf8(int state, byte[] bytes, int index, int limit) { + return processor.partialIsValidUtf8(state, bytes, index, limit); } private static int incompleteStateFor(int byte1) { @@ -352,19 +217,31 @@ final class Utf8 { default: throw new AssertionError(); } } - + + private static int incompleteStateFor( + final ByteBuffer buffer, final int byte1, final int index, final int remaining) { + switch (remaining) { + case 0: + return incompleteStateFor(byte1); + case 1: + return incompleteStateFor(byte1, buffer.get(index)); + case 2: + return incompleteStateFor(byte1, buffer.get(index), buffer.get(index + 1)); + default: + throw new AssertionError(); + } + } // These UTF-8 handling methods are copied from Guava's Utf8 class with a modification to throw // a protocol buffer local exception. This exception is then caught in CodedOutputStream so it can // fallback to more lenient behavior. static class UnpairedSurrogateException extends IllegalArgumentException { - - private UnpairedSurrogateException(int index, int length) { + UnpairedSurrogateException(int index, int length) { super("Unpaired surrogate at index " + index + " of " + length); } } - + /** * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string, * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in @@ -416,7 +293,7 @@ final class Utf8 { if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) { // Check that we have a well-formed surrogate pair. int cp = Character.codePointAt(sequence, i); - if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + if (cp < MIN_SUPPLEMENTARY_CODE_POINT) { throw new UnpairedSurrogateException(i, utf16Length); } i++; @@ -426,56 +303,1732 @@ final class Utf8 { return utf8Length; } - static int encode(CharSequence sequence, byte[] bytes, int offset, int length) { - int utf16Length = sequence.length(); - int j = offset; - int i = 0; - int limit = offset + length; - // Designed to take advantage of - // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination - for (char c; i < utf16Length && i + j < limit && (c = sequence.charAt(i)) < 0x80; i++) { - bytes[j + i] = (byte) c; - } - if (i == utf16Length) { - return j + utf16Length; - } - j += i; - for (char c; i < utf16Length; i++) { - c = sequence.charAt(i); - if (c < 0x80 && j < limit) { - bytes[j++] = (byte) c; - } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes - bytes[j++] = (byte) ((0xF << 6) | (c >>> 6)); - bytes[j++] = (byte) (0x80 | (0x3F & c)); - } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) { - // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes - bytes[j++] = (byte) ((0xF << 5) | (c >>> 12)); - bytes[j++] = (byte) (0x80 | (0x3F & (c >>> 6))); - bytes[j++] = (byte) (0x80 | (0x3F & c)); - } else if (j <= limit - 4) { - // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 bytes - final char low; - if (i + 1 == sequence.length() - || !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) { - throw new UnpairedSurrogateException((i - 1), utf16Length); - } - int codePoint = Character.toCodePoint(c, low); - bytes[j++] = (byte) ((0xF << 4) | (codePoint >>> 18)); - bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12))); - bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6))); - bytes[j++] = (byte) (0x80 | (0x3F & codePoint)); + static int encode(CharSequence in, byte[] out, int offset, int length) { + return processor.encodeUtf8(in, out, offset, length); + } + // End Guava UTF-8 methods. + + /** + * Determines if the given {@link ByteBuffer} is a valid UTF-8 string. + * + * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct) + * and the capabilities of the platform. + * + * @param buffer the buffer to check. + * @see Utf8#isValidUtf8(byte[], int, int) + */ + static boolean isValidUtf8(ByteBuffer buffer) { + return processor.isValidUtf8(buffer, buffer.position(), buffer.remaining()); + } + + /** + * Determines if the given {@link ByteBuffer} is a partially valid UTF-8 string. + * + * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct) + * and the capabilities of the platform. + * + * @param buffer the buffer to check. + * @see Utf8#partialIsValidUtf8(int, byte[], int, int) + */ + static int partialIsValidUtf8(int state, ByteBuffer buffer, int index, int limit) { + return processor.partialIsValidUtf8(state, buffer, index, limit); + } + + /** + * Decodes the given UTF-8 portion of the {@link ByteBuffer} into a {@link String}. + * + * @throws InvalidProtocolBufferException if the input is not valid UTF-8. + */ + static String decodeUtf8(ByteBuffer buffer, int index, int size) + throws InvalidProtocolBufferException { + return processor.decodeUtf8(buffer, index, size); + } + + /** + * Decodes the given UTF-8 encoded byte array slice into a {@link String}. + * + * @throws InvalidProtocolBufferException if the input is not valid UTF-8. + */ + static String decodeUtf8(byte[] bytes, int index, int size) + throws InvalidProtocolBufferException { + return processor.decodeUtf8(bytes, index, size); + } + + /** + * Encodes the given characters to the target {@link ByteBuffer} using UTF-8 encoding. + * + * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct) + * and the capabilities of the platform. + * + * @param in the source string to be encoded + * @param out the target buffer to receive the encoded string. + * @see Utf8#encode(CharSequence, byte[], int, int) + */ + static void encodeUtf8(CharSequence in, ByteBuffer out) { + processor.encodeUtf8(in, out); + } + + /** + * Counts (approximately) the number of consecutive ASCII characters in the given buffer. + * The byte order of the {@link ByteBuffer} does not matter, so performance can be improved if + * native byte order is used (i.e. no byte-swapping in {@link ByteBuffer#getLong(int)}). + * + * @param buffer the buffer to be scanned for ASCII chars + * @param index the starting index of the scan + * @param limit the limit within buffer for the scan + * @return the number of ASCII characters found. The stopping position will be at or + * before the first non-ASCII byte. + */ + private static int estimateConsecutiveAscii(ByteBuffer buffer, int index, int limit) { + int i = index; + final int lim = limit - 7; + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + // To speed things up further, we're reading longs instead of bytes so we use a mask to + // determine if any byte in the current long is non-ASCII. + for (; i < lim && (buffer.getLong(i) & ASCII_MASK_LONG) == 0; i += 8) {} + return i - index; + } + + /** + * A processor of UTF-8 strings, providing methods for checking validity and encoding. + */ + // TODO(nathanmittler): Add support for Memory/MemoryBlock on Android. + abstract static class Processor { + /** + * Returns {@code true} if the given byte array slice is a + * well-formed UTF-8 byte sequence. The range of bytes to be + * checked extends from index {@code index}, inclusive, to {@code + * limit}, exclusive. + * + * <p>This is a convenience method, equivalent to {@code + * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. + */ + final boolean isValidUtf8(byte[] bytes, int index, int limit) { + return partialIsValidUtf8(COMPLETE, bytes, index, limit) == COMPLETE; + } + + /** + * Tells whether the given byte array slice is a well-formed, + * malformed, or incomplete UTF-8 byte sequence. The range of bytes + * to be checked extends from index {@code index}, inclusive, to + * {@code limit}, exclusive. + * + * @param state either {@link Utf8#COMPLETE} (if this is the initial decoding + * operation) or the value returned from a call to a partial decoding method + * for the previous bytes + * + * @return {@link #MALFORMED} if the partial byte sequence is + * definitely not well-formed, {@link #COMPLETE} if it is well-formed + * (no additional input needed), or if the byte sequence is + * "incomplete", i.e. apparently terminated in the middle of a character, + * an opaque integer "state" value containing enough information to + * decode the character when passed to a subsequent invocation of a + * partial decoding method. + */ + abstract int partialIsValidUtf8(int state, byte[] bytes, int index, int limit); + + /** + * Returns {@code true} if the given portion of the {@link ByteBuffer} is a + * well-formed UTF-8 byte sequence. The range of bytes to be + * checked extends from index {@code index}, inclusive, to {@code + * limit}, exclusive. + * + * <p>This is a convenience method, equivalent to {@code + * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. + */ + final boolean isValidUtf8(ByteBuffer buffer, int index, int limit) { + return partialIsValidUtf8(COMPLETE, buffer, index, limit) == COMPLETE; + } + + /** + * Indicates whether or not the given buffer contains a valid UTF-8 string. + * + * @param buffer the buffer to check. + * @return {@code true} if the given buffer contains a valid UTF-8 string. + */ + final int partialIsValidUtf8( + final int state, final ByteBuffer buffer, int index, final int limit) { + if (buffer.hasArray()) { + final int offset = buffer.arrayOffset(); + return partialIsValidUtf8(state, buffer.array(), offset + index, offset + limit); + } else if (buffer.isDirect()){ + return partialIsValidUtf8Direct(state, buffer, index, limit); + } + return partialIsValidUtf8Default(state, buffer, index, limit); + } + + /** + * Performs validation for direct {@link ByteBuffer} instances. + */ + abstract int partialIsValidUtf8Direct( + final int state, final ByteBuffer buffer, int index, final int limit); + + /** + * Performs validation for {@link ByteBuffer} instances using the {@link ByteBuffer} API rather + * than potentially faster approaches. This first completes validation for the current + * character (provided by {@code state}) and then finishes validation for the sequence. + */ + final int partialIsValidUtf8Default( + final int state, final ByteBuffer buffer, int index, final int limit) { + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (index >= limit) { // No bytes? No progress. + return state; + } + + byte byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + byte byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = buffer.get(index++); + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + byte byte2 = (byte) ~(state >> 8); + byte byte3 = 0; + if (byte2 == 0) { + byte2 = buffer.get(index++); + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = buffer.get(index++); + if (index >= limit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + + // Finish validation for the sequence. + return partialIsValidUtf8(buffer, index, limit); + } + + /** + * Performs validation for {@link ByteBuffer} instances using the {@link ByteBuffer} API rather + * than potentially faster approaches. + */ + private static int partialIsValidUtf8(final ByteBuffer buffer, int index, final int limit) { + index += estimateConsecutiveAscii(buffer, index, limit); + + for (;;) { + // Optimize for interior runs of ASCII bytes. + // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? + // Maybe after seeing a few in a row that are ASCII, go back to fast mode? + int byte1; + do { + if (index >= limit) { + return COMPLETE; + } + } while ((byte1 = buffer.get(index++)) >= 0); + + // If we're here byte1 is not ASCII. Only need to handle 2-4 byte forms. + if (byte1 < (byte) 0xE0) { + // Two-byte form (110xxxxx 10xxxxxx) + if (index >= limit) { + // Incomplete sequence + return byte1; + } + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 || buffer.get(index) > (byte) 0xBF) { + return MALFORMED; + } + index++; + } else if (byte1 < (byte) 0xF0) { + // Three-byte form (1110xxxx 10xxxxxx 10xxxxxx) + if (index >= limit - 1) { + // Incomplete sequence + return incompleteStateFor(buffer, byte1, index, limit - index); + } + + final byte byte2 = buffer.get(index++); + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || buffer.get(index) > (byte) 0xBF) { + return MALFORMED; + } + index++; + } else { + // Four-byte form (1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx) + if (index >= limit - 2) { + // Incomplete sequence + return incompleteStateFor(buffer, byte1, index, limit - index); + } + + // TODO(nathanmittler): Consider using getInt() to improve performance. + final int byte2 = buffer.get(index++); + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || buffer.get(index++) > (byte) 0xBF + // byte4 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + + /** + * Decodes the given byte array slice into a {@link String}. + * + * @throws InvalidProtocolBufferException if the byte array slice is not valid UTF-8. + */ + abstract String decodeUtf8(byte[] bytes, int index, int size) + throws InvalidProtocolBufferException; + + /** + * Decodes the given portion of the {@link ByteBuffer} into a {@link String}. + * + * @throws InvalidProtocolBufferException if the portion of the buffer is not valid UTF-8. + */ + final String decodeUtf8(ByteBuffer buffer, int index, int size) + throws InvalidProtocolBufferException { + if (buffer.hasArray()) { + final int offset = buffer.arrayOffset(); + return decodeUtf8(buffer.array(), offset + index, size); + } else if (buffer.isDirect()) { + return decodeUtf8Direct(buffer, index, size); + } + return decodeUtf8Default(buffer, index, size); + } + + /** + * Decodes direct {@link ByteBuffer} instances into {@link String}. + */ + abstract String decodeUtf8Direct(ByteBuffer buffer, int index, int size) + throws InvalidProtocolBufferException; + + /** + * Decodes {@link ByteBuffer} instances using the {@link ByteBuffer} API rather than + * potentially faster approaches. + */ + final String decodeUtf8Default(ByteBuffer buffer, int index, int size) + throws InvalidProtocolBufferException { + // Bitwise OR combines the sign bits so any negative value fails the check. + if ((index | size | buffer.limit() - index - size) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("buffer limit=%d, index=%d, limit=%d", buffer.limit(), index, size)); + } + + int offset = index; + final int limit = offset + size; + + // The longest possible resulting String is the same as the number of input bytes, when it is + // all ASCII. For other cases, this over-allocates and we will truncate in the end. + char[] resultArr = new char[size]; + int resultPos = 0; + + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (offset < limit) { + byte b = buffer.get(offset); + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + + while (offset < limit) { + byte byte1 = buffer.get(offset++); + if (DecodeUtil.isOneByte(byte1)) { + DecodeUtil.handleOneByte(byte1, resultArr, resultPos++); + // It's common for there to be multiple ASCII characters in a run mixed in, so add an + // extra optimized loop to take care of these runs. + while (offset < limit) { + byte b = buffer.get(offset); + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + } else if (DecodeUtil.isTwoBytes(byte1)) { + if (offset >= limit) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleTwoBytes( + byte1, /* byte2 */ buffer.get(offset++), resultArr, resultPos++); + } else if (DecodeUtil.isThreeBytes(byte1)) { + if (offset >= limit - 1) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleThreeBytes( + byte1, + /* byte2 */ buffer.get(offset++), + /* byte3 */ buffer.get(offset++), + resultArr, + resultPos++); + } else { + if (offset >= limit - 2) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleFourBytes( + byte1, + /* byte2 */ buffer.get(offset++), + /* byte3 */ buffer.get(offset++), + /* byte4 */ buffer.get(offset++), + resultArr, + resultPos++); + // 4-byte case requires two chars. + resultPos++; + } + } + + return new String(resultArr, 0, resultPos); + } + + /** + * Encodes an input character sequence ({@code in}) to UTF-8 in the target array ({@code out}). + * For a string, this method is similar to + * <pre>{@code + * byte[] a = string.getBytes(UTF_8); + * System.arraycopy(a, 0, bytes, offset, a.length); + * return offset + a.length; + * }</pre> + * + * but is more efficient in both time and space. One key difference is that this method + * requires paired surrogates, and therefore does not support chunking. + * While {@code String.getBytes(UTF_8)} replaces unpaired surrogates with the default + * replacement character, this method throws {@link UnpairedSurrogateException}. + * + * <p>To ensure sufficient space in the output buffer, either call {@link #encodedLength} to + * compute the exact amount needed, or leave room for + * {@code Utf8.MAX_BYTES_PER_CHAR * sequence.length()}, which is the largest possible number + * of bytes that any input can be encoded to. + * + * @param in the input character sequence to be encoded + * @param out the target array + * @param offset the starting offset in {@code bytes} to start writing at + * @param length the length of the {@code bytes}, starting from {@code offset} + * @throws UnpairedSurrogateException if {@code sequence} contains ill-formed UTF-16 (unpaired + * surrogates) + * @throws ArrayIndexOutOfBoundsException if {@code sequence} encoded in UTF-8 is longer than + * {@code bytes.length - offset} + * @return the new offset, equivalent to {@code offset + Utf8.encodedLength(sequence)} + */ + abstract int encodeUtf8(CharSequence in, byte[] out, int offset, int length); + + /** + * Encodes an input character sequence ({@code in}) to UTF-8 in the target buffer ({@code out}). + * Upon returning from this method, the {@code out} position will point to the position after + * the last encoded byte. This method requires paired surrogates, and therefore does not + * support chunking. + * + * <p>To ensure sufficient space in the output buffer, either call {@link #encodedLength} to + * compute the exact amount needed, or leave room for + * {@code Utf8.MAX_BYTES_PER_CHAR * in.length()}, which is the largest possible number + * of bytes that any input can be encoded to. + * + * @param in the source character sequence to be encoded + * @param out the target buffer + * @throws UnpairedSurrogateException if {@code in} contains ill-formed UTF-16 (unpaired + * surrogates) + * @throws ArrayIndexOutOfBoundsException if {@code in} encoded in UTF-8 is longer than + * {@code out.remaining()} + */ + final void encodeUtf8(CharSequence in, ByteBuffer out) { + if (out.hasArray()) { + final int offset = out.arrayOffset(); + int endIndex = + Utf8.encode(in, out.array(), offset + out.position(), out.remaining()); + out.position(endIndex - offset); + } else if (out.isDirect()) { + encodeUtf8Direct(in, out); } else { - // If we are surrogates and we're not a surrogate pair, always throw an - // IllegalArgumentException instead of an ArrayOutOfBoundsException. - if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) - && (i + 1 == sequence.length() - || !Character.isSurrogatePair(c, sequence.charAt(i + 1)))) { - throw new UnpairedSurrogateException(i, utf16Length); + encodeUtf8Default(in, out); + } + } + + /** + * Encodes the input character sequence to a direct {@link ByteBuffer} instance. + */ + abstract void encodeUtf8Direct(CharSequence in, ByteBuffer out); + + /** + * Encodes the input character sequence to a {@link ByteBuffer} instance using the {@link + * ByteBuffer} API, rather than potentially faster approaches. + */ + final void encodeUtf8Default(CharSequence in, ByteBuffer out) { + final int inLength = in.length(); + int outIx = out.position(); + int inIx = 0; + + // Since ByteBuffer.putXXX() already checks boundaries for us, no need to explicitly check + // access. Assume the buffer is big enough and let it handle the out of bounds exception + // if it occurs. + try { + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + for (char c; inIx < inLength && (c = in.charAt(inIx)) < 0x80; ++inIx) { + out.put(outIx + inIx, (byte) c); + } + if (inIx == inLength) { + // Successfully encoded the entire string. + out.position(outIx + inIx); + return; } - throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j); + + outIx += inIx; + for (char c; inIx < inLength; ++inIx, ++outIx) { + c = in.charAt(inIx); + if (c < 0x80) { + // One byte (0xxx xxxx) + out.put(outIx, (byte) c); + } else if (c < 0x800) { + // Two bytes (110x xxxx 10xx xxxx) + + // Benchmarks show put performs better than putShort here (for HotSpot). + out.put(outIx++, (byte) (0xC0 | (c >>> 6))); + out.put(outIx, (byte) (0x80 | (0x3F & c))); + } else if (c < MIN_SURROGATE || MAX_SURROGATE < c) { + // Three bytes (1110 xxxx 10xx xxxx 10xx xxxx) + // Maximum single-char code point is 0xFFFF, 16 bits. + + // Benchmarks show put performs better than putShort here (for HotSpot). + out.put(outIx++, (byte) (0xE0 | (c >>> 12))); + out.put(outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + out.put(outIx, (byte) (0x80 | (0x3F & c))); + } else { + // Four bytes (1111 xxxx 10xx xxxx 10xx xxxx 10xx xxxx) + + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 + // bytes + final char low; + if (inIx + 1 == inLength || !isSurrogatePair(c, (low = in.charAt(++inIx)))) { + throw new UnpairedSurrogateException(inIx, inLength); + } + // TODO(nathanmittler): Consider using putInt() to improve performance. + int codePoint = toCodePoint(c, low); + out.put(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + out.put(outIx, (byte) (0x80 | (0x3F & codePoint))); + } + } + + // Successfully encoded the entire string. + out.position(outIx); + } catch (IndexOutOfBoundsException e) { + // TODO(nathanmittler): Consider making the API throw IndexOutOfBoundsException instead. + + // If we failed in the outer ASCII loop, outIx will not have been updated. In this case, + // use inIx to determine the bad write index. + int badWriteIndex = out.position() + Math.max(inIx, outIx - out.position() + 1); + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + in.charAt(inIx) + " at index " + badWriteIndex); } } - return j; } - // End Guava UTF-8 methods. + + /** + * {@link Processor} implementation that does not use any {@code sun.misc.Unsafe} methods. + */ + static final class SafeProcessor extends Processor { + @Override + int partialIsValidUtf8(int state, byte[] bytes, int index, int limit) { + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (index >= limit) { // No bytes? No progress. + return state; + } + int byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + int byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + int byte2 = (byte) ~(state >> 8); + int byte3 = 0; + if (byte2 == 0) { + byte2 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } + } + + return partialIsValidUtf8(bytes, index, limit); + } + + @Override + int partialIsValidUtf8Direct(int state, ByteBuffer buffer, int index, int limit) { + // For safe processing, we have to use the ByteBuffer API. + return partialIsValidUtf8Default(state, buffer, index, limit); + } + + @Override + String decodeUtf8(byte[] bytes, int index, int size) throws InvalidProtocolBufferException { + // Bitwise OR combines the sign bits so any negative value fails the check. + if ((index | size | bytes.length - index - size) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("buffer length=%d, index=%d, size=%d", bytes.length, index, size)); + } + + int offset = index; + final int limit = offset + size; + + // The longest possible resulting String is the same as the number of input bytes, when it is + // all ASCII. For other cases, this over-allocates and we will truncate in the end. + char[] resultArr = new char[size]; + int resultPos = 0; + + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (offset < limit) { + byte b = bytes[offset]; + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + + while (offset < limit) { + byte byte1 = bytes[offset++]; + if (DecodeUtil.isOneByte(byte1)) { + DecodeUtil.handleOneByte(byte1, resultArr, resultPos++); + // It's common for there to be multiple ASCII characters in a run mixed in, so add an + // extra optimized loop to take care of these runs. + while (offset < limit) { + byte b = bytes[offset]; + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + } else if (DecodeUtil.isTwoBytes(byte1)) { + if (offset >= limit) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleTwoBytes(byte1, /* byte2 */ bytes[offset++], resultArr, resultPos++); + } else if (DecodeUtil.isThreeBytes(byte1)) { + if (offset >= limit - 1) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleThreeBytes( + byte1, + /* byte2 */ bytes[offset++], + /* byte3 */ bytes[offset++], + resultArr, + resultPos++); + } else { + if (offset >= limit - 2) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleFourBytes( + byte1, + /* byte2 */ bytes[offset++], + /* byte3 */ bytes[offset++], + /* byte4 */ bytes[offset++], + resultArr, + resultPos++); + // 4-byte case requires two chars. + resultPos++; + } + } + + return new String(resultArr, 0, resultPos); + } + + @Override + String decodeUtf8Direct(ByteBuffer buffer, int index, int size) + throws InvalidProtocolBufferException { + // For safe processing, we have to use the ByteBufferAPI. + return decodeUtf8Default(buffer, index, size); + } + + @Override + int encodeUtf8(CharSequence in, byte[] out, int offset, int length) { + int utf16Length = in.length(); + int j = offset; + int i = 0; + int limit = offset + length; + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + for (char c; i < utf16Length && i + j < limit && (c = in.charAt(i)) < 0x80; i++) { + out[j + i] = (byte) c; + } + if (i == utf16Length) { + return j + utf16Length; + } + j += i; + for (char c; i < utf16Length; i++) { + c = in.charAt(i); + if (c < 0x80 && j < limit) { + out[j++] = (byte) c; + } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes + out[j++] = (byte) ((0xF << 6) | (c >>> 6)); + out[j++] = (byte) (0x80 | (0x3F & c)); + } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + out[j++] = (byte) ((0xF << 5) | (c >>> 12)); + out[j++] = (byte) (0x80 | (0x3F & (c >>> 6))); + out[j++] = (byte) (0x80 | (0x3F & c)); + } else if (j <= limit - 4) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, + // four UTF-8 bytes + final char low; + if (i + 1 == in.length() + || !Character.isSurrogatePair(c, (low = in.charAt(++i)))) { + throw new UnpairedSurrogateException((i - 1), utf16Length); + } + int codePoint = Character.toCodePoint(c, low); + out[j++] = (byte) ((0xF << 4) | (codePoint >>> 18)); + out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12))); + out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6))); + out[j++] = (byte) (0x80 | (0x3F & codePoint)); + } else { + // If we are surrogates and we're not a surrogate pair, always throw an + // UnpairedSurrogateException instead of an ArrayOutOfBoundsException. + if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) + && (i + 1 == in.length() + || !Character.isSurrogatePair(c, in.charAt(i + 1)))) { + throw new UnpairedSurrogateException(i, utf16Length); + } + throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j); + } + } + return j; + } + + @Override + void encodeUtf8Direct(CharSequence in, ByteBuffer out) { + // For safe processing, we have to use the ByteBuffer API. + encodeUtf8Default(in, out); + } + + private static int partialIsValidUtf8(byte[] bytes, int index, int limit) { + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (index < limit && bytes[index] >= 0) { + index++; + } + + return (index >= limit) ? COMPLETE : partialIsValidUtf8NonAscii(bytes, index, limit); + } + + private static int partialIsValidUtf8NonAscii(byte[] bytes, int index, int limit) { + for (;;) { + int byte1, byte2; + + // Optimize for interior runs of ASCII bytes. + do { + if (index >= limit) { + return COMPLETE; + } + } while ((byte1 = bytes[index++]) >= 0); + + if (byte1 < (byte) 0xE0) { + // two-byte form + + if (index >= limit) { + // Incomplete sequence + return byte1; + } + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + if (index >= limit - 1) { // incomplete sequence + return incompleteStateFor(bytes, index, limit); + } + if ((byte2 = bytes[index++]) > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + if (index >= limit - 2) { // incomplete sequence + return incompleteStateFor(bytes, index, limit); + } + if ((byte2 = bytes[index++]) > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || bytes[index++] > (byte) 0xBF + // byte4 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + } + + /** + * {@link Processor} that uses {@code sun.misc.Unsafe} where possible to improve performance. + */ + static final class UnsafeProcessor extends Processor { + /** + * Indicates whether or not all required unsafe operations are supported on this platform. + */ + static boolean isAvailable() { + return hasUnsafeArrayOperations() && hasUnsafeByteBufferOperations(); + } + + @Override + int partialIsValidUtf8(int state, byte[] bytes, final int index, final int limit) { + // Bitwise OR combines the sign bits so any negative value fails the check. + if ((index | limit | bytes.length - limit) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("Array length=%d, index=%d, limit=%d", bytes.length, index, limit)); + } + long offset = index; + final long offsetLimit = limit; + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (offset >= offsetLimit) { // No bytes? No progress. + return state; + } + int byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + int byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = UnsafeUtil.getByte(bytes, offset++); + if (offset >= offsetLimit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + int byte2 = (byte) ~(state >> 8); + int byte3 = 0; + if (byte2 == 0) { + byte2 = UnsafeUtil.getByte(bytes, offset++); + if (offset >= offsetLimit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = UnsafeUtil.getByte(bytes, offset++); + if (offset >= offsetLimit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + + return partialIsValidUtf8(bytes, offset, (int) (offsetLimit - offset)); + } + + @Override + int partialIsValidUtf8Direct( + final int state, ByteBuffer buffer, final int index, final int limit) { + // Bitwise OR combines the sign bits so any negative value fails the check. + if ((index | limit | buffer.limit() - limit) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("buffer limit=%d, index=%d, limit=%d", buffer.limit(), index, limit)); + } + long address = addressOffset(buffer) + index; + final long addressLimit = address + (limit - index); + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (address >= addressLimit) { // No bytes? No progress. + return state; + } + + final int byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + int byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = UnsafeUtil.getByte(address++); + if (address >= addressLimit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + int byte2 = (byte) ~(state >> 8); + int byte3 = 0; + if (byte2 == 0) { + byte2 = UnsafeUtil.getByte(address++); + if (address >= addressLimit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = UnsafeUtil.getByte(address++); + if (address >= addressLimit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + + return partialIsValidUtf8(address, (int) (addressLimit - address)); + } + + @Override + String decodeUtf8(byte[] bytes, int index, int size) throws InvalidProtocolBufferException { + if ((index | size | bytes.length - index - size) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("buffer length=%d, index=%d, size=%d", bytes.length, index, size)); + } + + int offset = index; + final int limit = offset + size; + + // The longest possible resulting String is the same as the number of input bytes, when it is + // all ASCII. For other cases, this over-allocates and we will truncate in the end. + char[] resultArr = new char[size]; + int resultPos = 0; + + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (offset < limit) { + byte b = UnsafeUtil.getByte(bytes, offset); + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + + while (offset < limit) { + byte byte1 = UnsafeUtil.getByte(bytes, offset++); + if (DecodeUtil.isOneByte(byte1)) { + DecodeUtil.handleOneByte(byte1, resultArr, resultPos++); + // It's common for there to be multiple ASCII characters in a run mixed in, so add an + // extra optimized loop to take care of these runs. + while (offset < limit) { + byte b = UnsafeUtil.getByte(bytes, offset); + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + } else if (DecodeUtil.isTwoBytes(byte1)) { + if (offset >= limit) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleTwoBytes( + byte1, /* byte2 */ UnsafeUtil.getByte(bytes, offset++), resultArr, resultPos++); + } else if (DecodeUtil.isThreeBytes(byte1)) { + if (offset >= limit - 1) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleThreeBytes( + byte1, + /* byte2 */ UnsafeUtil.getByte(bytes, offset++), + /* byte3 */ UnsafeUtil.getByte(bytes, offset++), + resultArr, + resultPos++); + } else { + if (offset >= limit - 2) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleFourBytes( + byte1, + /* byte2 */ UnsafeUtil.getByte(bytes, offset++), + /* byte3 */ UnsafeUtil.getByte(bytes, offset++), + /* byte4 */ UnsafeUtil.getByte(bytes, offset++), + resultArr, + resultPos++); + // 4-byte case requires two chars. + resultPos++; + } + } + + return new String(resultArr, 0, resultPos); + } + + @Override + String decodeUtf8Direct(ByteBuffer buffer, int index, int size) + throws InvalidProtocolBufferException { + // Bitwise OR combines the sign bits so any negative value fails the check. + if ((index | size | buffer.limit() - index - size) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("buffer limit=%d, index=%d, limit=%d", buffer.limit(), index, size)); + } + long address = UnsafeUtil.addressOffset(buffer) + index; + final long addressLimit = address + size; + + // The longest possible resulting String is the same as the number of input bytes, when it is + // all ASCII. For other cases, this over-allocates and we will truncate in the end. + char[] resultArr = new char[size]; + int resultPos = 0; + + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (address < addressLimit) { + byte b = UnsafeUtil.getByte(address); + if (!DecodeUtil.isOneByte(b)) { + break; + } + address++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + + while (address < addressLimit) { + byte byte1 = UnsafeUtil.getByte(address++); + if (DecodeUtil.isOneByte(byte1)) { + DecodeUtil.handleOneByte(byte1, resultArr, resultPos++); + // It's common for there to be multiple ASCII characters in a run mixed in, so add an + // extra optimized loop to take care of these runs. + while (address < addressLimit) { + byte b = UnsafeUtil.getByte(address); + if (!DecodeUtil.isOneByte(b)) { + break; + } + address++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + } else if (DecodeUtil.isTwoBytes(byte1)) { + if (address >= addressLimit) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleTwoBytes( + byte1, /* byte2 */ UnsafeUtil.getByte(address++), resultArr, resultPos++); + } else if (DecodeUtil.isThreeBytes(byte1)) { + if (address >= addressLimit - 1) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleThreeBytes( + byte1, + /* byte2 */ UnsafeUtil.getByte(address++), + /* byte3 */ UnsafeUtil.getByte(address++), + resultArr, + resultPos++); + } else { + if (address >= addressLimit - 2) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + DecodeUtil.handleFourBytes( + byte1, + /* byte2 */ UnsafeUtil.getByte(address++), + /* byte3 */ UnsafeUtil.getByte(address++), + /* byte4 */ UnsafeUtil.getByte(address++), + resultArr, + resultPos++); + // 4-byte case requires two chars. + resultPos++; + } + } + + return new String(resultArr, 0, resultPos); + } + + @Override + int encodeUtf8(final CharSequence in, final byte[] out, final int offset, final int length) { + long outIx = offset; + final long outLimit = outIx + length; + final int inLimit = in.length(); + if (inLimit > length || out.length - length < offset) { + // Not even enough room for an ASCII-encoded string. + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + in.charAt(inLimit - 1) + " at index " + (offset + length)); + } + + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + int inIx = 0; + for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) { + UnsafeUtil.putByte(out, outIx++, (byte) c); + } + if (inIx == inLimit) { + // We're done, it was ASCII encoded. + return (int) outIx; + } + + for (char c; inIx < inLimit; ++inIx) { + c = in.charAt(inIx); + if (c < 0x80 && outIx < outLimit) { + UnsafeUtil.putByte(out, outIx++, (byte) c); + } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + } else if (outIx <= outLimit - 4L) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 + // bytes + final char low; + if (inIx + 1 == inLimit || !isSurrogatePair(c, (low = in.charAt(++inIx)))) { + throw new UnpairedSurrogateException((inIx - 1), inLimit); + } + int codePoint = toCodePoint(c, low); + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint))); + } else { + if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE) + && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) { + // We are surrogates and we're not a surrogate pair. + throw new UnpairedSurrogateException(inIx, inLimit); + } + // Not enough space in the output buffer. + throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + outIx); + } + } + + // All bytes have been encoded. + return (int) outIx; + } + + @Override + void encodeUtf8Direct(CharSequence in, ByteBuffer out) { + final long address = addressOffset(out); + long outIx = address + out.position(); + final long outLimit = address + out.limit(); + final int inLimit = in.length(); + if (inLimit > outLimit - outIx) { + // Not even enough room for an ASCII-encoded string. + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + in.charAt(inLimit - 1) + " at index " + out.limit()); + } + + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + int inIx = 0; + for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) { + UnsafeUtil.putByte(outIx++, (byte) c); + } + if (inIx == inLimit) { + // We're done, it was ASCII encoded. + out.position((int) (outIx - address)); + return; + } + + for (char c; inIx < inLimit; ++inIx) { + c = in.charAt(inIx); + if (c < 0x80 && outIx < outLimit) { + UnsafeUtil.putByte(outIx++, (byte) c); + } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes + UnsafeUtil.putByte(outIx++, (byte) ((0xF << 6) | (c >>> 6))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c))); + } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + UnsafeUtil.putByte(outIx++, (byte) ((0xF << 5) | (c >>> 12))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c))); + } else if (outIx <= outLimit - 4L) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 + // bytes + final char low; + if (inIx + 1 == inLimit || !isSurrogatePair(c, (low = in.charAt(++inIx)))) { + throw new UnpairedSurrogateException((inIx - 1), inLimit); + } + int codePoint = toCodePoint(c, low); + UnsafeUtil.putByte(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & codePoint))); + } else { + if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE) + && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) { + // We are surrogates and we're not a surrogate pair. + throw new UnpairedSurrogateException(inIx, inLimit); + } + // Not enough space in the output buffer. + throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + outIx); + } + } + + // All bytes have been encoded. + out.position((int) (outIx - address)); + } + + /** + * Counts (approximately) the number of consecutive ASCII characters starting from the given + * position, using the most efficient method available to the platform. + * + * @param bytes the array containing the character sequence + * @param offset the offset position of the index (same as index + arrayBaseOffset) + * @param maxChars the maximum number of characters to count + * @return the number of ASCII characters found. The stopping position will be at or + * before the first non-ASCII byte. + */ + private static int unsafeEstimateConsecutiveAscii( + byte[] bytes, long offset, final int maxChars) { + if (maxChars < UNSAFE_COUNT_ASCII_THRESHOLD) { + // Don't bother with small strings. + return 0; + } + + for (int i = 0; i < maxChars; i++) { + if (UnsafeUtil.getByte(bytes, offset++) < 0) { + return i; + } + } + return maxChars; + } + + /** + * Same as {@link Utf8#estimateConsecutiveAscii(ByteBuffer, int, int)} except that it uses the + * most efficient method available to the platform. + */ + private static int unsafeEstimateConsecutiveAscii(long address, final int maxChars) { + int remaining = maxChars; + if (remaining < UNSAFE_COUNT_ASCII_THRESHOLD) { + // Don't bother with small strings. + return 0; + } + + // Read bytes until 8-byte aligned so that we can read longs in the loop below. + // We do this by ANDing the address with 7 to determine the number of bytes that need to + // be read before we're 8-byte aligned. + final int unaligned = 8 - ((int) address & 7); + for (int j = unaligned; j > 0; j--) { + if (UnsafeUtil.getByte(address++) < 0) { + return unaligned - j; + } + } + + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + // To speed things up further, we're reading longs instead of bytes so we use a mask to + // determine if any byte in the current long is non-ASCII. + remaining -= unaligned; + for (; remaining >= 8 && (UnsafeUtil.getLong(address) & ASCII_MASK_LONG) == 0; + address += 8, remaining -= 8) {} + return maxChars - remaining; + } + + private static int partialIsValidUtf8(final byte[] bytes, long offset, int remaining) { + // Skip past ASCII characters as quickly as possible. + final int skipped = unsafeEstimateConsecutiveAscii(bytes, offset, remaining); + remaining -= skipped; + offset += skipped; + + for (;;) { + // Optimize for interior runs of ASCII bytes. + // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? + // Maybe after seeing a few in a row that are ASCII, go back to fast mode? + int byte1 = 0; + for (; remaining > 0 && (byte1 = UnsafeUtil.getByte(bytes, offset++)) >= 0; --remaining) { + } + if (remaining == 0) { + return COMPLETE; + } + remaining--; + + // If we're here byte1 is not ASCII. Only need to handle 2-4 byte forms. + if (byte1 < (byte) 0xE0) { + // Two-byte form (110xxxxx 10xxxxxx) + if (remaining == 0) { + // Incomplete sequence + return byte1; + } + remaining--; + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // Three-byte form (1110xxxx 10xxxxxx 10xxxxxx) + if (remaining < 2) { + // Incomplete sequence + return unsafeIncompleteStateFor(bytes, byte1, offset, remaining); + } + remaining -= 2; + + final int byte2; + if ((byte2 = UnsafeUtil.getByte(bytes, offset++)) > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // Four-byte form (1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx) + if (remaining < 3) { + // Incomplete sequence + return unsafeIncompleteStateFor(bytes, byte1, offset, remaining); + } + remaining -= 3; + + final int byte2; + if ((byte2 = UnsafeUtil.getByte(bytes, offset++)) > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF + // byte4 trailing-byte test + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + + private static int partialIsValidUtf8(long address, int remaining) { + // Skip past ASCII characters as quickly as possible. + final int skipped = unsafeEstimateConsecutiveAscii(address, remaining); + address += skipped; + remaining -= skipped; + + for (;;) { + // Optimize for interior runs of ASCII bytes. + // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? + // Maybe after seeing a few in a row that are ASCII, go back to fast mode? + int byte1 = 0; + for (; remaining > 0 && (byte1 = UnsafeUtil.getByte(address++)) >= 0; --remaining) { + } + if (remaining == 0) { + return COMPLETE; + } + remaining--; + + if (byte1 < (byte) 0xE0) { + // Two-byte form + + if (remaining == 0) { + // Incomplete sequence + return byte1; + } + remaining--; + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 || UnsafeUtil.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // Three-byte form + + if (remaining < 2) { + // Incomplete sequence + return unsafeIncompleteStateFor(address, byte1, remaining); + } + remaining -= 2; + + final byte byte2 = UnsafeUtil.getByte(address++); + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // Four-byte form + + if (remaining < 3) { + // Incomplete sequence + return unsafeIncompleteStateFor(address, byte1, remaining); + } + remaining -= 3; + + final byte byte2 = UnsafeUtil.getByte(address++); + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || UnsafeUtil.getByte(address++) > (byte) 0xBF + // byte4 trailing-byte test + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + + private static int unsafeIncompleteStateFor(byte[] bytes, int byte1, long offset, + int remaining) { + switch (remaining) { + case 0: { + return incompleteStateFor(byte1); + } + case 1: { + return incompleteStateFor(byte1, UnsafeUtil.getByte(bytes, offset)); + } + case 2: { + return incompleteStateFor(byte1, UnsafeUtil.getByte(bytes, offset), + UnsafeUtil.getByte(bytes, offset + 1)); + } + default: { + throw new AssertionError(); + } + } + } + + private static int unsafeIncompleteStateFor(long address, final int byte1, int remaining) { + switch (remaining) { + case 0: { + return incompleteStateFor(byte1); + } + case 1: { + return incompleteStateFor(byte1, UnsafeUtil.getByte(address)); + } + case 2: { + return incompleteStateFor(byte1, UnsafeUtil.getByte(address), + UnsafeUtil.getByte(address + 1)); + } + default: { + throw new AssertionError(); + } + } + } + } + + /** + * Utility methods for decoding bytes into {@link String}. Callers are responsible for extracting + * bytes (possibly using Unsafe methods), and checking remaining bytes. All other UTF-8 validity + * checks and codepoint conversion happen in this class. + */ + private static class DecodeUtil { + + /** + * Returns whether this is a single-byte codepoint (i.e., ASCII) with the form '0XXXXXXX'. + */ + private static boolean isOneByte(byte b) { + return b >= 0; + } + + /** + * Returns whether this is a two-byte codepoint with the form '10XXXXXX'. + */ + private static boolean isTwoBytes(byte b) { + return b < (byte) 0xE0; + } + + /** + * Returns whether this is a three-byte codepoint with the form '110XXXXX'. + */ + private static boolean isThreeBytes(byte b) { + return b < (byte) 0xF0; + } + + private static void handleOneByte(byte byte1, char[] resultArr, int resultPos) { + resultArr[resultPos] = (char) byte1; + } + + private static void handleTwoBytes( + byte byte1, byte byte2, char[] resultArr, int resultPos) + throws InvalidProtocolBufferException { + // Simultaneously checks for illegal trailing-byte in leading position (<= '11000000') and + // overlong 2-byte, '11000001'. + if (byte1 < (byte) 0xC2 + || isNotTrailingByte(byte2)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + resultArr[resultPos] = (char) (((byte1 & 0x1F) << 6) | trailingByteValue(byte2)); + } + + private static void handleThreeBytes( + byte byte1, byte byte2, byte byte3, char[] resultArr, int resultPos) + throws InvalidProtocolBufferException { + if (isNotTrailingByte(byte2) + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + || isNotTrailingByte(byte3)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + resultArr[resultPos] = (char) + (((byte1 & 0x0F) << 12) | (trailingByteValue(byte2) << 6) | trailingByteValue(byte3)); + } + + private static void handleFourBytes( + byte byte1, byte byte2, byte byte3, byte byte4, char[] resultArr, int resultPos) + throws InvalidProtocolBufferException{ + if (isNotTrailingByte(byte2) + // Check that 1 <= plane <= 16. Tricky optimized form of: + // valid 4-byte leading byte? + // if (byte1 > (byte) 0xF4 || + // overlong? 4 most significant bits must not all be zero + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // codepoint larger than the highest code point (U+10FFFF)? + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + || isNotTrailingByte(byte3) + || isNotTrailingByte(byte4)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + int codepoint = ((byte1 & 0x07) << 18) + | (trailingByteValue(byte2) << 12) + | (trailingByteValue(byte3) << 6) + | trailingByteValue(byte4); + resultArr[resultPos] = DecodeUtil.highSurrogate(codepoint); + resultArr[resultPos + 1] = DecodeUtil.lowSurrogate(codepoint); + } + + /** + * Returns whether the byte is not a valid continuation of the form '10XXXXXX'. + */ + private static boolean isNotTrailingByte(byte b) { + return b > (byte) 0xBF; + } + + /** + * Returns the actual value of the trailing byte (removes the prefix '10') for composition. + */ + private static int trailingByteValue(byte b) { + return b & 0x3F; + } + + private static char highSurrogate(int codePoint) { + return (char) ((MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10)) + + (codePoint >>> 10)); + } + + private static char lowSurrogate(int codePoint) { + return (char) (MIN_LOW_SURROGATE + (codePoint & 0x3ff)); + } + } + + private Utf8() {} } diff --git a/java/core/src/main/java/com/google/protobuf/WireFormat.java b/java/core/src/main/java/com/google/protobuf/WireFormat.java index 8dbe1ae3..8b837ee5 100644 --- a/java/core/src/main/java/com/google/protobuf/WireFormat.java +++ b/java/core/src/main/java/com/google/protobuf/WireFormat.java @@ -47,6 +47,12 @@ public final class WireFormat { // Do not allow instantiation. private WireFormat() {} + static final int FIXED32_SIZE = 4; + static final int FIXED64_SIZE = 8; + static final int MAX_VARINT32_SIZE = 5; + static final int MAX_VARINT64_SIZE = 10; + static final int MAX_VARINT_SIZE = 10; + public static final int WIRETYPE_VARINT = 0; public static final int WIRETYPE_FIXED64 = 1; public static final int WIRETYPE_LENGTH_DELIMITED = 2; @@ -116,16 +122,24 @@ public final class WireFormat { FIXED32 (JavaType.INT , WIRETYPE_FIXED32 ), BOOL (JavaType.BOOLEAN , WIRETYPE_VARINT ), STRING (JavaType.STRING , WIRETYPE_LENGTH_DELIMITED) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, GROUP (JavaType.MESSAGE , WIRETYPE_START_GROUP ) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, MESSAGE (JavaType.MESSAGE , WIRETYPE_LENGTH_DELIMITED) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, BYTES (JavaType.BYTE_STRING, WIRETYPE_LENGTH_DELIMITED) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, UINT32 (JavaType.INT , WIRETYPE_VARINT ), ENUM (JavaType.ENUM , WIRETYPE_VARINT ), @@ -170,18 +184,21 @@ public final class WireFormat { enum Utf8Validation { /** Eagerly parses to String; silently accepts invalid UTF8 bytes. */ LOOSE { + @Override Object readString(CodedInputStream input) throws IOException { return input.readString(); } }, /** Eagerly parses to String; throws an IOException on invalid bytes. */ STRICT { + @Override Object readString(CodedInputStream input) throws IOException { return input.readStringRequireUtf8(); } }, /** Keep data as ByteString; validation/conversion to String is lazy. */ LAZY { + @Override Object readString(CodedInputStream input) throws IOException { return input.readBytes(); } |