diff --git a/java/BUILD.bazel b/java/BUILD.bazel
index 14838ae37..2a4bcb438 100644
--- a/java/BUILD.bazel
+++ b/java/BUILD.bazel
@@ -1,4 +1,4 @@
-load("@rules_pkg//:mappings.bzl", "pkg_filegroup", "pkg_files", "strip_prefix")
+load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files", "strip_prefix")
# Run Linkage Monitor
sh_test(
diff --git a/java/README.md b/java/README.md
index 6ed798b47..a6d9a6316 100644
--- a/java/README.md
+++ b/java/README.md
@@ -23,10 +23,14 @@ If you are using Maven, use the following:
com.google.protobufprotobuf-java
- 3.25.4
+
```
+And **replace `` with a version from the
+[Maven Protocol Buffers Repository](https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java).**
+For example, `3.25.3`.
+
Make sure the version number of the runtime matches (or is newer than) the
version number of the protoc.
@@ -37,17 +41,10 @@ protobuf-java-util package:
com.google.protobufprotobuf-java-util
- 3.25.4
+
```
-### Gradle
-
-If you are using Gradle, add the following to your `build.gradle` file's
-dependencies: `implementation 'com.google.protobuf:protobuf-java:3.25.4'` Again,
-be sure to check that the version number matches (or is newer than) the version
-number of protoc that you are using.
-
### Use Java Protocol Buffers on Android
For Android users, it's recommended to use protobuf Java Lite runtime because
@@ -70,8 +67,36 @@ If you are contributing code to protobuf or want to use a protobuf version
that hasn't been officially released yet, you can follow the instructions
below to build protobuf from source code.
+### Build from Source
+
+You may follow these instructions to build from source. This does not require
+Maven to be installed. Note that these instructions skip running unit tests and
+only describes how to install the core protobuf library (without the util
+package).
+
+1) Build the C++ code, or obtain a binary distribution of protoc (see
+ the toplevel [README.md](../README.md)). If you install a binary
+ distribution, make sure that it is the same version as this package.
+ If in doubt, run:
+
+ $ protoc --version
+
+ If you built the C++ code without installing, the compiler binary
+ should be located in ../src.
+
+2) Invoke protoc to build DescriptorProtos.java:
+
+ $ protoc --java_out=core/src/main/java -I../src \
+ ../src/google/protobuf/descriptor.proto
+
+3) Compile the code in core/src/main/java using whatever means you prefer.
+
+4) Install the classes wherever you prefer.
+
### Build from Source - With Maven
+WARNING: Building from source with Maven is deprecated and will be removed in the 4.28.x release.
+
1) Install Apache Maven if you don't have it:
http://maven.apache.org/
@@ -112,31 +137,6 @@ The above instructions will install 2 maven artifacts:
as well as utilities to work with proto3 well-known
types.
-### Build from Source - Without Maven
-
-If you would rather not install Maven to build the library, you may
-follow these instructions instead. Note that these instructions skip
-running unit tests and only describes how to install the core protobuf
-library (without the util package).
-
-1) Build the C++ code, or obtain a binary distribution of protoc. If
- you install a binary distribution, make sure that it is the same
- version as this package. If in doubt, run:
-
- $ protoc --version
-
- If you built the C++ code without installing, the compiler binary
- should be located in ../src.
-
-2) Invoke protoc to build DescriptorProtos.java:
-
- $ protoc --java_out=core/src/main/java -I../src \
- ../src/google/protobuf/descriptor.proto
-
-3) Compile the code in core/src/main/java using whatever means you prefer.
-
-4) Install the classes wherever you prefer.
-
## Compatibility Notice
* Protobuf minor version releases are backwards-compatible. If your code
diff --git a/java/bom/pom.xml b/java/bom/pom.xml
index 7d890239a..3fd0371f4 100644
--- a/java/bom/pom.xml
+++ b/java/bom/pom.xml
@@ -4,7 +4,7 @@
com.google.protobufprotobuf-bom
- 3.25.5
+ 4.28.3pomProtocol Buffers [BOM]
diff --git a/java/core/BUILD.bazel b/java/core/BUILD.bazel
index b896c426b..4e079e1b6 100644
--- a/java/core/BUILD.bazel
+++ b/java/core/BUILD.bazel
@@ -1,15 +1,18 @@
load("@bazel_skylib//rules:build_test.bzl", "build_test")
-load("@rules_java//java:defs.bzl", "java_lite_proto_library", "java_proto_library")
-load("@rules_pkg//:mappings.bzl", "pkg_files", "strip_prefix")
-load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain", "proto_library")
-load("//build_defs:java_opts.bzl", "protobuf_java_export", "protobuf_java_library", "protobuf_versioned_java_library")
-load("//conformance:defs.bzl", "conformance_test")
+load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix")
load("//:protobuf.bzl", "internal_gen_well_known_protos_java")
load("//:protobuf_version.bzl", "PROTOBUF_JAVA_VERSION")
+load("//bazel:java_lite_proto_library.bzl", "java_lite_proto_library")
+load("//bazel:java_proto_library.bzl", "java_proto_library")
+load("//bazel:proto_library.bzl", "proto_library")
+load("//bazel/toolchains:proto_lang_toolchain.bzl", "proto_lang_toolchain")
+load("//build_defs:java_opts.bzl", "protobuf_java_export", "protobuf_java_library", "protobuf_versioned_java_library")
+load("//conformance:defs.bzl", "conformance_test")
+load("//editions:defaults.bzl", "compile_edition_defaults", "embed_edition_defaults")
load("//java/internal:testing.bzl", "junit_tests")
+load("//upb/cmake:build_defs.bzl", "staleness_test")
LITE_SRCS = [
- # Keep in sync with `//java/lite:pom.xml`.
"src/main/java/com/google/protobuf/AbstractMessageLite.java",
"src/main/java/com/google/protobuf/AbstractParser.java",
"src/main/java/com/google/protobuf/AbstractProtobufList.java",
@@ -56,6 +59,8 @@ LITE_SRCS = [
"src/main/java/com/google/protobuf/LazyStringArrayList.java",
"src/main/java/com/google/protobuf/LazyStringList.java",
"src/main/java/com/google/protobuf/ListFieldSchema.java",
+ "src/main/java/com/google/protobuf/ListFieldSchemaLite.java",
+ "src/main/java/com/google/protobuf/ListFieldSchemas.java",
"src/main/java/com/google/protobuf/LongArrayList.java",
"src/main/java/com/google/protobuf/ManifestSchemaFactory.java",
"src/main/java/com/google/protobuf/MapEntryLite.java",
@@ -74,7 +79,6 @@ LITE_SRCS = [
"src/main/java/com/google/protobuf/NewInstanceSchema.java",
"src/main/java/com/google/protobuf/NewInstanceSchemaLite.java",
"src/main/java/com/google/protobuf/NewInstanceSchemas.java",
- "src/main/java/com/google/protobuf/NioByteString.java",
"src/main/java/com/google/protobuf/OneofInfo.java",
"src/main/java/com/google/protobuf/Parser.java",
"src/main/java/com/google/protobuf/PrimitiveNonBoxingCollection.java",
@@ -85,6 +89,7 @@ LITE_SRCS = [
"src/main/java/com/google/protobuf/RawMessageInfo.java",
"src/main/java/com/google/protobuf/Reader.java",
"src/main/java/com/google/protobuf/RopeByteString.java",
+ "src/main/java/com/google/protobuf/RuntimeVersion.java",
"src/main/java/com/google/protobuf/Schema.java",
"src/main/java/com/google/protobuf/SchemaFactory.java",
"src/main/java/com/google/protobuf/SchemaUtil.java",
@@ -103,15 +108,26 @@ LITE_SRCS = [
"src/main/java/com/google/protobuf/Writer.java",
]
+FULL_SRCS = glob(
+ [
+ "src/main/java/com/google/protobuf/*.java",
+ ],
+ exclude = LITE_SRCS,
+) + [
+ ":gen_well_known_protos_java",
+]
+
internal_gen_well_known_protos_java(
name = "gen_well_known_protos_javalite",
javalite = True,
deps = [
"//:any_proto",
"//:api_proto",
+ "//:descriptor_proto",
"//:duration_proto",
"//:empty_proto",
"//:field_mask_proto",
+ "//:java_features_proto",
"//:source_context_proto",
"//:struct_proto",
"//:timestamp_proto",
@@ -153,7 +169,9 @@ protobuf_java_export(
maven_coordinates = "com.google.protobuf:protobuf-javalite:%s" % PROTOBUF_JAVA_VERSION,
pom_template = "//java/lite:pom_template.xml",
resources = [
+ "//:java_features_proto",
"//:lite_well_known_protos",
+ "//src/google/protobuf:descriptor_proto_srcs",
],
tags = ["manual"],
runtime_deps = [":lite_bundle"],
@@ -166,11 +184,55 @@ protobuf_java_library(
proto_library(
name = "java_features_proto",
- srcs = ["src/main/java/com/google/protobuf/java_features.proto"],
- visibility = ["//pkg:__pkg__"],
+ srcs = ["src/main/resources/google/protobuf/java_features.proto"],
+ strip_import_prefix = "/java/core/src/main/resources",
+ visibility = [
+ "//:__pkg__",
+ "//editions:__pkg__",
+ "//java/__subpackages__",
+ "//pkg:__pkg__",
+ ],
deps = ["//:descriptor_proto"],
)
+cc_proto_library(
+ name = "java_features_cc_proto",
+ visibility = ["//editions:__pkg__"],
+ deps = [":java_features_proto"],
+)
+
+filegroup(
+ name = "java_features_proto_srcs",
+ srcs = ["src/main/resources/google/protobuf/java_features.proto"],
+ visibility = ["//pkg:__pkg__"],
+)
+
+compile_edition_defaults(
+ name = "java_edition_defaults",
+ srcs = [
+ "//:descriptor_proto",
+ "//:java_features_proto",
+ ],
+ maximum_edition = "2023",
+ minimum_edition = "PROTO2",
+)
+
+embed_edition_defaults(
+ name = "embedded_java_edition_defaults_generate",
+ defaults = "java_edition_defaults",
+ output = "generated/src/main/java/com/google/protobuf/JavaEditionDefaults.java",
+ placeholder = "DEFAULTS_VALUE",
+ template = "src/main/java/com/google/protobuf/JavaEditionDefaults.java.template",
+)
+
+staleness_test(
+ name = "generated_java_defaults_staleness_test",
+ outs = ["src/main/java/com/google/protobuf/JavaEditionDefaults.java"],
+ generated_pattern = "generated/%s",
+ tags = ["manual"],
+ target_files = ["src/main/java/com/google/protobuf/JavaEditionDefaults.java"],
+)
+
internal_gen_well_known_protos_java(
name = "gen_well_known_protos_java",
deps = [
@@ -181,6 +243,7 @@ internal_gen_well_known_protos_java(
"//:duration_proto",
"//:empty_proto",
"//:field_mask_proto",
+ "//:java_features_proto",
"//:source_context_proto",
"//:struct_proto",
"//:timestamp_proto",
@@ -191,14 +254,7 @@ internal_gen_well_known_protos_java(
java_library(
name = "core",
- srcs = glob(
- [
- "src/main/java/com/google/protobuf/*.java",
- ],
- exclude = LITE_SRCS,
- ) + [
- ":gen_well_known_protos_java",
- ],
+ srcs = FULL_SRCS,
visibility = ["//visibility:public"],
exports = [
":lite_runtime_only",
@@ -210,14 +266,7 @@ java_library(
protobuf_versioned_java_library(
name = "core_bundle",
- srcs = glob(
- [
- "src/main/java/com/google/protobuf/*.java",
- ],
- exclude = LITE_SRCS,
- ) + [
- ":gen_well_known_protos_java",
- ],
+ srcs = FULL_SRCS,
automatic_module_name = "com.google.protobuf",
bundle_description = "Core Protocol Buffers library. Protocol Buffers " +
"are a way of encoding structured data in an " +
@@ -239,6 +288,7 @@ protobuf_java_export(
maven_coordinates = "com.google.protobuf:protobuf-java:%s" % PROTOBUF_JAVA_VERSION,
pom_template = "pom_template.xml",
resources = [
+ ":java_features_proto_srcs",
"//:well_known_type_protos",
"//src/google/protobuf:descriptor_proto_srcs",
],
@@ -265,6 +315,7 @@ proto_lang_toolchain(
name = "toolchain",
# keep this in sync w/ WELL_KNOWN_PROTO_MAP in //:BUILD
blacklisted_protos = [
+ "//:java_features_proto",
"//:any_proto",
"//:api_proto",
"//:compiler_plugin_proto",
@@ -290,6 +341,7 @@ proto_library(
deps = [
"//:any_proto",
"//:descriptor_proto",
+ "//:java_features_proto",
"//:lite_test_protos",
"//:wrappers_proto",
"//src/google/protobuf:generic_test_protos",
@@ -304,6 +356,14 @@ java_proto_library(
deps = ["//src/google/protobuf:generic_test_protos"],
)
+java_proto_library(
+ name = "generic_test_protos_editions_java_proto",
+ visibility = [
+ "//java:__subpackages__",
+ ],
+ deps = ["//src/google/protobuf:generic_test_editions_protos"],
+)
+
java_proto_library(
name = "lite_test_protos_java_proto",
visibility = [
@@ -354,6 +414,7 @@ build_test(
conformance_test(
name = "conformance_test",
failure_list = "//conformance:failure_list_java.txt",
+ maximum_edition = "2023",
testee = "//conformance:conformance_java",
text_format_failure_list = "//conformance:text_format_failure_list_java.txt",
)
@@ -368,11 +429,13 @@ junit_tests(
"src/test/java/com/google/protobuf/IsValidUtf8Test.java",
"src/test/java/com/google/protobuf/TestUtil.java",
"src/test/java/com/google/protobuf/TestUtilLite.java",
+ "src/test/java/com/google/protobuf/RuntimeVersionTest.java",
],
),
data = ["//src/google/protobuf:testdata"],
deps = [
":core",
+ ":generic_test_protos_editions_java_proto",
":generic_test_protos_java_proto",
":java_test_protos_java_proto",
":lite_test_protos_java_proto",
@@ -459,12 +522,12 @@ protobuf_java_library(
)
LITE_TEST_EXCLUSIONS = [
- # Keep in sync with //java/lite:pom.xml id=copy-test-source-files execution.
"src/test/java/com/google/protobuf/AbstractMessageTest.java",
"src/test/java/com/google/protobuf/AbstractProto2SchemaTest.java",
"src/test/java/com/google/protobuf/AnyTest.java",
"src/test/java/com/google/protobuf/CodedInputStreamTest.java",
"src/test/java/com/google/protobuf/DeprecatedFieldTest.java",
+ "src/test/java/com/google/protobuf/DebugFormatTest.java",
"src/test/java/com/google/protobuf/DescriptorsTest.java",
"src/test/java/com/google/protobuf/DiscardUnknownFieldsTest.java",
"src/test/java/com/google/protobuf/DynamicMessageTest.java",
@@ -472,8 +535,10 @@ LITE_TEST_EXCLUSIONS = [
"src/test/java/com/google/protobuf/FieldPresenceTest.java",
"src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java",
"src/test/java/com/google/protobuf/GeneratedMessageTest.java",
+ "src/test/java/com/google/protobuf/LazilyParsedMessageSetTest.java",
"src/test/java/com/google/protobuf/LazyFieldTest.java",
"src/test/java/com/google/protobuf/LazyStringEndToEndTest.java",
+ "src/test/java/com/google/protobuf/LegacyUnredactedTextFormatTest.java",
"src/test/java/com/google/protobuf/MapForProto2Test.java",
"src/test/java/com/google/protobuf/MapTest.java",
"src/test/java/com/google/protobuf/MessageTest.java",
@@ -484,9 +549,9 @@ LITE_TEST_EXCLUSIONS = [
"src/test/java/com/google/protobuf/Proto2ExtensionLookupSchemaTest.java",
"src/test/java/com/google/protobuf/Proto2SchemaTest.java",
"src/test/java/com/google/protobuf/Proto2UnknownEnumValueTest.java",
- "src/test/java/com/google/protobuf/RepeatedFieldBuilderV3Test.java",
+ "src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java",
"src/test/java/com/google/protobuf/ServiceTest.java",
- "src/test/java/com/google/protobuf/SingleFieldBuilderV3Test.java",
+ "src/test/java/com/google/protobuf/SingleFieldBuilderTest.java",
"src/test/java/com/google/protobuf/TestBadIdentifiers.java",
"src/test/java/com/google/protobuf/TextFormatParseInfoTreeTest.java",
"src/test/java/com/google/protobuf/TextFormatParseLocationTest.java",
@@ -524,17 +589,115 @@ junit_tests(
],
)
+protobuf_java_library(
+ name = "v25_test_util_srcjar",
+ testonly = True,
+ srcs = [
+ "src/test/java/com/google/protobuf/TestUtil.java",
+ "src/test/java/com/google/protobuf/TestUtilLite.java",
+ ],
+ deps = [
+ ":core",
+ "//compatibility:v25_test_protos_srcjar",
+ "@maven//:com_google_guava_guava",
+ "@maven//:junit_junit",
+ ],
+)
+
+# Tests source compatibility against v25 gencode jar compiled against current runtime
+junit_tests(
+ name = "v25_core_tests_srcjar",
+ size = "small",
+ srcs = glob(
+ ["src/test/java/**/*.java"],
+ exclude = [
+ # Depends on test protos or API changes added in v4.x.x (e.g. editions)
+ "src/test/java/com/google/protobuf/TextFormatTest.java",
+ "src/test/java/com/google/protobuf/DescriptorsTest.java",
+ "src/test/java/com/google/protobuf/DebugFormatTest.java",
+ "src/test/java/com/google/protobuf/CodedOutputStreamTest.java",
+ "src/test/java/com/google/protobuf/CodedInputStreamTest.java",
+ "src/test/java/com/google/protobuf/ProtobufToStringOutputTest.java",
+ # Excluded in core_tests
+ "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",
+ "src/test/java/com/google/protobuf/RuntimeVersionTest.java",
+ ],
+ ),
+ test_prefix = "v25SrcJar",
+ deps = [
+ ":core",
+ ":v25_test_util_srcjar",
+ "//compatibility:v25_test_protos_srcjar",
+ "@maven//:com_google_guava_guava",
+ "@maven//:com_google_truth_truth",
+ "@maven//:junit_junit",
+ "@maven//:org_mockito_mockito_core",
+ ],
+)
+
+protobuf_java_library(
+ name = "v25_test_util_jar",
+ testonly = True,
+ srcs = [
+ "src/test/java/com/google/protobuf/TestUtil.java",
+ "src/test/java/com/google/protobuf/TestUtilLite.java",
+ ],
+ deps = [
+ ":core",
+ "//compatibility:v25_test_protos_jar",
+ "@maven//:com_google_guava_guava",
+ "@maven//:junit_junit",
+ ],
+)
+
+# Tests binary compatibility against v25 gencode ja compiled against v25 runtime
+junit_tests(
+ name = "v25_core_tests_jar",
+ size = "small",
+ srcs = glob(
+ ["src/test/java/**/*.java"],
+ exclude = [
+ # Depends on test protos or API changes added in v4.x.x (e.g. editions)
+ "src/test/java/com/google/protobuf/TextFormatTest.java",
+ "src/test/java/com/google/protobuf/DescriptorsTest.java",
+ "src/test/java/com/google/protobuf/DebugFormatTest.java",
+ "src/test/java/com/google/protobuf/CodedOutputStreamTest.java",
+ "src/test/java/com/google/protobuf/CodedInputStreamTest.java",
+ "src/test/java/com/google/protobuf/ProtobufToStringOutputTest.java",
+ # Excluded in core_tests
+ "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",
+ "src/test/java/com/google/protobuf/RuntimeVersionTest.java",
+ ],
+ ),
+ test_prefix = "v25Jar",
+ deps = [
+ ":core",
+ ":v25_test_util_jar",
+ "//compatibility:v25_test_protos_jar",
+ "@maven//:com_google_guava_guava",
+ "@maven//:com_google_truth_truth",
+ "@maven//:junit_junit",
+ "@maven//:org_mockito_mockito_core",
+ ],
+)
+
pkg_files(
name = "dist_files",
srcs = glob([
"src/main/java/com/google/protobuf/*.java",
+ "src/main/resources/google/protobuf/*.proto",
"src/test/java/**/*.java",
"src/test/proto/**/*.proto",
]) + [
"BUILD.bazel",
"generate-sources-build.xml",
"generate-test-sources-build.xml",
- "pom.xml",
"pom_template.xml",
],
strip_prefix = strip_prefix.from_root(""),
diff --git a/java/core/generate-sources-build.xml b/java/core/generate-sources-build.xml
index 0996e5fff..7021aa8e2 100644
--- a/java/core/generate-sources-build.xml
+++ b/java/core/generate-sources-build.xml
@@ -4,6 +4,8 @@
+
+
diff --git a/java/core/generate-test-sources-build.xml b/java/core/generate-test-sources-build.xml
index 68edf0bfc..66e841561 100644
--- a/java/core/generate-test-sources-build.xml
+++ b/java/core/generate-test-sources-build.xml
@@ -6,6 +6,7 @@
+
@@ -18,7 +19,10 @@
+
+
+
diff --git a/java/core/pom.xml b/java/core/pom.xml
deleted file mode 100644
index 644a4a7bc..000000000
--- a/java/core/pom.xml
+++ /dev/null
@@ -1,158 +0,0 @@
-
-
- 4.0.0
-
- com.google.protobuf
- protobuf-parent
- 3.25.5
-
-
- protobuf-java
- bundle
-
- Protocol Buffers [Core]
-
- Core Protocol Buffers library. Protocol Buffers are a way of encoding structured data in an
- efficient yet extensible format.
-
-
-
-
- junit
- junit
- test
-
-
- org.mockito
- mockito-core
- test
-
-
- com.google.guava
- guava
- test
-
-
- com.google.truth
- truth
- test
-
-
-
-
-
-
-
- ${protobuf.source.dir}
-
- google/protobuf/any.proto
- google/protobuf/api.proto
- google/protobuf/descriptor.proto
- google/protobuf/duration.proto
- google/protobuf/empty.proto
- google/protobuf/field_mask.proto
- google/protobuf/source_context.proto
- google/protobuf/struct.proto
- google/protobuf/timestamp.proto
- google/protobuf/type.proto
- google/protobuf/wrappers.proto
- google/protobuf/compiler/plugin.proto
-
-
-
-
-
- ${protobuf.source.dir}
-
- google/protobuf/testdata/golden_message_oneof_implemented
- google/protobuf/testdata/golden_packed_fields_message
-
-
-
-
-
-
-
- maven-antrun-plugin
-
-
-
- generate-sources
- generate-sources
-
-
-
-
-
-
- run
-
-
-
-
-
- generate-test-sources
- generate-test-sources
-
-
-
-
-
-
- run
-
-
-
-
-
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
-
-
- add-generated-sources
- generate-sources
-
- add-source
-
-
-
- ${generated.sources.dir}
-
-
-
-
- add-generated-test-sources
- generate-test-sources
-
- add-test-source
-
-
-
- ${generated.testsources.dir}
-
-
-
-
-
-
-
-
- org.apache.felix
- maven-bundle-plugin
- true
-
-
- com.google.protobuf
- https://developers.google.com/protocol-buffers/
- com.google.protobuf
- com.google.protobuf;version=${project.version}
- sun.misc;resolution:=optional,*
-
-
-
-
-
-
-
diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessage.java b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java
index f383625b2..30eccc1ec 100644
--- a/java/core/src/main/java/com/google/protobuf/AbstractMessage.java
+++ b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java
@@ -538,44 +538,4 @@ public abstract class AbstractMessage
return (BuilderType) super.mergeFrom(input, extensionRegistry);
}
}
-
- /**
- * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
- * generated code.
- */
- @Deprecated
- protected static int hashLong(long n) {
- return (int) (n ^ (n >>> 32));
- }
-
- /**
- * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
- * generated code.
- */
- @Deprecated
- protected static int hashBoolean(boolean b) {
- return b ? 1231 : 1237;
- }
-
- /**
- * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
- * generated code.
- */
- @Deprecated
- protected static int hashEnum(EnumLite e) {
- return e.getNumber();
- }
-
- /**
- * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
- * generated code.
- */
- @Deprecated
- protected static int hashEnumList(List extends EnumLite> list) {
- int hash = 1;
- for (EnumLite e : list) {
- hash = 31 * hash + hashEnum(e);
- }
- return hash;
- }
}
diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java
index 6a9d7bacf..dde30c1b7 100644
--- a/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java
+++ b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java
@@ -113,12 +113,6 @@ public abstract class AbstractMessageLite<
}
}
- // For binary compatibility
- @Deprecated
- protected static void addAll(final Iterable values, final Collection super T> list) {
- Builder.addAll(values, (List) list);
- }
-
protected static void addAll(final Iterable values, final List super T> list) {
Builder.addAll(values, list);
}
@@ -403,6 +397,8 @@ public abstract class AbstractMessageLite<
}
if (value instanceof ByteString) {
lazyList.add((ByteString) value);
+ } else if (value instanceof byte[]) {
+ lazyList.add(ByteString.copyFrom((byte[]) value));
} else {
lazyList.add((String) value);
}
diff --git a/java/core/src/main/java/com/google/protobuf/ByteString.java b/java/core/src/main/java/com/google/protobuf/ByteString.java
index 8ba729c8e..7b2455f12 100644
--- a/java/core/src/main/java/com/google/protobuf/ByteString.java
+++ b/java/core/src/main/java/com/google/protobuf/ByteString.java
@@ -7,6 +7,7 @@
package com.google.protobuf;
+import static com.google.protobuf.Internal.checkNotNull;
import static com.google.protobuf.TextFormatEscaper.escapeBytes;
import static java.lang.Integer.toHexString;
import static java.lang.System.identityHashCode;
@@ -21,6 +22,8 @@ import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.InvalidMarkException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
@@ -422,6 +425,11 @@ public abstract class ByteString implements Iterable, Serializable {
}
}
+ // For use in tests
+ static ByteString nioByteString(ByteBuffer buffer) {
+ return new NioByteString(buffer);
+ }
+
/**
* Wraps the given bytes into a {@code ByteString}. Intended for internal usage within the library
* to force a classload of ByteString before LiteralByteString.
@@ -953,6 +961,8 @@ public abstract class ByteString implements Iterable, Serializable {
* @return true for equality of substrings, else false.
*/
abstract boolean equalsRange(ByteString other, int offset, int length);
+
+ private LeafByteString() {}
}
/**
@@ -1662,4 +1672,254 @@ public abstract class ByteString implements Iterable, Serializable {
"BoundedByteStream instances are not to be serialized directly");
}
}
+
+ /** A {@link ByteString} that wraps around a {@link ByteBuffer}. */
+ // Keep this class private to avoid deadlocks in classloading across threads as ByteString's
+ // static initializer loads LiteralByteString and another thread loads BoundedByteString.
+ private static final class NioByteString extends ByteString.LeafByteString {
+ private final ByteBuffer buffer;
+
+ NioByteString(ByteBuffer buffer) {
+ checkNotNull(buffer, "buffer");
+
+ // Use native byte order for fast fixed32/64 operations.
+ this.buffer = buffer.slice().order(ByteOrder.nativeOrder());
+ }
+
+ // =================================================================
+ // Serializable
+
+ /** Magic method that lets us override serialization behavior. */
+ private Object writeReplace() {
+ return ByteString.copyFrom(buffer.slice());
+ }
+
+ /** Magic method that lets us override deserialization behavior. */
+ private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException {
+ throw new InvalidObjectException("NioByteString instances are not to be serialized directly");
+ }
+
+ // =================================================================
+
+ @Override
+ public byte byteAt(int index) {
+ try {
+ return buffer.get(index);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw e;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ArrayIndexOutOfBoundsException(e.getMessage());
+ }
+ }
+
+ @Override
+ public byte internalByteAt(int index) {
+ // it isn't possible to avoid the bounds checking inside of ByteBuffer, so just use the
+ // default
+ // implementation.
+ return byteAt(index);
+ }
+
+ @Override
+ public int size() {
+ return buffer.remaining();
+ }
+
+ @Override
+ public ByteString substring(int beginIndex, int endIndex) {
+ try {
+ ByteBuffer slice = slice(beginIndex, endIndex);
+ return new NioByteString(slice);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw e;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ArrayIndexOutOfBoundsException(e.getMessage());
+ }
+ }
+
+ @Override
+ protected void copyToInternal(
+ byte[] target, int sourceOffset, int targetOffset, int numberToCopy) {
+ ByteBuffer slice = buffer.slice();
+ Java8Compatibility.position(slice, sourceOffset);
+ slice.get(target, targetOffset, numberToCopy);
+ }
+
+ @Override
+ public void copyTo(ByteBuffer target) {
+ target.put(buffer.slice());
+ }
+
+ @Override
+ public void writeTo(OutputStream out) throws IOException {
+ out.write(toByteArray());
+ }
+
+ @Override
+ boolean equalsRange(ByteString other, int offset, int length) {
+ return substring(0, length).equals(other.substring(offset, offset + length));
+ }
+
+ @Override
+ void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite) throws IOException {
+ if (buffer.hasArray()) {
+ // Optimized write for array-backed buffers.
+ // Note that we're taking the risk that a malicious OutputStream could modify the array.
+ int bufferOffset = buffer.arrayOffset() + buffer.position() + sourceOffset;
+ out.write(buffer.array(), bufferOffset, numberToWrite);
+ return;
+ }
+
+ ByteBufferWriter.write(slice(sourceOffset, sourceOffset + numberToWrite), out);
+ }
+
+ @Override
+ void writeTo(ByteOutput output) throws IOException {
+ output.writeLazy(buffer.slice());
+ }
+
+ @Override
+ public ByteBuffer asReadOnlyByteBuffer() {
+ return buffer.asReadOnlyBuffer();
+ }
+
+ @Override
+ public List asReadOnlyByteBufferList() {
+ return Collections.singletonList(asReadOnlyByteBuffer());
+ }
+
+ @Override
+ protected String toStringInternal(Charset charset) {
+ final byte[] bytes;
+ final int offset;
+ final int length;
+ if (buffer.hasArray()) {
+ bytes = buffer.array();
+ offset = buffer.arrayOffset() + buffer.position();
+ length = buffer.remaining();
+ } else {
+ // TODO: Can we optimize this?
+ bytes = toByteArray();
+ offset = 0;
+ length = bytes.length;
+ }
+ return new String(bytes, offset, length, charset);
+ }
+
+ @Override
+ public boolean isValidUtf8() {
+ return Utf8.isValidUtf8(buffer);
+ }
+
+ @Override
+ protected int partialIsValidUtf8(int state, int offset, int length) {
+ return Utf8.partialIsValidUtf8(state, buffer, offset, offset + length);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof ByteString)) {
+ return false;
+ }
+ ByteString otherString = ((ByteString) other);
+ if (size() != otherString.size()) {
+ return false;
+ }
+ if (size() == 0) {
+ return true;
+ }
+ if (other instanceof NioByteString) {
+ return buffer.equals(((NioByteString) other).buffer);
+ }
+ if (other instanceof RopeByteString) {
+ return other.equals(this);
+ }
+ return buffer.equals(otherString.asReadOnlyByteBuffer());
+ }
+
+ @Override
+ protected int partialHash(int h, int offset, int length) {
+ for (int i = offset; i < offset + length; i++) {
+ h = h * 31 + buffer.get(i);
+ }
+ return h;
+ }
+
+ @Override
+ public InputStream newInput() {
+ return new InputStream() {
+ private final ByteBuffer buf = buffer.slice();
+
+ @Override
+ public void mark(int readlimit) {
+ Java8Compatibility.mark(buf);
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public void reset() throws IOException {
+ try {
+ Java8Compatibility.reset(buf);
+ } catch (InvalidMarkException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ return buf.remaining();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!buf.hasRemaining()) {
+ return -1;
+ }
+ return buf.get() & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ if (!buf.hasRemaining()) {
+ return -1;
+ }
+
+ len = Math.min(len, buf.remaining());
+ buf.get(bytes, off, len);
+ return len;
+ }
+ };
+ }
+
+ @Override
+ public CodedInputStream newCodedInput() {
+ return CodedInputStream.newInstance(buffer, true);
+ }
+
+ /**
+ * Creates a slice of a range of this buffer.
+ *
+ * @param beginIndex the beginning index of the slice (inclusive).
+ * @param endIndex the end index of the slice (exclusive).
+ * @return the requested slice.
+ */
+ private ByteBuffer slice(int beginIndex, int endIndex) {
+ if (beginIndex < buffer.position() || endIndex > buffer.limit() || beginIndex > endIndex) {
+ throw new IllegalArgumentException(
+ String.format("Invalid indices [%d, %d]", beginIndex, endIndex));
+ }
+
+ ByteBuffer slice = buffer.slice();
+ Java8Compatibility.position(slice, beginIndex - buffer.position());
+ Java8Compatibility.limit(slice, endIndex - buffer.position());
+ return slice;
+ }
+ }
}
diff --git a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java
index fd2991356..85b7a5bca 100644
--- a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java
+++ b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java
@@ -177,6 +177,7 @@ public abstract class CodedInputStream {
throw InvalidProtocolBufferException.recursionLimitExceeded();
}
}
+
/** Disable construction/inheritance outside of this class. */
private CodedInputStream() {}
@@ -1993,6 +1994,7 @@ public abstract class CodedInputStream {
private static final class StreamDecoder extends CodedInputStream {
private final InputStream input;
private final byte[] buffer;
+
/** bufferSize represents how many bytes are currently filled in the buffer */
private int bufferSize;
@@ -2407,7 +2409,7 @@ public abstract class CodedInputStream {
throw InvalidProtocolBufferException.negativeSize();
}
// Slow path: Build a byte array first then copy it.
-
+
// We must copy as the byte array was handed off to the InputStream and a malicious
// implementation could retain a reference.
return ByteBuffer.wrap(readRawBytesSlowPath(size, /* ensureNoLeakedReferences= */ true));
@@ -2656,6 +2658,9 @@ public abstract class CodedInputStream {
throw InvalidProtocolBufferException.negativeSize();
}
byteLimit += totalBytesRetired + pos;
+ if (byteLimit < 0) {
+ throw InvalidProtocolBufferException.parseFailure();
+ }
final int oldLimit = currentLimit;
if (byteLimit > oldLimit) {
throw InvalidProtocolBufferException.truncatedMessage();
@@ -2822,12 +2827,12 @@ public abstract class CodedInputStream {
/**
* Exactly like readRawBytes, but caller must have already checked the fast path: (size <=
* (bufferSize - pos) && size > 0)
- *
- * If ensureNoLeakedReferences is true, the value is guaranteed to have not escaped to
+ *
+ *
If ensureNoLeakedReferences is true, the value is guaranteed to have not escaped to
* untrusted code.
*/
- private byte[] readRawBytesSlowPath(
- final int size, boolean ensureNoLeakedReferences) throws IOException {
+ private byte[] readRawBytesSlowPath(final int size, boolean ensureNoLeakedReferences)
+ throws IOException {
// Attempt to read the data in one byte array when it's safe to do.
byte[] result = readRawBytesSlowPathOneChunk(size);
if (result != null) {
@@ -2927,8 +2932,8 @@ public abstract class CodedInputStream {
/**
* Reads the remaining data in small chunks from the input stream.
- *
- * Returns a byte[] that may have escaped to user code via InputStream APIs.
+ *
+ *
Returns a byte[] that may have escaped to user code via InputStream APIs.
*/
private List readRawBytesSlowPathRemainingChunks(int sizeLeft) throws IOException {
// The size is very large. For security reasons, we can't allocate the
@@ -2998,7 +3003,7 @@ public abstract class CodedInputStream {
System.arraycopy(chunk, 0, bytes, tempPos, chunk.length);
tempPos += chunk.length;
}
-
+
return ByteString.wrap(bytes);
}
@@ -3086,41 +3091,54 @@ public abstract class CodedInputStream {
private static final class IterableDirectByteBufferDecoder extends CodedInputStream {
/** The object that need to decode. */
private final Iterable input;
+
/** The {@link Iterator} with type {@link ByteBuffer} of {@code input} */
private final Iterator iterator;
+
/** The current ByteBuffer; */
private ByteBuffer currentByteBuffer;
+
/**
* If {@code true}, indicates that all the buffers are backing a {@link ByteString} and are
* therefore considered to be an immutable input source.
*/
private final boolean immutable;
+
/**
* If {@code true}, indicates that calls to read {@link ByteString} or {@code byte[]}
* may return slices of the underlying buffer, rather than copies.
*/
private boolean enableAliasing;
+
/** The global total message length limit */
private int totalBufferSize;
+
/** The amount of available data in the input beyond {@link #currentLimit}. */
private int bufferSizeAfterCurrentLimit;
+
/** The absolute position of the end of the current message. */
private int currentLimit = Integer.MAX_VALUE;
+
/** The last tag that was read from this stream. */
private int lastTag;
+
/** Total Bytes have been Read from the {@link Iterable} {@link ByteBuffer} */
private int totalBytesRead;
+
/** The start position offset of the whole message, used as to reset the totalBytesRead */
private int startOffset;
+
/** The current position for current ByteBuffer */
private long currentByteBufferPos;
private long currentByteBufferStartPos;
+
/**
* If the current ByteBuffer is unsafe-direct based, currentAddress is the start address of this
* ByteBuffer; otherwise should be zero.
*/
private long currentAddress;
+
/** The limit position for current ByteBuffer */
private long currentByteBufferLimit;
diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
index 37bb44a05..2c28536f4 100644
--- a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
+++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
@@ -40,7 +40,9 @@ public abstract class CodedOutputStream extends ByteOutput {
/** Used to adapt to the experimental {@link Writer} interface. */
CodedOutputStreamWriter wrapper;
- /** @deprecated Use {@link #computeFixed32SizeNoTag(int)} instead. */
+ /**
+ * @deprecated Use {@link #computeFixed32SizeNoTag(int)} instead.
+ */
@Deprecated public static final int LITTLE_ENDIAN_32_SIZE = FIXED32_SIZE;
/** The buffer size used in {@link #newInstance(OutputStream)}. */
@@ -669,9 +671,8 @@ public abstract class CodedOutputStream extends ByteOutput {
}
/**
- * Compute the number of bytes that would be needed to encode a lazily parsed MessageSet
- * extension field to the stream. For historical reasons, the wire format differs from normal
- * fields.
+ * Compute the number of bytes that would be needed to encode a lazily parsed MessageSet extension
+ * field to the stream. For historical reasons, the wire format differs from normal fields.
*/
public static int computeLazyFieldMessageSetExtensionSize(
final int fieldNumber, final LazyFieldLite value) {
@@ -692,29 +693,52 @@ public abstract class CodedOutputStream extends ByteOutput {
* tag.
*/
public static int computeInt32SizeNoTag(final int value) {
- if (value >= 0) {
- return computeUInt32SizeNoTag(value);
- } else {
- // Must sign-extend.
- return MAX_VARINT_SIZE;
- }
+ return computeUInt64SizeNoTag((long) value);
}
/** Compute the number of bytes that would be needed to encode a {@code uint32} field. */
public static int computeUInt32SizeNoTag(final int value) {
- if ((value & (~0 << 7)) == 0) {
- return 1;
- }
- if ((value & (~0 << 14)) == 0) {
- return 2;
- }
- if ((value & (~0 << 21)) == 0) {
- return 3;
- }
- if ((value & (~0 << 28)) == 0) {
- return 4;
- }
- return 5;
+ /*
+ This code is ported from the C++ varint implementation.
+ Implementation notes:
+
+ To calcuate varint size, we want to count the number of 7 bit chunks required. Rather than using
+ division by 7 to accomplish this, we use multiplication by 9/64. This has a number of important
+ properties:
+ * It's roughly 1/7.111111. This makes the 0 bits set case have the same value as the 7 bits set
+ case, so offsetting by 1 gives us the correct value we want for integers up to 448 bits.
+ * Multiplying by 9 is special. x * 9 = x << 3 + x, and so this multiplication can be done by a
+ single shifted add on arm (add w0, w0, w0, lsl #3), or a single lea instruction
+ (leal (%rax,%rax,8), %eax)) on x86.
+ * Dividing by 64 is a 6 bit right shift.
+
+ An explicit non-sign-extended right shift is used instead of the more obvious '/ 64' because
+ that actually produces worse code on android arm64 at time of authoring because of sign
+ extension. Rather than
+ lsr w0, w0, #6
+ It would emit:
+ add w16, w0, #0x3f (63)
+ cmp w0, #0x0 (0)
+ csel w0, w16, w0, lt
+ asr w0, w0, #6
+
+ Summarized:
+ floor(((Integer.SIZE - clz) / 7.1111) + 1
+ ((Integer.SIZE - clz) * 9) / 64 + 1
+ (((Integer.SIZE - clz) * 9) >>> 6) + 1
+ ((Integer.SIZE - clz) * 9 + (1 << 6)) >>> 6
+ (Integer.SIZE * 9 + (1 << 6) - clz * 9) >>> 6
+ (352 - clz * 9) >>> 6
+ on arm:
+ (352 - clz - (clz << 3)) >>> 6
+ on x86:
+ (352 - lea(clz, clz, 8)) >>> 6
+
+ If you make changes here, please validate their compiled output on different architectures and
+ runtimes.
+ */
+ int clz = Integer.numberOfLeadingZeros(value);
+ return ((Integer.SIZE * 9 + (1 << 6)) - (clz * 9)) >>> 6;
}
/** Compute the number of bytes that would be needed to encode an {@code sint32} field. */
@@ -745,27 +769,9 @@ public abstract class CodedOutputStream extends ByteOutput {
* tag.
*/
public static int computeUInt64SizeNoTag(long value) {
- // handle two popular special cases up front ...
- if ((value & (~0L << 7)) == 0L) {
- return 1;
- }
- if (value < 0L) {
- return 10;
- }
- // ... leaving us with 8 remaining, which we can divide and conquer
- int n = 2;
- if ((value & (~0L << 35)) != 0L) {
- n += 4;
- value >>>= 28;
- }
- if ((value & (~0L << 21)) != 0L) {
- n += 2;
- value >>>= 14;
- }
- if ((value & (~0L << 14)) != 0L) {
- n += 1;
- }
- return n;
+ int clz = Long.numberOfLeadingZeros(value);
+ // See computeUInt32SizeNoTag for explanation
+ return ((Long.SIZE * 9 + (1 << 6)) - (clz * 9)) >>> 6;
}
/** Compute the number of bytes that would be needed to encode an {@code sint64} field. */
@@ -1326,7 +1332,7 @@ public abstract class CodedOutputStream extends ByteOutput {
buffer[position++] = (byte) value;
return;
} else {
- buffer[position++] = (byte) ((value & 0x7F) | 0x80);
+ buffer[position++] = (byte) ((value | 0x80) & 0xFF);
value >>>= 7;
}
}
@@ -1357,7 +1363,7 @@ public abstract class CodedOutputStream extends ByteOutput {
UnsafeUtil.putByte(buffer, position++, (byte) value);
return;
} else {
- UnsafeUtil.putByte(buffer, position++, (byte) (((int) value & 0x7F) | 0x80));
+ UnsafeUtil.putByte(buffer, position++, (byte) (((int) value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -1368,7 +1374,7 @@ public abstract class CodedOutputStream extends ByteOutput {
buffer[position++] = (byte) value;
return;
} else {
- buffer[position++] = (byte) (((int) value & 0x7F) | 0x80);
+ buffer[position++] = (byte) (((int) value | 0x80) & 0xFF);
value >>>= 7;
}
}
@@ -1684,7 +1690,7 @@ public abstract class CodedOutputStream extends ByteOutput {
buffer.put((byte) value);
return;
} else {
- buffer.put((byte) ((value & 0x7F) | 0x80));
+ buffer.put((byte) ((value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -1710,7 +1716,7 @@ public abstract class CodedOutputStream extends ByteOutput {
buffer.put((byte) value);
return;
} else {
- buffer.put((byte) (((int) value & 0x7F) | 0x80));
+ buffer.put((byte) (((int) value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -2015,7 +2021,7 @@ public abstract class CodedOutputStream extends ByteOutput {
UnsafeUtil.putByte(position++, (byte) value);
return;
} else {
- UnsafeUtil.putByte(position++, (byte) ((value & 0x7F) | 0x80));
+ UnsafeUtil.putByte(position++, (byte) ((value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -2025,7 +2031,7 @@ public abstract class CodedOutputStream extends ByteOutput {
UnsafeUtil.putByte(position++, (byte) value);
return;
} else {
- UnsafeUtil.putByte(position++, (byte) ((value & 0x7F) | 0x80));
+ UnsafeUtil.putByte(position++, (byte) ((value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -2049,7 +2055,7 @@ public abstract class CodedOutputStream extends ByteOutput {
UnsafeUtil.putByte(position++, (byte) value);
return;
} else {
- UnsafeUtil.putByte(position++, (byte) (((int) value & 0x7F) | 0x80));
+ UnsafeUtil.putByte(position++, (byte) (((int) value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -2059,7 +2065,7 @@ public abstract class CodedOutputStream extends ByteOutput {
UnsafeUtil.putByte(position++, (byte) value);
return;
} else {
- UnsafeUtil.putByte(position++, (byte) (((int) value & 0x7F) | 0x80));
+ UnsafeUtil.putByte(position++, (byte) (((int) value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -2259,7 +2265,7 @@ public abstract class CodedOutputStream extends ByteOutput {
UnsafeUtil.putByte(buffer, position++, (byte) value);
break;
} else {
- UnsafeUtil.putByte(buffer, position++, (byte) ((value & 0x7F) | 0x80));
+ UnsafeUtil.putByte(buffer, position++, (byte) ((value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -2272,7 +2278,7 @@ public abstract class CodedOutputStream extends ByteOutput {
totalBytesWritten++;
return;
} else {
- buffer[position++] = (byte) ((value & 0x7F) | 0x80);
+ buffer[position++] = (byte) ((value | 0x80) & 0xFF);
totalBytesWritten++;
value >>>= 7;
}
@@ -2292,7 +2298,7 @@ public abstract class CodedOutputStream extends ByteOutput {
UnsafeUtil.putByte(buffer, position++, (byte) value);
break;
} else {
- UnsafeUtil.putByte(buffer, position++, (byte) (((int) value & 0x7F) | 0x80));
+ UnsafeUtil.putByte(buffer, position++, (byte) (((int) value | 0x80) & 0xFF));
value >>>= 7;
}
}
@@ -2305,7 +2311,7 @@ public abstract class CodedOutputStream extends ByteOutput {
totalBytesWritten++;
return;
} else {
- buffer[position++] = (byte) (((int) value & 0x7F) | 0x80);
+ buffer[position++] = (byte) (((int) value | 0x80) & 0xFF);
totalBytesWritten++;
value >>>= 7;
}
diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStreamWriter.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStreamWriter.java
index 6c060cc3a..a20a579e3 100644
--- a/java/core/src/main/java/com/google/protobuf/CodedOutputStreamWriter.java
+++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStreamWriter.java
@@ -167,6 +167,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeInt32List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof IntArrayList) {
+ writeInt32ListInternal(fieldNumber, (IntArrayList) value, packed);
+ } else {
+ writeInt32ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeInt32ListInternal(int fieldNumber, IntArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeInt32SizeNoTag(value.getInt(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeInt32NoTag(value.getInt(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeInt32(fieldNumber, value.getInt(i));
+ }
+ }
+ }
+
+ private void writeInt32ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -191,6 +223,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeFixed32List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof IntArrayList) {
+ writeFixed32ListInternal(fieldNumber, (IntArrayList) value, packed);
+ } else {
+ writeFixed32ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeFixed32ListInternal(int fieldNumber, IntArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeFixed32SizeNoTag(value.getInt(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeFixed32NoTag(value.getInt(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeFixed32(fieldNumber, value.getInt(i));
+ }
+ }
+ }
+
+ private void writeFixed32ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -214,6 +278,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeInt64List(int fieldNumber, List value, boolean packed) throws IOException {
+ if (value instanceof LongArrayList) {
+ writeInt64ListInternal(fieldNumber, (LongArrayList) value, packed);
+ } else {
+ writeInt64ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeInt64ListInternal(int fieldNumber, LongArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeInt64SizeNoTag(value.getLong(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeInt64NoTag(value.getLong(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeInt64(fieldNumber, value.getLong(i));
+ }
+ }
+ }
+
+ private void writeInt64ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -234,10 +330,41 @@ final class CodedOutputStreamWriter implements Writer {
}
}
}
-
@Override
public void writeUInt64List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof LongArrayList) {
+ writeUInt64ListInternal(fieldNumber, (LongArrayList) value, packed);
+ } else {
+ writeUInt64ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeUInt64ListInternal(int fieldNumber, LongArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeUInt64SizeNoTag(value.getLong(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeUInt64NoTag(value.getLong(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeUInt64(fieldNumber, value.getLong(i));
+ }
+ }
+ }
+
+ private void writeUInt64ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -262,6 +389,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeFixed64List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof LongArrayList) {
+ writeFixed64ListInternal(fieldNumber, (LongArrayList) value, packed);
+ } else {
+ writeFixed64ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeFixed64ListInternal(int fieldNumber, LongArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeFixed64SizeNoTag(value.getLong(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeFixed64NoTag(value.getLong(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeFixed64(fieldNumber, value.getLong(i));
+ }
+ }
+ }
+
+ private void writeFixed64ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -286,6 +445,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeFloatList(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof FloatArrayList) {
+ writeFloatListInternal(fieldNumber, (FloatArrayList) value, packed);
+ } else {
+ writeFloatListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeFloatListInternal(int fieldNumber, FloatArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeFloatSizeNoTag(value.getFloat(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeFloatNoTag(value.getFloat(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeFloat(fieldNumber, value.getFloat(i));
+ }
+ }
+ }
+
+ private void writeFloatListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -310,6 +501,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeDoubleList(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof DoubleArrayList) {
+ writeDoubleListInternal(fieldNumber, (DoubleArrayList) value, packed);
+ } else {
+ writeDoubleListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeDoubleListInternal(int fieldNumber, DoubleArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeDoubleSizeNoTag(value.getDouble(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeDoubleNoTag(value.getDouble(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeDouble(fieldNumber, value.getDouble(i));
+ }
+ }
+ }
+
+ private void writeDoubleListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -334,6 +557,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeEnumList(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof IntArrayList) {
+ writeEnumListInternal(fieldNumber, (IntArrayList) value, packed);
+ } else {
+ writeEnumListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeEnumListInternal(int fieldNumber, IntArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeEnumSizeNoTag(value.getInt(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeEnumNoTag(value.getInt(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeEnum(fieldNumber, value.getInt(i));
+ }
+ }
+ }
+
+ private void writeEnumListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -358,6 +613,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeBoolList(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof BooleanArrayList) {
+ writeBoolListInternal(fieldNumber, (BooleanArrayList) value, packed);
+ } else {
+ writeBoolListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeBoolListInternal(int fieldNumber, BooleanArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeBoolSizeNoTag(value.getBoolean(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeBoolNoTag(value.getBoolean(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeBool(fieldNumber, value.getBoolean(i));
+ }
+ }
+ }
+
+ private void writeBoolListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -411,6 +698,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeUInt32List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof IntArrayList) {
+ writeUInt32ListInternal(fieldNumber, (IntArrayList) value, packed);
+ } else {
+ writeUInt32ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeUInt32ListInternal(int fieldNumber, IntArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeUInt32SizeNoTag(value.getInt(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeUInt32NoTag(value.getInt(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeUInt32(fieldNumber, value.getInt(i));
+ }
+ }
+ }
+
+ public void writeUInt32ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -435,6 +754,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeSFixed32List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof IntArrayList) {
+ writeSFixed32ListInternal(fieldNumber, (IntArrayList) value, packed);
+ } else {
+ writeSFixed32ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeSFixed32ListInternal(int fieldNumber, IntArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeSFixed32SizeNoTag(value.getInt(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSFixed32NoTag(value.getInt(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSFixed32(fieldNumber, value.getInt(i));
+ }
+ }
+ }
+
+ private void writeSFixed32ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -459,6 +810,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeSFixed64List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof LongArrayList) {
+ writeSFixed64ListInternal(fieldNumber, (LongArrayList) value, packed);
+ } else {
+ writeSFixed64ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeSFixed64ListInternal(int fieldNumber, LongArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeSFixed64SizeNoTag(value.getLong(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSFixed64NoTag(value.getLong(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSFixed64(fieldNumber, value.getLong(i));
+ }
+ }
+ }
+
+ private void writeSFixed64ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -483,6 +866,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeSInt32List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof IntArrayList) {
+ writeSInt32ListInternal(fieldNumber, (IntArrayList) value, packed);
+ } else {
+ writeSInt32ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeSInt32ListInternal(int fieldNumber, IntArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeSInt32SizeNoTag(value.getInt(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSInt32NoTag(value.getInt(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSInt32(fieldNumber, value.getInt(i));
+ }
+ }
+ }
+
+ public void writeSInt32ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
@@ -507,6 +922,38 @@ final class CodedOutputStreamWriter implements Writer {
@Override
public void writeSInt64List(int fieldNumber, List value, boolean packed)
throws IOException {
+ if (value instanceof LongArrayList) {
+ writeSInt64ListInternal(fieldNumber, (LongArrayList) value, packed);
+ } else {
+ writeSInt64ListInternal(fieldNumber, value, packed);
+ }
+ }
+
+ private void writeSInt64ListInternal(int fieldNumber, LongArrayList value, boolean packed)
+ throws IOException {
+ if (packed) {
+ output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+ // Compute and write the length of the data.
+ int dataSize = 0;
+ for (int i = 0; i < value.size(); ++i) {
+ dataSize += CodedOutputStream.computeSInt64SizeNoTag(value.getLong(i));
+ }
+ output.writeUInt32NoTag(dataSize);
+
+ // Write the data itself, without any tags.
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSInt64NoTag(value.getLong(i));
+ }
+ } else {
+ for (int i = 0; i < value.size(); ++i) {
+ output.writeSInt64(fieldNumber, value.getLong(i));
+ }
+ }
+ }
+
+ private void writeSInt64ListInternal(int fieldNumber, List value, boolean packed)
+ throws IOException {
if (packed) {
output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
diff --git a/java/core/src/main/java/com/google/protobuf/DebugFormat.java b/java/core/src/main/java/com/google/protobuf/DebugFormat.java
new file mode 100644
index 000000000..2d63ef171
--- /dev/null
+++ b/java/core/src/main/java/com/google/protobuf/DebugFormat.java
@@ -0,0 +1,79 @@
+package com.google.protobuf;
+
+import com.google.protobuf.Descriptors.FieldDescriptor;
+
+/**
+ * Provides an explicit API for unstable, redacting debug output suitable for debug logging. This
+ * implementation is based on TextFormat, but should not be parsed.
+ */
+public final class DebugFormat {
+
+ private final boolean isSingleLine;
+
+ private DebugFormat(boolean singleLine) {
+ isSingleLine = singleLine;
+ }
+
+ public static DebugFormat singleLine() {
+ return new DebugFormat(true);
+ }
+
+ public static DebugFormat multiline() {
+ return new DebugFormat(false);
+ }
+
+ public String toString(MessageOrBuilder message) {
+ return TextFormat.printer()
+ .emittingSingleLine(this.isSingleLine)
+ .enablingSafeDebugFormat(true)
+ .printToString(message);
+ }
+
+ public String toString(FieldDescriptor field, Object value) {
+ return TextFormat.printer()
+ .emittingSingleLine(this.isSingleLine)
+ .enablingSafeDebugFormat(true)
+ .printFieldToString(field, value);
+ }
+
+ public String toString(UnknownFieldSet fields) {
+ return TextFormat.printer()
+ .emittingSingleLine(this.isSingleLine)
+ .enablingSafeDebugFormat(true)
+ .printToString(fields);
+ }
+
+ public Object lazyToString(MessageOrBuilder message) {
+ return new LazyDebugOutput(message, this);
+ }
+
+ public Object lazyToString(UnknownFieldSet fields) {
+ return new LazyDebugOutput(fields, this);
+ }
+
+ private static class LazyDebugOutput {
+ private final MessageOrBuilder message;
+ private final UnknownFieldSet fields;
+ private final DebugFormat format;
+
+ LazyDebugOutput(MessageOrBuilder message, DebugFormat format) {
+ this.message = message;
+ this.fields = null;
+ this.format = format;
+ }
+
+ LazyDebugOutput(UnknownFieldSet fields, DebugFormat format) {
+ this.message = null;
+ this.fields = fields;
+ this.format = format;
+ }
+
+ @Override
+ public String toString() {
+ if (message != null) {
+ return format.toString(message);
+ }
+ return format.toString(fields);
+ }
+ }
+}
diff --git a/java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java b/java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java
index 821096903..8ce15422c 100644
--- a/java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java
+++ b/java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java
@@ -20,7 +20,6 @@ import static com.google.protobuf.FieldInfo.forRepeatedMessageField;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.Type;
-import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -34,7 +33,7 @@ import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
-/** A factory for message info based on protobuf descriptors for a {@link GeneratedMessageV3}. */
+/** A factory for message info based on protobuf descriptors for a {@link GeneratedMessage}. */
@ExperimentalApi
final class DescriptorMessageInfoFactory implements MessageInfoFactory {
private static final String GET_DEFAULT_INSTANCE_METHOD_NAME = "getDefaultInstance";
@@ -74,12 +73,12 @@ final class DescriptorMessageInfoFactory implements MessageInfoFactory {
@Override
public boolean isSupported(Class> messageType) {
- return GeneratedMessageV3.class.isAssignableFrom(messageType);
+ return GeneratedMessage.class.isAssignableFrom(messageType);
}
@Override
public MessageInfo messageInfoFor(Class> messageType) {
- if (!GeneratedMessageV3.class.isAssignableFrom(messageType)) {
+ if (!GeneratedMessage.class.isAssignableFrom(messageType)) {
throw new IllegalArgumentException("Unsupported message type: " + messageType.getName());
}
@@ -100,16 +99,14 @@ final class DescriptorMessageInfoFactory implements MessageInfoFactory {
return getDefaultInstance(messageType).getDescriptorForType();
}
- private static ProtoSyntax convertSyntax(FileDescriptor.Syntax syntax) {
- switch (syntax) {
- case PROTO2:
+ private static ProtoSyntax convertSyntax(DescriptorProtos.Edition edition) {
+ switch (edition) {
+ case EDITION_PROTO2:
return ProtoSyntax.PROTO2;
- case PROTO3:
+ case EDITION_PROTO3:
return ProtoSyntax.PROTO3;
- case EDITIONS:
- return ProtoSyntax.EDITIONS;
default:
- throw new IllegalArgumentException("Unsupported syntax: " + syntax);
+ return ProtoSyntax.EDITIONS;
}
}
@@ -118,7 +115,7 @@ final class DescriptorMessageInfoFactory implements MessageInfoFactory {
StructuralMessageInfo.Builder builder =
StructuralMessageInfo.newBuilder(fieldDescriptors.size());
builder.withDefaultInstance(getDefaultInstance(messageType));
- builder.withSyntax(convertSyntax(messageDescriptor.getFile().getSyntax()));
+ builder.withSyntax(convertSyntax(messageDescriptor.getFile().getEdition()));
builder.withMessageSetWireFormat(messageDescriptor.getOptions().getMessageSetWireFormat());
OneofState oneofState = new OneofState();
diff --git a/java/core/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java
index fb9b661bb..569fa26e0 100644
--- a/java/core/src/main/java/com/google/protobuf/Descriptors.java
+++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java
@@ -15,6 +15,9 @@ import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumOptions;
import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumValueOptions;
+import com.google.protobuf.DescriptorProtos.FeatureSet;
+import com.google.protobuf.DescriptorProtos.FeatureSetDefaults;
+import com.google.protobuf.DescriptorProtos.FeatureSetDefaults.FeatureSetEditionDefault;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldOptions;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
@@ -26,7 +29,8 @@ import com.google.protobuf.DescriptorProtos.OneofDescriptorProto;
import com.google.protobuf.DescriptorProtos.OneofOptions;
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
import com.google.protobuf.DescriptorProtos.ServiceOptions;
-import com.google.protobuf.Descriptors.FileDescriptor.Syntax;
+import com.google.protobuf.Descriptors.DescriptorValidationException;
+import com.google.protobuf.JavaFeaturesProto.JavaFeatures;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -38,6 +42,7 @@ import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/**
@@ -65,6 +70,84 @@ public final class Descriptors {
private static final EnumDescriptor[] EMPTY_ENUM_DESCRIPTORS = new EnumDescriptor[0];
private static final ServiceDescriptor[] EMPTY_SERVICE_DESCRIPTORS = new ServiceDescriptor[0];
private static final OneofDescriptor[] EMPTY_ONEOF_DESCRIPTORS = new OneofDescriptor[0];
+ private static final ConcurrentHashMap FEATURE_CACHE =
+ new ConcurrentHashMap<>();
+
+ @SuppressWarnings("NonFinalStaticField")
+ private static volatile FeatureSetDefaults javaEditionDefaults = null;
+
+ /** Sets the default feature mappings used during the build. Exposed for tests. */
+ static void setTestJavaEditionDefaults(FeatureSetDefaults defaults) {
+ javaEditionDefaults = defaults;
+ }
+
+ /** Gets the default feature mappings used during the build. */
+ static FeatureSetDefaults getJavaEditionDefaults() {
+ // Force explicit initialization before synchronized block which can trigger initialization in
+ // `JavaFeaturesProto.registerAllExtensions()` and `FeatureSetdefaults.parseFrom()` calls.
+ // Otherwise, this can result in deadlock if another threads holds the static init block's
+ // implicit lock. This operation should be cheap if initialization has already occurred.
+ Descriptor unused1 = FeatureSetDefaults.getDescriptor();
+ FileDescriptor unused2 = JavaFeaturesProto.getDescriptor();
+ if (javaEditionDefaults == null) {
+ synchronized (Descriptors.class) {
+ if (javaEditionDefaults == null) {
+ try {
+ ExtensionRegistry registry = ExtensionRegistry.newInstance();
+ registry.add(JavaFeaturesProto.java_);
+ setTestJavaEditionDefaults(
+ FeatureSetDefaults.parseFrom(
+ JavaEditionDefaults.PROTOBUF_INTERNAL_JAVA_EDITION_DEFAULTS.getBytes(
+ Internal.ISO_8859_1),
+ registry));
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+ }
+ return javaEditionDefaults;
+ }
+
+ static FeatureSet getEditionDefaults(Edition edition) {
+ FeatureSetDefaults javaEditionDefaults = getJavaEditionDefaults();
+ if (edition.getNumber() < javaEditionDefaults.getMinimumEdition().getNumber()) {
+ throw new IllegalArgumentException(
+ "Edition "
+ + edition
+ + " is lower than the minimum supported edition "
+ + javaEditionDefaults.getMinimumEdition()
+ + "!");
+ }
+ if (edition.getNumber() > javaEditionDefaults.getMaximumEdition().getNumber()) {
+ throw new IllegalArgumentException(
+ "Edition "
+ + edition
+ + " is greater than the maximum supported edition "
+ + javaEditionDefaults.getMaximumEdition()
+ + "!");
+ }
+ FeatureSetEditionDefault found = null;
+ for (FeatureSetEditionDefault editionDefault : javaEditionDefaults.getDefaultsList()) {
+ if (editionDefault.getEdition().getNumber() > edition.getNumber()) {
+ break;
+ }
+ found = editionDefault;
+ }
+ if (found == null) {
+ throw new IllegalArgumentException(
+ "Edition " + edition + " does not have a valid default FeatureSet!");
+ }
+ return found.getFixedFeatures().toBuilder().mergeFrom(found.getOverridableFeatures()).build();
+ }
+
+ private static FeatureSet internFeatures(FeatureSet features) {
+ FeatureSet cached = FEATURE_CACHE.putIfAbsent(features.hashCode(), features);
+ if (cached == null) {
+ return features;
+ }
+ return cached;
+ }
/**
* Describes a {@code .proto} file, including everything defined within. That includes, in
@@ -106,7 +189,21 @@ public final class Descriptors {
/** Get the {@code FileOptions}, defined in {@code descriptor.proto}. */
public FileOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ FileOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
/** Get a list of top-level message types declared in this file. */
@@ -139,59 +236,28 @@ public final class Descriptors {
return Collections.unmodifiableList(Arrays.asList(publicDependencies));
}
- /** The syntax of the .proto file. */
- @Deprecated
- public
- enum Syntax {
- UNKNOWN("unknown"),
- PROTO2("proto2"),
- PROTO3("proto3"),
- EDITIONS("editions");
-
- Syntax(String name) {
- this.name = name;
- }
-
- private final String name;
- }
-
- /** Get the syntax of the .proto file. */
- @Deprecated
- public
- Syntax getSyntax() {
- if (Syntax.PROTO3.name.equals(proto.getSyntax())) {
- return Syntax.PROTO3;
- } else if (Syntax.EDITIONS.name.equals(proto.getSyntax())) {
- return Syntax.EDITIONS;
- }
- return Syntax.PROTO2;
- }
-
/** Get the edition of the .proto file. */
- public Edition getEdition() {
- return proto.getEdition();
- }
-
- /** Gets the name of the edition as specified in the .proto file. */
- public String getEditionName() {
- if (proto.getEdition().equals(Edition.EDITION_UNKNOWN)) {
- return "";
+ Edition getEdition() {
+ switch (proto.getSyntax()) {
+ case "editions":
+ return proto.getEdition();
+ case "proto3":
+ return Edition.EDITION_PROTO3;
+ default:
+ return Edition.EDITION_PROTO2;
}
- return proto.getEdition().name().substring("EDITION_".length());
}
public void copyHeadingTo(FileDescriptorProto.Builder protoBuilder) {
- protoBuilder.setName(getName()).setSyntax(getSyntax().name);
+ protoBuilder.setName(getName()).setSyntax(proto.getSyntax());
if (!getPackage().isEmpty()) {
protoBuilder.setPackage(getPackage());
}
-
- if (getSyntax().equals(Syntax.EDITIONS)) {
- protoBuilder.setEdition(getEdition());
+ if (proto.getSyntax().equals("editions")) {
+ protoBuilder.setEdition(proto.getEdition());
}
-
- if (!getOptions().equals(FileOptions.getDefaultInstance())) {
- protoBuilder.setOptions(getOptions());
+ if (proto.hasOptions() && !proto.getOptions().equals(FileOptions.getDefaultInstance())) {
+ protoBuilder.setOptions(proto.getOptions());
}
}
@@ -293,7 +359,7 @@ public final class Descriptors {
* Construct a {@code FileDescriptor}.
*
* @param proto the protocol message form of the FileDescriptort
- * @param dependencies {@code FileDescriptor}s corresponding to all of the file's dependencies
+ * @param dependencies {@code FileDescriptor}s corresponding to all of the file's dependencies.
* @throws DescriptorValidationException {@code proto} is not a valid descriptor. This can occur
* for a number of reasons; for instance, because a field has an undefined type or because
* two messages were defined with the same name.
@@ -318,6 +384,15 @@ public final class Descriptors {
public static FileDescriptor buildFrom(
FileDescriptorProto proto, FileDescriptor[] dependencies, boolean allowUnknownDependencies)
throws DescriptorValidationException {
+ return buildFrom(proto, dependencies, allowUnknownDependencies, false);
+ }
+
+ private static FileDescriptor buildFrom(
+ FileDescriptorProto proto,
+ FileDescriptor[] dependencies,
+ boolean allowUnknownDependencies,
+ boolean allowUnresolvedFeatures)
+ throws DescriptorValidationException {
// Building descriptors involves two steps: translating and linking.
// In the translation step (implemented by FileDescriptor's
// constructor), we build an object tree mirroring the
@@ -331,6 +406,12 @@ public final class Descriptors {
FileDescriptor result =
new FileDescriptor(proto, dependencies, pool, allowUnknownDependencies);
result.crossLink();
+ // Skip feature resolution until later for calls from gencode.
+ if (!allowUnresolvedFeatures) {
+ // We do not need to force feature resolution for proto1 dependencies
+ // since dependencies from non-gencode should already be fully feature resolved.
+ result.resolveAllFeaturesInternal();
+ }
return result;
}
@@ -372,50 +453,6 @@ public final class Descriptors {
return descriptors.toArray(new FileDescriptor[0]);
}
- /**
- * This method is for backward compatibility with generated code which passed an
- * InternalDescriptorAssigner.
- */
- @Deprecated
- public static void internalBuildGeneratedFileFrom(
- final String[] descriptorDataParts,
- final FileDescriptor[] dependencies,
- final InternalDescriptorAssigner descriptorAssigner) {
- final byte[] descriptorBytes = latin1Cat(descriptorDataParts);
-
- FileDescriptorProto proto;
- try {
- proto = FileDescriptorProto.parseFrom(descriptorBytes);
- } catch (InvalidProtocolBufferException e) {
- throw new IllegalArgumentException(
- "Failed to parse protocol buffer descriptor for generated code.", e);
- }
-
- final FileDescriptor result;
- try {
- // When building descriptors for generated code, we allow unknown
- // dependencies by default.
- result = buildFrom(proto, dependencies, true);
- } catch (DescriptorValidationException e) {
- throw new IllegalArgumentException(
- "Invalid embedded descriptor for \"" + proto.getName() + "\".", e);
- }
-
- final ExtensionRegistry registry = descriptorAssigner.assignDescriptors(result);
-
- if (registry != null) {
- // We must re-parse the proto using the registry.
- try {
- proto = FileDescriptorProto.parseFrom(descriptorBytes, registry);
- } catch (InvalidProtocolBufferException e) {
- throw new IllegalArgumentException(
- "Failed to parse protocol buffer descriptor for generated code.", e);
- }
-
- result.setProto(proto);
- }
- }
-
/**
* This method is to be called by generated code only. It is equivalent to {@code buildFrom}
* except that the {@code FileDescriptorProto} is encoded in protocol buffer wire format.
@@ -434,30 +471,14 @@ public final class Descriptors {
try {
// When building descriptors for generated code, we allow unknown
- // dependencies by default.
- return buildFrom(proto, dependencies, true);
+ // dependencies by default and delay feature resolution until later.
+ return buildFrom(proto, dependencies, true, true);
} catch (DescriptorValidationException e) {
throw new IllegalArgumentException(
"Invalid embedded descriptor for \"" + proto.getName() + "\".", e);
}
}
- /**
- * This method is for backward compatibility with generated code which passed an
- * InternalDescriptorAssigner.
- */
- @Deprecated
- public static void internalBuildGeneratedFileFrom(
- final String[] descriptorDataParts,
- final Class> descriptorOuterClass,
- final String[] dependencyClassNames,
- final String[] dependencyFileNames,
- final InternalDescriptorAssigner descriptorAssigner) {
- FileDescriptor[] dependencies =
- findDescriptors(descriptorOuterClass, dependencyClassNames, dependencyFileNames);
- internalBuildGeneratedFileFrom(descriptorDataParts, dependencies, descriptorAssigner);
- }
-
/**
* This method is to be called by generated code only. It uses Java reflection to load the
* dependencies' descriptors.
@@ -508,6 +529,7 @@ public final class Descriptors {
}
private FileDescriptorProto proto;
+ private volatile FileOptions options;
private final Descriptor[] messageTypes;
private final EnumDescriptor[] enumTypes;
private final ServiceDescriptor[] services;
@@ -586,6 +608,7 @@ public final class Descriptors {
/** Create a placeholder FileDescriptor for a message Descriptor. */
FileDescriptor(String packageName, Descriptor message) throws DescriptorValidationException {
+ this.parent = null;
this.pool = new DescriptorPool(new FileDescriptor[0], true);
this.proto =
FileDescriptorProto.newBuilder()
@@ -605,6 +628,79 @@ public final class Descriptors {
pool.addSymbol(message);
}
+ public void resolveAllFeaturesImmutable() {
+ try {
+ resolveAllFeaturesInternal();
+ } catch (DescriptorValidationException e) {
+ throw new IllegalArgumentException("Invalid features for \"" + proto.getName() + "\".", e);
+ }
+ }
+
+ /**
+ * This method is to be called by generated code only. It resolves features for the descriptor
+ * and all of its children.
+ */
+ private void resolveAllFeaturesInternal() throws DescriptorValidationException {
+ if (this.features != null) {
+ return;
+ }
+
+ synchronized (this) {
+ if (this.features != null) {
+ return;
+ }
+ resolveFeatures(proto.getOptions().getFeatures());
+
+ for (Descriptor messageType : messageTypes) {
+ messageType.resolveAllFeatures();
+ }
+
+ for (EnumDescriptor enumType : enumTypes) {
+ enumType.resolveAllFeatures();
+ }
+
+ for (ServiceDescriptor service : services) {
+ service.resolveAllFeatures();
+ }
+
+ for (FieldDescriptor extension : extensions) {
+ extension.resolveAllFeatures();
+ }
+ }
+ }
+
+ @Override
+ FeatureSet inferLegacyProtoFeatures() {
+ FeatureSet.Builder features = FeatureSet.newBuilder();
+ if (getEdition().getNumber() >= Edition.EDITION_2023.getNumber()) {
+ return features.build();
+ }
+
+ if (getEdition() == Edition.EDITION_PROTO2) {
+ if (proto.getOptions().getJavaStringCheckUtf8()) {
+ features.setExtension(
+ JavaFeaturesProto.java_,
+ JavaFeatures.newBuilder()
+ .setUtf8Validation(JavaFeatures.Utf8Validation.VERIFY)
+ .build());
+ }
+ }
+ return features.build();
+ }
+
+ @Override
+ boolean hasInferredLegacyProtoFeatures() {
+ if (getEdition().getNumber() >= Edition.EDITION_2023.getNumber()) {
+ return false;
+ }
+ if (getEdition() == Edition.EDITION_PROTO2) {
+ if (proto.getOptions().getJavaStringCheckUtf8()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Look up and cross-link all field types, etc. */
private void crossLink() throws DescriptorValidationException {
for (final Descriptor messageType : messageTypes) {
@@ -628,23 +724,29 @@ public final class Descriptors {
* construct the descriptors we have to have parsed the descriptor protos. So, we have to parse
* the descriptor protos a second time after constructing the descriptors.
*/
- private void setProto(final FileDescriptorProto proto) {
+ private synchronized void setProto(final FileDescriptorProto proto) {
this.proto = proto;
+ this.options = null;
+ try {
+ resolveFeatures(proto.getOptions().getFeatures());
- for (int i = 0; i < messageTypes.length; i++) {
- messageTypes[i].setProto(proto.getMessageType(i));
- }
+ for (int i = 0; i < messageTypes.length; i++) {
+ messageTypes[i].setProto(proto.getMessageType(i));
+ }
- for (int i = 0; i < enumTypes.length; i++) {
- enumTypes[i].setProto(proto.getEnumType(i));
- }
+ for (int i = 0; i < enumTypes.length; i++) {
+ enumTypes[i].setProto(proto.getEnumType(i));
+ }
- for (int i = 0; i < services.length; i++) {
- services[i].setProto(proto.getService(i));
- }
+ for (int i = 0; i < services.length; i++) {
+ services[i].setProto(proto.getService(i));
+ }
- for (int i = 0; i < extensions.length; i++) {
- extensions[i].setProto(proto.getExtension(i));
+ for (int i = 0; i < extensions.length; i++) {
+ extensions[i].setProto(proto.getExtension(i));
+ }
+ } catch (DescriptorValidationException e) {
+ throw new IllegalArgumentException("Invalid features for \"" + proto.getName() + "\".", e);
}
}
}
@@ -715,7 +817,21 @@ public final class Descriptors {
/** Get the {@code MessageOptions}, defined in {@code descriptor.proto}. */
public MessageOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ MessageOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
/** Get a list of this message type's fields. */
@@ -850,6 +966,7 @@ public final class Descriptors {
private final int index;
private DescriptorProto proto;
+ private volatile MessageOptions options;
private final String fullName;
private final FileDescriptor file;
private final Descriptor containingType;
@@ -893,6 +1010,7 @@ public final class Descriptors {
// Create a placeholder FileDescriptor to hold this message.
this.file = new FileDescriptor(packageName, this);
+ this.parent = this.file;
extensionRangeLowerBounds = new int[] {1};
extensionRangeUpperBounds = new int[] {536870912};
@@ -904,6 +1022,11 @@ public final class Descriptors {
final Descriptor parent,
final int index)
throws DescriptorValidationException {
+ if (parent == null) {
+ this.parent = file;
+ } else {
+ this.parent = parent;
+ }
this.index = index;
this.proto = proto;
fullName = computeFullName(file, parent, proto.getName());
@@ -997,6 +1120,32 @@ public final class Descriptors {
}
}
+ /** See {@link FileDescriptor#resolveAllFeatures}. */
+ private void resolveAllFeatures() throws DescriptorValidationException {
+ resolveFeatures(proto.getOptions().getFeatures());
+
+ for (Descriptor nestedType : nestedTypes) {
+ nestedType.resolveAllFeatures();
+ }
+
+ for (EnumDescriptor enumType : enumTypes) {
+ enumType.resolveAllFeatures();
+ }
+
+ // Oneofs must be resolved before any children oneof fields.
+ for (OneofDescriptor oneof : oneofs) {
+ oneof.resolveAllFeatures();
+ }
+
+ for (FieldDescriptor field : fields) {
+ field.resolveAllFeatures();
+ }
+
+ for (FieldDescriptor extension : extensions) {
+ extension.resolveAllFeatures();
+ }
+ }
+
/** Look up and cross-link all field types, etc. */
private void crossLink() throws DescriptorValidationException {
for (final Descriptor nestedType : nestedTypes) {
@@ -1033,8 +1182,10 @@ public final class Descriptors {
}
/** See {@link FileDescriptor#setProto}. */
- private void setProto(final DescriptorProto proto) {
+ private void setProto(final DescriptorProto proto) throws DescriptorValidationException {
this.proto = proto;
+ this.options = null;
+ resolveFeatures(proto.getOptions().getFeatures());
for (int i = 0; i < nestedTypes.length; i++) {
nestedTypes[i].setProto(proto.getNestedType(i));
@@ -1125,7 +1276,7 @@ public final class Descriptors {
* FieldDescriptorProto.Type} maps to exactly one Java type.
*/
public JavaType getJavaType() {
- return type.getJavaType();
+ return getType().getJavaType();
}
/** For internal use only. */
@@ -1142,34 +1293,47 @@ public final class Descriptors {
/** Get the field's declared type. */
public Type getType() {
+ // Override delimited messages as legacy group type. Leaves unresolved messages as-is
+ // since these are used before feature resolution when parsing java feature set defaults
+ // (custom options) into unknown fields.
+ if (type == Type.MESSAGE
+ && !(messageType != null && messageType.toProto().getOptions().getMapEntry())
+ && !(containingType != null && containingType.toProto().getOptions().getMapEntry())
+ && this.features != null
+ && getFeatures().getMessageEncoding() == FeatureSet.MessageEncoding.DELIMITED) {
+ return Type.GROUP;
+ }
return type;
}
/** For internal use only. */
@Override
public WireFormat.FieldType getLiteType() {
- return table[type.ordinal()];
+ return table[getType().ordinal()];
}
/** For internal use only. */
public boolean needsUtf8Check() {
- if (type != Type.STRING) {
+ if (getType() != Type.STRING) {
return false;
}
- if (getContainingType().getOptions().getMapEntry()) {
+ if (getContainingType().toProto().getOptions().getMapEntry()) {
// Always enforce strict UTF-8 checking for map fields.
return true;
}
- if (getFile().getSyntax() == Syntax.PROTO3) {
+ if (getFeatures()
+ .getExtension(JavaFeaturesProto.java_)
+ .getUtf8Validation()
+ .equals(JavaFeatures.Utf8Validation.VERIFY)) {
return true;
}
- return getFile().getOptions().getJavaStringCheckUtf8();
+ return getFeatures().getUtf8Validation().equals(FeatureSet.Utf8Validation.VERIFY);
}
public boolean isMapField() {
return getType() == Type.MESSAGE
&& isRepeated()
- && getMessageType().getOptions().getMapEntry();
+ && getMessageType().toProto().getOptions().getMapEntry();
}
// I'm pretty sure values() constructs a new array every time, since there
@@ -1179,12 +1343,15 @@ public final class Descriptors {
/** Is this field declared required? */
public boolean isRequired() {
- return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REQUIRED;
+ return getFeatures().getFieldPresence()
+ == DescriptorProtos.FeatureSet.FieldPresence.LEGACY_REQUIRED;
}
/** Is this field declared optional? */
public boolean isOptional() {
- return proto.getLabel() == FieldDescriptorProto.Label.LABEL_OPTIONAL;
+ return proto.getLabel() == FieldDescriptorProto.Label.LABEL_OPTIONAL
+ && getFeatures().getFieldPresence()
+ != DescriptorProtos.FeatureSet.FieldPresence.LEGACY_REQUIRED;
}
/** Is this field declared repeated? */
@@ -1202,11 +1369,9 @@ public final class Descriptors {
if (!isPackable()) {
return false;
}
- if (getFile().getSyntax() == FileDescriptor.Syntax.PROTO2) {
- return getOptions().getPacked();
- } else {
- return !getOptions().hasPacked() || getOptions().getPacked();
- }
+ return getFeatures()
+ .getRepeatedFieldEncoding()
+ .equals(FeatureSet.RepeatedFieldEncoding.PACKED);
}
/** Can this field be packed? That is, is it a repeated primitive field? */
@@ -1234,7 +1399,21 @@ public final class Descriptors {
/** Get the {@code FieldOptions}, defined in {@code descriptor.proto}. */
public FieldOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ FieldOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
/** Is this field an extension? */
@@ -1264,11 +1443,11 @@ public final class Descriptors {
* Returns true if this field was syntactically written with "optional" in the .proto file.
* Excludes singular proto3 fields that do not have a label.
*/
- @Deprecated
- public
boolean hasOptionalKeyword() {
return isProto3Optional
- || (file.getSyntax() == Syntax.PROTO2 && isOptional() && getContainingOneof() == null);
+ || (file.getEdition() == Edition.EDITION_PROTO2
+ && isOptional()
+ && getContainingOneof() == null);
}
/**
@@ -1285,10 +1464,41 @@ public final class Descriptors {
if (isRepeated()) {
return false;
}
- return getType() == Type.MESSAGE
+ return isProto3Optional
+ || getType() == Type.MESSAGE
|| getType() == Type.GROUP
+ || isExtension()
|| getContainingOneof() != null
- || file.getSyntax() == Syntax.PROTO2;
+ || getFeatures().getFieldPresence() != DescriptorProtos.FeatureSet.FieldPresence.IMPLICIT;
+ }
+
+ /**
+ * Returns true if this field is structured like the synthetic field of a proto2 group. This
+ * allows us to expand our treatment of delimited fields without breaking proto2 files that have
+ * been upgraded to editions.
+ */
+ boolean isGroupLike() {
+ if (getType() != Type.GROUP) {
+ // Groups are always tag-delimited.
+ return false;
+ }
+
+ if (!getMessageType().getName().toLowerCase().equals(getName())) {
+ // Group fields always are always the lowercase type name.
+ return false;
+ }
+
+ if (getMessageType().getFile() != getFile()) {
+ // Groups could only be defined in the same file they're used.
+ return false;
+ }
+
+ // Group messages are always defined in the same scope as the field. File level extensions
+ // will compare NULL == NULL here, which is why the file comparison above is necessary to
+ // ensure both come from the same file.
+ return isExtension()
+ ? getMessageType().getContainingType() == getExtensionScope()
+ : getMessageType().getContainingType() == getContainingType();
}
/**
@@ -1360,7 +1570,17 @@ public final class Descriptors {
* handling quirks.
*/
public boolean legacyEnumFieldTreatedAsClosed() {
- return getType() == Type.ENUM && getFile().getSyntax() == Syntax.PROTO2;
+ // Don't check JavaFeaturesProto extension for files without dependencies.
+ // This is especially important for descriptor.proto since getting the JavaFeaturesProto
+ // extension itself involves calling legacyEnumFieldTreatedAsClosed() which would otherwise
+ // infinite loop.
+ if (getFile().getDependencies().isEmpty()) {
+ return getType() == Type.ENUM && enumType.isClosed();
+ }
+
+ return getType() == Type.ENUM
+ && (getFeatures().getExtension(JavaFeaturesProto.java_).getLegacyClosedEnum()
+ || enumType.isClosed());
}
/**
@@ -1389,6 +1609,7 @@ public final class Descriptors {
private final int index;
private FieldDescriptorProto proto;
+ private volatile FieldOptions options;
private final String fullName;
private String jsonName;
private final FileDescriptor file;
@@ -1507,6 +1728,7 @@ public final class Descriptors {
final int index,
final boolean isExtension)
throws DescriptorValidationException {
+ this.parent = parent;
this.index = index;
this.proto = proto;
fullName = computeFullName(file, parent, proto.getName());
@@ -1532,6 +1754,7 @@ public final class Descriptors {
extensionScope = parent;
} else {
extensionScope = null;
+ this.parent = file;
}
if (proto.hasOneofIndex()) {
@@ -1555,6 +1778,7 @@ public final class Descriptors {
}
containingOneof = parent.getOneofs().get(proto.getOneofIndex());
containingOneof.fieldCount++;
+ this.parent = containingOneof;
} else {
containingOneof = null;
}
@@ -1564,6 +1788,79 @@ public final class Descriptors {
file.pool.addSymbol(this);
}
+ /** See {@link FileDescriptor#resolveAllFeatures}. */
+ private void resolveAllFeatures() throws DescriptorValidationException {
+ resolveFeatures(proto.getOptions().getFeatures());
+ }
+
+ @Override
+ FeatureSet inferLegacyProtoFeatures() {
+ FeatureSet.Builder features = FeatureSet.newBuilder();
+ if (getFile().getEdition().getNumber() >= Edition.EDITION_2023.getNumber()) {
+ return features.build();
+ }
+
+ if (proto.getLabel() == FieldDescriptorProto.Label.LABEL_REQUIRED) {
+ features.setFieldPresence(FeatureSet.FieldPresence.LEGACY_REQUIRED);
+ }
+
+ if (proto.getType() == FieldDescriptorProto.Type.TYPE_GROUP) {
+ features.setMessageEncoding(FeatureSet.MessageEncoding.DELIMITED);
+ }
+
+ if (getFile().getEdition() == Edition.EDITION_PROTO2 && proto.getOptions().getPacked()) {
+ features.setRepeatedFieldEncoding(FeatureSet.RepeatedFieldEncoding.PACKED);
+ }
+
+ if (getFile().getEdition() == Edition.EDITION_PROTO3) {
+ if (proto.getOptions().hasPacked() && !proto.getOptions().getPacked()) {
+ features.setRepeatedFieldEncoding(FeatureSet.RepeatedFieldEncoding.EXPANDED);
+ }
+
+ }
+ return features.build();
+ }
+
+ @Override
+ boolean hasInferredLegacyProtoFeatures() {
+ if (getFile().getEdition().getNumber() >= Edition.EDITION_2023.getNumber()) {
+ return false;
+ }
+
+ if (proto.getLabel() == FieldDescriptorProto.Label.LABEL_REQUIRED) {
+ return true;
+ }
+
+ if (proto.getType() == FieldDescriptorProto.Type.TYPE_GROUP) {
+ return true;
+ }
+
+ if (proto.getOptions().getPacked()) {
+ return true;
+ }
+
+ if (getFile().getEdition() == Edition.EDITION_PROTO3) {
+ if (proto.getOptions().hasPacked() && !proto.getOptions().getPacked()) {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ @Override
+ void validateFeatures() throws DescriptorValidationException {
+ if (containingType != null
+ && containingType.toProto().getOptions().getMessageSetWireFormat()) {
+ if (isExtension()) {
+ if (!isOptional() || getType() != Type.MESSAGE) {
+ throw new DescriptorValidationException(
+ this, "Extensions of MessageSets must be optional messages.");
+ }
+ }
+ }
+ }
+
/** Look up and cross-link all field types, etc. */
private void crossLink() throws DescriptorValidationException {
if (proto.hasExtendee()) {
@@ -1604,7 +1901,9 @@ public final class Descriptors {
}
}
- if (getJavaType() == JavaType.MESSAGE) {
+ // Use raw type since inferred type considers messageType which may not be fully cross
+ // linked yet.
+ if (type.getJavaType() == JavaType.MESSAGE) {
if (!(typeDescriptor instanceof Descriptor)) {
throw new DescriptorValidationException(
this, '\"' + proto.getTypeName() + "\" is not a message type.");
@@ -1614,7 +1913,7 @@ public final class Descriptors {
if (proto.hasDefaultValue()) {
throw new DescriptorValidationException(this, "Messages can't have default values.");
}
- } else if (getJavaType() == JavaType.ENUM) {
+ } else if (type.getJavaType() == JavaType.ENUM) {
if (!(typeDescriptor instanceof EnumDescriptor)) {
throw new DescriptorValidationException(
this, '\"' + proto.getTypeName() + "\" is not an enum type.");
@@ -1624,7 +1923,7 @@ public final class Descriptors {
throw new DescriptorValidationException(this, "Field with primitive type has type_name.");
}
} else {
- if (getJavaType() == JavaType.MESSAGE || getJavaType() == JavaType.ENUM) {
+ if (type.getJavaType() == JavaType.MESSAGE || type.getJavaType() == JavaType.ENUM) {
throw new DescriptorValidationException(
this, "Field with message or enum type missing type_name.");
}
@@ -1645,7 +1944,7 @@ public final class Descriptors {
}
try {
- switch (getType()) {
+ switch (type) {
case INT32:
case SINT32:
case SFIXED32:
@@ -1720,7 +2019,7 @@ public final class Descriptors {
if (isRepeated()) {
defaultValue = Collections.emptyList();
} else {
- switch (getJavaType()) {
+ switch (type.getJavaType()) {
case ENUM:
// We guarantee elsewhere that an enum type always has at least
// one possible value.
@@ -1730,28 +2029,18 @@ public final class Descriptors {
defaultValue = null;
break;
default:
- defaultValue = getJavaType().defaultDefault;
+ defaultValue = type.getJavaType().defaultDefault;
break;
}
}
}
-
- if (containingType != null && containingType.getOptions().getMessageSetWireFormat()) {
- if (isExtension()) {
- if (!isOptional() || getType() != Type.MESSAGE) {
- throw new DescriptorValidationException(
- this, "Extensions of MessageSets must be optional messages.");
- }
- } else {
- throw new DescriptorValidationException(
- this, "MessageSets cannot have fields, only extensions.");
- }
- }
}
/** See {@link FileDescriptor#setProto}. */
- private void setProto(final FieldDescriptorProto proto) {
+ private void setProto(final FieldDescriptorProto proto) throws DescriptorValidationException {
this.proto = proto;
+ this.options = null;
+ resolveFeatures(proto.getOptions().getFeatures());
}
/** For internal use only. This is to satisfy the FieldDescriptorLite interface. */
@@ -1830,7 +2119,7 @@ public final class Descriptors {
* handling quirks.
*/
public boolean isClosed() {
- return getFile().getSyntax() != Syntax.PROTO3;
+ return getFeatures().getEnumType() == DescriptorProtos.FeatureSet.EnumType.CLOSED;
}
/** If this is a nested type, get the outer descriptor, otherwise null. */
@@ -1840,7 +2129,21 @@ public final class Descriptors {
/** Get the {@code EnumOptions}, defined in {@code descriptor.proto}. */
public EnumOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ EnumOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
/** Get a list of defined values for this enum. */
@@ -1951,6 +2254,7 @@ public final class Descriptors {
private final int index;
private EnumDescriptorProto proto;
+ private volatile EnumOptions options;
private final String fullName;
private final FileDescriptor file;
private final Descriptor containingType;
@@ -1966,6 +2270,11 @@ public final class Descriptors {
final Descriptor parent,
final int index)
throws DescriptorValidationException {
+ if (parent == null) {
+ this.parent = file;
+ } else {
+ this.parent = parent;
+ }
this.index = index;
this.proto = proto;
fullName = computeFullName(file, parent, proto.getName());
@@ -1999,9 +2308,20 @@ public final class Descriptors {
file.pool.addSymbol(this);
}
+ /** See {@link FileDescriptor#resolveAllFeatures}. */
+ private void resolveAllFeatures() throws DescriptorValidationException {
+ resolveFeatures(proto.getOptions().getFeatures());
+
+ for (EnumValueDescriptor value : values) {
+ value.resolveAllFeatures();
+ }
+ }
+
/** See {@link FileDescriptor#setProto}. */
- private void setProto(final EnumDescriptorProto proto) {
+ private void setProto(final EnumDescriptorProto proto) throws DescriptorValidationException {
this.proto = proto;
+ this.options = null;
+ resolveFeatures(proto.getOptions().getFeatures());
for (int i = 0; i < values.length; i++) {
values[i].setProto(proto.getValue(i));
@@ -2016,6 +2336,7 @@ public final class Descriptors {
* number. In generated Java code, all values with the same number after the first become aliases
* of the first. However, they still have independent EnumValueDescriptors.
*/
+ @SuppressWarnings("ShouldNotSubclass")
public static final class EnumValueDescriptor extends GenericDescriptor
implements Internal.EnumLite {
static final Comparator BY_NUMBER =
@@ -2089,11 +2410,26 @@ public final class Descriptors {
/** Get the {@code EnumValueOptions}, defined in {@code descriptor.proto}. */
public EnumValueOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ EnumValueOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
private final int index;
private EnumValueDescriptorProto proto;
+ private volatile EnumValueOptions options;
private final String fullName;
private final EnumDescriptor type;
@@ -2103,12 +2439,11 @@ public final class Descriptors {
final EnumDescriptor parent,
final int index)
throws DescriptorValidationException {
+ this.parent = parent;
this.index = index;
this.proto = proto;
- type = parent;
-
- fullName = parent.getFullName() + '.' + proto.getName();
-
+ this.type = parent;
+ this.fullName = parent.getFullName() + '.' + proto.getName();
file.pool.addSymbol(this);
}
@@ -2117,6 +2452,7 @@ public final class Descriptors {
String name = "UNKNOWN_ENUM_VALUE_" + parent.getName() + "_" + number;
EnumValueDescriptorProto proto =
EnumValueDescriptorProto.newBuilder().setName(name).setNumber(number).build();
+ this.parent = parent;
this.index = -1;
this.proto = proto;
this.type = parent;
@@ -2125,9 +2461,17 @@ public final class Descriptors {
// Don't add this descriptor into pool.
}
+ /** See {@link FileDescriptor#resolveAllFeatures}. */
+ private void resolveAllFeatures() throws DescriptorValidationException {
+ resolveFeatures(proto.getOptions().getFeatures());
+ }
+
/** See {@link FileDescriptor#setProto}. */
- private void setProto(final EnumValueDescriptorProto proto) {
+ private void setProto(final EnumValueDescriptorProto proto)
+ throws DescriptorValidationException {
this.proto = proto;
+ this.options = null;
+ resolveFeatures(proto.getOptions().getFeatures());
}
}
@@ -2172,7 +2516,21 @@ public final class Descriptors {
/** Get the {@code ServiceOptions}, defined in {@code descriptor.proto}. */
public ServiceOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ ServiceOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
/** Get a list of methods for this service. */
@@ -2197,6 +2555,7 @@ public final class Descriptors {
private final int index;
private ServiceDescriptorProto proto;
+ private volatile ServiceOptions options;
private final String fullName;
private final FileDescriptor file;
private MethodDescriptor[] methods;
@@ -2204,6 +2563,7 @@ public final class Descriptors {
private ServiceDescriptor(
final ServiceDescriptorProto proto, final FileDescriptor file, final int index)
throws DescriptorValidationException {
+ this.parent = file;
this.index = index;
this.proto = proto;
fullName = computeFullName(file, null, proto.getName());
@@ -2217,6 +2577,15 @@ public final class Descriptors {
file.pool.addSymbol(this);
}
+ /** See {@link FileDescriptor#resolveAllFeatures}. */
+ private void resolveAllFeatures() throws DescriptorValidationException {
+ resolveFeatures(proto.getOptions().getFeatures());
+
+ for (MethodDescriptor method : methods) {
+ method.resolveAllFeatures();
+ }
+ }
+
private void crossLink() throws DescriptorValidationException {
for (final MethodDescriptor method : methods) {
method.crossLink();
@@ -2224,8 +2593,10 @@ public final class Descriptors {
}
/** See {@link FileDescriptor#setProto}. */
- private void setProto(final ServiceDescriptorProto proto) {
+ private void setProto(final ServiceDescriptorProto proto) throws DescriptorValidationException {
this.proto = proto;
+ this.options = null;
+ resolveFeatures(proto.getOptions().getFeatures());
for (int i = 0; i < methods.length; i++) {
methods[i].setProto(proto.getMethod(i));
@@ -2299,11 +2670,26 @@ public final class Descriptors {
/** Get the {@code MethodOptions}, defined in {@code descriptor.proto}. */
public MethodOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ MethodOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
private final int index;
private MethodDescriptorProto proto;
+ private volatile MethodOptions options;
private final String fullName;
private final FileDescriptor file;
private final ServiceDescriptor service;
@@ -2318,6 +2704,7 @@ public final class Descriptors {
final ServiceDescriptor parent,
final int index)
throws DescriptorValidationException {
+ this.parent = parent;
this.index = index;
this.proto = proto;
this.file = file;
@@ -2328,6 +2715,11 @@ public final class Descriptors {
file.pool.addSymbol(this);
}
+ /** See {@link FileDescriptor#resolveAllFeatures}. */
+ private void resolveAllFeatures() throws DescriptorValidationException {
+ resolveFeatures(proto.getOptions().getFeatures());
+ }
+
private void crossLink() throws DescriptorValidationException {
final GenericDescriptor input =
getFile()
@@ -2351,8 +2743,10 @@ public final class Descriptors {
}
/** See {@link FileDescriptor#setProto}. */
- private void setProto(final MethodDescriptorProto proto) {
+ private void setProto(final MethodDescriptorProto proto) throws DescriptorValidationException {
this.proto = proto;
+ this.options = null;
+ resolveFeatures(proto.getOptions().getFeatures());
}
}
@@ -2379,7 +2773,6 @@ public final class Descriptors {
* DescriptorPool}.
*/
public abstract static class GenericDescriptor {
-
// Private constructor to prevent subclasses outside of com.google.protobuf.Descriptors
private GenericDescriptor() {}
@@ -2390,6 +2783,79 @@ public final class Descriptors {
public abstract String getFullName();
public abstract FileDescriptor getFile();
+
+ void resolveFeatures(FeatureSet unresolvedFeatures) throws DescriptorValidationException {
+ if (this.parent != null
+ && unresolvedFeatures.equals(FeatureSet.getDefaultInstance())
+ && !hasInferredLegacyProtoFeatures()) {
+ this.features = this.parent.features;
+ validateFeatures();
+ return;
+ }
+
+ // Java features from a custom pool (i.e. buildFrom) may end up in unknown fields or
+ // use a different descriptor from the generated pool used by the Java runtime.
+ boolean hasPossibleCustomJavaFeature = false;
+ for (FieldDescriptor f : unresolvedFeatures.getExtensionFields().keySet()) {
+ if (f.getNumber() == JavaFeaturesProto.java_.getNumber()
+ && f != JavaFeaturesProto.java_.getDescriptor()) {
+ hasPossibleCustomJavaFeature = true;
+ continue;
+ }
+ }
+ boolean hasPossibleUnknownJavaFeature =
+ !unresolvedFeatures.getUnknownFields().isEmpty()
+ && unresolvedFeatures
+ .getUnknownFields()
+ .hasField(JavaFeaturesProto.java_.getNumber());
+ if (hasPossibleCustomJavaFeature || hasPossibleUnknownJavaFeature) {
+ ExtensionRegistry registry = ExtensionRegistry.newInstance();
+ registry.add(JavaFeaturesProto.java_);
+ ByteString bytes = unresolvedFeatures.toByteString();
+ try {
+ unresolvedFeatures = FeatureSet.parseFrom(bytes, registry);
+ } catch (InvalidProtocolBufferException e) {
+ throw new DescriptorValidationException(
+ this, "Failed to parse features with Java feature extension registry.", e);
+ }
+ }
+
+ FeatureSet.Builder features;
+ if (this.parent == null) {
+ Edition edition = getFile().getEdition();
+ features = getEditionDefaults(edition).toBuilder();
+ } else {
+ features = this.parent.features.toBuilder();
+ }
+ features.mergeFrom(inferLegacyProtoFeatures());
+ features.mergeFrom(unresolvedFeatures);
+ this.features = internFeatures(features.build());
+ validateFeatures();
+ }
+
+ FeatureSet inferLegacyProtoFeatures() {
+ return FeatureSet.getDefaultInstance();
+ }
+
+ boolean hasInferredLegacyProtoFeatures() {
+ return false;
+ }
+
+ void validateFeatures() throws DescriptorValidationException {}
+
+ FeatureSet getFeatures() {
+ // TODO: Remove lazy resolution of unresolved features for legacy syntax for
+ // compatibility with older <4.26.x gencode in the next breaking release.
+ if (this.features == null
+ && (getFile().getEdition() == Edition.EDITION_PROTO2
+ || getFile().getEdition() == Edition.EDITION_PROTO3)) {
+ getFile().resolveAllFeaturesImmutable();
+ }
+ return this.features;
+ }
+
+ GenericDescriptor parent;
+ volatile FeatureSet features;
}
/** Thrown when building descriptors fails because the source DescriptorProtos are not valid. */
@@ -2817,7 +3283,21 @@ public final class Descriptors {
}
public OneofOptions getOptions() {
- return proto.getOptions();
+ if (this.options == null) {
+ OneofOptions strippedOptions = this.proto.getOptions();
+ if (strippedOptions.hasFeatures()) {
+ // Clients should be using feature accessor methods, not accessing features on the
+ // options
+ // proto.
+ strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
+ }
+ synchronized (this) {
+ if (this.options == null) {
+ this.options = strippedOptions;
+ }
+ }
+ }
+ return this.options;
}
/** Get a list of this message type's fields. */
@@ -2834,14 +3314,19 @@ public final class Descriptors {
return proto;
}
- @Deprecated
- public
boolean isSynthetic() {
return fields.length == 1 && fields[0].isProto3Optional;
}
- private void setProto(final OneofDescriptorProto proto) {
+ /** See {@link FileDescriptor#resolveAllFeatures}. */
+ private void resolveAllFeatures() throws DescriptorValidationException {
+ resolveFeatures(proto.getOptions().getFeatures());
+ }
+
+ private void setProto(final OneofDescriptorProto proto) throws DescriptorValidationException {
this.proto = proto;
+ this.options = null;
+ resolveFeatures(proto.getOptions().getFeatures());
}
private OneofDescriptor(
@@ -2849,6 +3334,7 @@ public final class Descriptors {
final FileDescriptor file,
final Descriptor parent,
final int index) {
+ this.parent = parent;
this.proto = proto;
fullName = computeFullName(file, parent, proto.getName());
this.file = file;
@@ -2860,6 +3346,7 @@ public final class Descriptors {
private final int index;
private OneofDescriptorProto proto;
+ private volatile OneofOptions options;
private final String fullName;
private final FileDescriptor file;
diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java
index 91aae2bc6..b9fa9ed74 100644
--- a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java
+++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java
@@ -48,7 +48,8 @@ final class ExtensionRegistryFactory {
}
static boolean isFullRegistry(ExtensionRegistryLite registry) {
- return EXTENSION_REGISTRY_CLASS != null
+ return !Protobuf.assumeLiteRuntime
+ && EXTENSION_REGISTRY_CLASS != null
&& EXTENSION_REGISTRY_CLASS.isAssignableFrom(registry.getClass());
}
diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java
index 18d14bf37..6529071e2 100644
--- a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java
+++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java
@@ -53,10 +53,6 @@ public class ExtensionRegistryLite {
// applications. Need to support this feature on smaller granularity.
private static volatile boolean eagerlyParseMessageSets = false;
- // short circuit the ExtensionRegistryFactory via assumevalues trickery
- @SuppressWarnings("JavaOptionalSuggestions")
- private static boolean doFullRuntimeInheritanceCheck = true;
-
// Visible for testing.
static final String EXTENSION_CLASS_NAME = "com.google.protobuf.Extension";
@@ -88,9 +84,9 @@ public class ExtensionRegistryLite {
* available.
*/
public static ExtensionRegistryLite newInstance() {
- return doFullRuntimeInheritanceCheck
- ? ExtensionRegistryFactory.create()
- : new ExtensionRegistryLite();
+ return Protobuf.assumeLiteRuntime
+ ? new ExtensionRegistryLite()
+ : ExtensionRegistryFactory.create();
}
private static volatile ExtensionRegistryLite emptyRegistry;
@@ -100,7 +96,7 @@ public class ExtensionRegistryLite {
* ExtensionRegistry} (if the full (non-Lite) proto libraries are available).
*/
public static ExtensionRegistryLite getEmptyRegistry() {
- if (!doFullRuntimeInheritanceCheck) {
+ if (Protobuf.assumeLiteRuntime) {
return EMPTY_REGISTRY_LITE;
}
ExtensionRegistryLite result = emptyRegistry;
@@ -148,7 +144,7 @@ public class ExtensionRegistryLite {
if (GeneratedMessageLite.GeneratedExtension.class.isAssignableFrom(extension.getClass())) {
add((GeneratedMessageLite.GeneratedExtension, ?>) extension);
}
- if (doFullRuntimeInheritanceCheck && ExtensionRegistryFactory.isFullRegistry(this)) {
+ if (!Protobuf.assumeLiteRuntime && ExtensionRegistryFactory.isFullRegistry(this)) {
try {
this.getClass().getMethod("add", ExtensionClassHolder.INSTANCE).invoke(this, extension);
} catch (Exception e) {
diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionSchemaFull.java b/java/core/src/main/java/com/google/protobuf/ExtensionSchemaFull.java
index 9133e7a56..f01884a2c 100644
--- a/java/core/src/main/java/com/google/protobuf/ExtensionSchemaFull.java
+++ b/java/core/src/main/java/com/google/protobuf/ExtensionSchemaFull.java
@@ -23,7 +23,7 @@ final class ExtensionSchemaFull extends ExtensionSchema {
private static long getExtensionsFieldOffset() {
try {
- Field field = GeneratedMessageV3.ExtendableMessage.class.getDeclaredField("extensions");
+ Field field = GeneratedMessage.ExtendableMessage.class.getDeclaredField("extensions");
return UnsafeUtil.objectFieldOffset(field);
} catch (Throwable e) {
throw new IllegalStateException("Unable to lookup extension field offset");
@@ -32,7 +32,7 @@ final class ExtensionSchemaFull extends ExtensionSchema {
@Override
boolean hasExtensions(MessageLite prototype) {
- return prototype instanceof GeneratedMessageV3.ExtendableMessage;
+ return prototype instanceof GeneratedMessage.ExtendableMessage;
}
@Override
diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionSchemas.java b/java/core/src/main/java/com/google/protobuf/ExtensionSchemas.java
index cd8c852e9..059fdd011 100644
--- a/java/core/src/main/java/com/google/protobuf/ExtensionSchemas.java
+++ b/java/core/src/main/java/com/google/protobuf/ExtensionSchemas.java
@@ -13,6 +13,9 @@ final class ExtensionSchemas {
private static final ExtensionSchema> FULL_SCHEMA = loadSchemaForFullRuntime();
private static ExtensionSchema> loadSchemaForFullRuntime() {
+ if (Protobuf.assumeLiteRuntime) {
+ return null;
+ }
try {
Class> clazz = Class.forName("com.google.protobuf.ExtensionSchemaFull");
return (ExtensionSchema) clazz.getDeclaredConstructor().newInstance();
@@ -31,4 +34,6 @@ final class ExtensionSchemas {
}
return FULL_SCHEMA;
}
+
+ private ExtensionSchemas() {}
}
diff --git a/java/core/src/main/java/com/google/protobuf/FieldSet.java b/java/core/src/main/java/com/google/protobuf/FieldSet.java
index d2a5d48fb..f536be264 100644
--- a/java/core/src/main/java/com/google/protobuf/FieldSet.java
+++ b/java/core/src/main/java/com/google/protobuf/FieldSet.java
@@ -49,21 +49,19 @@ final class FieldSet> {
MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from);
}
- private static final int DEFAULT_FIELD_MAP_ARRAY_SIZE = 16;
-
private final SmallSortedMap fields;
private boolean isImmutable;
private boolean hasLazyField;
/** Construct a new FieldSet. */
private FieldSet() {
- this.fields = SmallSortedMap.newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE);
+ this.fields = SmallSortedMap.newFieldMap();
}
/** Construct an empty FieldSet. This is only used to initialize DEFAULT_INSTANCE. */
@SuppressWarnings("unused")
private FieldSet(final boolean dummy) {
- this(SmallSortedMap.newFieldMap(0));
+ this(SmallSortedMap.newFieldMap());
makeImmutable();
}
@@ -80,7 +78,7 @@ final class FieldSet> {
/** Get an immutable empty FieldSet. */
@SuppressWarnings("unchecked")
public static > FieldSet emptySet() {
- return DEFAULT_INSTANCE;
+ return (FieldSet) DEFAULT_INSTANCE;
}
/** Construct a new Builder. */
@@ -88,8 +86,7 @@ final class FieldSet> {
return new Builder();
}
- @SuppressWarnings("rawtypes")
- private static final FieldSet DEFAULT_INSTANCE = new FieldSet(true);
+ private static final FieldSet> DEFAULT_INSTANCE = new FieldSet<>(true);
/** Returns {@code true} if empty, {@code false} otherwise. */
boolean isEmpty() {
@@ -101,7 +98,8 @@ final class FieldSet> {
if (isImmutable) {
return;
}
- for (int i = 0; i < fields.getNumArrayEntries(); ++i) {
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; ++i) {
Entry entry = fields.getArrayEntryAt(i);
if (entry.getValue() instanceof GeneratedMessageLite) {
((GeneratedMessageLite, ?>) entry.getValue()).makeImmutable();
@@ -151,7 +149,8 @@ final class FieldSet> {
// We can't just call fields.clone because List objects in the map
// should not be shared.
FieldSet clone = FieldSet.newFieldSet();
- for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
Map.Entry entry = fields.getArrayEntryAt(i);
clone.setField(entry.getKey(), entry.getValue());
}
@@ -173,7 +172,8 @@ final class FieldSet> {
/** Get a simple map containing all the fields. */
public Map getAllFields() {
if (hasLazyField) {
- SmallSortedMap result = cloneAllFieldsMap(fields, /* copyList */ false);
+ SmallSortedMap result =
+ cloneAllFieldsMap(fields, /* copyList= */ false, /* resolveLazyFields= */ true);
if (fields.isImmutable()) {
result.makeImmutable();
}
@@ -183,22 +183,23 @@ final class FieldSet> {
}
private static > SmallSortedMap cloneAllFieldsMap(
- SmallSortedMap fields, boolean copyList) {
- SmallSortedMap result = SmallSortedMap.newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE);
- for (int i = 0; i < fields.getNumArrayEntries(); i++) {
- cloneFieldEntry(result, fields.getArrayEntryAt(i), copyList);
+ SmallSortedMap fields, boolean copyList, boolean resolveLazyFields) {
+ SmallSortedMap result = SmallSortedMap.newFieldMap();
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
+ cloneFieldEntry(result, fields.getArrayEntryAt(i), copyList, resolveLazyFields);
}
for (Map.Entry entry : fields.getOverflowEntries()) {
- cloneFieldEntry(result, entry, copyList);
+ cloneFieldEntry(result, entry, copyList, resolveLazyFields);
}
return result;
}
private static > void cloneFieldEntry(
- Map map, Map.Entry entry, boolean copyList) {
+ Map map, Map.Entry entry, boolean copyList, boolean resolveLazyFields) {
T key = entry.getKey();
Object value = entry.getValue();
- if (value instanceof LazyField) {
+ if (resolveLazyFields && value instanceof LazyField) {
map.put(key, ((LazyField) value).getValue());
} else if (copyList && value instanceof List) {
map.put(key, new ArrayList<>((List>) value));
@@ -212,6 +213,10 @@ final class FieldSet> {
* library as it is not protected from mutation when fields is not immutable.
*/
public Iterator> iterator() {
+ // Avoid allocation in the common case of empty FieldSet.
+ if (isEmpty()) {
+ return Collections.emptyIterator();
+ }
if (hasLazyField) {
return new LazyIterator(fields.entrySet().iterator());
}
@@ -224,6 +229,10 @@ final class FieldSet> {
* fields is not immutable.
*/
Iterator> descendingIterator() {
+ // Avoid an allocation in the common case of empty FieldSet.
+ if (isEmpty()) {
+ return Collections.emptyIterator();
+ }
if (hasLazyField) {
return new LazyIterator(fields.descendingEntrySet().iterator());
}
@@ -419,7 +428,8 @@ final class FieldSet> {
* caller to check that all required fields are present.
*/
public boolean isInitialized() {
- for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
if (!isInitialized(fields.getArrayEntryAt(i))) {
return false;
}
@@ -432,12 +442,17 @@ final class FieldSet> {
return true;
}
+ // Avoid iterator allocation.
+ @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"})
private static > boolean isInitialized(
final Map.Entry entry) {
final T descriptor = entry.getKey();
if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
if (descriptor.isRepeated()) {
- for (final Object element : (List>) entry.getValue()) {
+ List> list = (List>) entry.getValue();
+ int listSize = list.size();
+ for (int i = 0; i < listSize; i++) {
+ Object element = list.get(i);
if (!isMessageFieldValueInitialized(element)) {
return false;
}
@@ -477,7 +492,8 @@ final class FieldSet> {
/** Like {@link Message.Builder#mergeFrom(Message)}, but merges from another {@link FieldSet}. */
public void mergeFrom(final FieldSet other) {
- for (int i = 0; i < other.fields.getNumArrayEntries(); i++) {
+ int n = other.fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
mergeFromField(other.fields.getArrayEntryAt(i));
}
for (final Map.Entry entry : other.fields.getOverflowEntries()) {
@@ -500,11 +516,12 @@ final class FieldSet> {
private void mergeFromField(final Map.Entry entry) {
final T descriptor = entry.getKey();
Object otherValue = entry.getValue();
- if (otherValue instanceof LazyField) {
- otherValue = ((LazyField) otherValue).getValue();
- }
+ boolean isLazyField = otherValue instanceof LazyField;
if (descriptor.isRepeated()) {
+ if (isLazyField) {
+ throw new IllegalStateException("Lazy fields can not be repeated");
+ }
Object value = getField(descriptor);
if (value == null) {
value = new ArrayList<>();
@@ -516,9 +533,17 @@ final class FieldSet> {
} else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
Object value = getField(descriptor);
if (value == null) {
+ // New field.
fields.put(descriptor, cloneIfMutable(otherValue));
+ if (isLazyField) {
+ hasLazyField = true;
+ }
} else {
- // Merge the messages.
+ // There is an existing field. Need to merge the messages.
+ if (otherValue instanceof LazyField) {
+ // Extract the actual value for lazy fields.
+ otherValue = ((LazyField) otherValue).getValue();
+ }
value =
descriptor
.internalMergeFrom(((MessageLite) value).toBuilder(), (MessageLite) otherValue)
@@ -526,6 +551,9 @@ final class FieldSet> {
fields.put(descriptor, value);
}
} else {
+ if (isLazyField) {
+ throw new IllegalStateException("Lazy fields must be message-valued");
+ }
fields.put(descriptor, cloneIfMutable(otherValue));
}
}
@@ -552,7 +580,8 @@ final class FieldSet> {
/** See {@link Message#writeTo(CodedOutputStream)}. */
public void writeTo(final CodedOutputStream output) throws IOException {
- for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
final Map.Entry entry = fields.getArrayEntryAt(i);
writeField(entry.getKey(), entry.getValue(), output);
}
@@ -563,7 +592,8 @@ final class FieldSet> {
/** Like {@link #writeTo} but uses MessageSet wire format. */
public void writeMessageSetTo(final CodedOutputStream output) throws IOException {
- for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
writeMessageSetTo(fields.getArrayEntryAt(i), output);
}
for (final Map.Entry entry : fields.getOverflowEntries()) {
@@ -697,6 +727,8 @@ final class FieldSet> {
}
/** Write a single field. */
+ // Avoid iterator allocation.
+ @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"})
public static void writeField(
final FieldDescriptorLite> descriptor, final Object value, final CodedOutputStream output)
throws IOException {
@@ -704,6 +736,7 @@ final class FieldSet> {
int number = descriptor.getNumber();
if (descriptor.isRepeated()) {
final List> valueList = (List>) value;
+ int valueListSize = valueList.size();
if (descriptor.isPacked()) {
if (valueList.isEmpty()) {
// The tag should not be written for empty packed fields.
@@ -712,16 +745,19 @@ final class FieldSet> {
output.writeTag(number, WireFormat.WIRETYPE_LENGTH_DELIMITED);
// Compute the total data size so the length can be written.
int dataSize = 0;
- for (final Object element : valueList) {
+ for (int i = 0; i < valueListSize; i++) {
+ Object element = valueList.get(i);
dataSize += computeElementSizeNoTag(type, element);
}
output.writeUInt32NoTag(dataSize);
// Write the data itself, without any tags.
- for (final Object element : valueList) {
+ for (int i = 0; i < valueListSize; i++) {
+ Object element = valueList.get(i);
writeElementNoTag(output, type, element);
}
} else {
- for (final Object element : valueList) {
+ for (int i = 0; i < valueListSize; i++) {
+ Object element = valueList.get(i);
writeElement(output, type, number, element);
}
}
@@ -740,7 +776,8 @@ final class FieldSet> {
*/
public int getSerializedSize() {
int size = 0;
- for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
final Map.Entry entry = fields.getArrayEntryAt(i);
size += computeFieldSize(entry.getKey(), entry.getValue());
}
@@ -753,7 +790,8 @@ final class FieldSet> {
/** Like {@link #getSerializedSize} but uses MessageSet wire format. */
public int getMessageSetSerializedSize() {
int size = 0;
- for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ int n = fields.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
size += getMessageSetSerializedSize(fields.getArrayEntryAt(i));
}
for (final Map.Entry entry : fields.getOverflowEntries()) {
@@ -870,17 +908,21 @@ final class FieldSet> {
}
/** Compute the number of bytes needed to encode a particular field. */
+ // Avoid iterator allocation.
+ @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"})
public static int computeFieldSize(final FieldDescriptorLite> descriptor, final Object value) {
WireFormat.FieldType type = descriptor.getLiteType();
int number = descriptor.getNumber();
if (descriptor.isRepeated()) {
List> valueList = (List>) value;
+ int valueListSize = valueList.size();
if (descriptor.isPacked()) {
if (valueList.isEmpty()) {
return 0;
}
int dataSize = 0;
- for (final Object element : valueList) {
+ for (int i = 0; i < valueListSize; i++) {
+ Object element = valueList.get(i);
dataSize += computeElementSizeNoTag(type, element);
}
return dataSize
@@ -888,7 +930,8 @@ final class FieldSet> {
+ CodedOutputStream.computeUInt32SizeNoTag(dataSize);
} else {
int size = 0;
- for (final Object element : valueList) {
+ for (int i = 0; i < valueListSize; i++) {
+ Object element = valueList.get(i);
size += computeElementSize(type, number, element);
}
return size;
@@ -910,7 +953,7 @@ final class FieldSet> {
private boolean hasNestedBuilders;
private Builder() {
- this(SmallSortedMap.newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE));
+ this(SmallSortedMap.newFieldMap());
}
private Builder(SmallSortedMap fields) {
@@ -946,7 +989,8 @@ final class FieldSet> {
SmallSortedMap fieldsForBuild = fields;
if (hasNestedBuilders) {
// Make a copy of the fields map with all Builders replaced by Message.
- fieldsForBuild = cloneAllFieldsMap(fields, /* copyList */ false);
+ fieldsForBuild =
+ cloneAllFieldsMap(fields, /* copyList= */ false, /* resolveLazyFields= */ false);
replaceBuilders(fieldsForBuild, partial);
}
FieldSet fieldSet = new FieldSet<>(fieldsForBuild);
@@ -956,7 +1000,8 @@ final class FieldSet> {
private static > void replaceBuilders(
SmallSortedMap fieldMap, boolean partial) {
- for (int i = 0; i < fieldMap.getNumArrayEntries(); i++) {
+ int n = fieldMap.getNumArrayEntries(); // Optimisation: hoist out of hot loop.
+ for (int i = 0; i < n; i++) {
replaceBuilders(fieldMap.getArrayEntryAt(i), partial);
}
for (Map.Entry entry : fieldMap.getOverflowEntries()) {
@@ -1018,7 +1063,10 @@ final class FieldSet> {
/** Returns a new Builder using the fields from {@code fieldSet}. */
public static > Builder fromFieldSet(FieldSet fieldSet) {
- Builder builder = new Builder(cloneAllFieldsMap(fieldSet.fields, /* copyList */ true));
+ Builder builder =
+ new Builder(
+ cloneAllFieldsMap(
+ fieldSet.fields, /* copyList= */ true, /* resolveLazyFields= */ false));
builder.hasLazyField = fieldSet.hasLazyField;
return builder;
}
@@ -1028,7 +1076,8 @@ final class FieldSet> {
/** Get a simple map containing all the fields. */
public Map getAllFields() {
if (hasLazyField) {
- SmallSortedMap result = cloneAllFieldsMap(fields, /* copyList */ false);
+ SmallSortedMap result =
+ cloneAllFieldsMap(fields, /* copyList= */ false, /* resolveLazyFields= */ true);
if (fields.isImmutable()) {
result.makeImmutable();
} else {
@@ -1069,7 +1118,7 @@ final class FieldSet> {
private void ensureIsMutable() {
if (!isMutable) {
- fields = cloneAllFieldsMap(fields, /* copyList */ true);
+ fields = cloneAllFieldsMap(fields, /* copyList= */ true, /* resolveLazyFields= */ false);
isMutable = true;
}
}
@@ -1078,7 +1127,8 @@ final class FieldSet> {
* Useful for implementing {@link Message.Builder#setField(Descriptors.FieldDescriptor,
* Object)}.
*/
- @SuppressWarnings({"unchecked", "rawtypes"})
+ // Avoid iterator allocation.
+ @SuppressWarnings({"unchecked", "ForeachList", "ForeachListWithUserVar"})
public void setField(final T descriptor, Object value) {
ensureIsMutable();
if (descriptor.isRepeated()) {
@@ -1089,8 +1139,10 @@ final class FieldSet> {
// 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((List) value);
- for (final Object element : newList) {
+ final List