diff --git a/CHANGES.txt b/CHANGES.txt
index 69207d6ae9dfc..1abd0faf3c33c 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,8 @@
+2022-01-05 version 3.18.2 (C++/Java/Python/PHP/Objective-C/C#/Ruby/JavaScript)
+
+ Java
+ * Improve performance characteristics of UnknownFieldSet parsing (#9371)
+
2021-10-04 version 3.18.1 (C++/Java/Python/PHP/Objective-C/C#/Ruby/JavaScript)
Python
diff --git a/Makefile.am b/Makefile.am
index cd209f3cdca95..7ce954e04884e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -491,6 +491,7 @@ java_EXTRA_DIST=
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/UnknownFieldSetPerformanceTest.java \
java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java \
java/core/src/test/java/com/google/protobuf/Utf8Test.java \
java/core/src/test/java/com/google/protobuf/Utf8Utils.java \
diff --git a/Protobuf-C++.podspec b/Protobuf-C++.podspec
index f285e58af8e3d..271a249c6556b 100644
--- a/Protobuf-C++.podspec
+++ b/Protobuf-C++.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'Protobuf-C++'
- s.version = '3.18.1'
+ s.version = '3.18.2'
s.summary = 'Protocol Buffers v3 runtime library for C++.'
s.homepage = 'https://github.com/google/protobuf'
s.license = '3-Clause BSD License'
diff --git a/Protobuf.podspec b/Protobuf.podspec
index 4a5f7dd9c17fd..80926fb206663 100644
--- a/Protobuf.podspec
+++ b/Protobuf.podspec
@@ -5,7 +5,7 @@
# dependent projects use the :git notation to refer to the library.
Pod::Spec.new do |s|
s.name = 'Protobuf'
- s.version = '3.18.1'
+ s.version = '3.18.2'
s.summary = 'Protocol Buffers v.3 runtime library for Objective-C.'
s.homepage = 'https://github.com/protocolbuffers/protobuf'
s.license = '3-Clause BSD License'
diff --git a/configure.ac b/configure.ac
index b7466c28b5f93..4a6d50a07853c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -17,7 +17,7 @@ AC_PREREQ(2.59)
# In the SVN trunk, the version should always be the next anticipated release
# version with the "-pre" suffix. (We used to use "-SNAPSHOT" but this pushed
# the size of one file name in the dist tarfile over the 99-char limit.)
-AC_INIT([Protocol Buffers],[3.18.1],[protobuf@googlegroups.com],[protobuf])
+AC_INIT([Protocol Buffers],[3.18.2],[protobuf@googlegroups.com],[protobuf])
AM_MAINTAINER_MODE([enable])
diff --git a/csharp/Google.Protobuf.Tools.nuspec b/csharp/Google.Protobuf.Tools.nuspec
index 8e5786fdbdd46..d94a2c98e8cf9 100644
--- a/csharp/Google.Protobuf.Tools.nuspec
+++ b/csharp/Google.Protobuf.Tools.nuspec
@@ -5,7 +5,7 @@
Google Protocol Buffers toolsTools for Protocol Buffers - Google's data interchange format.See project site for more info.
- 3.18.1
+ 3.18.2Google Inc.protobuf-packageshttps://github.com/protocolbuffers/protobuf/blob/master/LICENSE
diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
index f6d6069102ef7..619811027b831 100644
--- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj
+++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
@@ -4,7 +4,7 @@
C# runtime library for Protocol Buffers - Google's data interchange format.Copyright 2015, Google Inc.Google Protocol Buffers
- 3.18.1
+ 3.18.27.2Google Inc.
diff --git a/java/bom/pom.xml b/java/bom/pom.xml
index c4ec6855370f4..e07b849094f64 100644
--- a/java/bom/pom.xml
+++ b/java/bom/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-bom
- 3.18.1
+ 3.18.2pomProtocol Buffers [BOM]
diff --git a/java/core/BUILD b/java/core/BUILD
index 9308ba71af5b3..5ac4b147915e6 100644
--- a/java/core/BUILD
+++ b/java/core/BUILD
@@ -222,6 +222,7 @@ test_suite(
"core_build_test",
"conformance_test",
"core_tests",
+ "utf8_tests",
],
)
@@ -241,12 +242,17 @@ conformance_test(
junit_tests(
name = "core_tests",
- srcs = glob(["src/test/java/**/*.java"], exclude = [
- "src/test/java/com/google/protobuf/TestUtil.java",
- "src/test/java/com/google/protobuf/TestUtilLite.java",
- ]),
+ size = "small",
+ srcs = glob(
+ ["src/test/java/**/*.java"],
+ exclude = [
+ "src/test/java/com/google/protobuf/DecodeUtf8Test.java",
+ "src/test/java/com/google/protobuf/IsValidUtf8Test.java",
+ "src/test/java/com/google/protobuf/TestUtil.java",
+ "src/test/java/com/google/protobuf/TestUtilLite.java",
+ ],
+ ),
data = ["//:testdata"],
- size = "large",
deps = [
":core",
":generic_test_protos_java_proto",
@@ -260,6 +266,24 @@ junit_tests(
]
)
+# The UTF-8 validation tests are much slower than the other tests, so they get
+# their own test target with a longer timeout.
+junit_tests(
+ name = "utf8_tests",
+ size = "large",
+ srcs = [
+ "src/test/java/com/google/protobuf/DecodeUtf8Test.java",
+ "src/test/java/com/google/protobuf/IsValidUtf8Test.java",
+ "src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java",
+ ],
+ deps = [
+ ":core",
+ "@maven//:com_google_guava_guava",
+ "@maven//:com_google_truth_truth",
+ "@maven//:junit_junit",
+ ],
+)
+
java_lite_proto_library(
name = "generic_test_protos_java_proto_lite",
visibility = [
@@ -342,6 +366,7 @@ LITE_TEST_EXCLUSIONS = [
"src/test/java/com/google/protobuf/TypeRegistryTest.java",
"src/test/java/com/google/protobuf/UnknownEnumValueTest.java",
"src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java",
+ "src/test/java/com/google/protobuf/UnknownFieldSetPerformanceTest.java",
"src/test/java/com/google/protobuf/UnknownFieldSetTest.java",
"src/test/java/com/google/protobuf/WellKnownTypesTest.java",
"src/test/java/com/google/protobuf/WireFormatTest.java",
diff --git a/java/core/pom.xml b/java/core/pom.xml
index 838f4a71310ee..dce983ed1b98c 100644
--- a/java/core/pom.xml
+++ b/java/core/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-parent
- 3.18.1
+ 3.18.2protobuf-java
diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
index ba2f9df08e675..5c482d62dab52 100644
--- a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
+++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
@@ -43,13 +43,13 @@
import java.util.TreeMap;
/**
- * {@code UnknownFieldSet} is used to keep track of fields which were seen when parsing a protocol
+ * {@code UnknownFieldSet} keeps track of fields which were seen when parsing a protocol
* message but whose field numbers or types are unrecognized. This most frequently occurs when new
* fields are added to a message type and then messages containing those fields are read by old
* software that was compiled before the new types were added.
*
*
Every {@link Message} contains an {@code UnknownFieldSet} (and every {@link Message.Builder}
- * contains an {@link Builder}).
+ * contains a {@link Builder}).
*
*
Most users will never need to use this class.
*
@@ -57,9 +57,13 @@
*/
public final class UnknownFieldSet implements MessageLite {
- private UnknownFieldSet() {
- fields = null;
- fieldsDescending = null;
+ private final TreeMap fields;
+
+ /**
+ * Construct an {@code UnknownFieldSet} around the given map.
+ */
+ UnknownFieldSet(TreeMap fields) {
+ this.fields = fields;
}
/** Create a new {@link Builder}. */
@@ -68,7 +72,7 @@ public static Builder newBuilder() {
}
/** Create a new {@link Builder} and initialize it to be a copy of {@code copyFrom}. */
- public static Builder newBuilder(final UnknownFieldSet copyFrom) {
+ public static Builder newBuilder(UnknownFieldSet copyFrom) {
return newBuilder().mergeFrom(copyFrom);
}
@@ -83,25 +87,11 @@ public UnknownFieldSet getDefaultInstanceForType() {
}
private static final UnknownFieldSet defaultInstance =
- new UnknownFieldSet(
- Collections.emptyMap(), Collections.emptyMap());
-
- /**
- * Construct an {@code UnknownFieldSet} around the given map. The map is expected to be immutable.
- */
- UnknownFieldSet(final Map fields, final Map fieldsDescending) {
- this.fields = fields;
- this.fieldsDescending = fieldsDescending;
- }
-
- private final Map fields;
-
- /** A copy of {@link #fields} who's iterator order is reversed. */
- private final Map fieldsDescending;
+ new UnknownFieldSet(new TreeMap());
@Override
- public boolean equals(final Object other) {
+ public boolean equals(Object other) {
if (this == other) {
return true;
}
@@ -110,29 +100,33 @@ public boolean equals(final Object other) {
@Override
public int hashCode() {
+ if (fields.isEmpty()) { // avoid allocation of iterator.
+ // This optimization may not be helpful but it is needed for the allocation tests to pass.
+ return 0;
+ }
return fields.hashCode();
}
/** Get a map of fields in the set by number. */
public Map asMap() {
- return fields;
+ return (Map) fields.clone();
}
/** Check if the given field number is present in the set. */
- public boolean hasField(final int number) {
+ public boolean hasField(int number) {
return fields.containsKey(number);
}
/** Get a field by number. Returns an empty field if not present. Never returns {@code null}. */
- public Field getField(final int number) {
- final Field result = fields.get(number);
+ public Field getField(int number) {
+ Field result = fields.get(number);
return (result == null) ? Field.getDefaultInstance() : result;
}
/** Serializes the set and writes it to {@code output}. */
@Override
- public void writeTo(final CodedOutputStream output) throws IOException {
- for (final Map.Entry entry : fields.entrySet()) {
+ public void writeTo(CodedOutputStream output) throws IOException {
+ for (Map.Entry entry : fields.entrySet()) {
Field field = entry.getValue();
field.writeTo(entry.getKey(), output);
}
@@ -154,10 +148,10 @@ public String toString() {
@Override
public ByteString toByteString() {
try {
- final ByteString.CodedBuilder out = ByteString.newCodedBuilder(getSerializedSize());
+ ByteString.CodedBuilder out = ByteString.newCodedBuilder(getSerializedSize());
writeTo(out.getCodedOutput());
return out.build();
- } catch (final IOException e) {
+ } catch (IOException e) {
throw new RuntimeException(
"Serializing to a ByteString threw an IOException (should never happen).", e);
}
@@ -170,12 +164,12 @@ public ByteString toByteString() {
@Override
public byte[] toByteArray() {
try {
- final byte[] result = new byte[getSerializedSize()];
- final CodedOutputStream output = CodedOutputStream.newInstance(result);
+ byte[] result = new byte[getSerializedSize()];
+ CodedOutputStream output = CodedOutputStream.newInstance(result);
writeTo(output);
output.checkNoSpaceLeft();
return result;
- } catch (final IOException e) {
+ } catch (IOException e) {
throw new RuntimeException(
"Serializing to a byte array threw an IOException (should never happen).", e);
}
@@ -186,16 +180,16 @@ public byte[] toByteArray() {
* {@link #writeTo(CodedOutputStream)}.
*/
@Override
- public void writeTo(final OutputStream output) throws IOException {
- final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
+ public void writeTo(OutputStream output) throws IOException {
+ CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
writeTo(codedOutput);
codedOutput.flush();
}
@Override
public void writeDelimitedTo(OutputStream output) throws IOException {
- final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
- codedOutput.writeRawVarint32(getSerializedSize());
+ CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
+ codedOutput.writeUInt32NoTag(getSerializedSize());
writeTo(codedOutput);
codedOutput.flush();
}
@@ -204,15 +198,17 @@ public void writeDelimitedTo(OutputStream output) throws IOException {
@Override
public int getSerializedSize() {
int result = 0;
- for (final Map.Entry entry : fields.entrySet()) {
- result += entry.getValue().getSerializedSize(entry.getKey());
+ if (!fields.isEmpty()) {
+ for (Map.Entry entry : fields.entrySet()) {
+ result += entry.getValue().getSerializedSize(entry.getKey());
+ }
}
return result;
}
/** Serializes the set and writes it to {@code output} using {@code MessageSet} wire format. */
- public void writeAsMessageSetTo(final CodedOutputStream output) throws IOException {
- for (final Map.Entry entry : fields.entrySet()) {
+ public void writeAsMessageSetTo(CodedOutputStream output) throws IOException {
+ for (Map.Entry entry : fields.entrySet()) {
entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), output);
}
}
@@ -221,7 +217,7 @@ public void writeAsMessageSetTo(final CodedOutputStream output) throws IOExcepti
void writeTo(Writer writer) throws IOException {
if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
// Write fields in descending order.
- for (Map.Entry entry : fieldsDescending.entrySet()) {
+ for (Map.Entry entry : fields.descendingMap().entrySet()) {
entry.getValue().writeTo(entry.getKey(), writer);
}
} else {
@@ -233,15 +229,15 @@ void writeTo(Writer writer) throws IOException {
}
/** Serializes the set and writes it to {@code writer} using {@code MessageSet} wire format. */
- void writeAsMessageSetTo(final Writer writer) throws IOException {
+ void writeAsMessageSetTo(Writer writer) throws IOException {
if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
// Write fields in descending order.
- for (final Map.Entry entry : fieldsDescending.entrySet()) {
+ for (Map.Entry entry : fields.descendingMap().entrySet()) {
entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), writer);
}
} else {
// Write fields in ascending order.
- for (final Map.Entry entry : fields.entrySet()) {
+ for (Map.Entry entry : fields.entrySet()) {
entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), writer);
}
}
@@ -250,7 +246,7 @@ void writeAsMessageSetTo(final Writer writer) throws IOException {
/** Get the number of bytes required to encode this set using {@code MessageSet} wire format. */
public int getSerializedSizeAsMessageSet() {
int result = 0;
- for (final Map.Entry entry : fields.entrySet()) {
+ for (Map.Entry entry : fields.entrySet()) {
result += entry.getValue().getSerializedSizeAsMessageSetExtension(entry.getKey());
}
return result;
@@ -264,23 +260,23 @@ public boolean isInitialized() {
}
/** Parse an {@code UnknownFieldSet} from the given input stream. */
- public static UnknownFieldSet parseFrom(final CodedInputStream input) throws IOException {
+ public static UnknownFieldSet parseFrom(CodedInputStream input) throws IOException {
return newBuilder().mergeFrom(input).build();
}
/** Parse {@code data} as an {@code UnknownFieldSet} and return it. */
- public static UnknownFieldSet parseFrom(final ByteString data)
+ public static UnknownFieldSet parseFrom(ByteString data)
throws InvalidProtocolBufferException {
return newBuilder().mergeFrom(data).build();
}
/** Parse {@code data} as an {@code UnknownFieldSet} and return it. */
- public static UnknownFieldSet parseFrom(final byte[] data) throws InvalidProtocolBufferException {
+ public static UnknownFieldSet parseFrom(byte[] data) throws InvalidProtocolBufferException {
return newBuilder().mergeFrom(data).build();
}
/** Parse an {@code UnknownFieldSet} from {@code input} and return it. */
- public static UnknownFieldSet parseFrom(final InputStream input) throws IOException {
+ public static UnknownFieldSet parseFrom(InputStream input) throws IOException {
return newBuilder().mergeFrom(input).build();
}
@@ -309,64 +305,43 @@ public static final class Builder implements MessageLite.Builder {
// This constructor should never be called directly (except from 'create').
private Builder() {}
- private Map fields;
-
- // Optimization: We keep around a builder for the last field that was
- // modified so that we can efficiently add to it multiple times in a
- // row (important when parsing an unknown repeated field).
- private int lastFieldNumber;
- private Field.Builder lastField;
+ private TreeMap fieldBuilders = new TreeMap<>();
private static Builder create() {
- Builder builder = new Builder();
- builder.reinitialize();
- return builder;
+ return new Builder();
}
/**
* Get a field builder for the given field number which includes any values that already exist.
*/
- private Field.Builder getFieldBuilder(final int number) {
- if (lastField != null) {
- if (number == lastFieldNumber) {
- return lastField;
- }
- // Note: addField() will reset lastField and lastFieldNumber.
- addField(lastFieldNumber, lastField.build());
- }
+ private Field.Builder getFieldBuilder(int number) {
if (number == 0) {
return null;
} else {
- final Field existing = fields.get(number);
- lastFieldNumber = number;
- lastField = Field.newBuilder();
- if (existing != null) {
- lastField.mergeFrom(existing);
+ Field.Builder builder = fieldBuilders.get(number);
+ if (builder == null) {
+ builder = Field.newBuilder();
+ fieldBuilders.put(number, builder);
}
- return lastField;
+ return builder;
}
}
/**
* Build the {@link UnknownFieldSet} and return it.
- *
- *
Once {@code build()} has been called, the {@code Builder} will no longer be usable.
- * Calling any method after {@code build()} will result in undefined behavior and can cause a
- * {@code NullPointerException} to be thrown.
*/
@Override
public UnknownFieldSet build() {
- getFieldBuilder(0); // Force lastField to be built.
- final UnknownFieldSet result;
- if (fields.isEmpty()) {
+ UnknownFieldSet result;
+ if (fieldBuilders.isEmpty()) {
result = getDefaultInstance();
} else {
- Map descendingFields = null;
- descendingFields =
- Collections.unmodifiableMap(((TreeMap) fields).descendingMap());
- result = new UnknownFieldSet(Collections.unmodifiableMap(fields), descendingFields);
+ TreeMap fields = new TreeMap<>();
+ for (Map.Entry entry : fieldBuilders.entrySet()) {
+ fields.put(entry.getKey(), entry.getValue().build());
+ }
+ result = new UnknownFieldSet(fields);
}
- fields = null;
return result;
}
@@ -378,11 +353,13 @@ public UnknownFieldSet buildPartial() {
@Override
public Builder clone() {
- getFieldBuilder(0); // Force lastField to be built.
- Map descendingFields = null;
- descendingFields =
- Collections.unmodifiableMap(((TreeMap) fields).descendingMap());
- return UnknownFieldSet.newBuilder().mergeFrom(new UnknownFieldSet(fields, descendingFields));
+ Builder clone = UnknownFieldSet.newBuilder();
+ for (Map.Entry entry : fieldBuilders.entrySet()) {
+ Integer key = entry.getKey();
+ Field.Builder value = entry.getValue();
+ clone.fieldBuilders.put(key, value.clone());
+ }
+ return clone;
}
@Override
@@ -390,31 +367,24 @@ public UnknownFieldSet getDefaultInstanceForType() {
return UnknownFieldSet.getDefaultInstance();
}
- private void reinitialize() {
- fields = Collections.emptyMap();
- lastFieldNumber = 0;
- lastField = null;
- }
-
/** Reset the builder to an empty set. */
@Override
public Builder clear() {
- reinitialize();
+ fieldBuilders = new TreeMap<>();
return this;
}
- /** Clear fields from the set with a given field number. */
- public Builder clearField(final int number) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
- }
- if (lastField != null && lastFieldNumber == number) {
- // Discard this.
- lastField = null;
- lastFieldNumber = 0;
+ /**
+ * Clear fields from the set with a given field number.
+ *
+ * @throws IllegalArgumentException if number is not positive
+ */
+ public Builder clearField(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
- if (fields.containsKey(number)) {
- fields.remove(number);
+ if (fieldBuilders.containsKey(number)) {
+ fieldBuilders.remove(number);
}
return this;
}
@@ -423,9 +393,9 @@ public Builder clearField(final int number) {
* Merge the fields from {@code other} into this set. If a field number exists in both sets,
* {@code other}'s values for that field will be appended to the values in this set.
*/
- public Builder mergeFrom(final UnknownFieldSet other) {
+ public Builder mergeFrom(UnknownFieldSet other) {
if (other != getDefaultInstance()) {
- for (final Map.Entry entry : other.fields.entrySet()) {
+ for (Map.Entry entry : other.fields.entrySet()) {
mergeField(entry.getKey(), entry.getValue());
}
}
@@ -435,10 +405,12 @@ public Builder mergeFrom(final UnknownFieldSet other) {
/**
* Add a field to the {@code UnknownFieldSet}. If a field with the same number already exists,
* the two are merged.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
- public Builder mergeField(final int number, final Field field) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
+ public Builder mergeField(int number, final Field field) {
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
if (hasField(number)) {
getFieldBuilder(number).mergeFrom(field);
@@ -454,10 +426,12 @@ public Builder mergeField(final int number, final Field field) {
/**
* Convenience method for merging a new field containing a single varint value. This is used in
* particular when an unknown enum value is encountered.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
- public Builder mergeVarintField(final int number, final int value) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
+ public Builder mergeVarintField(int number, int value) {
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
getFieldBuilder(number).addVarint(value);
return this;
@@ -467,40 +441,33 @@ public Builder mergeVarintField(final int number, final int value) {
* Convenience method for merging a length-delimited field.
*
*
For use by generated code only.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
- public Builder mergeLengthDelimitedField(final int number, final ByteString value) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
+ public Builder mergeLengthDelimitedField(int number, ByteString value) {
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
getFieldBuilder(number).addLengthDelimited(value);
return this;
}
/** Check if the given field number is present in the set. */
- public boolean hasField(final int number) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
- }
- return number == lastFieldNumber || fields.containsKey(number);
+ public boolean hasField(int number) {
+ return fieldBuilders.containsKey(number);
}
/**
* Add a field to the {@code UnknownFieldSet}. If a field with the same number already exists,
* it is removed.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
- public Builder addField(final int number, final Field field) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
- }
- if (lastField != null && lastFieldNumber == number) {
- // Discard this.
- lastField = null;
- lastFieldNumber = 0;
+ public Builder addField(int number, Field field) {
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
- if (fields.isEmpty()) {
- fields = new TreeMap();
- }
- fields.put(number, field);
+ fieldBuilders.put(number, Field.newBuilder(field));
return this;
}
@@ -509,15 +476,18 @@ public Builder addField(final int number, final Field field) {
* changes may or may not be reflected in this map.
*/
public Map asMap() {
- getFieldBuilder(0); // Force lastField to be built.
+ TreeMap fields = new TreeMap<>();
+ for (Map.Entry entry : fieldBuilders.entrySet()) {
+ fields.put(entry.getKey(), entry.getValue().build());
+ }
return Collections.unmodifiableMap(fields);
}
/** Parse an entire message from {@code input} and merge its fields into this set. */
@Override
- public Builder mergeFrom(final CodedInputStream input) throws IOException {
+ public Builder mergeFrom(CodedInputStream input) throws IOException {
while (true) {
- final int tag = input.readTag();
+ int tag = input.readTag();
if (tag == 0 || !mergeFieldFrom(tag, input)) {
break;
}
@@ -531,8 +501,8 @@ public Builder mergeFrom(final CodedInputStream input) throws IOException {
* @param tag The field's tag number, which was already parsed.
* @return {@code false} if the tag is an end group tag.
*/
- public boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException {
- final int number = WireFormat.getTagFieldNumber(tag);
+ public boolean mergeFieldFrom(int tag, CodedInputStream input) throws IOException {
+ int number = WireFormat.getTagFieldNumber(tag);
switch (WireFormat.getTagWireType(tag)) {
case WireFormat.WIRETYPE_VARINT:
getFieldBuilder(number).addVarint(input.readInt64());
@@ -544,7 +514,7 @@ public boolean mergeFieldFrom(final int tag, final CodedInputStream input) throw
getFieldBuilder(number).addLengthDelimited(input.readBytes());
return true;
case WireFormat.WIRETYPE_START_GROUP:
- final Builder subBuilder = newBuilder();
+ Builder subBuilder = newBuilder();
input.readGroup(number, subBuilder, ExtensionRegistry.getEmptyRegistry());
getFieldBuilder(number).addGroup(subBuilder.build());
return true;
@@ -563,15 +533,15 @@ public boolean mergeFieldFrom(final int tag, final CodedInputStream input) throw
* is just a small wrapper around {@link #mergeFrom(CodedInputStream)}.
*/
@Override
- public Builder mergeFrom(final ByteString data) throws InvalidProtocolBufferException {
+ public Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException {
try {
- final CodedInputStream input = data.newCodedInput();
+ CodedInputStream input = data.newCodedInput();
mergeFrom(input);
input.checkLastTagWas(0);
return this;
- } catch (final InvalidProtocolBufferException e) {
+ } catch (InvalidProtocolBufferException e) {
throw e;
- } catch (final IOException e) {
+ } catch (IOException e) {
throw new RuntimeException(
"Reading from a ByteString threw an IOException (should never happen).", e);
}
@@ -582,15 +552,15 @@ public Builder mergeFrom(final ByteString data) throws InvalidProtocolBufferExce
* is just a small wrapper around {@link #mergeFrom(CodedInputStream)}.
*/
@Override
- public Builder mergeFrom(final byte[] data) throws InvalidProtocolBufferException {
+ public Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException {
try {
- final CodedInputStream input = CodedInputStream.newInstance(data);
+ CodedInputStream input = CodedInputStream.newInstance(data);
mergeFrom(input);
input.checkLastTagWas(0);
return this;
- } catch (final InvalidProtocolBufferException e) {
+ } catch (InvalidProtocolBufferException e) {
throw e;
- } catch (final IOException e) {
+ } catch (IOException e) {
throw new RuntimeException(
"Reading from a byte array threw an IOException (should never happen).", e);
}
@@ -601,8 +571,8 @@ public Builder mergeFrom(final byte[] data) throws InvalidProtocolBufferExceptio
* This is just a small wrapper around {@link #mergeFrom(CodedInputStream)}.
*/
@Override
- public Builder mergeFrom(final InputStream input) throws IOException {
- final CodedInputStream codedInput = CodedInputStream.newInstance(input);
+ public Builder mergeFrom(InputStream input) throws IOException {
+ CodedInputStream codedInput = CodedInputStream.newInstance(input);
mergeFrom(codedInput);
codedInput.checkLastTagWas(0);
return this;
@@ -610,12 +580,12 @@ public Builder mergeFrom(final InputStream input) throws IOException {
@Override
public boolean mergeDelimitedFrom(InputStream input) throws IOException {
- final int firstByte = input.read();
+ int firstByte = input.read();
if (firstByte == -1) {
return false;
}
- final int size = CodedInputStream.readRawVarint32(firstByte, input);
- final InputStream limitedInput = new LimitedInputStream(input, size);
+ int size = CodedInputStream.readRawVarint32(firstByte, input);
+ InputStream limitedInput = new LimitedInputStream(input, size);
mergeFrom(limitedInput);
return true;
}
@@ -644,7 +614,7 @@ public Builder mergeFrom(ByteString data, ExtensionRegistryLite extensionRegistr
@Override
public Builder mergeFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException {
try {
- final CodedInputStream input = CodedInputStream.newInstance(data, off, len);
+ CodedInputStream input = CodedInputStream.newInstance(data, off, len);
mergeFrom(input);
input.checkLastTagWas(0);
return this;
@@ -718,7 +688,7 @@ public static Builder newBuilder() {
}
/** Construct a new {@link Builder} and initialize it to a copy of {@code copyFrom}. */
- public static Builder newBuilder(final Field copyFrom) {
+ public static Builder newBuilder(Field copyFrom) {
return newBuilder().mergeFrom(copyFrom);
}
@@ -758,7 +728,7 @@ public List getGroupList() {
}
@Override
- public boolean equals(final Object other) {
+ public boolean equals(Object other) {
if (this == other) {
return true;
}
@@ -785,7 +755,7 @@ private Object[] getIdentityArray() {
public ByteString toByteString(int fieldNumber) {
try {
// TODO(lukes): consider caching serialized size in a volatile long
- final ByteString.CodedBuilder out =
+ ByteString.CodedBuilder out =
ByteString.newCodedBuilder(getSerializedSize(fieldNumber));
writeTo(fieldNumber, out.getCodedOutput());
return out.build();
@@ -796,40 +766,40 @@ public ByteString toByteString(int fieldNumber) {
}
/** Serializes the field, including field number, and writes it to {@code output}. */
- public void writeTo(final int fieldNumber, final CodedOutputStream output) throws IOException {
- for (final long value : varint) {
+ public void writeTo(int fieldNumber, CodedOutputStream output) throws IOException {
+ for (long value : varint) {
output.writeUInt64(fieldNumber, value);
}
- for (final int value : fixed32) {
+ for (int value : fixed32) {
output.writeFixed32(fieldNumber, value);
}
- for (final long value : fixed64) {
+ for (long value : fixed64) {
output.writeFixed64(fieldNumber, value);
}
- for (final ByteString value : lengthDelimited) {
+ for (ByteString value : lengthDelimited) {
output.writeBytes(fieldNumber, value);
}
- for (final UnknownFieldSet value : group) {
+ for (UnknownFieldSet value : group) {
output.writeGroup(fieldNumber, value);
}
}
/** Get the number of bytes required to encode this field, including field number. */
- public int getSerializedSize(final int fieldNumber) {
+ public int getSerializedSize(int fieldNumber) {
int result = 0;
- for (final long value : varint) {
+ for (long value : varint) {
result += CodedOutputStream.computeUInt64Size(fieldNumber, value);
}
- for (final int value : fixed32) {
+ for (int value : fixed32) {
result += CodedOutputStream.computeFixed32Size(fieldNumber, value);
}
- for (final long value : fixed64) {
+ for (long value : fixed64) {
result += CodedOutputStream.computeFixed64Size(fieldNumber, value);
}
- for (final ByteString value : lengthDelimited) {
+ for (ByteString value : lengthDelimited) {
result += CodedOutputStream.computeBytesSize(fieldNumber, value);
}
- for (final UnknownFieldSet value : group) {
+ for (UnknownFieldSet value : group) {
result += CodedOutputStream.computeGroupSize(fieldNumber, value);
}
return result;
@@ -839,15 +809,15 @@ public int getSerializedSize(final int fieldNumber) {
* Serializes the field, including field number, and writes it to {@code output}, using {@code
* MessageSet} wire format.
*/
- public void writeAsMessageSetExtensionTo(final int fieldNumber, final CodedOutputStream output)
+ public void writeAsMessageSetExtensionTo(int fieldNumber, CodedOutputStream output)
throws IOException {
- for (final ByteString value : lengthDelimited) {
+ for (ByteString value : lengthDelimited) {
output.writeRawMessageSetExtension(fieldNumber, value);
}
}
/** Serializes the field, including field number, and writes it to {@code writer}. */
- void writeTo(final int fieldNumber, final Writer writer) throws IOException {
+ void writeTo(int fieldNumber, Writer writer) throws IOException {
writer.writeInt64List(fieldNumber, varint, false);
writer.writeFixed32List(fieldNumber, fixed32, false);
writer.writeFixed64List(fieldNumber, fixed64, false);
@@ -872,7 +842,7 @@ void writeTo(final int fieldNumber, final Writer writer) throws IOException {
* Serializes the field, including field number, and writes it to {@code writer}, using {@code
* MessageSet} wire format.
*/
- private void writeAsMessageSetExtensionTo(final int fieldNumber, final Writer writer)
+ private void writeAsMessageSetExtensionTo(int fieldNumber, Writer writer)
throws IOException {
if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
// Write in descending field order.
@@ -882,7 +852,7 @@ private void writeAsMessageSetExtensionTo(final int fieldNumber, final Writer wr
}
} else {
// Write in ascending field order.
- for (final ByteString value : lengthDelimited) {
+ for (ByteString value : lengthDelimited) {
writer.writeMessageSetItem(fieldNumber, value);
}
}
@@ -892,9 +862,9 @@ private void writeAsMessageSetExtensionTo(final int fieldNumber, final Writer wr
* Get the number of bytes required to encode this field, including field number, using {@code
* MessageSet} wire format.
*/
- public int getSerializedSizeAsMessageSetExtension(final int fieldNumber) {
+ public int getSerializedSizeAsMessageSetExtension(int fieldNumber) {
int result = 0;
- for (final ByteString value : lengthDelimited) {
+ for (ByteString value : lengthDelimited) {
result += CodedOutputStream.computeRawMessageSetExtensionSize(fieldNumber, value);
}
return result;
@@ -912,52 +882,85 @@ public int getSerializedSizeAsMessageSetExtension(final int fieldNumber) {
*
Use {@link Field#newBuilder()} to construct a {@code Builder}.
*/
public static final class Builder {
- // This constructor should never be called directly (except from 'create').
- private Builder() {}
+ // This constructor should only be called directly from 'create' and 'clone'.
+ private Builder() {
+ result = new Field();
+ }
private static Builder create() {
Builder builder = new Builder();
- builder.result = new Field();
return builder;
}
private Field result;
+ @Override
+ public Builder clone() {
+ Field copy = new Field();
+ if (result.varint == null) {
+ copy.varint = null;
+ } else {
+ copy.varint = new ArrayList<>(result.varint);
+ }
+ if (result.fixed32 == null) {
+ copy.fixed32 = null;
+ } else {
+ copy.fixed32 = new ArrayList<>(result.fixed32);
+ }
+ if (result.fixed64 == null) {
+ copy.fixed64 = null;
+ } else {
+ copy.fixed64 = new ArrayList<>(result.fixed64);
+ }
+ if (result.lengthDelimited == null) {
+ copy.lengthDelimited = null;
+ } else {
+ copy.lengthDelimited = new ArrayList<>(result.lengthDelimited);
+ }
+ if (result.group == null) {
+ copy.group = null;
+ } else {
+ copy.group = new ArrayList<>(result.group);
+ }
+
+ Builder clone = new Builder();
+ clone.result = copy;
+ return clone;
+ }
+
/**
- * Build the field. After {@code build()} has been called, the {@code Builder} is no longer
- * usable. Calling any other method will result in undefined behavior and can cause a {@code
- * NullPointerException} to be thrown.
+ * Build the field.
*/
public Field build() {
+ Field built = new Field();
if (result.varint == null) {
- result.varint = Collections.emptyList();
+ built.varint = Collections.emptyList();
} else {
- result.varint = Collections.unmodifiableList(result.varint);
+ built.varint = Collections.unmodifiableList(new ArrayList<>(result.varint));
}
if (result.fixed32 == null) {
- result.fixed32 = Collections.emptyList();
+ built.fixed32 = Collections.emptyList();
} else {
- result.fixed32 = Collections.unmodifiableList(result.fixed32);
+ built.fixed32 = Collections.unmodifiableList(new ArrayList<>(result.fixed32));
}
if (result.fixed64 == null) {
- result.fixed64 = Collections.emptyList();
+ built.fixed64 = Collections.emptyList();
} else {
- result.fixed64 = Collections.unmodifiableList(result.fixed64);
+ built.fixed64 = Collections.unmodifiableList(new ArrayList<>(result.fixed64));
}
if (result.lengthDelimited == null) {
- result.lengthDelimited = Collections.emptyList();
+ built.lengthDelimited = Collections.emptyList();
} else {
- result.lengthDelimited = Collections.unmodifiableList(result.lengthDelimited);
+ built.lengthDelimited = Collections.unmodifiableList(
+ new ArrayList<>(result.lengthDelimited));
}
if (result.group == null) {
- result.group = Collections.emptyList();
+ built.group = Collections.emptyList();
} else {
- result.group = Collections.unmodifiableList(result.group);
+ built.group = Collections.unmodifiableList(new ArrayList<>(result.group));
}
- final Field returnMe = result;
- result = null;
- return returnMe;
+ return built;
}
/** Discard the field's contents. */
@@ -970,7 +973,7 @@ public Builder clear() {
* Merge the values in {@code other} into this field. For each list of values, {@code other}'s
* values are append to the ones in this field.
*/
- public Builder mergeFrom(final Field other) {
+ public Builder mergeFrom(Field other) {
if (!other.varint.isEmpty()) {
if (result.varint == null) {
result.varint = new ArrayList();
@@ -985,19 +988,19 @@ public Builder mergeFrom(final Field other) {
}
if (!other.fixed64.isEmpty()) {
if (result.fixed64 == null) {
- result.fixed64 = new ArrayList();
+ result.fixed64 = new ArrayList<>();
}
result.fixed64.addAll(other.fixed64);
}
if (!other.lengthDelimited.isEmpty()) {
if (result.lengthDelimited == null) {
- result.lengthDelimited = new ArrayList();
+ result.lengthDelimited = new ArrayList<>();
}
result.lengthDelimited.addAll(other.lengthDelimited);
}
if (!other.group.isEmpty()) {
if (result.group == null) {
- result.group = new ArrayList();
+ result.group = new ArrayList<>();
}
result.group.addAll(other.group);
}
@@ -1005,45 +1008,45 @@ public Builder mergeFrom(final Field other) {
}
/** Add a varint value. */
- public Builder addVarint(final long value) {
+ public Builder addVarint(long value) {
if (result.varint == null) {
- result.varint = new ArrayList();
+ result.varint = new ArrayList<>();
}
result.varint.add(value);
return this;
}
/** Add a fixed32 value. */
- public Builder addFixed32(final int value) {
+ public Builder addFixed32(int value) {
if (result.fixed32 == null) {
- result.fixed32 = new ArrayList();
+ result.fixed32 = new ArrayList<>();
}
result.fixed32.add(value);
return this;
}
/** Add a fixed64 value. */
- public Builder addFixed64(final long value) {
+ public Builder addFixed64(long value) {
if (result.fixed64 == null) {
- result.fixed64 = new ArrayList();
+ result.fixed64 = new ArrayList<>();
}
result.fixed64.add(value);
return this;
}
/** Add a length-delimited value. */
- public Builder addLengthDelimited(final ByteString value) {
+ public Builder addLengthDelimited(ByteString value) {
if (result.lengthDelimited == null) {
- result.lengthDelimited = new ArrayList();
+ result.lengthDelimited = new ArrayList<>();
}
result.lengthDelimited.add(value);
return this;
}
/** Add an embedded group. */
- public Builder addGroup(final UnknownFieldSet value) {
+ public Builder addGroup(UnknownFieldSet value) {
if (result.group == null) {
- result.group = new ArrayList();
+ result.group = new ArrayList<>();
}
result.group.add(value);
return this;
diff --git a/java/core/src/test/java/com/google/protobuf/UnknownFieldSetPerformanceTest.java b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetPerformanceTest.java
new file mode 100644
index 0000000000000..6ce0fc7e348b8
--- /dev/null
+++ b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetPerformanceTest.java
@@ -0,0 +1,78 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class UnknownFieldSetPerformanceTest {
+
+ private static byte[] generateBytes(int length) {
+ assertThat(length % 4).isEqualTo(0);
+ byte[] input = new byte[length];
+ for (int i = 0; i < length; i += 4) {
+ input[i] = (byte) 0x08; // field 1, wiretype 0
+ input[i + 1] = (byte) 0x08; // field 1, payload 8
+ input[i + 2] = (byte) 0x20; // field 4, wiretype 0
+ input[i + 3] = (byte) 0x20; // field 4, payload 32
+ }
+ return input;
+ }
+
+ @Test
+ // This is a performance test. Failure here is a timeout.
+ public void testAlternatingFieldNumbers() throws IOException {
+ byte[] input = generateBytes(800000);
+ InputStream in = new ByteArrayInputStream(input);
+ UnknownFieldSet.Builder builder = UnknownFieldSet.newBuilder();
+ CodedInputStream codedInput = CodedInputStream.newInstance(in);
+ builder.mergeFrom(codedInput);
+ }
+
+ @Test
+ // This is a performance test. Failure here is a timeout.
+ public void testAddField() {
+ UnknownFieldSet.Builder builder = UnknownFieldSet.newBuilder();
+ for (int i = 1; i <= 100000; i++) {
+ UnknownFieldSet.Field field = UnknownFieldSet.Field.newBuilder().addFixed32(i).build();
+ builder.addField(i, field);
+ }
+ UnknownFieldSet fieldSet = builder.build();
+ assertThat(fieldSet.getField(100000).getFixed32List().get(0)).isEqualTo(100000);
+ }
+}
diff --git a/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
index 1e5bc9649a18d..fbc3bb8fcfbcd 100644
--- a/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
+++ b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
@@ -42,7 +42,9 @@
import protobuf_unittest.UnittestProto.TestPackedExtensions;
import protobuf_unittest.UnittestProto.TestPackedTypes;
import proto3_unittest.UnittestProto3;
+import java.util.List;
import java.util.Map;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,7 +63,7 @@ public void setUp() throws Exception {
unknownFields = emptyMessage.getUnknownFields();
}
- UnknownFieldSet.Field getField(String name) {
+ private UnknownFieldSet.Field getField(String name) {
Descriptors.FieldDescriptor field = descriptor.findFieldByName(name);
assertThat(field).isNotNull();
return unknownFields.getField(field.getNumber());
@@ -100,6 +102,174 @@ ByteString getBizarroData() throws Exception {
// =================================================================
+ @Test
+ public void testFieldBuildersAreReusable() {
+ UnknownFieldSet.Field.Builder fieldBuilder = UnknownFieldSet.Field.newBuilder();
+ fieldBuilder.addFixed32(10);
+ UnknownFieldSet.Field first = fieldBuilder.build();
+ UnknownFieldSet.Field second = fieldBuilder.build();
+ fieldBuilder.addFixed32(11);
+ UnknownFieldSet.Field third = fieldBuilder.build();
+
+ assertThat(first).isEqualTo(second);
+ assertThat(first).isNotEqualTo(third);
+ }
+
+ @Test
+ public void testClone() {
+ UnknownFieldSet.Builder unknownSetBuilder = UnknownFieldSet.newBuilder();
+ UnknownFieldSet.Field.Builder fieldBuilder = UnknownFieldSet.Field.newBuilder();
+ fieldBuilder.addFixed32(10);
+ unknownSetBuilder.addField(8, fieldBuilder.build());
+ // necessary to call clone twice to expose the bug
+ UnknownFieldSet.Builder clone1 = unknownSetBuilder.clone();
+ UnknownFieldSet.Builder clone2 = unknownSetBuilder.clone(); // failure is a NullPointerException
+ assertThat(clone1).isNotSameInstanceAs(clone2);
+ }
+
+ @Test
+ public void testClone_lengthDelimited() {
+ UnknownFieldSet.Builder destUnknownFieldSet =
+ UnknownFieldSet.newBuilder()
+ .addField(997, UnknownFieldSet.Field.newBuilder().addVarint(99).build())
+ .addField(
+ 999,
+ UnknownFieldSet.Field.newBuilder()
+ .addLengthDelimited(ByteString.copyFromUtf8("some data"))
+ .addLengthDelimited(ByteString.copyFromUtf8("some more data"))
+ .build());
+ UnknownFieldSet clone = destUnknownFieldSet.clone().build();
+ assertThat(clone.getField(997)).isNotNull();
+ UnknownFieldSet.Field field999 = clone.getField(999);
+ List lengthDelimited = field999.getLengthDelimitedList();
+ assertThat(lengthDelimited.get(0).toStringUtf8()).isEqualTo("some data");
+ assertThat(lengthDelimited.get(1).toStringUtf8()).isEqualTo("some more data");
+
+ UnknownFieldSet clone2 = destUnknownFieldSet.clone().build();
+ assertThat(clone2.getField(997)).isNotNull();
+ UnknownFieldSet.Field secondField = clone2.getField(999);
+ List lengthDelimited2 = secondField.getLengthDelimitedList();
+ assertThat(lengthDelimited2.get(0).toStringUtf8()).isEqualTo("some data");
+ assertThat(lengthDelimited2.get(1).toStringUtf8()).isEqualTo("some more data");
+ }
+
+ @Test
+ public void testReuse() {
+ UnknownFieldSet.Builder builder =
+ UnknownFieldSet.newBuilder()
+ .addField(997, UnknownFieldSet.Field.newBuilder().addVarint(99).build())
+ .addField(
+ 999,
+ UnknownFieldSet.Field.newBuilder()
+ .addLengthDelimited(ByteString.copyFromUtf8("some data"))
+ .addLengthDelimited(ByteString.copyFromUtf8("some more data"))
+ .build());
+
+ UnknownFieldSet fieldSet1 = builder.build();
+ UnknownFieldSet fieldSet2 = builder.build();
+ builder.addField(1000, UnknownFieldSet.Field.newBuilder().addVarint(-90).build());
+ UnknownFieldSet fieldSet3 = builder.build();
+
+ assertThat(fieldSet1).isEqualTo(fieldSet2);
+ assertThat(fieldSet1).isNotEqualTo(fieldSet3);
+ }
+
+ @Test
+ @SuppressWarnings("ModifiedButNotUsed")
+ public void testAddField_zero() {
+ UnknownFieldSet.Field field = getField("optional_int32");
+ try {
+ UnknownFieldSet.newBuilder().addField(0, field);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("0 is not a valid field number.");
+ }
+ }
+
+ @Test
+ @SuppressWarnings("ModifiedButNotUsed")
+ public void testAddField_negative() {
+ UnknownFieldSet.Field field = getField("optional_int32");
+ try {
+ UnknownFieldSet.newBuilder().addField(-2, field);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("-2 is not a valid field number.");
+ }
+ }
+
+ @Test
+ @SuppressWarnings("ModifiedButNotUsed")
+ public void testClearField_negative() {
+ try {
+ UnknownFieldSet.newBuilder().clearField(-28);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("-28 is not a valid field number.");
+ }
+ }
+
+ @Test
+ @SuppressWarnings("ModifiedButNotUsed")
+ public void testMergeField_negative() {
+ UnknownFieldSet.Field field = getField("optional_int32");
+ try {
+ UnknownFieldSet.newBuilder().mergeField(-2, field);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("-2 is not a valid field number.");
+ }
+ }
+
+ @Test
+ @SuppressWarnings("ModifiedButNotUsed")
+ public void testMergeVarintField_negative() {
+ try {
+ UnknownFieldSet.newBuilder().mergeVarintField(-2, 78);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("-2 is not a valid field number.");
+ }
+ }
+
+ @Test
+ @SuppressWarnings("ModifiedButNotUsed")
+ public void testHasField_negative() {
+ assertThat(UnknownFieldSet.newBuilder().hasField(-2)).isFalse();
+ }
+
+ @Test
+ @SuppressWarnings("ModifiedButNotUsed")
+ public void testMergeLengthDelimitedField_negative() {
+ ByteString byteString = ByteString.copyFromUtf8("some data");
+ try {
+ UnknownFieldSet.newBuilder().mergeLengthDelimitedField(-2, byteString);
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("-2 is not a valid field number.");
+ }
+ }
+
+ @Test
+ public void testAddField() {
+ UnknownFieldSet.Field field = getField("optional_int32");
+ UnknownFieldSet fieldSet = UnknownFieldSet.newBuilder().addField(1, field).build();
+ assertThat(fieldSet.getField(1)).isEqualTo(field);
+ }
+
+ @Test
+ public void testAddField_withReplacement() {
+ UnknownFieldSet.Field first = UnknownFieldSet.Field.newBuilder().addFixed32(56).build();
+ UnknownFieldSet.Field second = UnknownFieldSet.Field.newBuilder().addFixed32(25).build();
+ UnknownFieldSet fieldSet = UnknownFieldSet.newBuilder()
+ .addField(1, first)
+ .addField(1, second)
+ .build();
+ List list = fieldSet.getField(1).getFixed32List();
+ assertThat(list).hasSize(1);
+ assertThat(list.get(0)).isEqualTo(25);
+ }
+
@Test
public void testVarint() throws Exception {
UnknownFieldSet.Field field = getField("optional_int32");
@@ -185,6 +355,16 @@ public void testMergeFrom() throws Exception {
assertThat(destination.toString()).isEqualTo("1: 1\n2: 2\n3: 3\n3: 4\n");
}
+ @Test
+ public void testAsMap() throws Exception {
+ UnknownFieldSet.Builder builder = UnknownFieldSet.newBuilder().mergeFrom(unknownFields);
+ Map mapFromBuilder = builder.asMap();
+ assertThat(mapFromBuilder).isNotEmpty();
+ UnknownFieldSet fields = builder.build();
+ Map mapFromFieldSet = fields.asMap();
+ assertThat(mapFromFieldSet).containsExactlyEntriesIn(mapFromBuilder);
+ }
+
@Test
public void testClear() throws Exception {
UnknownFieldSet fields = UnknownFieldSet.newBuilder().mergeFrom(unknownFields).clear().build();
diff --git a/java/kotlin-lite/pom.xml b/java/kotlin-lite/pom.xml
index 0720766132770..09200595e5bbd 100644
--- a/java/kotlin-lite/pom.xml
+++ b/java/kotlin-lite/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-parent
- 3.18.1
+ 3.18.2protobuf-kotlin-lite
diff --git a/java/kotlin/pom.xml b/java/kotlin/pom.xml
index ad25304949da0..ae3296db196df 100644
--- a/java/kotlin/pom.xml
+++ b/java/kotlin/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-parent
- 3.18.1
+ 3.18.2protobuf-kotlin
diff --git a/java/lite/pom.xml b/java/lite/pom.xml
index 0990fcfb444bc..734a9ec17ade4 100644
--- a/java/lite/pom.xml
+++ b/java/lite/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-parent
- 3.18.1
+ 3.18.2protobuf-javalite
@@ -235,6 +235,7 @@
TypeRegistryTest.javaUnknownEnumValueTest.javaUnknownFieldSetLiteTest.java
+ UnknownFieldSetPerformanceTest.javaUnknownFieldSetTest.javaWellKnownTypesTest.javaWireFormatTest.java
diff --git a/java/pom.xml b/java/pom.xml
index d1464dd3ca39c..32aee82b7daa1 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-parent
- 3.18.1
+ 3.18.2pomProtocol Buffers [Parent]
diff --git a/java/util/pom.xml b/java/util/pom.xml
index 83d8d7bb2e0a9..a9a9471522c82 100644
--- a/java/util/pom.xml
+++ b/java/util/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-parent
- 3.18.1
+ 3.18.2protobuf-java-util
diff --git a/js/package.json b/js/package.json
index 33f3009255530..63d4b917e84cf 100644
--- a/js/package.json
+++ b/js/package.json
@@ -1,6 +1,6 @@
{
"name": "google-protobuf",
- "version": "3.18.1",
+ "version": "3.18.2",
"description": "Protocol Buffers for JavaScript",
"main": "google-protobuf.js",
"files": [
diff --git a/php/ext/google/protobuf/package.xml b/php/ext/google/protobuf/package.xml
index f083785bb1e94..53cfbd6f8e556 100644
--- a/php/ext/google/protobuf/package.xml
+++ b/php/ext/google/protobuf/package.xml
@@ -10,11 +10,11 @@
protobuf-opensource@google.comyes
- 2021-10-04
-
+ 2022-01-05
+
- 3.18.1
- 3.18.1
+ 3.18.2
+ 3.18.2stable
@@ -22,7 +22,7 @@
3-Clause BSD License
- * No new changes in 3.18.1
+ * No new changes in 3.18.2
@@ -1098,5 +1098,20 @@ G A release.
+
+
+ 3.18.2
+ 3.18.2
+
+
+ stable
+ stable
+
+ 2022-01-05
+
+ 3-Clause BSD License
+
+
+
diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h
index 8b5c8aa8570e3..9b70d33b017c6 100644
--- a/php/ext/google/protobuf/protobuf.h
+++ b/php/ext/google/protobuf/protobuf.h
@@ -91,7 +91,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_setter, 0, 0, 1)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
-#define PHP_PROTOBUF_VERSION "3.18.1"
+#define PHP_PROTOBUF_VERSION "3.18.2"
// ptr -> PHP object cache. This is a weak map that caches lazily-created
// wrapper objects around upb types:
diff --git a/protobuf_version.bzl b/protobuf_version.bzl
index 7f6d71460ca26..d215a862a8e16 100644
--- a/protobuf_version.bzl
+++ b/protobuf_version.bzl
@@ -1 +1 @@
-PROTOBUF_VERSION = '3.18.1'
+PROTOBUF_VERSION = '3.18.2'
diff --git a/protoc-artifacts/pom.xml b/protoc-artifacts/pom.xml
index c3aa37771f3ed..65e5ff6795594 100644
--- a/protoc-artifacts/pom.xml
+++ b/protoc-artifacts/pom.xml
@@ -8,7 +8,7 @@
com.google.protobufprotoc
- 3.18.1
+ 3.18.2pomProtobuf Compiler
diff --git a/python/google/protobuf/__init__.py b/python/google/protobuf/__init__.py
index b8122e82ae6fe..3813eeb966630 100644
--- a/python/google/protobuf/__init__.py
+++ b/python/google/protobuf/__init__.py
@@ -30,4 +30,4 @@
# Copyright 2007 Google Inc. All Rights Reserved.
-__version__ = '3.18.1'
+__version__ = '3.18.2'
diff --git a/ruby/google-protobuf.gemspec b/ruby/google-protobuf.gemspec
index 7a9e3f38b761e..1f2b385b56e3f 100644
--- a/ruby/google-protobuf.gemspec
+++ b/ruby/google-protobuf.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = "google-protobuf"
- s.version = "3.18.1"
+ s.version = "3.18.2"
git_tag = "v#{s.version.to_s.sub('.rc.', '-rc')}" # Converts X.Y.Z.rc.N to vX.Y.Z-rcN, used for the git tag
s.licenses = ["BSD-3-Clause"]
s.summary = "Protocol Buffers"
diff --git a/src/Makefile.am b/src/Makefile.am
index 3da92e75090be..e6b4d0fcaa15c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -18,7 +18,7 @@ else
PTHREAD_DEF =
endif
-PROTOBUF_VERSION = 29:1:0
+PROTOBUF_VERSION = 29:2:0
if GCC
# Turn on all warnings except for sign comparison (we ignore sign comparison
diff --git a/src/google/protobuf/any.pb.h b/src/google/protobuf/any.pb.h
index 13c5086b7f1ca..31d896fe143f0 100644
--- a/src/google/protobuf/any.pb.h
+++ b/src/google/protobuf/any.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/api.pb.h b/src/google/protobuf/api.pb.h
index 347a9f9bfaf21..e737a15c5d869 100644
--- a/src/google/protobuf/api.pb.h
+++ b/src/google/protobuf/api.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/compiler/plugin.pb.h b/src/google/protobuf/compiler/plugin.pb.h
index baa72a2d4413a..9ce6a77c0e8f8 100644
--- a/src/google/protobuf/compiler/plugin.pb.h
+++ b/src/google/protobuf/compiler/plugin.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/descriptor.pb.h b/src/google/protobuf/descriptor.pb.h
index 51261e4b4d523..b62091ea48851 100644
--- a/src/google/protobuf/descriptor.pb.h
+++ b/src/google/protobuf/descriptor.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/duration.pb.h b/src/google/protobuf/duration.pb.h
index 4e7d6adc97efa..3db6429dcdc57 100644
--- a/src/google/protobuf/duration.pb.h
+++ b/src/google/protobuf/duration.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/empty.pb.h b/src/google/protobuf/empty.pb.h
index 637ddf4d65202..e6f045945f9e9 100644
--- a/src/google/protobuf/empty.pb.h
+++ b/src/google/protobuf/empty.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/field_mask.pb.h b/src/google/protobuf/field_mask.pb.h
index 849f4f2f7b012..9ad4046e90b36 100644
--- a/src/google/protobuf/field_mask.pb.h
+++ b/src/google/protobuf/field_mask.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/port_def.inc b/src/google/protobuf/port_def.inc
index 01384c8e741f0..ee6d67c069d81 100644
--- a/src/google/protobuf/port_def.inc
+++ b/src/google/protobuf/port_def.inc
@@ -153,7 +153,7 @@
#ifdef PROTOBUF_VERSION
#error PROTOBUF_VERSION was previously defined
#endif
-#define PROTOBUF_VERSION 3018001
+#define PROTOBUF_VERSION 3018002
#ifdef PROTOBUF_MIN_HEADER_VERSION_FOR_PROTOC
#error PROTOBUF_MIN_HEADER_VERSION_FOR_PROTOC was previously defined
diff --git a/src/google/protobuf/source_context.pb.h b/src/google/protobuf/source_context.pb.h
index 13dce1c3fb783..102ea49cea89c 100644
--- a/src/google/protobuf/source_context.pb.h
+++ b/src/google/protobuf/source_context.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/struct.pb.h b/src/google/protobuf/struct.pb.h
index e84004b73c320..6f6798cc012c1 100644
--- a/src/google/protobuf/struct.pb.h
+++ b/src/google/protobuf/struct.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/stubs/common.h b/src/google/protobuf/stubs/common.h
index ed4e0613897c1..ad7dfe53a84ed 100644
--- a/src/google/protobuf/stubs/common.h
+++ b/src/google/protobuf/stubs/common.h
@@ -82,7 +82,7 @@ namespace internal {
// The current version, represented as a single integer to make comparison
// easier: major * 10^6 + minor * 10^3 + micro
-#define GOOGLE_PROTOBUF_VERSION 3018001
+#define GOOGLE_PROTOBUF_VERSION 3018002
// A suffix string for alpha, beta or rc releases. Empty for stable releases.
#define GOOGLE_PROTOBUF_VERSION_SUFFIX ""
diff --git a/src/google/protobuf/timestamp.pb.h b/src/google/protobuf/timestamp.pb.h
index f5c7f9882d936..e726578c03035 100644
--- a/src/google/protobuf/timestamp.pb.h
+++ b/src/google/protobuf/timestamp.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/type.pb.h b/src/google/protobuf/type.pb.h
index 4baf3760d036f..523370c7157dc 100644
--- a/src/google/protobuf/type.pb.h
+++ b/src/google/protobuf/type.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/wrappers.pb.h b/src/google/protobuf/wrappers.pb.h
index 5794557c21295..3e0c8ade9ed64 100644
--- a/src/google/protobuf/wrappers.pb.h
+++ b/src/google/protobuf/wrappers.pb.h
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3018001 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3018002 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.