Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:15.4:ARM
protobuf.26315
protobuf-7bff8393cab939bfbb9b5c69b3fe76b4d83c41...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File protobuf-7bff8393cab939bfbb9b5c69b3fe76b4d83c41ee.patch of Package protobuf.26315
From 7bff8393cab939bfbb9b5c69b3fe76b4d83c41ee Mon Sep 17 00:00:00 2001 From: Paul Yang <TeBoring@users.noreply.github.com> Date: Fri, 19 Jul 2019 14:49:01 -0700 Subject: [PATCH] Down Integrate to GitHub (#6414) * Down integrate to GitHub * Fix broken tests --- Makefile.am | 2 java/core/src/main/java/com/google/protobuf/FieldSet.java | 573 ++++++++-- java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java | 110 + java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java | 3 java/core/src/main/java/com/google/protobuf/TextFormat.java | 196 +++ java/core/src/main/java/com/google/protobuf/TypeRegistry.java | 160 ++ java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java | 154 ++ java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java | 6 java/core/src/test/java/com/google/protobuf/TextFormatTest.java | 191 +++ java/core/src/test/java/com/google/protobuf/TypeRegistryTest.java | 70 + java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto | 2 java/lite/pom.xml | 1 java/util/src/main/java/com/google/protobuf/util/JsonFormat.java | 140 +- java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java | 51 src/google/protobuf/compiler/java/java_context.cc | 1 src/google/protobuf/compiler/java/java_doc_comment.cc | 80 - src/google/protobuf/compiler/java/java_doc_comment.h | 42 src/google/protobuf/compiler/java/java_enum.cc | 11 src/google/protobuf/compiler/java/java_enum_field.cc | 4 src/google/protobuf/compiler/java/java_string_field_lite.cc | 7 src/google/protobuf/compiler/js/js_generator.cc | 138 +- 21 files changed, 1645 insertions(+), 297 deletions(-) create mode 100755 java/core/src/main/java/com/google/protobuf/TypeRegistry.java create mode 100755 java/core/src/test/java/com/google/protobuf/TypeRegistryTest.java --- a/Makefile.am +++ b/Makefile.am @@ -339,6 +339,7 @@ java_EXTRA_DIST= java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java \ java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java \ java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java \ + java/core/src/main/java/com/google/protobuf/TypeRegistry.java \ java/core/src/main/java/com/google/protobuf/UninitializedMessageException.java \ java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java \ java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java \ @@ -435,6 +436,7 @@ java_EXTRA_DIST= java/core/src/test/java/com/google/protobuf/TextFormatParseInfoTreeTest.java \ java/core/src/test/java/com/google/protobuf/TextFormatParseLocationTest.java \ java/core/src/test/java/com/google/protobuf/TextFormatTest.java \ + java/core/src/test/java/com/google/protobuf/TypeRegistryTest.java \ java/core/src/test/java/com/google/protobuf/UnknownEnumValueTest.java \ java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java \ java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java \ --- a/java/core/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/FieldSet.java @@ -48,8 +48,7 @@ import java.util.Map; * * @author kenton@google.com Kenton Varda */ -final class FieldSet< - FieldDescriptorType extends FieldSet.FieldDescriptorLite<FieldDescriptorType>> { +final class FieldSet<T extends FieldSet.FieldDescriptorLite<T>> { /** * Interface for a FieldDescriptor or lite extension descriptor. This prevents FieldSet from * depending on {@link Descriptors.FieldDescriptor}. @@ -72,18 +71,26 @@ final class FieldSet< MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from); } - private final SmallSortedMap<FieldDescriptorType, Object> fields; + private static final int DEFAULT_FIELD_MAP_ARRAY_SIZE = 16; + + private final SmallSortedMap<T, Object> fields; private boolean isImmutable; - private boolean hasLazyField = false; + private boolean hasLazyField; /** Construct a new FieldSet. */ private FieldSet() { - this.fields = SmallSortedMap.newFieldMap(16); + this.fields = SmallSortedMap.newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE); } /** Construct an empty FieldSet. This is only used to initialize DEFAULT_INSTANCE. */ + @SuppressWarnings("unused") private FieldSet(final boolean dummy) { - this.fields = SmallSortedMap.newFieldMap(0); + this(SmallSortedMap.<T>newFieldMap(0)); + makeImmutable(); + } + + private FieldSet(SmallSortedMap<T, Object> fields) { + this.fields = fields; makeImmutable(); } @@ -98,6 +105,11 @@ final class FieldSet< return DEFAULT_INSTANCE; } + /** Construct a new Builder. */ + public static <T extends FieldDescriptorLite<T>> Builder<T> newBuilder() { + return new Builder<T>(); + } + @SuppressWarnings("rawtypes") private static final FieldSet DEFAULT_INSTANCE = new FieldSet(true); @@ -152,18 +164,16 @@ final class FieldSet< * @return the newly cloned FieldSet */ @Override - public FieldSet<FieldDescriptorType> clone() { + public FieldSet<T> clone() { // We can't just call fields.clone because List objects in the map // should not be shared. - FieldSet<FieldDescriptorType> clone = FieldSet.newFieldSet(); + FieldSet<T> clone = FieldSet.newFieldSet(); for (int i = 0; i < fields.getNumArrayEntries(); i++) { - Map.Entry<FieldDescriptorType, Object> entry = fields.getArrayEntryAt(i); - FieldDescriptorType descriptor = entry.getKey(); - clone.setField(descriptor, entry.getValue()); - } - for (Map.Entry<FieldDescriptorType, Object> entry : fields.getOverflowEntries()) { - FieldDescriptorType descriptor = entry.getKey(); - clone.setField(descriptor, entry.getValue()); + Map.Entry<T, Object> entry = fields.getArrayEntryAt(i); + clone.setField(entry.getKey(), entry.getValue()); + } + for (Map.Entry<T, Object> entry : fields.getOverflowEntries()) { + clone.setField(entry.getKey(), entry.getValue()); } clone.hasLazyField = hasLazyField; return clone; @@ -179,15 +189,9 @@ final class FieldSet< } /** Get a simple map containing all the fields. */ - public Map<FieldDescriptorType, Object> getAllFields() { + public Map<T, Object> getAllFields() { if (hasLazyField) { - SmallSortedMap<FieldDescriptorType, Object> result = SmallSortedMap.newFieldMap(16); - for (int i = 0; i < fields.getNumArrayEntries(); i++) { - cloneFieldEntry(result, fields.getArrayEntryAt(i)); - } - for (Map.Entry<FieldDescriptorType, Object> entry : fields.getOverflowEntries()) { - cloneFieldEntry(result, entry); - } + SmallSortedMap<T, Object> result = cloneAllFieldsMap(fields, /* copyList */ false); if (fields.isImmutable()) { result.makeImmutable(); } @@ -196,12 +200,26 @@ final class FieldSet< return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields); } - private void cloneFieldEntry( - Map<FieldDescriptorType, Object> map, Map.Entry<FieldDescriptorType, Object> entry) { - FieldDescriptorType key = entry.getKey(); + private static <T extends FieldDescriptorLite<T>> SmallSortedMap<T, Object> cloneAllFieldsMap( + SmallSortedMap<T, Object> fields, boolean copyList) { + SmallSortedMap<T, Object> result = SmallSortedMap.newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE); + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + cloneFieldEntry(result, fields.getArrayEntryAt(i), copyList); + } + for (Map.Entry<T, Object> entry : fields.getOverflowEntries()) { + cloneFieldEntry(result, entry, copyList); + } + return result; + } + + private static <T extends FieldDescriptorLite<T>> void cloneFieldEntry( + Map<T, Object> map, Map.Entry<T, Object> entry, boolean copyList) { + T key = entry.getKey(); Object value = entry.getValue(); if (value instanceof LazyField) { map.put(key, ((LazyField) value).getValue()); + } else if (copyList && value instanceof List) { + map.put(key, new ArrayList<>((List<?>) value)); } else { map.put(key, value); } @@ -211,9 +229,9 @@ final class FieldSet< * Get an iterator to the field map. This iterator should not be leaked out of the protobuf * library as it is not protected from mutation when fields is not immutable. */ - public Iterator<Map.Entry<FieldDescriptorType, Object>> iterator() { + public Iterator<Map.Entry<T, Object>> iterator() { if (hasLazyField) { - return new LazyIterator<FieldDescriptorType>(fields.entrySet().iterator()); + return new LazyIterator<T>(fields.entrySet().iterator()); } return fields.entrySet().iterator(); } @@ -223,15 +241,15 @@ final class FieldSet< * should not be leaked out of the protobuf library as it is not protected from mutation when * fields is not immutable. */ - Iterator<Map.Entry<FieldDescriptorType, Object>> descendingIterator() { + Iterator<Map.Entry<T, Object>> descendingIterator() { if (hasLazyField) { - return new LazyIterator<FieldDescriptorType>(fields.descendingEntrySet().iterator()); + return new LazyIterator<T>(fields.descendingEntrySet().iterator()); } return fields.descendingEntrySet().iterator(); } /** Useful for implementing {@link Message#hasField(Descriptors.FieldDescriptor)}. */ - public boolean hasField(final FieldDescriptorType descriptor) { + public boolean hasField(final T descriptor) { if (descriptor.isRepeated()) { throw new IllegalArgumentException("hasField() can only be called on non-repeated fields."); } @@ -244,7 +262,7 @@ final class FieldSet< * returns {@code null} if the field is not set; in this case it is up to the caller to fetch the * field's default value. */ - public Object getField(final FieldDescriptorType descriptor) { + public Object getField(final T descriptor) { Object o = fields.get(descriptor); if (o instanceof LazyField) { return ((LazyField) o).getValue(); @@ -256,7 +274,7 @@ final class FieldSet< * Useful for implementing {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}. */ @SuppressWarnings({"unchecked", "rawtypes"}) - public void setField(final FieldDescriptorType descriptor, Object value) { + public void setField(final T descriptor, Object value) { if (descriptor.isRepeated()) { if (!(value instanceof List)) { throw new IllegalArgumentException( @@ -282,7 +300,7 @@ final class FieldSet< } /** Useful for implementing {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */ - public void clearField(final FieldDescriptorType descriptor) { + public void clearField(final T descriptor) { fields.remove(descriptor); if (fields.isEmpty()) { hasLazyField = false; @@ -290,7 +308,7 @@ final class FieldSet< } /** Useful for implementing {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. */ - public int getRepeatedFieldCount(final FieldDescriptorType descriptor) { + public int getRepeatedFieldCount(final T descriptor) { if (!descriptor.isRepeated()) { throw new IllegalArgumentException( "getRepeatedField() can only be called on repeated fields."); @@ -305,7 +323,7 @@ final class FieldSet< } /** Useful for implementing {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. */ - public Object getRepeatedField(final FieldDescriptorType descriptor, final int index) { + public Object getRepeatedField(final T descriptor, final int index) { if (!descriptor.isRepeated()) { throw new IllegalArgumentException( "getRepeatedField() can only be called on repeated fields."); @@ -325,8 +343,7 @@ final class FieldSet< * Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}. */ @SuppressWarnings("unchecked") - public void setRepeatedField( - final FieldDescriptorType descriptor, final int index, final Object value) { + public void setRepeatedField(final T descriptor, final int index, final Object value) { if (!descriptor.isRepeated()) { throw new IllegalArgumentException( "getRepeatedField() can only be called on repeated fields."); @@ -346,7 +363,7 @@ final class FieldSet< * Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}. */ @SuppressWarnings("unchecked") - public void addRepeatedField(final FieldDescriptorType descriptor, final Object value) { + public void addRepeatedField(final T descriptor, final Object value) { if (!descriptor.isRepeated()) { throw new IllegalArgumentException( "addRepeatedField() can only be called on repeated fields."); @@ -373,53 +390,45 @@ final class FieldSet< * * @throws IllegalArgumentException The value is not of the right type. */ - private static void verifyType(final WireFormat.FieldType type, final Object value) { - checkNotNull(value); + private void verifyType(final WireFormat.FieldType type, final Object value) { + if (!isValidType(type, value)) { + // TODO(kenton): When chaining calls to setField(), it can be hard to + // tell from the stack trace which exact call failed, since the whole + // chain is considered one line of code. It would be nice to print + // more information here, e.g. naming the field. We used to do that. + // But we can't now that FieldSet doesn't use descriptors. Maybe this + // isn't a big deal, though, since it would only really apply when using + // reflection and generally people don't chain reflection setters. + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection."); + } + } - boolean isValid = false; + private static boolean isValidType(final WireFormat.FieldType type, final Object value) { + checkNotNull(value); switch (type.getJavaType()) { case INT: - isValid = value instanceof Integer; - break; + return value instanceof Integer; case LONG: - isValid = value instanceof Long; - break; + return value instanceof Long; case FLOAT: - isValid = value instanceof Float; - break; + return value instanceof Float; case DOUBLE: - isValid = value instanceof Double; - break; + return value instanceof Double; case BOOLEAN: - isValid = value instanceof Boolean; - break; + return value instanceof Boolean; case STRING: - isValid = value instanceof String; - break; + return value instanceof String; case BYTE_STRING: - isValid = value instanceof ByteString || value instanceof byte[]; - break; + return value instanceof ByteString || value instanceof byte[]; case ENUM: // TODO(kenton): Caller must do type checking here, I guess. - isValid = (value instanceof Integer || value instanceof Internal.EnumLite); - break; + return (value instanceof Integer || value instanceof Internal.EnumLite); case MESSAGE: // TODO(kenton): Caller must do type checking here, I guess. - isValid = (value instanceof MessageLite) || (value instanceof LazyField); - break; - } - - if (!isValid) { - // TODO(kenton): When chaining calls to setField(), it can be hard to - // tell from the stack trace which exact call failed, since the whole - // chain is considered one line of code. It would be nice to print - // more information here, e.g. naming the field. We used to do that. - // But we can't now that FieldSet doesn't use descriptors. Maybe this - // isn't a big deal, though, since it would only really apply when using - // reflection and generally people don't chain reflection setters. - throw new IllegalArgumentException( - "Wrong object type used with protocol message reflection."); + return (value instanceof MessageLite) || (value instanceof LazyField); } + return false; } // ================================================================= @@ -436,7 +445,7 @@ final class FieldSet< return false; } } - for (final Map.Entry<FieldDescriptorType, Object> entry : fields.getOverflowEntries()) { + for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) { if (!isInitialized(entry)) { return false; } @@ -445,8 +454,9 @@ final class FieldSet< } @SuppressWarnings("unchecked") - private boolean isInitialized(final Map.Entry<FieldDescriptorType, Object> entry) { - final FieldDescriptorType descriptor = entry.getKey(); + private static <T extends FieldDescriptorLite<T>> boolean isInitialized( + final Map.Entry<T, Object> entry) { + final T descriptor = entry.getKey(); if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { if (descriptor.isRepeated()) { for (final MessageLite element : (List<MessageLite>) entry.getValue()) { @@ -485,16 +495,16 @@ final class FieldSet< } /** Like {@link Message.Builder#mergeFrom(Message)}, but merges from another {@link FieldSet}. */ - public void mergeFrom(final FieldSet<FieldDescriptorType> other) { + public void mergeFrom(final FieldSet<T> other) { for (int i = 0; i < other.fields.getNumArrayEntries(); i++) { mergeFromField(other.fields.getArrayEntryAt(i)); } - for (final Map.Entry<FieldDescriptorType, Object> entry : other.fields.getOverflowEntries()) { + for (final Map.Entry<T, Object> entry : other.fields.getOverflowEntries()) { mergeFromField(entry); } } - private Object cloneIfMutable(Object value) { + private static Object cloneIfMutable(Object value) { if (value instanceof byte[]) { byte[] bytes = (byte[]) value; byte[] copy = new byte[bytes.length]; @@ -506,8 +516,8 @@ final class FieldSet< } @SuppressWarnings({"unchecked", "rawtypes"}) - private void mergeFromField(final Map.Entry<FieldDescriptorType, Object> entry) { - final FieldDescriptorType descriptor = entry.getKey(); + private void mergeFromField(final Map.Entry<T, Object> entry) { + final T descriptor = entry.getKey(); Object otherValue = entry.getValue(); if (otherValue instanceof LazyField) { otherValue = ((LazyField) otherValue).getValue(); @@ -532,7 +542,6 @@ final class FieldSet< descriptor .internalMergeFrom(((MessageLite) value).toBuilder(), (MessageLite) otherValue) .build(); - fields.put(descriptor, value); } } else { @@ -567,10 +576,10 @@ final class FieldSet< /** See {@link Message#writeTo(CodedOutputStream)}. */ public void writeTo(final CodedOutputStream output) throws IOException { for (int i = 0; i < fields.getNumArrayEntries(); i++) { - final Map.Entry<FieldDescriptorType, Object> entry = fields.getArrayEntryAt(i); + final Map.Entry<T, Object> entry = fields.getArrayEntryAt(i); writeField(entry.getKey(), entry.getValue(), output); } - for (final Map.Entry<FieldDescriptorType, Object> entry : fields.getOverflowEntries()) { + for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) { writeField(entry.getKey(), entry.getValue(), output); } } @@ -580,15 +589,14 @@ final class FieldSet< for (int i = 0; i < fields.getNumArrayEntries(); i++) { writeMessageSetTo(fields.getArrayEntryAt(i), output); } - for (final Map.Entry<FieldDescriptorType, Object> entry : fields.getOverflowEntries()) { + for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) { writeMessageSetTo(entry, output); } } - private void writeMessageSetTo( - final Map.Entry<FieldDescriptorType, Object> entry, final CodedOutputStream output) + private void writeMessageSetTo(final Map.Entry<T, Object> entry, final CodedOutputStream output) throws IOException { - final FieldDescriptorType descriptor = entry.getKey(); + final T descriptor = entry.getKey(); if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE && !descriptor.isRepeated() && !descriptor.isPacked()) { @@ -750,10 +758,10 @@ final class FieldSet< public int getSerializedSize() { int size = 0; for (int i = 0; i < fields.getNumArrayEntries(); i++) { - final Map.Entry<FieldDescriptorType, Object> entry = fields.getArrayEntryAt(i); + final Map.Entry<T, Object> entry = fields.getArrayEntryAt(i); size += computeFieldSize(entry.getKey(), entry.getValue()); } - for (final Map.Entry<FieldDescriptorType, Object> entry : fields.getOverflowEntries()) { + for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) { size += computeFieldSize(entry.getKey(), entry.getValue()); } return size; @@ -765,14 +773,14 @@ final class FieldSet< for (int i = 0; i < fields.getNumArrayEntries(); i++) { size += getMessageSetSerializedSize(fields.getArrayEntryAt(i)); } - for (final Map.Entry<FieldDescriptorType, Object> entry : fields.getOverflowEntries()) { + for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) { size += getMessageSetSerializedSize(entry); } return size; } - private int getMessageSetSerializedSize(final Map.Entry<FieldDescriptorType, Object> entry) { - final FieldDescriptorType descriptor = entry.getKey(); + private int getMessageSetSerializedSize(final Map.Entry<T, Object> entry) { + final T descriptor = entry.getKey(); Object value = entry.getValue(); if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE && !descriptor.isRepeated() @@ -904,4 +912,385 @@ final class FieldSet< return computeElementSize(type, number, value); } } + + /** + * A FieldSet Builder that accept a {@link MessageLite.Builder} as a field value. This is useful + * for implementing methods in {@link MessageLite.Builder}. + */ + static final class Builder<T extends FieldDescriptorLite<T>> { + + private SmallSortedMap<T, Object> fields; + private boolean hasLazyField; + private boolean isMutable; + private boolean hasNestedBuilders; + + private Builder() { + this(SmallSortedMap.<T>newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE)); + } + + private Builder(SmallSortedMap<T, Object> fields) { + this.fields = fields; + this.isMutable = true; + } + + /** Creates the FieldSet */ + public FieldSet<T> build() { + if (fields.isEmpty()) { + return FieldSet.emptySet(); + } + isMutable = false; + SmallSortedMap<T, Object> fieldsForBuild = fields; + if (hasNestedBuilders) { + // Make a copy of the fields map with all Builders replaced by Message. + fieldsForBuild = cloneAllFieldsMap(fields, /* copyList */ false); + replaceBuilders(fieldsForBuild); + } + FieldSet<T> fieldSet = new FieldSet<>(fieldsForBuild); + fieldSet.hasLazyField = hasLazyField; + return fieldSet; + } + + private static <T extends FieldDescriptorLite<T>> void replaceBuilders( + SmallSortedMap<T, Object> fieldMap) { + for (int i = 0; i < fieldMap.getNumArrayEntries(); i++) { + replaceBuilders(fieldMap.getArrayEntryAt(i)); + } + for (Map.Entry<T, Object> entry : fieldMap.getOverflowEntries()) { + replaceBuilders(entry); + } + } + + private static <T extends FieldDescriptorLite<T>> void replaceBuilders( + Map.Entry<T, Object> entry) { + entry.setValue(replaceBuilders(entry.getKey(), entry.getValue())); + } + + private static <T extends FieldDescriptorLite<T>> Object replaceBuilders( + T descriptor, Object value) { + if (value == null) { + return value; + } + if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { + if (descriptor.isRepeated()) { + if (!(value instanceof List)) { + throw new IllegalStateException( + "Repeated field should contains a List but actually contains type: " + + value.getClass()); + } + @SuppressWarnings("unchecked") // We just check that value is an instance of List above. + List<Object> list = (List<Object>) value; + for (int i = 0; i < list.size(); i++) { + Object oldElement = list.get(i); + Object newElement = replaceBuilder(oldElement); + if (newElement != oldElement) { + // If the list contains a Message.Builder, then make a copy of that list and then + // modify the Message.Builder into a Message and return the new list. This way, the + // existing Message.Builder will still be able to modify the inner fields of the + // original FieldSet.Builder. + if (list == value) { + list = new ArrayList<>(list); + } + list.set(i, newElement); + } + } + return list; + } else { + return replaceBuilder(value); + } + } + return value; + } + + private static Object replaceBuilder(Object value) { + return (value instanceof MessageLite.Builder) ? ((MessageLite.Builder) value).build() : value; + } + + /** Returns a new Builder using the fields from {@code fieldSet}. */ + public static <T extends FieldDescriptorLite<T>> Builder<T> fromFieldSet(FieldSet<T> fieldSet) { + Builder<T> builder = new Builder<T>(cloneAllFieldsMap(fieldSet.fields, /* copyList */ true)); + builder.hasLazyField = fieldSet.hasLazyField; + return builder; + } + + // ================================================================= + + /** Get a simple map containing all the fields. */ + public Map<T, Object> getAllFields() { + if (hasLazyField) { + SmallSortedMap<T, Object> result = cloneAllFieldsMap(fields, /* copyList */ false); + if (fields.isImmutable()) { + result.makeImmutable(); + } else { + replaceBuilders(result); + } + return result; + } + return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields); + } + + /** Useful for implementing {@link Message#hasField(Descriptors.FieldDescriptor)}. */ + public boolean hasField(final T descriptor) { + if (descriptor.isRepeated()) { + throw new IllegalArgumentException("hasField() can only be called on non-repeated fields."); + } + + return fields.get(descriptor) != null; + } + + /** + * Useful for implementing {@link Message#getField(Descriptors.FieldDescriptor)}. This method + * returns {@code null} if the field is not set; in this case it is up to the caller to fetch + * the field's default value. + */ + public Object getField(final T descriptor) { + Object value = getFieldAllowBuilders(descriptor); + return replaceBuilders(descriptor, value); + } + + /** Same as {@link #getField(F)}, but allow a {@link MessageLite.Builder} to be returned. */ + Object getFieldAllowBuilders(final T descriptor) { + Object o = fields.get(descriptor); + if (o instanceof LazyField) { + return ((LazyField) o).getValue(); + } + return o; + } + + private void ensureIsMutable() { + if (!isMutable) { + fields = cloneAllFieldsMap(fields, /* copyList */ true); + isMutable = true; + } + } + + /** + * Useful for implementing {@link Message.Builder#setField(Descriptors.FieldDescriptor, + * Object)}. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public void setField(final T descriptor, Object value) { + ensureIsMutable(); + if (descriptor.isRepeated()) { + if (!(value instanceof List)) { + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection."); + } + + // Wrap the contents in a new list so that the caller cannot change + // the list's contents after setting it. + final List newList = new ArrayList(); + newList.addAll((List) value); + for (final Object element : newList) { + verifyType(descriptor.getLiteType(), element); + hasNestedBuilders = hasNestedBuilders || element instanceof MessageLite.Builder; + } + value = newList; + } else { + verifyType(descriptor.getLiteType(), value); + } + + if (value instanceof LazyField) { + hasLazyField = true; + } + hasNestedBuilders = hasNestedBuilders || value instanceof MessageLite.Builder; + + fields.put(descriptor, value); + } + + /** Useful for implementing {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */ + public void clearField(final T descriptor) { + ensureIsMutable(); + fields.remove(descriptor); + if (fields.isEmpty()) { + hasLazyField = false; + } + } + + /** + * Useful for implementing {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. + */ + public int getRepeatedFieldCount(final T descriptor) { + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedField() can only be called on repeated fields."); + } + + final Object value = getField(descriptor); + if (value == null) { + return 0; + } else { + return ((List<?>) value).size(); + } + } + + /** + * Useful for implementing {@link Message#getRepeatedField(Descriptors.FieldDescriptor, int)}. + */ + public Object getRepeatedField(final T descriptor, final int index) { + if (hasNestedBuilders) { + ensureIsMutable(); + } + Object value = getRepeatedFieldAllowBuilders(descriptor, index); + return replaceBuilder(value); + } + + /** + * Same as {@link #getRepeatedField(F, int)}, but allow a {@link MessageLite.Builder} to be + * returned. + */ + Object getRepeatedFieldAllowBuilders(final T descriptor, final int index) { + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedField() can only be called on repeated fields."); + } + + final Object value = getFieldAllowBuilders(descriptor); + + if (value == null) { + throw new IndexOutOfBoundsException(); + } else { + return ((List<?>) value).get(index); + } + } + + /** + * Useful for implementing {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor, + * int, Object)}. + */ + @SuppressWarnings("unchecked") + public void setRepeatedField(final T descriptor, final int index, final Object value) { + ensureIsMutable(); + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedField() can only be called on repeated fields."); + } + + hasNestedBuilders = hasNestedBuilders || value instanceof MessageLite.Builder; + + final Object list = getField(descriptor); + if (list == null) { + throw new IndexOutOfBoundsException(); + } + + verifyType(descriptor.getLiteType(), value); + ((List<Object>) list).set(index, value); + } + + /** + * Useful for implementing {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor, + * Object)}. + */ + @SuppressWarnings("unchecked") + public void addRepeatedField(final T descriptor, final Object value) { + ensureIsMutable(); + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "addRepeatedField() can only be called on repeated fields."); + } + + hasNestedBuilders = hasNestedBuilders || value instanceof MessageLite.Builder; + + verifyType(descriptor.getLiteType(), value); + + final Object existingValue = getField(descriptor); + List<Object> list; + if (existingValue == null) { + list = new ArrayList<>(); + fields.put(descriptor, list); + } else { + list = (List<Object>) existingValue; + } + + list.add(value); + } + + /** + * Verifies that the given object is of the correct type to be a valid value for the given + * field. (For repeated fields, this checks if the object is the right type to be one element of + * the field.) + * + * @throws IllegalArgumentException The value is not of the right type. + */ + private static void verifyType(final WireFormat.FieldType type, final Object value) { + if (!FieldSet.isValidType(type, value)) { + // Builder can accept Message.Builder values even though FieldSet will reject. + if (type.getJavaType() == WireFormat.JavaType.MESSAGE + && value instanceof MessageLite.Builder) { + return; + } + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection."); + } + } + + /** + * See {@link Message#isInitialized()}. Note: Since {@code FieldSet} itself does not have any + * way of knowing about required fields that aren't actually present in the set, it is up to the + * caller to check that all required fields are present. + */ + public boolean isInitialized() { + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + if (!FieldSet.isInitialized(fields.getArrayEntryAt(i))) { + return false; + } + } + for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) { + if (!FieldSet.isInitialized(entry)) { + return false; + } + } + return true; + } + + /** + * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another {@link FieldSet}. + */ + public void mergeFrom(final FieldSet<T> other) { + ensureIsMutable(); + for (int i = 0; i < other.fields.getNumArrayEntries(); i++) { + mergeFromField(other.fields.getArrayEntryAt(i)); + } + for (final Map.Entry<T, Object> entry : other.fields.getOverflowEntries()) { + mergeFromField(entry); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void mergeFromField(final Map.Entry<T, Object> entry) { + final T descriptor = entry.getKey(); + Object otherValue = entry.getValue(); + if (otherValue instanceof LazyField) { + otherValue = ((LazyField) otherValue).getValue(); + } + + if (descriptor.isRepeated()) { + Object value = getField(descriptor); + if (value == null) { + value = new ArrayList(); + } + for (Object element : (List) otherValue) { + ((List) value).add(FieldSet.cloneIfMutable(element)); + } + fields.put(descriptor, value); + } else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { + Object value = getField(descriptor); + if (value == null) { + fields.put(descriptor, FieldSet.cloneIfMutable(otherValue)); + } else { + // Merge the messages. + if (value instanceof MessageLite.Builder) { + descriptor.internalMergeFrom((MessageLite.Builder) value, (MessageLite) otherValue); + } else { + value = + descriptor + .internalMergeFrom(((MessageLite) value).toBuilder(), (MessageLite) otherValue) + .build(); + fields.put(descriptor, value); + } + } + } else { + fields.put(descriptor, cloneIfMutable(otherValue)); + } + } + } } --- a/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java @@ -1363,7 +1363,7 @@ public abstract class GeneratedMessageV3 extends Builder<BuilderType> implements ExtendableMessageOrBuilder<MessageType> { - private FieldSet<FieldDescriptor> extensions = FieldSet.emptySet(); + private FieldSet.Builder<FieldDescriptor> extensions; protected ExtendableBuilder() {} @@ -1374,18 +1374,18 @@ public abstract class GeneratedMessageV3 // For immutable message conversion. void internalSetExtensionSet(FieldSet<FieldDescriptor> extensions) { - this.extensions = extensions; + this.extensions = FieldSet.Builder.fromFieldSet(extensions); } @Override public BuilderType clear() { - extensions = FieldSet.emptySet(); + extensions = null; return super.clear(); } private void ensureExtensionsIsMutable() { - if (extensions.isImmutable()) { - extensions = extensions.clone(); + if (extensions == null) { + extensions = FieldSet.newBuilder(); } } @@ -1408,7 +1408,7 @@ public abstract class GeneratedMessageV3 Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); - return extensions.hasField(extension.getDescriptor()); + return extensions == null ? false : extensions.hasField(extension.getDescriptor()); } /** Get the number of elements in a repeated extension. */ @@ -1419,7 +1419,7 @@ public abstract class GeneratedMessageV3 verifyExtensionContainingType(extension); final FieldDescriptor descriptor = extension.getDescriptor(); - return extensions.getRepeatedFieldCount(descriptor); + return extensions == null ? 0 : extensions.getRepeatedFieldCount(descriptor); } /** Get the value of an extension. */ @@ -1429,7 +1429,7 @@ public abstract class GeneratedMessageV3 verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); - final Object value = extensions.getField(descriptor); + final Object value = extensions == null ? null : extensions.getField(descriptor); if (value == null) { if (descriptor.isRepeated()) { return (Type) Collections.emptyList(); @@ -1453,8 +1453,11 @@ public abstract class GeneratedMessageV3 verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); - return (Type) extension.singularFromReflectionType( - extensions.getRepeatedField(descriptor, index)); + if (extensions == null) { + throw new IndexOutOfBoundsException(); + } + return (Type) + extension.singularFromReflectionType(extensions.getRepeatedField(descriptor, index)); } /** Set the value of an extension. */ @@ -1605,7 +1608,7 @@ public abstract class GeneratedMessageV3 /** Called by subclasses to check if all extensions are initialized. */ protected boolean extensionsAreInitialized() { - return extensions.isInitialized(); + return extensions == null ? true : extensions.isInitialized(); } /** @@ -1613,8 +1616,9 @@ public abstract class GeneratedMessageV3 * building the message. */ private FieldSet<FieldDescriptor> buildExtensions() { - extensions.makeImmutable(); - return extensions; + return extensions == null + ? (FieldSet<FieldDescriptor>) FieldSet.emptySet() + : extensions.build(); } @Override @@ -1628,7 +1632,9 @@ public abstract class GeneratedMessageV3 @Override public Map<FieldDescriptor, Object> getAllFields() { final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable(); - result.putAll(extensions.getAllFields()); + if (extensions != null) { + result.putAll(extensions.getAllFields()); + } return Collections.unmodifiableMap(result); } @@ -1636,7 +1642,7 @@ public abstract class GeneratedMessageV3 public Object getField(final FieldDescriptor field) { if (field.isExtension()) { verifyContainingType(field); - final Object value = extensions.getField(field); + final Object value = extensions == null ? null : extensions.getField(field); if (value == null) { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { // Lacking an ExtensionRegistry, we have no way to determine the @@ -1654,10 +1660,43 @@ public abstract class GeneratedMessageV3 } @Override + public Message.Builder getFieldBuilder(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + throw new UnsupportedOperationException( + "getFieldBuilder() called on a non-Message type."); + } + ensureExtensionsIsMutable(); + final Object value = extensions.getFieldAllowBuilders(field); + if (value == null) { + Message.Builder builder = DynamicMessage.newBuilder(field.getMessageType()); + extensions.setField(field, builder); + onChanged(); + return builder; + } else { + if (value instanceof Message.Builder) { + return (Message.Builder) value; + } else if (value instanceof Message) { + Message.Builder builder = ((Message) value).toBuilder(); + extensions.setField(field, builder); + onChanged(); + return builder; + } else { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a non-Message type."); + } + } + } else { + return super.getFieldBuilder(field); + } + } + + @Override public int getRepeatedFieldCount(final FieldDescriptor field) { if (field.isExtension()) { verifyContainingType(field); - return extensions.getRepeatedFieldCount(field); + return extensions == null ? 0 : extensions.getRepeatedFieldCount(field); } else { return super.getRepeatedFieldCount(field); } @@ -1668,6 +1707,9 @@ public abstract class GeneratedMessageV3 final int index) { if (field.isExtension()) { verifyContainingType(field); + if (extensions == null) { + throw new IndexOutOfBoundsException(); + } return extensions.getRepeatedField(field, index); } else { return super.getRepeatedField(field, index); @@ -1675,10 +1717,36 @@ public abstract class GeneratedMessageV3 } @Override + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, final int index) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a non-Message type."); + } + final Object value = extensions.getRepeatedFieldAllowBuilders(field, index); + if (value instanceof Message.Builder) { + return (Message.Builder) value; + } else if (value instanceof Message) { + Message.Builder builder = ((Message) value).toBuilder(); + extensions.setRepeatedField(field, index, builder); + onChanged(); + return builder; + } else { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a non-Message type."); + } + } else { + return super.getRepeatedFieldBuilder(field, index); + } + } + + @Override public boolean hasField(final FieldDescriptor field) { if (field.isExtension()) { verifyContainingType(field); - return extensions.hasField(field); + return extensions == null ? false : extensions.hasField(field); } else { return super.hasField(field); } @@ -1749,9 +1817,11 @@ public abstract class GeneratedMessageV3 } protected final void mergeExtensionFields(final ExtendableMessage other) { - ensureExtensionsIsMutable(); - extensions.mergeFrom(other.extensions); - onChanged(); + if (other.extensions != null) { + ensureExtensionsIsMutable(); + extensions.mergeFrom(other.extensions); + onChanged(); + } } private void verifyContainingType(final FieldDescriptor field) { --- a/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java @@ -32,9 +32,10 @@ package com.google.protobuf; import com.google.protobuf.Internal.ProtobufList; import java.util.Arrays; +import java.util.RandomAccess; /** Implements {@link ProtobufList} for non-primitive and {@link String} types. */ -final class ProtobufArrayList<E> extends AbstractProtobufList<E> { +final class ProtobufArrayList<E> extends AbstractProtobufList<E> implements RandomAccess { private static final ProtobufArrayList<Object> EMPTY_LIST = new ProtobufArrayList<Object>(new Object[0], 0); --- a/java/core/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -113,8 +113,8 @@ public final class TextFormat { } /** - * Generates a human readable form of the field, useful for debugging and other purposes, with no - * newline characters. + * Generates a human readable form of the field, useful for debugging and other purposes, with + * no newline characters. * * @deprecated Use {@code printer().shortDebugString(FieldDescriptor, Object)} */ @@ -122,10 +122,10 @@ public final class TextFormat { public static String shortDebugString(final FieldDescriptor field, final Object value) { return printer().shortDebugString(field, value); } - + // /** - * Generates a human readable form of the unknown fields, useful for debugging and other purposes, - * with no newline characters. + * Generates a human readable form of the unknown fields, useful for debugging and other + * purposes, with no newline characters. * * @deprecated Use {@code printer().shortDebugString(UnknownFieldSet)} */ @@ -166,8 +166,8 @@ public final class TextFormat { } /** - * Same as {@code printToString()}, except that non-ASCII characters in string type fields are not - * escaped in backslash+octals. + * Same as {@code printToString()}, except that non-ASCII characters in string type fields are + * not escaped in backslash+octals. * * @deprecated Use {@code printer().escapingNonAscii(false).printToString(UnknownFieldSet)} */ @@ -175,20 +175,21 @@ public final class TextFormat { public static String printToUnicodeString(final UnknownFieldSet fields) { return printer().escapingNonAscii(false).printToString(fields); } - + // /** @deprecated Use {@code printer().printField(FieldDescriptor, Object, Appendable)} */ @Deprecated public static void printField( - final FieldDescriptor field, final Object value, final Appendable output) throws IOException { + final FieldDescriptor field, final Object value, final Appendable output) + throws IOException { printer().printField(field, value, output); } - + // /** @deprecated Use {@code printer().printFieldToString(FieldDescriptor, Object)} */ @Deprecated public static String printFieldToString(final FieldDescriptor field, final Object value) { return printer().printFieldToString(field, value); } - + // /** * Outputs a unicode textual representation of the value of given field value. * @@ -205,7 +206,8 @@ public final class TextFormat { */ @Deprecated public static void printUnicodeFieldValue( - final FieldDescriptor field, final Object value, final Appendable output) throws IOException { + final FieldDescriptor field, final Object value, final Appendable output) + throws IOException { printer().escapingNonAscii(false).printFieldValue(field, value, output); } @@ -285,13 +287,16 @@ public final class TextFormat { public static final class Printer { // Printer instance which escapes non-ASCII characters. - private static final Printer DEFAULT = new Printer(true); + private static final Printer DEFAULT = new Printer(true, TypeRegistry.getEmptyTypeRegistry()); /** Whether to escape non ASCII characters with backslash and octal. */ private final boolean escapeNonAscii; - private Printer(boolean escapeNonAscii) { + private final TypeRegistry typeRegistry; + + private Printer(boolean escapeNonAscii, TypeRegistry typeRegistry) { this.escapeNonAscii = escapeNonAscii; + this.typeRegistry = typeRegistry; } /** @@ -304,7 +309,20 @@ public final class TextFormat { * with the escape mode set to the given parameter. */ public Printer escapingNonAscii(boolean escapeNonAscii) { - return new Printer(escapeNonAscii); + return new Printer(escapeNonAscii, typeRegistry); + } + + /** + * Creates a new {@link Printer} using the given typeRegistry. The new Printer clones all other + * configurations from the current {@link Printer}. + * + * @throws IllegalArgumentException if a registry is already set. + */ + public Printer usingTypeRegistry(TypeRegistry typeRegistry) { + if (this.typeRegistry != TypeRegistry.getEmptyTypeRegistry()) { + throw new IllegalArgumentException("Only one typeRegistry is allowed."); + } + return new Printer(escapeNonAscii, typeRegistry); } /** @@ -323,9 +341,66 @@ public final class TextFormat { private void print(final MessageOrBuilder message, final TextGenerator generator) throws IOException { + if (message.getDescriptorForType().getFullName().equals("google.protobuf.Any") + && printAny(message, generator)) { + return; + } printMessage(message, generator); } + /** + * Attempt to print the 'google.protobuf.Any' message in a human-friendly format. Returns false + * if the message isn't a valid 'google.protobuf.Any' message (in which case the message should + * be rendered just like a regular message to help debugging). + */ + private boolean printAny(final MessageOrBuilder message, final TextGenerator generator) + throws IOException { + Descriptor messageType = message.getDescriptorForType(); + FieldDescriptor typeUrlField = messageType.findFieldByNumber(1); + FieldDescriptor valueField = messageType.findFieldByNumber(2); + if (typeUrlField == null + || typeUrlField.getType() != FieldDescriptor.Type.STRING + || valueField == null + || valueField.getType() != FieldDescriptor.Type.BYTES) { + // The message may look like an Any but isn't actually an Any message (might happen if the + // user tries to use DynamicMessage to construct an Any from incomplete Descriptor). + return false; + } + String typeUrl = (String) message.getField(typeUrlField); + // If type_url is not set, we will not be able to decode the content of the value, so just + // print out the Any like a regular message. + if (typeUrl.isEmpty()) { + return false; + } + Object value = message.getField(valueField); + + Message.Builder contentBuilder = null; + try { + Descriptor contentType = typeRegistry.getDescriptorForTypeUrl(typeUrl); + if (contentType == null) { + return false; + } + contentBuilder = DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); + contentBuilder.mergeFrom((ByteString) value); + } catch (InvalidProtocolBufferException e) { + // The value of Any is malformed. We cannot print it out nicely, so fallback to printing out + // the type_url and value as bytes. Note that we fail open here to be consistent with + // text_format.cc, and also to allow a way for users to inspect the content of the broken + // message. + return false; + } + generator.print("["); + generator.print(typeUrl); + generator.print("] {"); + generator.eol(); + generator.indent(); + print(contentBuilder, generator); + generator.outdent(); + generator.print("}"); + generator.eol(); + return true; + } + public String printFieldToString(final FieldDescriptor field, final Object value) { try { final StringBuilder text = new StringBuilder(); @@ -1382,6 +1457,7 @@ public final class TextFormat { FORBID_SINGULAR_OVERWRITES } + private final TypeRegistry typeRegistry; private final boolean allowUnknownFields; private final boolean allowUnknownEnumValues; private final boolean allowUnknownExtensions; @@ -1389,11 +1465,13 @@ public final class TextFormat { private TextFormatParseInfoTree.Builder parseInfoTreeBuilder; private Parser( + TypeRegistry typeRegistry, boolean allowUnknownFields, boolean allowUnknownEnumValues, boolean allowUnknownExtensions, SingularOverwritePolicy singularOverwritePolicy, TextFormatParseInfoTree.Builder parseInfoTreeBuilder) { + this.typeRegistry = typeRegistry; this.allowUnknownFields = allowUnknownFields; this.allowUnknownEnumValues = allowUnknownEnumValues; this.allowUnknownExtensions = allowUnknownExtensions; @@ -1414,6 +1492,18 @@ public final class TextFormat { private SingularOverwritePolicy singularOverwritePolicy = SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES; private TextFormatParseInfoTree.Builder parseInfoTreeBuilder = null; + private TypeRegistry typeRegistry = TypeRegistry.getEmptyTypeRegistry(); + + /** + * Sets the TypeRegistry for resolving Any. If this is not set, TextFormat will not be able to + * parse Any unless Any is write as bytes. + * + * @throws IllegalArgumentException if a registry is already set. + */ + public Builder setTypeRegistry(TypeRegistry typeRegistry) { + this.typeRegistry = typeRegistry; + return this; + } /** * Set whether this parser will allow unknown fields. By default, an exception is thrown if an @@ -1452,6 +1542,7 @@ public final class TextFormat { public Parser build() { return new Parser( + typeRegistry, allowUnknownFields, allowUnknownEnumValues, allowUnknownExtensions, @@ -1834,6 +1925,14 @@ public final class TextFormat { endToken = "}"; } + // Try to parse human readable format of Any in the form: [type_url]: { ... } + if (field.getMessageType().getFullName().equals("google.protobuf.Any") + && tokenizer.tryConsume("[")) { + value = + consumeAnyFieldValue( + tokenizer, extensionRegistry, field, parseTreeBuilder, unknownFields); + tokenizer.consume(endToken); + } else { Message defaultInstance = (extension == null) ? null : extension.defaultInstance; MessageReflection.MergeTarget subField = target.newMergeTargetForField(field, defaultInstance); @@ -1846,6 +1945,8 @@ public final class TextFormat { } value = subField.finish(); + } + } else { switch (field.getType()) { case INT32: @@ -1951,6 +2052,71 @@ public final class TextFormat { } } + private Object consumeAnyFieldValue( + final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final FieldDescriptor field, + final TextFormatParseInfoTree.Builder parseTreeBuilder, + List<UnknownField> unknownFields) + throws ParseException { + // Try to parse human readable format of Any in the form: [type_url]: { ... } + StringBuilder typeUrlBuilder = new StringBuilder(); + // Parse the type_url inside []. + while (true) { + typeUrlBuilder.append(tokenizer.consumeIdentifier()); + if (tokenizer.tryConsume("]")) { + break; + } + if (tokenizer.tryConsume("/")) { + typeUrlBuilder.append("/"); + } else if (tokenizer.tryConsume(".")) { + typeUrlBuilder.append("."); + } else { + throw tokenizer.parseExceptionPreviousToken("Expected a valid type URL."); + } + } + tokenizer.tryConsume(":"); + final String anyEndToken; + if (tokenizer.tryConsume("<")) { + anyEndToken = ">"; + } else { + tokenizer.consume("{"); + anyEndToken = "}"; + } + String typeUrl = typeUrlBuilder.toString(); + Descriptor contentType = null; + try { + contentType = typeRegistry.getDescriptorForTypeUrl(typeUrl); + } catch (InvalidProtocolBufferException e) { + throw tokenizer.parseException("Invalid valid type URL. Found: " + typeUrl); + } + if (contentType == null) { + throw tokenizer.parseException( + "Unable to parse Any of type: " + + typeUrl + + ". Please make sure that the TypeRegistry contains the descriptors for the given" + + " types."); + } + Message.Builder contentBuilder = + DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); + MessageReflection.BuilderAdapter contentTarget = + new MessageReflection.BuilderAdapter(contentBuilder); + while (!tokenizer.tryConsume(anyEndToken)) { + mergeField(tokenizer, extensionRegistry, contentTarget, parseTreeBuilder, unknownFields); + } + + // Serialize the content and put it back into an Any. Note that we can't depend on Any here + // because of a cyclic dependency (java_proto_library for any_java_proto depends on the + // protobuf_impl), so we need to construct the Any using proto reflection. + Descriptor anyDescriptor = field.getMessageType(); + Message.Builder anyBuilder = + DynamicMessage.getDefaultInstance(anyDescriptor).newBuilderForType(); + anyBuilder.setField(anyDescriptor.findFieldByName("type_url"), typeUrlBuilder.toString()); + anyBuilder.setField( + anyDescriptor.findFieldByName("value"), contentBuilder.build().toByteString()); + + return anyBuilder.build(); + } /** Skips the next field including the field's name and value. */ private void skipField(Tokenizer tokenizer) throws ParseException { --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TypeRegistry.java @@ -0,0 +1,160 @@ +// 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.Descriptor; +import com.google.protobuf.Descriptors.FileDescriptor; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * A TypeRegistry is used to resolve Any messages. You must provide a TypeRegistry containing all + * message types used in Any message fields. + */ +public class TypeRegistry { + private static final Logger logger = Logger.getLogger(TypeRegistry.class.getName()); + + private static class EmptyTypeRegistryHolder { + private static final TypeRegistry EMPTY = + new TypeRegistry(Collections.<String, Descriptor>emptyMap()); + } + + public static TypeRegistry getEmptyTypeRegistry() { + return EmptyTypeRegistryHolder.EMPTY; + } + + + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Find a type by its full name. Returns null if it cannot be found in this {@link TypeRegistry}. + */ + public Descriptor find(String name) { + return types.get(name); + } + + /** + * Find a type by its typeUrl. Returns null if it cannot be found in this {@link TypeRegistry}. + */ + /* @Nullable */ + public final Descriptor getDescriptorForTypeUrl(String typeUrl) + throws InvalidProtocolBufferException { + return find(getTypeName(typeUrl)); + } + + private final Map<String, Descriptor> types; + + TypeRegistry(Map<String, Descriptor> types) { + this.types = types; + } + + private static String getTypeName(String typeUrl) throws InvalidProtocolBufferException { + String[] parts = typeUrl.split("/"); + if (parts.length == 1) { + throw new InvalidProtocolBufferException("Invalid type url found: " + typeUrl); + } + return parts[parts.length - 1]; + } + + /** A Builder is used to build {@link TypeRegistry}. */ + public static final class Builder { + private Builder() {} + + /** + * Adds a message type and all types defined in the same .proto file as well as all transitively + * imported .proto files to this {@link Builder}. + */ + public Builder add(Descriptor messageType) { + if (types == null) { + throw new IllegalStateException("A TypeRegistry.Builder can only be used once."); + } + addFile(messageType.getFile()); + return this; + } + + /** + * Adds message types and all types defined in the same .proto file as well as all transitively + * imported .proto files to this {@link Builder}. + */ + public Builder add(Iterable<Descriptor> messageTypes) { + if (types == null) { + throw new IllegalStateException("A TypeRegistry.Builder can only be used once."); + } + for (Descriptor type : messageTypes) { + addFile(type.getFile()); + } + return this; + } + + /** Builds a {@link TypeRegistry}. This method can only be called once for one Builder. */ + public TypeRegistry build() { + TypeRegistry result = new TypeRegistry(types); + // Make sure the built {@link TypeRegistry} is immutable. + types = null; + return result; + } + + private void addFile(FileDescriptor file) { + // Skip the file if it's already added. + if (!files.add(file.getFullName())) { + return; + } + for (FileDescriptor dependency : file.getDependencies()) { + addFile(dependency); + } + for (Descriptor message : file.getMessageTypes()) { + addMessage(message); + } + } + + private void addMessage(Descriptor message) { + for (Descriptor nestedType : message.getNestedTypes()) { + addMessage(nestedType); + } + + if (types.containsKey(message.getFullName())) { + logger.warning("Type " + message.getFullName() + " is added multiple times."); + return; + } + + types.put(message.getFullName(), message); + } + + private final Set<String> files = new HashSet<>(); + private Map<String, Descriptor> types = new HashMap<>(); + } +} --- a/java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java +++ b/java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -115,6 +115,160 @@ public class GeneratedMessageTest extend GeneratedMessageV3.setAlwaysUseFieldBuildersForTesting(false); } + public void testGetFieldBuilderForExtensionField() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + Message.Builder fieldBuilder = + builder.getFieldBuilder(UnittestProto.optionalNestedMessageExtension.getDescriptor()); + int expected = 7432; + FieldDescriptor field = + NestedMessage.getDescriptor().findFieldByNumber(NestedMessage.BB_FIELD_NUMBER); + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.optionalNestedMessageExtension).getBb()); + + // fieldBuilder still updates the builder after builder build() has been called. + expected += 100; + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.optionalNestedMessageExtension).getBb()); + } + + public void testGetFieldBuilderWithExistingMessage() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + builder.setExtension( + UnittestProto.optionalNestedMessageExtension, + NestedMessage.newBuilder().setBb(123).build()); + Message.Builder fieldBuilder = + builder.getFieldBuilder(UnittestProto.optionalNestedMessageExtension.getDescriptor()); + int expected = 7432; + FieldDescriptor field = + NestedMessage.getDescriptor().findFieldByNumber(NestedMessage.BB_FIELD_NUMBER); + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.optionalNestedMessageExtension).getBb()); + + // fieldBuilder still updates the builder after builder build() has been called. + expected += 100; + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.optionalNestedMessageExtension).getBb()); + } + + public void testGetFieldBuilderWithExistingBuilder() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + NestedMessage.Builder nestedMessageBuilder = NestedMessage.newBuilder().setBb(123); + builder.setField( + UnittestProto.optionalNestedMessageExtension.getDescriptor(), nestedMessageBuilder); + Message.Builder fieldBuilder = + builder.getFieldBuilder(UnittestProto.optionalNestedMessageExtension.getDescriptor()); + int expected = 7432; + FieldDescriptor field = + NestedMessage.getDescriptor().findFieldByNumber(NestedMessage.BB_FIELD_NUMBER); + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.optionalNestedMessageExtension).getBb()); + + // Existing nestedMessageBuilder will also update builder. + expected += 100; + nestedMessageBuilder.setBb(expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.optionalNestedMessageExtension).getBb()); + + // fieldBuilder still updates the builder. + expected += 100; + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.optionalNestedMessageExtension).getBb()); + } + + public void testGetRepeatedFieldBuilderForExtensionField() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + builder.addExtension( + UnittestProto.repeatedNestedMessageExtension, + NestedMessage.newBuilder().setBb(123).build()); + Message.Builder fieldBuilder = + builder.getRepeatedFieldBuilder( + UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0); + int expected = 7432; + FieldDescriptor field = + NestedMessage.getDescriptor().findFieldByNumber(NestedMessage.BB_FIELD_NUMBER); + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.repeatedNestedMessageExtension, 0).getBb()); + + // fieldBuilder still updates the builder after builder build() has been called. + expected += 100; + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.repeatedNestedMessageExtension, 0).getBb()); + } + + public void testGetRepeatedFieldBuilderForExistingBuilder() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + NestedMessage.Builder nestedMessageBuilder = NestedMessage.newBuilder().setBb(123); + builder.addRepeatedField( + UnittestProto.repeatedNestedMessageExtension.getDescriptor(), nestedMessageBuilder); + Message.Builder fieldBuilder = + builder.getRepeatedFieldBuilder( + UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0); + int expected = 7432; + FieldDescriptor field = + NestedMessage.getDescriptor().findFieldByNumber(NestedMessage.BB_FIELD_NUMBER); + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.repeatedNestedMessageExtension, 0).getBb()); + + // Existing nestedMessageBuilder will also update builder. + expected += 100; + nestedMessageBuilder.setBb(expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.repeatedNestedMessageExtension, 0).getBb()); + + // fieldBuilder still updates the builder. + expected += 100; + fieldBuilder.setField(field, expected); + assertEquals( + expected, + builder.build().getExtension(UnittestProto.repeatedNestedMessageExtension, 0).getBb()); + } + + public void testGetExtensionFieldOutOfBound() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + try { + builder.getRepeatedField(UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0); + fail("Expected IndexOutOfBoundsException to be thrown"); + } catch (IndexOutOfBoundsException expected) { + } + try { + builder.getExtension(UnittestProto.repeatedNestedMessageExtension, 0); + fail("Expected IndexOutOfBoundsException to be thrown"); + } catch (IndexOutOfBoundsException expected) { + } + TestAllExtensions extensionsMessage = builder.build(); + try { + extensionsMessage.getRepeatedField( + UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0); + fail("Expected IndexOutOfBoundsException to be thrown"); + } catch (IndexOutOfBoundsException expected) { + } + try { + extensionsMessage.getExtension(UnittestProto.repeatedNestedMessageExtension, 0); + fail("Expected IndexOutOfBoundsException to be thrown"); + } catch (IndexOutOfBoundsException expected) { + } + } + public void testDefaultInstance() throws Exception { assertSame( TestAllTypes.getDefaultInstance(), --- a/java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java +++ b/java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java @@ -30,6 +30,7 @@ package com.google.protobuf; +import protobuf_unittest.Engine; import protobuf_unittest.Vehicle; import protobuf_unittest.Wheel; import java.util.ArrayList; @@ -64,7 +65,7 @@ public class NestedBuildersTest extends for (int i = 0; i < 4; i++) { vehicleBuilder.getWheelBuilder(i).setRadius(5).setWidth(i + 10); } - vehicleBuilder.getEngineBuilder().setLiters(20); + Engine.Builder engineBuilder = vehicleBuilder.getEngineBuilder().setLiters(20); vehicle = vehicleBuilder.build(); for (int i = 0; i < 4; i++) { @@ -74,6 +75,9 @@ public class NestedBuildersTest extends } assertEquals(20, vehicle.getEngine().getLiters()); assertTrue(vehicle.hasEngine()); + + engineBuilder.setLiters(50); + assertEquals(50, vehicleBuilder.getEngine().getLiters()); } public void testMessagesAreCached() { --- a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java @@ -33,9 +33,14 @@ package com.google.protobuf; import static com.google.protobuf.TestUtil.TEST_REQUIRED_INITIALIZED; import static com.google.protobuf.TestUtil.TEST_REQUIRED_UNINITIALIZED; +import com.google.protobuf.DescriptorProtos.DescriptorProto; +import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; +import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.TextFormat.Parser.SingularOverwritePolicy; +import any_test.AnyTestProto.TestAny; import map_test.MapTestProto.TestMap; import protobuf_unittest.UnittestMset.TestMessageSetExtension1; import protobuf_unittest.UnittestMset.TestMessageSetExtension2; @@ -48,6 +53,7 @@ import protobuf_unittest.UnittestProto.T import protobuf_unittest.UnittestProto.TestRequired; import proto2_wireformat_unittest.UnittestMsetWireFormat.TestMessageSet; import java.io.StringReader; +import java.util.Arrays; import java.util.List; import java.util.logging.Logger; import junit.framework.TestCase; @@ -506,6 +512,191 @@ public class TextFormatTest extends Test assertEquals(2, builder.getOptionalInt64()); } + public void testPrintAny_customBuiltTypeRegistry() throws Exception { + TestAny testAny = + TestAny.newBuilder() + .setValue( + Any.newBuilder() + .setTypeUrl("type.googleapis.com/" + TestAllTypes.getDescriptor().getFullName()) + .setValue( + TestAllTypes.newBuilder().setOptionalInt32(12345).build().toByteString()) + .build()) + .build(); + String actual = + TextFormat.printer() + .usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .printToString(testAny); + String expected = + "value {\n" + + " [type.googleapis.com/protobuf_unittest.TestAllTypes] {\n" + + " optional_int32: 12345\n" + + " }\n" + + "}\n"; + assertEquals(expected, actual); + } + + private static Descriptor createDescriptorForAny(FieldDescriptorProto... fields) + throws Exception { + FileDescriptor fileDescriptor = + FileDescriptor.buildFrom( + FileDescriptorProto.newBuilder() + .setName("any.proto") + .setPackage("google.protobuf") + .setSyntax("proto3") + .addMessageType( + DescriptorProto.newBuilder() + .setName("Any") + .addAllField(Arrays.asList(fields))) + .build(), + new FileDescriptor[0]); + return fileDescriptor.getMessageTypes().get(0); + } + + public void testPrintAny_anyWithDynamicMessage() throws Exception { + Descriptor descriptor = + createDescriptorForAny( + FieldDescriptorProto.newBuilder() + .setName("type_url") + .setNumber(1) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_STRING) + .build(), + FieldDescriptorProto.newBuilder() + .setName("value") + .setNumber(2) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_BYTES) + .build()); + DynamicMessage testAny = + DynamicMessage.newBuilder(descriptor) + .setField( + descriptor.findFieldByNumber(1), + "type.googleapis.com/" + TestAllTypes.getDescriptor().getFullName()) + .setField( + descriptor.findFieldByNumber(2), + TestAllTypes.newBuilder().setOptionalInt32(12345).build().toByteString()) + .build(); + String actual = + TextFormat.printer() + .usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .printToString(testAny); + String expected = + "[type.googleapis.com/protobuf_unittest.TestAllTypes] {\n" + + " optional_int32: 12345\n" + + "}\n"; + assertEquals(expected, actual); + } + + public void testPrintAny_anyFromWithNoValueField() throws Exception { + Descriptor descriptor = + createDescriptorForAny( + FieldDescriptorProto.newBuilder() + .setName("type_url") + .setNumber(1) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_STRING) + .build()); + DynamicMessage testAny = + DynamicMessage.newBuilder(descriptor) + .setField( + descriptor.findFieldByNumber(1), + "type.googleapis.com/" + TestAllTypes.getDescriptor().getFullName()) + .build(); + String actual = + TextFormat.printer() + .usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .printToString(testAny); + String expected = "type_url: \"type.googleapis.com/protobuf_unittest.TestAllTypes\"\n"; + assertEquals(expected, actual); + } + + public void testPrintAny_anyFromWithNoTypeUrlField() throws Exception { + Descriptor descriptor = + createDescriptorForAny( + FieldDescriptorProto.newBuilder() + .setName("value") + .setNumber(2) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_BYTES) + .build()); + DynamicMessage testAny = + DynamicMessage.newBuilder(descriptor) + .setField( + descriptor.findFieldByNumber(2), + TestAllTypes.newBuilder().setOptionalInt32(12345).build().toByteString()) + .build(); + String actual = + TextFormat.printer() + .usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .printToString(testAny); + String expected = "value: \"\\b\\271`\"\n"; + assertEquals(expected, actual); + } + + public void testPrintAny_anyWithInvalidFieldType() throws Exception { + Descriptor descriptor = + createDescriptorForAny( + FieldDescriptorProto.newBuilder() + .setName("type_url") + .setNumber(1) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_STRING) + .build(), + FieldDescriptorProto.newBuilder() + .setName("value") + .setNumber(2) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_STRING) + .build()); + DynamicMessage testAny = + DynamicMessage.newBuilder(descriptor) + .setField( + descriptor.findFieldByNumber(1), + "type.googleapis.com/" + TestAllTypes.getDescriptor().getFullName()) + .setField(descriptor.findFieldByNumber(2), "test") + .build(); + String actual = + TextFormat.printer() + .usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .printToString(testAny); + String expected = + "type_url: \"type.googleapis.com/protobuf_unittest.TestAllTypes\"\n" + "value: \"test\"\n"; + assertEquals(expected, actual); + } + + + public void testMergeAny_customBuiltTypeRegistry() throws Exception { + TestAny.Builder builder = TestAny.newBuilder(); + TextFormat.Parser.newBuilder() + .setTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .build() + .merge( + "value: {\n" + + "[type.googleapis.com/protobuf_unittest.TestAllTypes] {\n" + + "optional_int32: 12345\n" + + "optional_nested_message {\n" + + " bb: 123\n" + + "}\n" + + "}\n" + + "}", + builder); + assertEquals( + TestAny.newBuilder() + .setValue( + Any.newBuilder() + .setTypeUrl("type.googleapis.com/" + TestAllTypes.getDescriptor().getFullName()) + .setValue( + TestAllTypes.newBuilder() + .setOptionalInt32(12345) + .setOptionalNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(123)) + .build() + .toByteString()) + .build()) + .build(), + builder.build()); + } + private void assertParseError(String error, String text) { // Test merge(). --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/TypeRegistryTest.java @@ -0,0 +1,70 @@ +// 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 org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import com.google.protobuf.Descriptors.Descriptor; +import protobuf_unittest.UnittestProto; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class TypeRegistryTest { + + @Test + public void findDescriptorByFullName() throws Exception { + Descriptor descriptor = UnittestProto.TestAllTypes.getDescriptor(); + assertNull(TypeRegistry.getEmptyTypeRegistry().find(descriptor.getFullName())); + + assertSame( + descriptor, + TypeRegistry.newBuilder().add(descriptor).build().find(descriptor.getFullName())); + } + + @Test + public void findDescriptorByTypeUrl() throws Exception { + Descriptor descriptor = UnittestProto.TestAllTypes.getDescriptor(); + assertNull( + TypeRegistry.getEmptyTypeRegistry() + .getDescriptorForTypeUrl("type.googleapis.com/" + descriptor.getFullName())); + + assertSame( + descriptor, + TypeRegistry.newBuilder() + .add(descriptor) + .build() + .getDescriptorForTypeUrl("type.googleapis.com/" + descriptor.getFullName())); + } + +} --- a/java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto +++ b/java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto @@ -36,8 +36,6 @@ package protobuf_unittest; message Proto1 { option experimental_java_message_interface = "com.google.protobuf.ExtraInterfaces.HasBoolValue"; - option experimental_java_interface_extends = - "com.google.protobuf.ExtraInterfaces.HasByteValue"; option experimental_java_message_interface = "com.google.protobuf.ExtraInterfaces.HasStringValue<Proto1>"; option experimental_java_builder_interface = --- a/java/lite/pom.xml +++ b/java/lite/pom.xml @@ -224,6 +224,7 @@ <exclude>TextFormatParseLocationTest.java</exclude> <exclude>TextFormatTest.java</exclude> <exclude>TestUtil.java</exclude> + <exclude>TypeRegistryTest.java</exclude> <exclude>UnknownEnumValueTest.java</exclude> <exclude>UnknownFieldSetLiteTest.java</exclude> <exclude>UnknownFieldSetTest.java</exclude> --- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java +++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java @@ -108,15 +108,22 @@ public class JsonFormat { */ public static Printer printer() { return new Printer( - TypeRegistry.getEmptyTypeRegistry(), false, Collections.<FieldDescriptor>emptySet(), - false, false, false, false); + com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(), + TypeRegistry.getEmptyTypeRegistry(), + /* alwaysOutputDefaultValueFields */ false, + /* includingDefaultValueFields */ Collections.<FieldDescriptor>emptySet(), + /* preservingProtoFieldNames */ false, + /* omittingInsignificantWhitespace */ false, + /* printingEnumsAsInts */ false, + /* sortingMapKeys */ false); } /** * A Printer converts protobuf message to JSON format. */ public static class Printer { - private final TypeRegistry registry; + private final com.google.protobuf.TypeRegistry registry; + private final TypeRegistry oldRegistry; // NOTE: There are 3 states for these *defaultValueFields variables: // 1) Default - alwaysOutput is false & including is empty set. Fields only output if they are // set to non-default values. @@ -133,7 +140,8 @@ public class JsonFormat { private final boolean sortingMapKeys; private Printer( - TypeRegistry registry, + com.google.protobuf.TypeRegistry registry, + TypeRegistry oldRegistry, boolean alwaysOutputDefaultValueFields, Set<FieldDescriptor> includingDefaultValueFields, boolean preservingProtoFieldNames, @@ -141,6 +149,7 @@ public class JsonFormat { boolean printingEnumsAsInts, boolean sortingMapKeys) { this.registry = registry; + this.oldRegistry = oldRegistry; this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields; this.includingDefaultValueFields = includingDefaultValueFields; this.preservingProtoFieldNames = preservingProtoFieldNames; @@ -150,17 +159,41 @@ public class JsonFormat { } /** - * Creates a new {@link Printer} using the given registry. The new Printer - * clones all other configurations from the current {@link Printer}. + * Creates a new {@link Printer} using the given registry. The new Printer clones all other + * configurations from the current {@link Printer}. * * @throws IllegalArgumentException if a registry is already set. */ - public Printer usingTypeRegistry(TypeRegistry registry) { - if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { + public Printer usingTypeRegistry(TypeRegistry oldRegistry) { + if (this.oldRegistry != TypeRegistry.getEmptyTypeRegistry() + || this.registry != com.google.protobuf.TypeRegistry.getEmptyTypeRegistry()) { + throw new IllegalArgumentException("Only one registry is allowed."); + } + return new Printer( + com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(), + oldRegistry, + alwaysOutputDefaultValueFields, + includingDefaultValueFields, + preservingProtoFieldNames, + omittingInsignificantWhitespace, + printingEnumsAsInts, + sortingMapKeys); + } + + /** + * Creates a new {@link Printer} using the given registry. The new Printer clones all other + * configurations from the current {@link Printer}. + * + * @throws IllegalArgumentException if a registry is already set. + */ + public Printer usingTypeRegistry(com.google.protobuf.TypeRegistry registry) { + if (this.oldRegistry != TypeRegistry.getEmptyTypeRegistry() + || this.registry != com.google.protobuf.TypeRegistry.getEmptyTypeRegistry()) { throw new IllegalArgumentException("Only one registry is allowed."); } return new Printer( registry, + oldRegistry, alwaysOutputDefaultValueFields, includingDefaultValueFields, preservingProtoFieldNames, @@ -179,6 +212,7 @@ public class JsonFormat { checkUnsetIncludingDefaultValueFields(); return new Printer( registry, + oldRegistry, true, Collections.<FieldDescriptor>emptySet(), preservingProtoFieldNames, @@ -197,6 +231,7 @@ public class JsonFormat { checkUnsetPrintingEnumsAsInts(); return new Printer( registry, + oldRegistry, alwaysOutputDefaultValueFields, Collections.<FieldDescriptor>emptySet(), preservingProtoFieldNames, @@ -226,6 +261,7 @@ public class JsonFormat { checkUnsetIncludingDefaultValueFields(); return new Printer( registry, + oldRegistry, false, Collections.unmodifiableSet(new HashSet<>(fieldsToAlwaysOutput)), preservingProtoFieldNames, @@ -250,6 +286,7 @@ public class JsonFormat { public Printer preservingProtoFieldNames() { return new Printer( registry, + oldRegistry, alwaysOutputDefaultValueFields, includingDefaultValueFields, true, @@ -279,6 +316,7 @@ public class JsonFormat { public Printer omittingInsignificantWhitespace() { return new Printer( registry, + oldRegistry, alwaysOutputDefaultValueFields, includingDefaultValueFields, preservingProtoFieldNames, @@ -302,6 +340,7 @@ public class JsonFormat { public Printer sortingMapKeys() { return new Printer( registry, + oldRegistry, alwaysOutputDefaultValueFields, includingDefaultValueFields, preservingProtoFieldNames, @@ -322,6 +361,7 @@ public class JsonFormat { // mobile. new PrinterImpl( registry, + oldRegistry, alwaysOutputDefaultValueFields, includingDefaultValueFields, preservingProtoFieldNames, @@ -354,37 +394,66 @@ public class JsonFormat { * Creates a {@link Parser} with default configuration. */ public static Parser parser() { - return new Parser(TypeRegistry.getEmptyTypeRegistry(), false, Parser.DEFAULT_RECURSION_LIMIT); + return new Parser( + com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(), + TypeRegistry.getEmptyTypeRegistry(), + false, + Parser.DEFAULT_RECURSION_LIMIT); } /** * A Parser parses JSON to protobuf message. */ public static class Parser { - private final TypeRegistry registry; + private final com.google.protobuf.TypeRegistry registry; + private final TypeRegistry oldRegistry; private final boolean ignoringUnknownFields; private final int recursionLimit; // The default parsing recursion limit is aligned with the proto binary parser. private static final int DEFAULT_RECURSION_LIMIT = 100; - private Parser(TypeRegistry registry, boolean ignoreUnknownFields, int recursionLimit) { + private Parser( + com.google.protobuf.TypeRegistry registry, + TypeRegistry oldRegistry, + boolean ignoreUnknownFields, + int recursionLimit) { this.registry = registry; + this.oldRegistry = oldRegistry; this.ignoringUnknownFields = ignoreUnknownFields; this.recursionLimit = recursionLimit; } /** - * Creates a new {@link Parser} using the given registry. The new Parser - * clones all other configurations from this Parser. + * Creates a new {@link Parser} using the given registry. The new Parser clones all other + * configurations from this Parser. * * @throws IllegalArgumentException if a registry is already set. */ - public Parser usingTypeRegistry(TypeRegistry registry) { - if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { + public Parser usingTypeRegistry(TypeRegistry oldRegistry) { + if (this.oldRegistry != TypeRegistry.getEmptyTypeRegistry() + || this.registry != com.google.protobuf.TypeRegistry.getEmptyTypeRegistry()) { throw new IllegalArgumentException("Only one registry is allowed."); } - return new Parser(registry, ignoringUnknownFields, recursionLimit); + return new Parser( + com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(), + oldRegistry, + ignoringUnknownFields, + recursionLimit); + } + + /** + * Creates a new {@link Parser} using the given registry. The new Parser clones all other + * configurations from this Parser. + * + * @throws IllegalArgumentException if a registry is already set. + */ + public Parser usingTypeRegistry(com.google.protobuf.TypeRegistry registry) { + if (this.oldRegistry != TypeRegistry.getEmptyTypeRegistry() + || this.registry != com.google.protobuf.TypeRegistry.getEmptyTypeRegistry()) { + throw new IllegalArgumentException("Only one registry is allowed."); + } + return new Parser(registry, oldRegistry, ignoringUnknownFields, recursionLimit); } /** @@ -392,7 +461,7 @@ public class JsonFormat { * encountered. The new Parser clones all other configurations from this Parser. */ public Parser ignoringUnknownFields() { - return new Parser(this.registry, true, recursionLimit); + return new Parser(this.registry, oldRegistry, true, recursionLimit); } /** @@ -404,7 +473,8 @@ public class JsonFormat { public void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. - new ParserImpl(registry, ignoringUnknownFields, recursionLimit).merge(json, builder); + new ParserImpl(registry, oldRegistry, ignoringUnknownFields, recursionLimit) + .merge(json, builder); } /** @@ -417,12 +487,13 @@ public class JsonFormat { public void merge(Reader json, Message.Builder builder) throws IOException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. - new ParserImpl(registry, ignoringUnknownFields, recursionLimit).merge(json, builder); + new ParserImpl(registry, oldRegistry, ignoringUnknownFields, recursionLimit) + .merge(json, builder); } // For testing only. Parser usingRecursionLimit(int recursionLimit) { - return new Parser(registry, ignoringUnknownFields, recursionLimit); + return new Parser(registry, oldRegistry, ignoringUnknownFields, recursionLimit); } } @@ -478,7 +549,7 @@ public class JsonFormat { @CanIgnoreReturnValue public Builder add(Descriptor messageType) { if (types == null) { - throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); + throw new IllegalStateException("A TypeRegistry.Builder can only be used once."); } addFile(messageType.getFile()); return this; @@ -641,7 +712,8 @@ public class JsonFormat { * A Printer converts protobuf messages to JSON format. */ private static final class PrinterImpl { - private final TypeRegistry registry; + private final com.google.protobuf.TypeRegistry registry; + private final TypeRegistry oldRegistry; private final boolean alwaysOutputDefaultValueFields; private final Set<FieldDescriptor> includingDefaultValueFields; private final boolean preservingProtoFieldNames; @@ -658,7 +730,8 @@ public class JsonFormat { } PrinterImpl( - TypeRegistry registry, + com.google.protobuf.TypeRegistry registry, + TypeRegistry oldRegistry, boolean alwaysOutputDefaultValueFields, Set<FieldDescriptor> includingDefaultValueFields, boolean preservingProtoFieldNames, @@ -667,6 +740,7 @@ public class JsonFormat { boolean printingEnumsAsInts, boolean sortingMapKeys) { this.registry = registry; + this.oldRegistry = oldRegistry; this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields; this.includingDefaultValueFields = includingDefaultValueFields; this.preservingProtoFieldNames = preservingProtoFieldNames; @@ -807,7 +881,10 @@ public class JsonFormat { String typeUrl = (String) message.getField(typeUrlField); Descriptor type = registry.getDescriptorForTypeUrl(typeUrl); if (type == null) { - throw new InvalidProtocolBufferException("Cannot find type for url: " + typeUrl); + type = oldRegistry.getDescriptorForTypeUrl(typeUrl); + if (type == null) { + throw new InvalidProtocolBufferException("Cannot find type for url: " + typeUrl); + } } ByteString content = (ByteString) message.getField(valueField); Message contentMessage = @@ -1218,14 +1295,20 @@ public class JsonFormat { } private static class ParserImpl { - private final TypeRegistry registry; + private final com.google.protobuf.TypeRegistry registry; + private final TypeRegistry oldRegistry; private final JsonParser jsonParser; private final boolean ignoringUnknownFields; private final int recursionLimit; private int currentDepth; - ParserImpl(TypeRegistry registry, boolean ignoreUnknownFields, int recursionLimit) { + ParserImpl( + com.google.protobuf.TypeRegistry registry, + TypeRegistry oldRegistry, + boolean ignoreUnknownFields, + int recursionLimit) { this.registry = registry; + this.oldRegistry = oldRegistry; this.ignoringUnknownFields = ignoreUnknownFields; this.jsonParser = new JsonParser(); this.recursionLimit = recursionLimit; @@ -1448,7 +1531,10 @@ public class JsonFormat { String typeUrl = typeUrlElement.getAsString(); Descriptor contentType = registry.getDescriptorForTypeUrl(typeUrl); if (contentType == null) { - throw new InvalidProtocolBufferException("Cannot resolve type: " + typeUrl); + contentType = oldRegistry.getDescriptorForTypeUrl(typeUrl); + if (contentType == null) { + throw new InvalidProtocolBufferException("Cannot resolve type: " + typeUrl); + } } builder.setField(typeUrlField, typeUrl); Message.Builder contentBuilder = --- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java @@ -152,6 +152,16 @@ public class JsonFormatTest extends Test assertEquals(message.toString(), parsedMessage.toString()); } + private void assertRoundTripEquals(Message message, com.google.protobuf.TypeRegistry registry) + throws Exception { + JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); + JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(registry); + Message.Builder builder = message.newBuilderForType(); + parser.merge(printer.print(message), builder); + Message parsedMessage = builder.build(); + assertEquals(message.toString(), parsedMessage.toString()); + } + private String toJsonString(Message message) throws IOException { return JsonFormat.printer().print(message); } @@ -850,6 +860,45 @@ public class JsonFormatTest extends Test } + public void testAnyFieldsWithCustomAddedTypeRegistry() throws Exception { + TestAllTypes content = TestAllTypes.newBuilder().setOptionalInt32(1234).build(); + TestAny message = TestAny.newBuilder().setAnyValue(Any.pack(content)).build(); + + com.google.protobuf.TypeRegistry registry = + com.google.protobuf.TypeRegistry.newBuilder().add(content.getDescriptorForType()).build(); + JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); + + assertEquals( + "{\n" + + " \"anyValue\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}", + printer.print(message)); + assertRoundTripEquals(message, registry); + + TestAny messageWithDefaultAnyValue = + TestAny.newBuilder().setAnyValue(Any.getDefaultInstance()).build(); + assertEquals("{\n" + " \"anyValue\": {}\n" + "}", printer.print(messageWithDefaultAnyValue)); + assertRoundTripEquals(messageWithDefaultAnyValue, registry); + + // Well-known types have a special formatting when embedded in Any. + // + // 1. Any in Any. + Any anyMessage = Any.pack(Any.pack(content)); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n" + + " \"value\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}", + printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + } + public void testAnyFields() throws Exception { TestAllTypes content = TestAllTypes.newBuilder().setOptionalInt32(1234).build(); TestAny message = TestAny.newBuilder().setAnyValue(Any.pack(content)).build(); @@ -1136,7 +1185,7 @@ public class JsonFormatTest extends Test Any.Builder builder = Any.newBuilder(); mergeFromJson( "{\n" - + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"@type\": \"type.googleapis.com/json_test.UnexpectedTypes\",\n" + " \"optionalInt32\": 12345\n" + "}", builder); --- a/src/google/protobuf/compiler/java/java_context.cc +++ b/src/google/protobuf/compiler/java/java_context.cc @@ -35,7 +35,6 @@ #include <google/protobuf/compiler/java/java_name_resolver.h> #include <google/protobuf/descriptor.h> #include <google/protobuf/stubs/strutil.h> - #include <google/protobuf/stubs/map_util.h> namespace google { --- a/src/google/protobuf/compiler/java/java_doc_comment.cc +++ b/src/google/protobuf/compiler/java/java_doc_comment.cc @@ -174,7 +174,8 @@ void WriteMessageDocComment(io::Printer* void WriteFieldDocComment(io::Printer* printer, const FieldDescriptor* field) { // We start the comment with the main body based on the comments from the - // .proto file (if present). We then continue with the field declaration, e.g.: + // .proto file (if present). We then continue with the field declaration, + // e.g.: // optional string foo = 5; // And then we end with the javadoc tags if applicable. // If the field is a group, the debug string might end with {. @@ -185,17 +186,17 @@ void WriteFieldDocComment(io::Printer* p printer->Print(" */\n"); } -void WriteFieldAccessorDocComment(io::Printer* printer, +void WriteFieldAccessorDocComment(io::Printer* printer, const FieldDescriptor* field, const FieldAccessorType type, const bool builder) { printer->Print("/**\n"); WriteDocCommentBody(printer, field); - printer->Print(" * <code>$def$</code>\n", "def", + printer->Print(" * <code>$def$</code>\n", "def", EscapeJavadoc(FirstLineOf(field->DebugString()))); switch (type) { case HAZZER: - printer->Print(" * @return Whether the $name$ field is set.\n", "name", + printer->Print(" * @return Whether the $name$ field is set.\n", "name", field->camelcase_name()); break; case GETTER: @@ -211,16 +212,16 @@ void WriteFieldAccessorDocComment(io::Pr break; // Repeated case LIST_COUNT: - printer->Print(" * @return The number of $name$(s).\n", "name", - field->camelcase_name()); + printer->Print(" * @return The count of $name$.\n", "name", + field->camelcase_name()); break; case LIST_GETTER: - printer->Print(" * @return A list containing the $name$(s).\n", "name", + printer->Print(" * @return A list containing the $name$.\n", "name", field->camelcase_name()); break; case LIST_INDEXED_GETTER: printer->Print(" * @param index The index of the element to return.\n"); - printer->Print(" * @return The $name$(s) at the given index.\n", "name", + printer->Print(" * @return The $name$ at the given index.\n", "name", field->camelcase_name()); break; case LIST_INDEXED_SETTER: @@ -233,8 +234,8 @@ void WriteFieldAccessorDocComment(io::Pr field->camelcase_name()); break; case LIST_MULTI_ADDER: - printer->Print(" * @param values The $name$(s) to add.\n", "name", - field->camelcase_name()); + printer->Print(" * @param values The $name$ to add.\n", "name", + field->camelcase_name()); break; } if (builder) { @@ -243,25 +244,28 @@ void WriteFieldAccessorDocComment(io::Pr printer->Print(" */\n"); } -void WriteFieldEnumValueAccessorDocComment(io::Printer* printer, +void WriteFieldEnumValueAccessorDocComment(io::Printer* printer, const FieldDescriptor* field, const FieldAccessorType type, const bool builder) { printer->Print("/**\n"); WriteDocCommentBody(printer, field); - printer->Print(" * <code>$def$</code>\n", "def", + printer->Print(" * <code>$def$</code>\n", "def", EscapeJavadoc(FirstLineOf(field->DebugString()))); switch (type) { case HAZZER: // Should never happen break; case GETTER: - printer->Print(" * @return The enum value for $name$.\n", "name", - field->camelcase_name()); + printer->Print( + " * @return The enum numeric value on the wire for $name$.\n", "name", + field->camelcase_name()); break; case SETTER: - printer->Print(" * @param value The enum value for $name$ to set.\n", - "name", field->camelcase_name()); + printer->Print( + " * @param value The enum numeric value on the wire for $name$ to " + "set.\n", + "name", field->camelcase_name()); break; case CLEARER: // Print nothing @@ -271,26 +275,36 @@ void WriteFieldEnumValueAccessorDocComme // Should never happen break; case LIST_GETTER: - printer->Print(" * @return A list containing the enum values for $name$(s).\n", - "name", field->camelcase_name()); + printer->Print( + " * @return A list containing the enum numeric values on the wire " + "for $name$.\n", + "name", field->camelcase_name()); break; case LIST_INDEXED_GETTER: printer->Print(" * @param index The index of the value to return.\n"); - printer->Print(" * @return The enum value of the $name$ at the given index.\n", - "name", field->camelcase_name()); + printer->Print( + " * @return The enum numeric value on the wire of $name$ at the " + "given index.\n", + "name", field->camelcase_name()); break; case LIST_INDEXED_SETTER: printer->Print(" * @param index The index of the value to return.\n"); - printer->Print(" * @param value The enum value of the $name$ to set.\n", - "name", field->camelcase_name()); + printer->Print( + " * @param value The enum numeric value on the wire for $name$ to " + "set.\n", + "name", field->camelcase_name()); break; case LIST_ADDER: - printer->Print(" * @param value The enum value of the $name$ to add.\n", - "name", field->camelcase_name()); + printer->Print( + " * @param value The enum numeric value on the wire for $name$ to " + "add.\n", + "name", field->camelcase_name()); break; case LIST_MULTI_ADDER: - printer->Print(" * @param values The enum values of the $name$(s) to add.\n", - "name", field->camelcase_name()); + printer->Print( + " * @param values The enum numeric values on the wire for $name$ to " + "add.\n", + "name", field->camelcase_name()); break; } if (builder) { @@ -299,13 +313,13 @@ void WriteFieldEnumValueAccessorDocComme printer->Print(" */\n"); } -void WriteFieldStringBytesAccessorDocComment(io::Printer* printer, +void WriteFieldStringBytesAccessorDocComment(io::Printer* printer, const FieldDescriptor* field, const FieldAccessorType type, const bool builder) { printer->Print("/**\n"); WriteDocCommentBody(printer, field); - printer->Print(" * <code>$def$</code>\n", "def", + printer->Print(" * <code>$def$</code>\n", "def", EscapeJavadoc(FirstLineOf(field->DebugString()))); switch (type) { case HAZZER: @@ -313,11 +327,11 @@ void WriteFieldStringBytesAccessorDocCom break; case GETTER: printer->Print(" * @return The bytes for $name$.\n", "name", - field->camelcase_name()); + field->camelcase_name()); break; case SETTER: - printer->Print(" * @param value The bytes for $name$ to set.\n", - "name", field->camelcase_name()); + printer->Print(" * @param value The bytes for $name$ to set.\n", "name", + field->camelcase_name()); break; case CLEARER: // Print nothing @@ -327,7 +341,7 @@ void WriteFieldStringBytesAccessorDocCom // Should never happen break; case LIST_GETTER: - printer->Print(" * @return A list containing the bytes for $name$(s).\n", + printer->Print(" * @return A list containing the bytes for $name$.\n", "name", field->camelcase_name()); break; case LIST_INDEXED_GETTER: @@ -345,7 +359,7 @@ void WriteFieldStringBytesAccessorDocCom "name", field->camelcase_name()); break; case LIST_MULTI_ADDER: - printer->Print(" * @param values The bytes of the $name$(s) to add.\n", + printer->Print(" * @param values The bytes of the $name$ to add.\n", "name", field->camelcase_name()); break; } --- a/src/google/protobuf/compiler/java/java_doc_comment.h +++ b/src/google/protobuf/compiler/java/java_doc_comment.h @@ -53,35 +53,33 @@ namespace compiler { namespace java { enum FieldAccessorType { - - HAZZER, - GETTER, - SETTER, - CLEARER, - // Repeated - LIST_COUNT, - LIST_GETTER, - LIST_INDEXED_GETTER, - LIST_INDEXED_SETTER, - LIST_ADDER, - LIST_MULTI_ADDER - + HAZZER, + GETTER, + SETTER, + CLEARER, + // Repeated + LIST_COUNT, + LIST_GETTER, + LIST_INDEXED_GETTER, + LIST_INDEXED_SETTER, + LIST_ADDER, + LIST_MULTI_ADDER }; void WriteMessageDocComment(io::Printer* printer, const Descriptor* message); void WriteFieldDocComment(io::Printer* printer, const FieldDescriptor* field); void WriteFieldAccessorDocComment(io::Printer* printer, - const FieldDescriptor* field, - const FieldAccessorType type, - const bool builder = false); + const FieldDescriptor* field, + const FieldAccessorType type, + const bool builder = false); void WriteFieldEnumValueAccessorDocComment(io::Printer* printer, - const FieldDescriptor* field, - const FieldAccessorType type, - const bool builder = false); + const FieldDescriptor* field, + const FieldAccessorType type, + const bool builder = false); void WriteFieldStringBytesAccessorDocComment(io::Printer* printer, - const FieldDescriptor* field, - const FieldAccessorType type, - const bool builder = false); + const FieldDescriptor* field, + const FieldAccessorType type, + const bool builder = false); void WriteEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_); void WriteEnumValueDocComment(io::Printer* printer, const EnumValueDescriptor* value); --- a/src/google/protobuf/compiler/java/java_enum.cc +++ b/src/google/protobuf/compiler/java/java_enum.cc @@ -44,7 +44,6 @@ #include <google/protobuf/io/printer.h> #include <google/protobuf/stubs/strutil.h> - namespace google { namespace protobuf { namespace compiler { @@ -175,8 +174,9 @@ void EnumGenerator::Generate(io::Printer "}\n" "\n" "/**\n" - " * @param value The number of the enum to look for.\n" - " * @return The enum associated with the given number.\n" + " * @param value The numeric wire value of the corresponding enum " + "entry.\n" + " * @return The enum associated with the given numeric wire value.\n" " * @deprecated Use {@link #forNumber(int)} instead.\n" " */\n" "@java.lang.Deprecated\n" @@ -184,6 +184,11 @@ void EnumGenerator::Generate(io::Printer " return forNumber(value);\n" "}\n" "\n" + "/**\n" + " * @param value The numeric wire value of the corresponding enum " + "entry.\n" + " * @return The enum associated with the given numeric wire value.\n" + " */\n" "public static $classname$ forNumber(int value) {\n" " switch (value) {\n", "classname", descriptor_->name()); --- a/src/google/protobuf/compiler/java/java_enum_field.cc +++ b/src/google/protobuf/compiler/java/java_enum_field.cc @@ -46,7 +46,6 @@ #include <google/protobuf/wire_format.h> #include <google/protobuf/stubs/strutil.h> - namespace google { namespace protobuf { namespace compiler { @@ -220,7 +219,8 @@ void ImmutableEnumFieldGenerator::Genera " return $name$_;\n" "}\n"); printer->Annotate("{", "}", descriptor_); - WriteFieldEnumValueAccessorDocComment(printer, descriptor_, SETTER); + WriteFieldEnumValueAccessorDocComment(printer, descriptor_, SETTER, + /* builder */ true); printer->Print(variables_, "$deprecation$public Builder " "${$set$capitalized_name$Value$}$(int value) {\n" --- a/src/google/protobuf/compiler/java/java_string_field_lite.cc +++ b/src/google/protobuf/compiler/java/java_string_field_lite.cc @@ -47,7 +47,6 @@ #include <google/protobuf/wire_format.h> #include <google/protobuf/stubs/strutil.h> - namespace google { namespace protobuf { namespace compiler { @@ -292,7 +291,8 @@ void ImmutableStringFieldLiteGenerator:: "}\n"); printer->Annotate("{", "}", descriptor_); - WriteFieldStringBytesAccessorDocComment(printer, descriptor_, SETTER, true); + WriteFieldStringBytesAccessorDocComment(printer, descriptor_, SETTER, + /* builder */ true); printer->Print( variables_, "$deprecation$public Builder ${$set$capitalized_name$Bytes$}$(\n" @@ -470,7 +470,8 @@ void ImmutableStringOneofFieldLiteGenera "}\n"); printer->Annotate("{", "}", descriptor_); - WriteFieldStringBytesAccessorDocComment(printer, descriptor_, SETTER); + WriteFieldStringBytesAccessorDocComment(printer, descriptor_, SETTER, + /* builder */ true); printer->Print( variables_, "$deprecation$public Builder ${$set$capitalized_name$Bytes$}$(\n" --- a/src/google/protobuf/compiler/js/js_generator.cc +++ b/src/google/protobuf/compiler/js/js_generator.cc @@ -43,7 +43,6 @@ #include <google/protobuf/stubs/common.h> #include <google/protobuf/stubs/stringprintf.h> #include <google/protobuf/stubs/strutil.h> - #include <google/protobuf/compiler/scc.h> #include <google/protobuf/compiler/js/well_known_types_embed.h> #include <google/protobuf/io/printer.h> @@ -51,7 +50,6 @@ #include <google/protobuf/descriptor.pb.h> #include <google/protobuf/descriptor.h> - namespace google { namespace protobuf { namespace compiler { @@ -1118,9 +1116,6 @@ std::string JSBinaryWriterMethodName(con JSBinaryReadWriteMethodName(field, /* is_writer = */ true); } -std::string JSReturnClause(const FieldDescriptor* desc) { - return ""; -} std::string JSTypeTag(const FieldDescriptor* desc) { switch (desc->type()) { @@ -1156,10 +1151,6 @@ std::string JSTypeTag(const FieldDescrip return ""; } -std::string JSReturnDoc(const GeneratorOptions& options, - const FieldDescriptor* desc) { - return ""; -} bool HasRepeatedFields(const GeneratorOptions& options, const Descriptor* desc) { @@ -1426,11 +1417,6 @@ bool HasFieldPresence(const GeneratorOpt return false; } - if (UseBrokenPresenceSemantics(options, field)) { - // Proto3 files with broken presence semantics have field presence. - return true; - } - return field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE || field->containing_oneof() != NULL || field->file()->syntax() == FileDescriptor::SYNTAX_PROTO2; @@ -2393,7 +2379,7 @@ void Generator::GenerateClassFieldToObje if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 && // Repeated fields get initialized to their default in the constructor // (why?), so we emit a plain getField() call for them. - !field->is_repeated() && !UseBrokenPresenceSemantics(options, field)) { + !field->is_repeated()) { // Proto3 puts all defaults (including implicit defaults) in toObject(). // But for proto2 we leave the existing semantics unchanged: unset fields // without default are unset. @@ -2677,30 +2663,31 @@ void Generator::GenerateClassField(const (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : "")); printer->Annotate("gettername", field); printer->Print( - "/** @param {$optionaltype$} value$returndoc$ */\n" + "/**\n" + " * @param {$optionaltype$} value\n" + " * @return {!$class$} returns this\n" + "*/\n" "$class$.prototype.$settername$ = function(value) {\n" - " jspb.Message.set$oneoftag$$repeatedtag$WrapperField(", + " return jspb.Message.set$oneoftag$$repeatedtag$WrapperField(", "optionaltype", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ true, /* force_present = */ false, /* singular_if_not_packed = */ false), - "returndoc", JSReturnDoc(options, field), "class", - GetMessagePath(options, field->containing_type()), "settername", - "set" + JSGetterName(options, field), "oneoftag", + "class", GetMessagePath(options, field->containing_type()), + "settername", "set" + JSGetterName(options, field), "oneoftag", (field->containing_oneof() ? "Oneof" : ""), "repeatedtag", (field->is_repeated() ? "Repeated" : "")); printer->Annotate("settername", field); printer->Print( - "this, $index$$oneofgroup$, value);$returnvalue$\n" + "this, $index$$oneofgroup$, value);\n" "};\n" "\n" "\n", "index", JSFieldIndex(field), "oneofgroup", (field->containing_oneof() ? (", " + JSOneofArray(options, field)) - : ""), - "returnvalue", JSReturnClause(field)); + : "")); if (field->is_repeated()) { GenerateRepeatedMessageHelperMethods(options, printer, field); @@ -2787,21 +2774,18 @@ void Generator::GenerateClassField(const GenerateBytesWrapper(options, printer, field, BYTES_U8); } - if (untyped) { - printer->Print( - "/**\n" - " * @param {*} value$returndoc$\n" - " */\n", - "returndoc", JSReturnDoc(options, field)); - } else { - printer->Print( - "/** @param {$optionaltype$} value$returndoc$ */\n", "optionaltype", - JSFieldTypeAnnotation(options, field, - /* is_setter_argument = */ true, - /* force_present = */ false, - /* singular_if_not_packed = */ false), - "returndoc", JSReturnDoc(options, field)); - } + printer->Print( + "/**\n" + " * @param {$optionaltype$} value\n" + " * @return {!$class$} returns this\n" + " */\n", + "class", GetMessagePath(options, field->containing_type()), + "optionaltype", + untyped ? "*" + : JSFieldTypeAnnotation(options, field, + /* is_setter_argument = */ true, + /* force_present = */ false, + /* singular_if_not_packed = */ false)); if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 && !field->is_repeated() && !field->is_map() && @@ -2810,28 +2794,28 @@ void Generator::GenerateClassField(const // setProto3*Field function. printer->Print( "$class$.prototype.$settername$ = function(value) {\n" - " jspb.Message.setProto3$typetag$Field(this, $index$, " - "value);$returnvalue$\n" + " return jspb.Message.setProto3$typetag$Field(this, $index$, " + "value);" + "\n" "};\n" "\n" "\n", "class", GetMessagePath(options, field->containing_type()), "settername", "set" + JSGetterName(options, field), "typetag", - JSTypeTag(field), "index", JSFieldIndex(field), "returnvalue", - JSReturnClause(field)); + JSTypeTag(field), "index", JSFieldIndex(field)); printer->Annotate("settername", field); } else { // Otherwise, use the regular setField function. printer->Print( "$class$.prototype.$settername$ = function(value) {\n" - " jspb.Message.set$oneoftag$Field(this, $index$", + " return jspb.Message.set$oneoftag$Field(this, $index$", "class", GetMessagePath(options, field->containing_type()), "settername", "set" + JSGetterName(options, field), "oneoftag", (field->containing_oneof() ? "Oneof" : ""), "index", JSFieldIndex(field)); printer->Annotate("settername", field); printer->Print( - "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);$returnvalue$\n" + "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);\n" "};\n" "\n" "\n", @@ -2840,16 +2824,16 @@ void Generator::GenerateClassField(const "typeclose", untyped ? ")" : "", "oneofgroup", (field->containing_oneof() ? (", " + JSOneofArray(options, field)) : ""), - "returnvalue", JSReturnClause(field), "rptvalueinit", - (field->is_repeated() ? " || []" : "")); + "rptvalueinit", (field->is_repeated() ? " || []" : "")); } if (untyped) { printer->Print( "/**\n" - " * Clears the value.$returndoc$\n" + " * Clears the value.\n" + " * @return {!$class$} returns this\n" " */\n", - "returndoc", JSReturnDoc(options, field)); + "class", GetMessagePath(options, field->containing_type())); } if (field->is_repeated()) { @@ -2863,19 +2847,18 @@ void Generator::GenerateClassField(const // clang-format off printer->Print( "/**\n" - " * Clears values from the map. The map will be non-null." - "$returndoc$\n" + " * Clears values from the map. The map will be non-null.\n" + " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$clearername$ = function() {\n" - " this.$gettername$().clear();$returnvalue$\n" + " this.$gettername$().clear();\n" + " return this;" "};\n" "\n" "\n", - "returndoc", JSReturnDoc(options, field), "class", GetMessagePath(options, field->containing_type()), "clearername", "clear" + JSGetterName(options, field), - "gettername", "get" + JSGetterName(options, field), - "returnvalue", JSReturnClause(field)); + "gettername", "get" + JSGetterName(options, field)); // clang-format on printer->Annotate("clearername", field); } else if (field->is_repeated() || @@ -2885,22 +2868,21 @@ void Generator::GenerateClassField(const // clang-format off printer->Print( "/**\n" - " * $jsdoc$$returndoc$\n" + " * $jsdoc$\n" + " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$clearername$ = function() {\n" - " this.$settername$($clearedvalue$);$returnvalue$\n" + " return this.$settername$($clearedvalue$);\n" "};\n" "\n" "\n", "jsdoc", field->is_repeated() ? "Clears the list making it empty but non-null." : "Clears the message field making it undefined.", - "returndoc", JSReturnDoc(options, field), "class", GetMessagePath(options, field->containing_type()), "clearername", "clear" + JSGetterName(options, field), "settername", "set" + JSGetterName(options, field), - "clearedvalue", (field->is_repeated() ? "[]" : "undefined"), - "returnvalue", JSReturnClause(field)); + "clearedvalue", (field->is_repeated() ? "[]" : "undefined")); // clang-format on printer->Annotate("clearername", field); } else if (HasFieldPresence(options, field)) { @@ -2909,12 +2891,12 @@ void Generator::GenerateClassField(const // clang-format off printer->Print( "/**\n" - " * Clears the field making it undefined.$returndoc$\n" + " * Clears the field making it undefined.\n" + " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$clearername$ = function() {\n" - " jspb.Message.set$maybeoneof$Field(this, " - "$index$$maybeoneofgroup$, ", - "returndoc", JSReturnDoc(options, field), + " return jspb.Message.set$maybeoneof$Field(this, " + "$index$$maybeoneofgroup$, ", "class", GetMessagePath(options, field->containing_type()), "clearername", "clear" + JSGetterName(options, field), "maybeoneof", (field->containing_oneof() ? "Oneof" : ""), @@ -2925,12 +2907,11 @@ void Generator::GenerateClassField(const // clang-format on printer->Annotate("clearername", field); printer->Print( - "$clearedvalue$);$returnvalue$\n" + "$clearedvalue$);\n" "};\n" "\n" "\n", - "clearedvalue", (field->is_repeated() ? "[]" : "undefined"), - "returnvalue", JSReturnClause(field)); + "clearedvalue", (field->is_repeated() ? "[]" : "undefined")); } if (HasFieldPresence(options, field)) { @@ -2957,10 +2938,12 @@ void Generator::GenerateRepeatedPrimitiv printer->Print( "/**\n" " * @param {$optionaltype$} value\n" - " * @param {number=} opt_index$returndoc$\n" + " * @param {number=} opt_index\n" + " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$addername$ = function(value, opt_index) {\n" - " jspb.Message.addToRepeatedField(this, $index$", + " return jspb.Message.addToRepeatedField(this, " + "$index$", "class", GetMessagePath(options, field->containing_type()), "addername", "add" + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true), @@ -2972,20 +2955,18 @@ void Generator::GenerateRepeatedPrimitiv /* singular_if_not_packed = */ false, BYTES_DEFAULT, /* force_singular = */ true), - "index", JSFieldIndex(field), - "returndoc", JSReturnDoc(options, field)); + "index", JSFieldIndex(field)); printer->Annotate("addername", field); printer->Print( "$oneofgroup$, $type$value$rptvalueinit$$typeclose$, " - "opt_index);$returnvalue$\n" + "opt_index);\n" "};\n" "\n" "\n", "type", untyped ? "/** @type{string|number|boolean|!Uint8Array} */(" : "", "typeclose", untyped ? ")" : "", "oneofgroup", (field->containing_oneof() ? (", " + JSOneofArray(options, field)) : ""), - "rptvalueinit", "", - "returnvalue", JSReturnClause(field)); + "rptvalueinit", ""); // clang-format on } @@ -3374,12 +3355,21 @@ void Generator::GenerateEnum(const Gener enumdesc->name()); printer->Annotate("name", enumdesc); + std::set<string> used_name; + std::vector<int> valid_index; for (int i = 0; i < enumdesc->value_count(); i++) { + if (enumdesc->options().allow_alias() && + !used_name.insert(ToEnumCase(enumdesc->value(i)->name())).second) { + continue; + } + valid_index.push_back(i); + } + for (auto i : valid_index) { const EnumValueDescriptor* value = enumdesc->value(i); printer->Print(" $name$: $value$$comma$\n", "name", ToEnumCase(value->name()), "value", StrCat(value->number()), "comma", - (i == enumdesc->value_count() - 1) ? "" : ","); + (i == valid_index.back()) ? "" : ","); printer->Annotate("name", value); }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor