From 3fcadb43fd90c11c6cd9c1d46456817e651e0a7b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 13 Mar 2025 10:56:38 -0700 Subject: [PATCH] Add DefaultInstanceMessageLiteFactory PiperOrigin-RevId: 736566507 --- common/internal/BUILD.bazel | 5 + .../java/dev/cel/common/internal/BUILD.bazel | 35 ++- .../DefaultInstanceMessageFactory.java | 209 ++---------------- .../DefaultInstanceMessageLiteFactory.java | 109 +++++++++ .../internal/ProtoJavaQualifiedNames.java | 156 +++++++++++++ .../cel/common/internal/ReflectionUtil.java | 52 +++++ .../java/dev/cel/common/internal/BUILD.bazel | 1 + .../DefaultInstanceMessageFactoryTest.java | 6 +- 8 files changed, 377 insertions(+), 196 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageLiteFactory.java create mode 100644 common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java create mode 100644 common/src/main/java/dev/cel/common/internal/ReflectionUtil.java diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index b328986cc..3ff22d570 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -57,6 +57,11 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/internal:default_instance_message_factory"], ) +java_library( + name = "default_instance_message_lite_factory", + exports = ["//common/src/main/java/dev/cel/common/internal:default_instance_message_lite_factory"], +) + java_library( name = "well_known_proto", exports = ["//common/src/main/java/dev/cel/common/internal:well_known_proto"], diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index be5fc29a5..af1214ff6 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -136,13 +136,25 @@ java_library( tags = [ ], deps = [ + ":default_instance_message_lite_factory", + ":proto_java_qualified_names", "//common/annotations", - "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", ], ) -# keep sorted +java_library( + name = "default_instance_message_lite_factory", + srcs = ["DefaultInstanceMessageLiteFactory.java"], + tags = [ + ], + deps = [ + ":reflection_util", + "//common/annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) java_library( name = "dynamic_proto", @@ -274,3 +286,22 @@ java_library( "@maven//:com_google_re2j_re2j", ], ) + +java_library( + name = "proto_java_qualified_names", + srcs = ["ProtoJavaQualifiedNames.java"], + tags = [ + ], + deps = [ + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "reflection_util", + srcs = ["ReflectionUtil.java"], + deps = [ + "//common/annotations", + ], +) diff --git a/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java b/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java index 1147da7ad..fcb0e7056 100644 --- a/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java +++ b/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java @@ -14,23 +14,11 @@ package dev.cel.common.internal; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.CaseFormat; -import com.google.common.base.Joiner; -import com.google.common.base.Strings; -import com.google.common.io.Files; -import com.google.protobuf.DescriptorProtos.FileOptions; import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.EnumDescriptor; -import com.google.protobuf.Descriptors.FileDescriptor; -import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.protobuf.Message; +import com.google.protobuf.MessageLite; import dev.cel.common.annotations.Internal; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayDeque; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; /** * Singleton factory for creating default messages from a protobuf descriptor. @@ -39,19 +27,11 @@ */ @Internal public final class DefaultInstanceMessageFactory { - - // Controls how many times we should recursively inspect a nested message for building fully - // qualified java class name before aborting. - public static final int SAFE_RECURSE_LIMIT = 50; - - private static final DefaultInstanceMessageFactory instance = new DefaultInstanceMessageFactory(); - - private final Map messageByDescriptorName = - new ConcurrentHashMap<>(); + private static final DefaultInstanceMessageFactory INSTANCE = new DefaultInstanceMessageFactory(); /** Gets a single instance of this MessageFactory */ public static DefaultInstanceMessageFactory getInstance() { - return instance; + return INSTANCE; } /** @@ -63,182 +43,29 @@ public static DefaultInstanceMessageFactory getInstance() { * descriptor class isn't loaded in the binary. */ public Optional getPrototype(Descriptor descriptor) { - String descriptorName = descriptor.getFullName(); - LazyGeneratedMessageDefaultInstance lazyDefaultInstance = - messageByDescriptorName.computeIfAbsent( - descriptorName, - (unused) -> - new LazyGeneratedMessageDefaultInstance( - getFullyQualifiedJavaClassName(descriptor))); - - Message defaultInstance = lazyDefaultInstance.getDefaultInstance(); + MessageLite defaultInstance = + DefaultInstanceMessageLiteFactory.getInstance() + .getPrototype( + descriptor.getFullName(), + ProtoJavaQualifiedNames.getFullyQualifiedJavaClassName(descriptor)) + .orElse(null); if (defaultInstance == null) { return Optional.empty(); } - // Reference equality is intended. We want to make sure the descriptors are equal - // to guarantee types to be hermetic if linked types is disabled. - if (defaultInstance.getDescriptorForType() != descriptor) { - return Optional.empty(); - } - return Optional.of(defaultInstance); - } - - /** - * Retrieves the full Java class name from the given descriptor - * - * @return fully qualified class name. - *

