in modules/lang-painless/src/main/java/org/opensearch/painless/lookup/PainlessLookupBuilder.java [625:881]
public void addPainlessMethod(
Class<?> targetClass,
Class<?> augmentedClass,
String methodName,
Class<?> returnType,
List<Class<?>> typeParameters,
Map<Class<?>, Object> annotations
) {
Objects.requireNonNull(targetClass);
Objects.requireNonNull(methodName);
Objects.requireNonNull(returnType);
Objects.requireNonNull(typeParameters);
if (targetClass == def.class) {
throw new IllegalArgumentException("cannot add method to reserved class [" + DEF_CLASS_NAME + "]");
}
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
throw new IllegalArgumentException(
"invalid method name [" + methodName + "] for target class [" + targetCanonicalClassName + "]."
);
}
PainlessClassBuilder painlessClassBuilder = classesToPainlessClassBuilders.get(targetClass);
if (painlessClassBuilder == null) {
throw new IllegalArgumentException(
"target class ["
+ targetCanonicalClassName
+ "] not found for method "
+ "[["
+ targetCanonicalClassName
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "]"
);
}
int typeParametersSize = typeParameters.size();
int augmentedParameterOffset = augmentedClass == null ? 0 : 1;
List<Class<?>> javaTypeParameters = new ArrayList<>(typeParametersSize + augmentedParameterOffset);
if (augmentedClass != null) {
javaTypeParameters.add(targetClass);
}
for (Class<?> typeParameter : typeParameters) {
if (isValidType(typeParameter) == false) {
throw new IllegalArgumentException(
"type parameter ["
+ typeToCanonicalTypeName(typeParameter)
+ "] "
+ "not found for method [["
+ targetCanonicalClassName
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "]"
);
}
javaTypeParameters.add(typeToJavaType(typeParameter));
}
if (isValidType(returnType) == false) {
throw new IllegalArgumentException(
"return type ["
+ typeToCanonicalTypeName(returnType)
+ "] not found for method "
+ "[["
+ targetCanonicalClassName
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "]"
);
}
Method javaMethod;
if (augmentedClass == null) {
try {
javaMethod = targetClass.getMethod(methodName, javaTypeParameters.toArray(new Class<?>[typeParametersSize]));
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException(
"reflection object not found for method [["
+ targetCanonicalClassName
+ "], "
+ "["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "]",
nsme
);
}
} else {
try {
javaMethod = augmentedClass.getMethod(methodName, javaTypeParameters.toArray(new Class<?>[typeParametersSize]));
if (Modifier.isStatic(javaMethod.getModifiers()) == false) {
throw new IllegalArgumentException(
"method [["
+ targetCanonicalClassName
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "] with augmented class "
+ "["
+ typeToCanonicalTypeName(augmentedClass)
+ "] must be static"
);
}
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException(
"reflection object not found for method "
+ "[["
+ targetCanonicalClassName
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "] "
+ "with augmented class ["
+ typeToCanonicalTypeName(augmentedClass)
+ "]",
nsme
);
}
}
// injections alter the type parameters required for the user to call this method, since some are injected by compiler
if (annotations.containsKey(InjectConstantAnnotation.class)) {
int numInjections = ((InjectConstantAnnotation) annotations.get(InjectConstantAnnotation.class)).injects.size();
if (numInjections > 0) {
typeParameters.subList(0, numInjections).clear();
}
typeParametersSize = typeParameters.size();
}
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
throw new IllegalArgumentException(
"return type ["
+ typeToCanonicalTypeName(javaMethod.getReturnType())
+ "] "
+ "does not match the specified returned type ["
+ typeToCanonicalTypeName(returnType)
+ "] "
+ "for method [["
+ targetClass.getCanonicalName()
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "]"
);
}
MethodHandle methodHandle;
if (augmentedClass == null) {
try {
methodHandle = MethodHandles.publicLookup().in(targetClass).unreflect(javaMethod);
} catch (IllegalAccessException iae) {
throw new IllegalArgumentException(
"method handle not found for method "
+ "[["
+ targetClass.getCanonicalName()
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "]",
iae
);
}
} else {
try {
methodHandle = MethodHandles.publicLookup().in(augmentedClass).unreflect(javaMethod);
} catch (IllegalAccessException iae) {
throw new IllegalArgumentException(
"method handle not found for method "
+ "[["
+ targetClass.getCanonicalName()
+ "], ["
+ methodName
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "]"
+ "with augmented class ["
+ typeToCanonicalTypeName(augmentedClass)
+ "]",
iae
);
}
}
MethodType methodType = methodHandle.type();
boolean isStatic = augmentedClass == null && Modifier.isStatic(javaMethod.getModifiers());
String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);
PainlessMethod existingPainlessMethod = isStatic
? painlessClassBuilder.staticMethods.get(painlessMethodKey)
: painlessClassBuilder.methods.get(painlessMethodKey);
PainlessMethod newPainlessMethod = new PainlessMethod(
javaMethod,
targetClass,
returnType,
typeParameters,
methodHandle,
methodType,
annotations
);
if (existingPainlessMethod == null) {
newPainlessMethod = painlessMethodCache.computeIfAbsent(newPainlessMethod, key -> key);
if (isStatic) {
painlessClassBuilder.staticMethods.put(painlessMethodKey.intern(), newPainlessMethod);
} else {
painlessClassBuilder.methods.put(painlessMethodKey.intern(), newPainlessMethod);
}
} else if (newPainlessMethod.equals(existingPainlessMethod) == false) {
throw new IllegalArgumentException(
"cannot add methods with the same name and arity but are not equivalent for methods "
+ "[["
+ targetCanonicalClassName
+ "], ["
+ methodName
+ "], "
+ "["
+ typeToCanonicalTypeName(returnType)
+ "], "
+ typesToCanonicalTypeNames(typeParameters)
+ "] and "
+ "[["
+ targetCanonicalClassName
+ "], ["
+ methodName
+ "], "
+ "["
+ typeToCanonicalTypeName(existingPainlessMethod.returnType)
+ "], "
+ typesToCanonicalTypeNames(existingPainlessMethod.typeParameters)
+ "]"
);
}
}