1414
1515package 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 ;
2317import 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 ;
2718import com .google .protobuf .Message ;
19+ import com .google .protobuf .MessageLite ;
2820import dev .cel .common .annotations .Internal ;
29- import java .lang .reflect .InvocationTargetException ;
30- import java .util .ArrayDeque ;
31- import java .util .Map ;
3221import java .util .Optional ;
33- import java .util .concurrent .ConcurrentHashMap ;
3422
3523/**
3624 * Singleton factory for creating default messages from a protobuf descriptor.
3927 */
4028@ Internal
4129public 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