public void addPainlessMethod()

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)
                    + "]"
            );
        }
    }