Example 1: dev.cel.expr.Value - *

Example 2: com.google.rpc.context.AttributeContext$Resource (Nested classes) - *

Example 3: com.google.api.expr.cel.internal.testdata$SingleFileProto$SingleFile$Path - * (Nested class with java multiple files disabled) - */ - private String getFullyQualifiedJavaClassName(Descriptor descriptor) { - StringBuilder fullClassName = new StringBuilder(); - fullClassName.append(getJavaPackageName(descriptor)); - - String javaOuterClass = getJavaOuterClassName(descriptor); - if (!Strings.isNullOrEmpty(javaOuterClass)) { - fullClassName.append(javaOuterClass).append("$"); - } - - // Recursively build the target class name in case if the message is nested. - ArrayDeque classNames = new ArrayDeque<>(); - Descriptor d = descriptor; - - int recurseCount = 0; - while (d != null) { - classNames.push(d.getName()); - d = d.getContainingType(); - recurseCount++; - if (recurseCount >= SAFE_RECURSE_LIMIT) { - throw new IllegalStateException( - String.format( - "Recursion limit of %d hit while inspecting descriptor: %s", - SAFE_RECURSE_LIMIT, descriptor.getFullName())); - } - } - - Joiner.on("$").appendTo(fullClassName, classNames); - - return fullClassName.toString(); - } - - /** - * Gets the java package name from the descriptor. See - * https://developers.google.com/protocol-buffers/docs/reference/java-generated#package for rules - * on package name generation - */ - private String getJavaPackageName(Descriptor descriptor) { - FileOptions options = descriptor.getFile().getOptions(); - StringBuilder javaPackageName = new StringBuilder(); - if (options.hasJavaPackage()) { - javaPackageName.append(descriptor.getFile().getOptions().getJavaPackage()).append("."); - } else { - javaPackageName - // CEL-Internal-1 - .append(descriptor.getFile().getPackage()) - .append("."); + if (!(defaultInstance instanceof Message)) { + throw new IllegalArgumentException( + "Expected a full protobuf message, but got: " + defaultInstance.getClass()); } - // CEL-Internal-2 + Message fullMessage = (Message) defaultInstance; - return javaPackageName.toString(); - } - - /** - * Gets a wrapping outer class name from the descriptor. The outer class name differs depending on - * the proto options set. See - * https://developers.google.com/protocol-buffers/docs/reference/java-generated#invocation - */ - private String getJavaOuterClassName(Descriptor descriptor) { - FileOptions options = descriptor.getFile().getOptions(); - - if (options.getJavaMultipleFiles()) { - // If java_multiple_files is enabled, protoc does not generate a wrapper outer class - return ""; - } - - if (options.hasJavaOuterClassname()) { - return options.getJavaOuterClassname(); - } else { - // If an outer class name is not explicitly set, the name is converted into - // Pascal case based on the snake cased file name - // Ex: messages_proto.proto becomes MessagesProto - String protoFileNameWithoutExtension = - Files.getNameWithoutExtension(descriptor.getFile().getFullName()); - String outerClassName = - CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, protoFileNameWithoutExtension); - if (hasConflictingClassName(descriptor.getFile(), outerClassName)) { - outerClassName += "OuterClass"; - } - return outerClassName; - } - } - - private boolean hasConflictingClassName(FileDescriptor file, String name) { - for (EnumDescriptor enumDesc : file.getEnumTypes()) { - if (name.equals(enumDesc.getName())) { - return true; - } - } - for (ServiceDescriptor serviceDesc : file.getServices()) { - if (name.equals(serviceDesc.getName())) { - return true; - } - } - for (Descriptor messageDesc : file.getMessageTypes()) { - if (name.equals(messageDesc.getName())) { - return true; - } - } - return false; - } - - /** A placeholder to lazily load the generated messages' defaultInstances. */ - private static final class LazyGeneratedMessageDefaultInstance { - private final String fullClassName; - private volatile Message defaultInstance = null; - private volatile boolean loaded = false; - - public LazyGeneratedMessageDefaultInstance(String fullClassName) { - this.fullClassName = fullClassName; - } - - public Message getDefaultInstance() { - if (!loaded) { - synchronized (this) { - if (!loaded) { - loadDefaultInstance(); - loaded = true; - } - } - } - return defaultInstance; - } - - private void loadDefaultInstance() { - try { - defaultInstance = - (Message) Class.forName(fullClassName).getMethod("getDefaultInstance").invoke(null); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new LinkageError( - String.format("getDefaultInstance for class: %s failed.", fullClassName), e); - } catch (NoSuchMethodException e) { - throw new LinkageError( - String.format("getDefaultInstance method does not exist in class: %s.", fullClassName), - e); - } catch (ClassNotFoundException e) { - // The class may not exist in some instances (Ex: evaluating a checked expression from a - // cached source). - } + // Reference equality is intended. We want to make sure the descriptors are equal + // to guarantee types to be hermetic if linked types is disabled. + if (fullMessage.getDescriptorForType() != descriptor) { + return Optional.empty(); } - } - - /** Clears the descriptor map. This should not be used outside testing. */ - @VisibleForTesting - void resetDescriptorMapForTesting() { - messageByDescriptorName.clear(); + return Optional.of(fullMessage); } private DefaultInstanceMessageFactory() {} diff --git a/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageLiteFactory.java b/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageLiteFactory.java new file mode 100644 index 000000000..8adb79248 --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageLiteFactory.java @@ -0,0 +1,109 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.common.annotations.VisibleForTesting; +import com.google.protobuf.MessageLite; +import dev.cel.common.annotations.Internal; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Singleton factory for creating default messages from a fully qualified protobuf type name and its + * java class name. + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +public final class DefaultInstanceMessageLiteFactory { + + private static final DefaultInstanceMessageLiteFactory INSTANCE = + new DefaultInstanceMessageLiteFactory(); + + private final Map messageByTypeName = + new ConcurrentHashMap<>(); + + /** Gets a single instance of this DefaultInstanceMessageLiteFactory */ + public static DefaultInstanceMessageLiteFactory getInstance() { + return INSTANCE; + } + + /** + * Creates a default instance of a protobuf message given a fully qualified type name. This is + * essentially the same as calling FooMessage.getDefaultInstance(), except reflection is + * leveraged. + * + * @return Default instance of a type. Returns an empty optional if the java class for the + * protobuf message isn't linked in the binary. + */ + public Optional getPrototype(String protoFqn, String protoJavaClassFqn) { + LazyGeneratedMessageDefaultInstance lazyDefaultInstance = + messageByTypeName.computeIfAbsent( + protoFqn, (unused) -> new LazyGeneratedMessageDefaultInstance(protoJavaClassFqn)); + + MessageLite defaultInstance = lazyDefaultInstance.getDefaultInstance(); + + return Optional.ofNullable(defaultInstance); + } + + /** A placeholder to lazily load the generated messages' defaultInstances. */ + private static final class LazyGeneratedMessageDefaultInstance { + private final String fullClassName; + private volatile MessageLite defaultInstance = null; + private volatile boolean loaded = false; + + public LazyGeneratedMessageDefaultInstance(String fullClassName) { + this.fullClassName = fullClassName; + } + + public MessageLite getDefaultInstance() { + if (!loaded) { + synchronized (this) { + if (!loaded) { + loadDefaultInstance(); + loaded = true; + } + } + } + return defaultInstance; + } + + private void loadDefaultInstance() { + Class clazz; + try { + clazz = Class.forName(fullClassName); + } catch (ClassNotFoundException e) { + // The class may not exist in cases where the java class for the generated message was not + // linked into the binary (Ex: evaluating a checked expression from a + // cached source), or a dynamic descriptor was explicitly used. CEL will return a dynamic + // message in such cases. + return; + } + + Method method = ReflectionUtil.getMethod(clazz, "getDefaultInstance"); + defaultInstance = (MessageLite) ReflectionUtil.invoke(method, null); + } + } + + /** Clears the descriptor map. This should not be used outside testing. */ + @VisibleForTesting + void resetTypeMap() { + messageByTypeName.clear(); + } + + private DefaultInstanceMessageLiteFactory() {} +} diff --git a/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java b/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java new file mode 100644 index 000000000..dc7e4f536 --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java @@ -0,0 +1,156 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.io.Files; +import com.google.protobuf.DescriptorProtos.FileOptions; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.GenericDescriptor; +import com.google.protobuf.Descriptors.ServiceDescriptor; +import java.util.ArrayDeque; + +/** Helper class for constructing a fully qualified Java class name from a protobuf descriptor. */ +final class ProtoJavaQualifiedNames { + // Controls how many times we should recursively inspect a nested message for building fully + // qualified java class name before aborting. + private static final int SAFE_RECURSE_LIMIT = 50; + + /** + * Retrieves the full Java class name from the given descriptor + * + * @return fully qualified class name. + *

Example 1: dev.cel.expr.Value + *

Example 2: com.google.rpc.context.AttributeContext$Resource (Nested classes) + *

Example 3: com.google.api.expr.cel.internal.testdata$SingleFileProto$SingleFile$Path + * (Nested class with java multiple files disabled) + */ + public static String getFullyQualifiedJavaClassName(Descriptor descriptor) { + return getFullyQualifiedJavaClassNameImpl(descriptor); + } + + private static String getFullyQualifiedJavaClassNameImpl(GenericDescriptor descriptor) { + StringBuilder fullClassName = new StringBuilder(); + + fullClassName.append(getJavaPackageName(descriptor.getFile())).append("."); + + String javaOuterClass = getJavaOuterClassName(descriptor.getFile()); + if (!Strings.isNullOrEmpty(javaOuterClass)) { + fullClassName.append(javaOuterClass).append("$"); + } + + // Recursively build the target class name in case if the message is nested. + ArrayDeque classNames = new ArrayDeque<>(); + GenericDescriptor d = descriptor; + + int recurseCount = 0; + while (d != null) { + classNames.push(d.getName()); + + if (d instanceof EnumDescriptor) { + d = ((EnumDescriptor) d).getContainingType(); + } else { + d = ((Descriptor) d).getContainingType(); + } + recurseCount++; + if (recurseCount >= SAFE_RECURSE_LIMIT) { + throw new IllegalStateException( + String.format( + "Recursion limit of %d hit while inspecting descriptor: %s", + SAFE_RECURSE_LIMIT, descriptor.getFullName())); + } + } + + Joiner.on("$").appendTo(fullClassName, classNames); + + return fullClassName.toString(); + } + + /** + * Gets the java package name from the descriptor. See + * https://developers.google.com/protocol-buffers/docs/reference/java-generated#package for rules + * on package name generation + */ + public static String getJavaPackageName(FileDescriptor fileDescriptor) { + FileOptions options = fileDescriptor.getFile().getOptions(); + StringBuilder javaPackageName = new StringBuilder(); + if (options.hasJavaPackage()) { + javaPackageName.append(fileDescriptor.getFile().getOptions().getJavaPackage()); + } else { + javaPackageName + // CEL-Internal-1 + .append(fileDescriptor.getPackage()); + } + + // CEL-Internal-2 + + return javaPackageName.toString(); + } + + /** + * Gets a wrapping outer class name from the descriptor. The outer class name differs depending on + * the proto options set. See + * https://developers.google.com/protocol-buffers/docs/reference/java-generated#invocation + */ + private static String getJavaOuterClassName(FileDescriptor descriptor) { + FileOptions options = descriptor.getOptions(); + + if (options.getJavaMultipleFiles()) { + // If java_multiple_files is enabled, protoc does not generate a wrapper outer class + return ""; + } + + if (options.hasJavaOuterClassname()) { + return options.getJavaOuterClassname(); + } else { + // If an outer class name is not explicitly set, the name is converted into + // Pascal case based on the snake cased file name + // Ex: messages_proto.proto becomes MessagesProto + String protoFileNameWithoutExtension = + Files.getNameWithoutExtension(descriptor.getFile().getFullName()); + String outerClassName = + CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, protoFileNameWithoutExtension); + if (hasConflictingClassName(descriptor.getFile(), outerClassName)) { + outerClassName += "OuterClass"; + } + return outerClassName; + } + } + + private static boolean hasConflictingClassName(FileDescriptor file, String name) { + for (EnumDescriptor enumDesc : file.getEnumTypes()) { + if (name.equals(enumDesc.getName())) { + return true; + } + } + for (ServiceDescriptor serviceDesc : file.getServices()) { + if (name.equals(serviceDesc.getName())) { + return true; + } + } + for (Descriptor messageDesc : file.getMessageTypes()) { + if (name.equals(messageDesc.getName())) { + return true; + } + } + return false; + } + + private ProtoJavaQualifiedNames() {} +} diff --git a/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java b/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java new file mode 100644 index 000000000..e513a446b --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java @@ -0,0 +1,52 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import dev.cel.common.annotations.Internal; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Utility class for invoking Java reflection. + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +public final class ReflectionUtil { + + public static Method getMethod(Class clazz, String methodName, Class... params) { + try { + return clazz.getMethod(methodName, params); + } catch (NoSuchMethodException e) { + throw new LinkageError( + String.format("method [%s] does not exist in class: [%s].", methodName, clazz.getName()), + e); + } + } + + public static Object invoke(Method method, Object object, Object... params) { + try { + return method.invoke(object, params); + } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { + throw new LinkageError( + String.format( + "method [%s] invocation failed on class [%s].", + method.getName(), method.getDeclaringClass()), + e); + } + } + + private ReflectionUtil() {} +} diff --git a/common/src/test/java/dev/cel/common/internal/BUILD.bazel b/common/src/test/java/dev/cel/common/internal/BUILD.bazel index d1fb8b8e2..025db96b8 100644 --- a/common/src/test/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/internal/BUILD.bazel @@ -21,6 +21,7 @@ java_library( "//common/internal:comparison_functions", "//common/internal:converter", "//common/internal:default_instance_message_factory", + "//common/internal:default_instance_message_lite_factory", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", "//common/internal:errors", diff --git a/common/src/test/java/dev/cel/common/internal/DefaultInstanceMessageFactoryTest.java b/common/src/test/java/dev/cel/common/internal/DefaultInstanceMessageFactoryTest.java index 4fc36f1b5..d6749a391 100644 --- a/common/src/test/java/dev/cel/common/internal/DefaultInstanceMessageFactoryTest.java +++ b/common/src/test/java/dev/cel/common/internal/DefaultInstanceMessageFactoryTest.java @@ -44,12 +44,12 @@ import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) -public final class DefaultInstanceMessageFactoryTest { +public class DefaultInstanceMessageFactoryTest { @Before public void setUp() { // Reset the statically initialized descriptor map to get clean test runs. - DefaultInstanceMessageFactory.getInstance().resetDescriptorMapForTesting(); + DefaultInstanceMessageLiteFactory.getInstance().resetTypeMap(); } private enum PrototypeDescriptorTestCase { @@ -112,7 +112,7 @@ public void getPrototype_cached_success(@TestParameter PrototypeDescriptorTestCa @Test public void getPrototype_concurrentAccess_doesNotThrow( - @TestParameter(valuesProvider = RepeatedTestProvider.class) int testRunIndex) + @TestParameter(valuesProvider = RepeatedTestProvider.class) int unusedTestRunIndex) throws Exception { // Arrange int threadCount = 10;