Skip to content

Commit 711cd1a

Browse files
l46kokcopybara-github
authored andcommitted
Add DefaultInstanceMessageLiteFactory
PiperOrigin-RevId: 734716485
1 parent 1ccf1b9 commit 711cd1a

8 files changed

Lines changed: 377 additions & 196 deletions

File tree

common/internal/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ java_library(
5757
exports = ["//common/src/main/java/dev/cel/common/internal:default_instance_message_factory"],
5858
)
5959

60+
java_library(
61+
name = "default_instance_message_lite_factory",
62+
exports = ["//common/src/main/java/dev/cel/common/internal:default_instance_message_lite_factory"],
63+
)
64+
6065
java_library(
6166
name = "well_known_proto",
6267
exports = ["//common/src/main/java/dev/cel/common/internal:well_known_proto"],

common/src/main/java/dev/cel/common/internal/BUILD.bazel

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,25 @@ java_library(
136136
tags = [
137137
],
138138
deps = [
139+
":default_instance_message_lite_factory",
140+
":proto_java_qualified_names",
139141
"//common/annotations",
140-
"@maven//:com_google_guava_guava",
141142
"@maven//:com_google_protobuf_protobuf_java",
142143
],
143144
)
144145

145-
# keep sorted
146+
java_library(
147+
name = "default_instance_message_lite_factory",
148+
srcs = ["DefaultInstanceMessageLiteFactory.java"],
149+
tags = [
150+
],
151+
deps = [
152+
":reflection_util",
153+
"//common/annotations",
154+
"@maven//:com_google_guava_guava",
155+
"@maven//:com_google_protobuf_protobuf_java",
156+
],
157+
)
146158

147159
java_library(
148160
name = "dynamic_proto",
@@ -274,3 +286,22 @@ java_library(
274286
"@maven//:com_google_re2j_re2j",
275287
],
276288
)
289+
290+
java_library(
291+
name = "proto_java_qualified_names",
292+
srcs = ["ProtoJavaQualifiedNames.java"],
293+
tags = [
294+
],
295+
deps = [
296+
"@maven//:com_google_guava_guava",
297+
"@maven//:com_google_protobuf_protobuf_java",
298+
],
299+
)
300+
301+
java_library(
302+
name = "reflection_util",
303+
srcs = ["ReflectionUtil.java"],
304+
deps = [
305+
"//common/annotations",
306+
],
307+
)

common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java

Lines changed: 18 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,11 @@
1414

1515
package dev.cel.common.internal;
1616

17-
import com.google.common.annotations.VisibleForTesting;
18-
import com.google.common.base.CaseFormat;
19-
import com.google.common.base.Joiner;
20-
import com.google.common.base.Strings;
21-
import com.google.common.io.Files;
22-
import com.google.protobuf.DescriptorProtos.FileOptions;
2317
import com.google.protobuf.Descriptors.Descriptor;
24-
import com.google.protobuf.Descriptors.EnumDescriptor;
25-
import com.google.protobuf.Descriptors.FileDescriptor;
26-
import com.google.protobuf.Descriptors.ServiceDescriptor;
2718
import com.google.protobuf.Message;
19+
import com.google.protobuf.MessageLite;
2820
import dev.cel.common.annotations.Internal;
29-
import java.lang.reflect.InvocationTargetException;
30-
import java.util.ArrayDeque;
31-
import java.util.Map;
3221
import java.util.Optional;
33-
import java.util.concurrent.ConcurrentHashMap;
3422

3523
/**
3624
* Singleton factory for creating default messages from a protobuf descriptor.
@@ -39,19 +27,11 @@
3927
*/
4028
@Internal
4129
public final class DefaultInstanceMessageFactory {
42-
43-
// Controls how many times we should recursively inspect a nested message for building fully
44-
// qualified java class name before aborting.
45-
public static final int SAFE_RECURSE_LIMIT = 50;
46-
47-
private static final DefaultInstanceMessageFactory instance = new DefaultInstanceMessageFactory();
48-
49-
private final Map<String, LazyGeneratedMessageDefaultInstance> messageByDescriptorName =
50-
new ConcurrentHashMap<>();
30+
private static final DefaultInstanceMessageFactory INSTANCE = new DefaultInstanceMessageFactory();
5131

5232
/** Gets a single instance of this MessageFactory */
5333
public static DefaultInstanceMessageFactory getInstance() {
54-
return instance;
34+
return INSTANCE;
5535
}
5636

5737
/**
@@ -63,182 +43,29 @@ public static DefaultInstanceMessageFactory getInstance() {
6343
* descriptor class isn't loaded in the binary.
6444
*/
6545
public Optional<Message> getPrototype(Descriptor descriptor) {
66-
String descriptorName = descriptor.getFullName();
67-
LazyGeneratedMessageDefaultInstance lazyDefaultInstance =
68-
messageByDescriptorName.computeIfAbsent(
69-
descriptorName,
70-
(unused) ->
71-
new LazyGeneratedMessageDefaultInstance(
72-
getFullyQualifiedJavaClassName(descriptor)));
73-
74-
Message defaultInstance = lazyDefaultInstance.getDefaultInstance();
46+
MessageLite defaultInstance =
47+
DefaultInstanceMessageLiteFactory.getInstance()
48+
.getPrototype(
49+
descriptor.getFullName(),
50+
ProtoJavaQualifiedNames.getFullyQualifiedJavaClassName(descriptor))
51+
.orElse(null);
7552
if (defaultInstance == null) {
7653
return Optional.empty();
7754
}
78-
// Reference equality is intended. We want to make sure the descriptors are equal
79-
// to guarantee types to be hermetic if linked types is disabled.
80-
if (defaultInstance.getDescriptorForType() != descriptor) {
81-
return Optional.empty();
82-
}
83-
return Optional.of(defaultInstance);
84-
}
85-
86-
/**
87-
* Retrieves the full Java class name from the given descriptor
88-
*
89-
* @return fully qualified class name.
90-
* <p>Example 1: dev.cel.expr.Value
91-
* <p>Example 2: com.google.rpc.context.AttributeContext$Resource (Nested classes)
92-
* <p>Example 3: com.google.api.expr.cel.internal.testdata$SingleFileProto$SingleFile$Path
93-
* (Nested class with java multiple files disabled)
94-
*/
95-
private String getFullyQualifiedJavaClassName(Descriptor descriptor) {
96-
StringBuilder fullClassName = new StringBuilder();
9755

98-
fullClassName.append(getJavaPackageName(descriptor));
99-
100-
String javaOuterClass = getJavaOuterClassName(descriptor);
101-
if (!Strings.isNullOrEmpty(javaOuterClass)) {
102-
fullClassName.append(javaOuterClass).append("$");
103-
}
104-
105-
// Recursively build the target class name in case if the message is nested.
106-
ArrayDeque<String> classNames = new ArrayDeque<>();
107-
Descriptor d = descriptor;
108-
109-
int recurseCount = 0;
110-
while (d != null) {
111-
classNames.push(d.getName());
112-
d = d.getContainingType();
113-
recurseCount++;
114-
if (recurseCount >= SAFE_RECURSE_LIMIT) {
115-
throw new IllegalStateException(
116-
String.format(
117-
"Recursion limit of %d hit while inspecting descriptor: %s",
118-
SAFE_RECURSE_LIMIT, descriptor.getFullName()));
119-
}
120-
}
121-
122-
Joiner.on("$").appendTo(fullClassName, classNames);
123-
124-
return fullClassName.toString();
125-
}
126-
127-
/**
128-
* Gets the java package name from the descriptor. See
129-
* https://developers.google.com/protocol-buffers/docs/reference/java-generated#package for rules
130-
* on package name generation
131-
*/
132-
private String getJavaPackageName(Descriptor descriptor) {
133-
FileOptions options = descriptor.getFile().getOptions();
134-
StringBuilder javaPackageName = new StringBuilder();
135-
if (options.hasJavaPackage()) {
136-
javaPackageName.append(descriptor.getFile().getOptions().getJavaPackage()).append(".");
137-
} else {
138-
javaPackageName
139-
// CEL-Internal-1
140-
.append(descriptor.getFile().getPackage())
141-
.append(".");
56+
if (!(defaultInstance instanceof Message)) {
57+
throw new IllegalArgumentException(
58+
"Expected a full protobuf message, but got: " + defaultInstance.getClass());
14259
}
14360

144-
// CEL-Internal-2
61+
Message fullMessage = (Message) defaultInstance;
14562

146-
return javaPackageName.toString();
147-
}
148-
149-
/**
150-
* Gets a wrapping outer class name from the descriptor. The outer class name differs depending on
151-
* the proto options set. See
152-
* https://developers.google.com/protocol-buffers/docs/reference/java-generated#invocation
153-
*/
154-
private String getJavaOuterClassName(Descriptor descriptor) {
155-
FileOptions options = descriptor.getFile().getOptions();
156-
157-
if (options.getJavaMultipleFiles()) {
158-
// If java_multiple_files is enabled, protoc does not generate a wrapper outer class
159-
return "";
160-
}
161-
162-
if (options.hasJavaOuterClassname()) {
163-
return options.getJavaOuterClassname();
164-
} else {
165-
// If an outer class name is not explicitly set, the name is converted into
166-
// Pascal case based on the snake cased file name
167-
// Ex: messages_proto.proto becomes MessagesProto
168-
String protoFileNameWithoutExtension =
169-
Files.getNameWithoutExtension(descriptor.getFile().getFullName());
170-
String outerClassName =
171-
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, protoFileNameWithoutExtension);
172-
if (hasConflictingClassName(descriptor.getFile(), outerClassName)) {
173-
outerClassName += "OuterClass";
174-
}
175-
return outerClassName;
176-
}
177-
}
178-
179-
private boolean hasConflictingClassName(FileDescriptor file, String name) {
180-
for (EnumDescriptor enumDesc : file.getEnumTypes()) {
181-
if (name.equals(enumDesc.getName())) {
182-
return true;
183-
}
184-
}
185-
for (ServiceDescriptor serviceDesc : file.getServices()) {
186-
if (name.equals(serviceDesc.getName())) {
187-
return true;
188-
}
189-
}
190-
for (Descriptor messageDesc : file.getMessageTypes()) {
191-
if (name.equals(messageDesc.getName())) {
192-
return true;
193-
}
194-
}
195-
return false;
196-
}
197-
198-
/** A placeholder to lazily load the generated messages' defaultInstances. */
199-
private static final class LazyGeneratedMessageDefaultInstance {
200-
private final String fullClassName;
201-
private volatile Message defaultInstance = null;
202-
private volatile boolean loaded = false;
203-
204-
public LazyGeneratedMessageDefaultInstance(String fullClassName) {
205-
this.fullClassName = fullClassName;
206-
}
207-
208-
public Message getDefaultInstance() {
209-
if (!loaded) {
210-
synchronized (this) {
211-
if (!loaded) {
212-
loadDefaultInstance();
213-
loaded = true;
214-
}
215-
}
216-
}
217-
return defaultInstance;
218-
}
219-
220-
private void loadDefaultInstance() {
221-
try {
222-
defaultInstance =
223-
(Message) Class.forName(fullClassName).getMethod("getDefaultInstance").invoke(null);
224-
} catch (IllegalAccessException | InvocationTargetException e) {
225-
throw new LinkageError(
226-
String.format("getDefaultInstance for class: %s failed.", fullClassName), e);
227-
} catch (NoSuchMethodException e) {
228-
throw new LinkageError(
229-
String.format("getDefaultInstance method does not exist in class: %s.", fullClassName),
230-
e);
231-
} catch (ClassNotFoundException e) {
232-
// The class may not exist in some instances (Ex: evaluating a checked expression from a
233-
// cached source).
234-
}
63+
// Reference equality is intended. We want to make sure the descriptors are equal
64+
// to guarantee types to be hermetic if linked types is disabled.
65+
if (fullMessage.getDescriptorForType() != descriptor) {
66+
return Optional.empty();
23567
}
236-
}
237-
238-
/** Clears the descriptor map. This should not be used outside testing. */
239-
@VisibleForTesting
240-
void resetDescriptorMapForTesting() {
241-
messageByDescriptorName.clear();
68+
return Optional.of(fullMessage);
24269
}
24370

24471
private DefaultInstanceMessageFactory() {}

0 commit comments

Comments
 (0)