All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.painless.lookup.PainlessLookupBuilder Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you 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
 *
 *    http://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 org.elasticsearch.painless.lookup;

import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.painless.Def;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.WriterConstants;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.spi.WhitelistClass;
import org.elasticsearch.painless.spi.WhitelistClassBinding;
import org.elasticsearch.painless.spi.WhitelistConstructor;
import org.elasticsearch.painless.spi.WhitelistField;
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
import org.elasticsearch.painless.spi.WhitelistMethod;
import org.elasticsearch.painless.spi.annotation.InjectConstantAnnotation;
import org.elasticsearch.painless.spi.annotation.NoImportAnnotation;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.SecureClassLoader;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_BYTE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_CHARACTER_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_DOUBLE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_FLOAT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_INTEGER_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_LONG_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_SHORT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
import static org.elasticsearch.painless.WriterConstants.OBJECT_TYPE;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.DEF_CLASS_NAME;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessConstructorKey;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessFieldKey;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessMethodKey;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToJavaType;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCanonicalTypeNames;

public final class PainlessLookupBuilder {

    private static final class BridgeLoader extends SecureClassLoader {
        BridgeLoader(ClassLoader parent) {
            super(parent);
        }

        @Override
        public Class findClass(String name) throws ClassNotFoundException {
            return Def.class.getName().equals(name) ? Def.class : super.findClass(name);
        }

        Class defineBridge(String name, byte[] bytes) {
            return defineClass(name, bytes, 0, bytes.length, CODESOURCE);
        }
    }

    private static final CodeSource CODESOURCE;

    private static final Map     painlessConstructorCache     = new HashMap<>();
    private static final Map          painlessMethodCache          = new HashMap<>();
    private static final Map           painlessFieldCache           = new HashMap<>();
    private static final Map    painlessClassBindingCache    = new HashMap<>();
    private static final Map painlessInstanceBindingCache = new HashMap<>();
    private static final Map          painlessBridgeCache          = new HashMap<>();

    private static final Pattern CLASS_NAME_PATTERN  = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
    private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
    private static final Pattern FIELD_NAME_PATTERN  = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");

    static {
        try {
            CODESOURCE = new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[])null);
        } catch (MalformedURLException mue) {
            throw new RuntimeException(mue);
        }
    }

    public static PainlessLookup buildFromWhitelists(List whitelists) {
        PainlessLookupBuilder painlessLookupBuilder = new PainlessLookupBuilder();
        String origin = "internal error";

        try {
            for (Whitelist whitelist : whitelists) {
                for (WhitelistClass whitelistClass : whitelist.whitelistClasses) {
                    origin = whitelistClass.origin;
                    painlessLookupBuilder.addPainlessClass(
                            whitelist.classLoader, whitelistClass.javaClassName,
                            whitelistClass.painlessAnnotations.containsKey(NoImportAnnotation.class) == false);
                }
            }

            for (Whitelist whitelist : whitelists) {
                for (WhitelistClass whitelistClass : whitelist.whitelistClasses) {
                    String targetCanonicalClassName = whitelistClass.javaClassName.replace('$', '.');

                    for (WhitelistConstructor whitelistConstructor : whitelistClass.whitelistConstructors) {
                        origin = whitelistConstructor.origin;
                        painlessLookupBuilder.addPainlessConstructor(
                                targetCanonicalClassName, whitelistConstructor.canonicalTypeNameParameters,
                                whitelistConstructor.painlessAnnotations);
                    }

                    for (WhitelistMethod whitelistMethod : whitelistClass.whitelistMethods) {
                        origin = whitelistMethod.origin;
                        painlessLookupBuilder.addPainlessMethod(
                                whitelist.classLoader, targetCanonicalClassName, whitelistMethod.augmentedCanonicalClassName,
                                whitelistMethod.methodName, whitelistMethod.returnCanonicalTypeName,
                                whitelistMethod.canonicalTypeNameParameters, whitelistMethod.painlessAnnotations);
                    }

                    for (WhitelistField whitelistField : whitelistClass.whitelistFields) {
                        origin = whitelistField.origin;
                        painlessLookupBuilder.addPainlessField(
                                targetCanonicalClassName, whitelistField.fieldName, whitelistField.canonicalTypeNameParameter);
                    }
                }

                for (WhitelistMethod whitelistStatic : whitelist.whitelistImportedMethods) {
                    origin = whitelistStatic.origin;
                    painlessLookupBuilder.addImportedPainlessMethod(
                            whitelist.classLoader, whitelistStatic.augmentedCanonicalClassName,
                            whitelistStatic.methodName, whitelistStatic.returnCanonicalTypeName,
                            whitelistStatic.canonicalTypeNameParameters,
                            whitelistStatic.painlessAnnotations);
                }

                for (WhitelistClassBinding whitelistClassBinding : whitelist.whitelistClassBindings) {
                    origin = whitelistClassBinding.origin;
                    painlessLookupBuilder.addPainlessClassBinding(
                            whitelist.classLoader, whitelistClassBinding.targetJavaClassName, whitelistClassBinding.methodName,
                            whitelistClassBinding.returnCanonicalTypeName, whitelistClassBinding.canonicalTypeNameParameters,
                            whitelistClassBinding.painlessAnnotations);
                }

                for (WhitelistInstanceBinding whitelistInstanceBinding : whitelist.whitelistInstanceBindings) {
                    origin = whitelistInstanceBinding.origin;
                    painlessLookupBuilder.addPainlessInstanceBinding(
                            whitelistInstanceBinding.targetInstance, whitelistInstanceBinding.methodName,
                            whitelistInstanceBinding.returnCanonicalTypeName, whitelistInstanceBinding.canonicalTypeNameParameters);
                }
            }
        } catch (Exception exception) {
            throw new IllegalArgumentException("error loading whitelist(s) " + origin, exception);
        }

        return painlessLookupBuilder.build();
    }

    // javaClassNamesToClasses is all the classes that need to be available to the custom classloader
    // including classes used as part of imported methods and class bindings but not necessarily whitelisted
    // individually. The values of javaClassNamesToClasses are a superset of the values of
    // canonicalClassNamesToClasses.
    private final Map> javaClassNamesToClasses;
    // canonicalClassNamesToClasses is all the whitelisted classes available in a Painless script including
    // classes with imported canonical names but does not include classes from imported methods or class
    // bindings unless also whitelisted separately. The values of canonicalClassNamesToClasses are a subset
    // of the values of javaClassNamesToClasses.
    private final Map> canonicalClassNamesToClasses;
    private final Map, PainlessClassBuilder> classesToPainlessClassBuilders;

    private final Map painlessMethodKeysToImportedPainlessMethods;
    private final Map painlessMethodKeysToPainlessClassBindings;
    private final Map painlessMethodKeysToPainlessInstanceBindings;

    public PainlessLookupBuilder() {
        javaClassNamesToClasses = new HashMap<>();
        canonicalClassNamesToClasses = new HashMap<>();
        classesToPainlessClassBuilders = new HashMap<>();

        painlessMethodKeysToImportedPainlessMethods = new HashMap<>();
        painlessMethodKeysToPainlessClassBindings = new HashMap<>();
        painlessMethodKeysToPainlessInstanceBindings = new HashMap<>();
    }

    private Class canonicalTypeNameToType(String canonicalTypeName) {
        return PainlessLookupUtility.canonicalTypeNameToType(canonicalTypeName, canonicalClassNamesToClasses);
    }

    private boolean isValidType(Class type) {
        while (type.getComponentType() != null) {
            type = type.getComponentType();
        }

        return type == def.class || classesToPainlessClassBuilders.containsKey(type);
    }

    public void addPainlessClass(ClassLoader classLoader, String javaClassName, boolean importClassName) {
        Objects.requireNonNull(classLoader);
        Objects.requireNonNull(javaClassName);

        Class clazz;

        if      ("void".equals(javaClassName))    clazz = void.class;
        else if ("boolean".equals(javaClassName)) clazz = boolean.class;
        else if ("byte".equals(javaClassName))    clazz = byte.class;
        else if ("short".equals(javaClassName))   clazz = short.class;
        else if ("char".equals(javaClassName))    clazz = char.class;
        else if ("int".equals(javaClassName))     clazz = int.class;
        else if ("long".equals(javaClassName))    clazz = long.class;
        else if ("float".equals(javaClassName))   clazz = float.class;
        else if ("double".equals(javaClassName))  clazz = double.class;
        else {
            try {
                clazz = Class.forName(javaClassName, true, classLoader);
            } catch (ClassNotFoundException cnfe) {
                throw new IllegalArgumentException("class [" + javaClassName + "] not found", cnfe);
            }
        }

        addPainlessClass(clazz, importClassName);
    }

    public void addPainlessClass(Class clazz, boolean importClassName) {
        Objects.requireNonNull(clazz);
        //Matcher m = new Matcher();

        if (clazz == def.class) {
            throw new IllegalArgumentException("cannot add reserved class [" + DEF_CLASS_NAME + "]");
        }

        String canonicalClassName = typeToCanonicalTypeName(clazz);

        if (clazz.isArray()) {
            throw new IllegalArgumentException("cannot add array type [" + canonicalClassName + "] as a class");
        }

        if (CLASS_NAME_PATTERN.matcher(canonicalClassName).matches() == false) {
            throw new IllegalArgumentException("invalid class name [" + canonicalClassName + "]");
        }

        Class existingClass = javaClassNamesToClasses.get(clazz.getName());

        if (existingClass == null) {
            javaClassNamesToClasses.put(clazz.getName().intern(), clazz);
        } else if (existingClass != clazz) {
            throw new IllegalArgumentException("class [" + canonicalClassName + "] " +
                    "cannot represent multiple java classes with the same name from different class loaders");
        }

        existingClass = canonicalClassNamesToClasses.get(canonicalClassName);

        if (existingClass != null && existingClass != clazz) {
            throw new IllegalArgumentException("class [" + canonicalClassName + "] " +
                    "cannot represent multiple java classes with the same name from different class loaders");
        }

        PainlessClassBuilder existingPainlessClassBuilder = classesToPainlessClassBuilders.get(clazz);

        if (existingPainlessClassBuilder == null) {
            PainlessClassBuilder painlessClassBuilder = new PainlessClassBuilder();

            canonicalClassNamesToClasses.put(canonicalClassName.intern(), clazz);
            classesToPainlessClassBuilders.put(clazz, painlessClassBuilder);
        }

        String javaClassName = clazz.getName();
        String importedCanonicalClassName = javaClassName.substring(javaClassName.lastIndexOf('.') + 1).replace('$', '.');

        if (canonicalClassName.equals(importedCanonicalClassName)) {
            if (importClassName) {
                throw new IllegalArgumentException("must use no_import parameter on class [" + canonicalClassName + "] with no package");
            }
        } else {
            Class importedClass = canonicalClassNamesToClasses.get(importedCanonicalClassName);

            if (importedClass == null) {
                if (importClassName) {
                    if (existingPainlessClassBuilder != null) {
                        throw new IllegalArgumentException(
                                "inconsistent no_import parameter found for class [" + canonicalClassName + "]");
                    }

                    canonicalClassNamesToClasses.put(importedCanonicalClassName.intern(), clazz);
                }
            } else if (importedClass != clazz) {
                throw new IllegalArgumentException("imported class [" + importedCanonicalClassName + "] cannot represent multiple " +
                        "classes [" + canonicalClassName + "] and [" + typeToCanonicalTypeName(importedClass) + "]");
            } else if (importClassName == false) {
                throw new IllegalArgumentException("inconsistent no_import parameter found for class [" + canonicalClassName + "]");
            }
        }
    }

    public void addPainlessConstructor(String targetCanonicalClassName, List canonicalTypeNameParameters,
            Map, Object> annotations) {
        Objects.requireNonNull(targetCanonicalClassName);
        Objects.requireNonNull(canonicalTypeNameParameters);

        Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName);

        if (targetClass == null) {
            throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found" +
                    "for constructor [[" + targetCanonicalClassName + "], " + canonicalTypeNameParameters  + "]");
        }

        List> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());

        for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
            Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);

            if (typeParameter == null) {
                throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found " +
                        "for constructor [[" + targetCanonicalClassName + "], " + canonicalTypeNameParameters  + "]");
            }

            typeParameters.add(typeParameter);
        }

        addPainlessConstructor(targetClass, typeParameters, annotations);
    }

    public void addPainlessConstructor(Class targetClass, List> typeParameters, Map, Object> annotations) {
        Objects.requireNonNull(targetClass);
        Objects.requireNonNull(typeParameters);

        if (targetClass == def.class) {
            throw new IllegalArgumentException("cannot add constructor to reserved class [" + DEF_CLASS_NAME + "]");
        }

        String targetCanonicalClassName = targetClass.getCanonicalName();
        PainlessClassBuilder painlessClassBuilder = classesToPainlessClassBuilders.get(targetClass);

        if (painlessClassBuilder == null) {
            throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found" +
                    "for constructor [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters)  + "]");
        }

        int typeParametersSize = typeParameters.size();
        List> javaTypeParameters = new ArrayList<>(typeParametersSize);

        for (Class typeParameter : typeParameters) {
            if (isValidType(typeParameter) == false) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
                        "for constructor [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
            }

            javaTypeParameters.add(typeToJavaType(typeParameter));
        }

        Constructor javaConstructor;

        try {
            javaConstructor = targetClass.getConstructor(javaTypeParameters.toArray(new Class[typeParametersSize]));
        } catch (NoSuchMethodException nsme) {
            throw new IllegalArgumentException("reflection object not found for constructor " +
                    "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]", nsme);
        }

        MethodHandle methodHandle;

        try {
            methodHandle = MethodHandles.publicLookup().in(targetClass).unreflectConstructor(javaConstructor);
        } catch (IllegalAccessException iae) {
            throw new IllegalArgumentException("method handle not found for constructor " +
                    "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]", iae);
        }

        MethodType methodType = methodHandle.type();

        String painlessConstructorKey = buildPainlessConstructorKey(typeParametersSize);
        PainlessConstructor existingPainlessConstructor = painlessClassBuilder.constructors.get(painlessConstructorKey);
        PainlessConstructor newPainlessConstructor = new PainlessConstructor(javaConstructor, typeParameters, methodHandle, methodType,
            annotations);

        if (existingPainlessConstructor == null) {
            newPainlessConstructor = painlessConstructorCache.computeIfAbsent(newPainlessConstructor, key -> key);
            painlessClassBuilder.constructors.put(painlessConstructorKey.intern(), newPainlessConstructor);
        } else if (newPainlessConstructor.equals(existingPainlessConstructor) == false){
            throw new IllegalArgumentException("cannot add constructors with the same arity but are not equivalent for constructors " +
                    "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] and " +
                    "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(existingPainlessConstructor.typeParameters) + "]");
        }
    }

    public void addPainlessMethod(ClassLoader classLoader, String targetCanonicalClassName, String augmentedCanonicalClassName,
            String methodName, String returnCanonicalTypeName, List canonicalTypeNameParameters,
            Map, Object> annotations) {

        Objects.requireNonNull(classLoader);
        Objects.requireNonNull(targetCanonicalClassName);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnCanonicalTypeName);
        Objects.requireNonNull(canonicalTypeNameParameters);

        Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName);

        if (targetClass == null) {
            throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for method " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
        }

        Class augmentedClass = null;

        if (augmentedCanonicalClassName != null) {
            try {
                augmentedClass = Class.forName(augmentedCanonicalClassName, true, classLoader);
            } catch (ClassNotFoundException cnfe) {
                throw new IllegalArgumentException("augmented class [" + augmentedCanonicalClassName + "] not found for method " +
                        "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]", cnfe);
            }
        }

        List> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());

        for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
            Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);

            if (typeParameter == null) {
                throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for method " +
                        "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
            }

            typeParameters.add(typeParameter);
        }

        Class returnType = canonicalTypeNameToType(returnCanonicalTypeName);

        if (returnType == null) {
            throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for method " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
        }

        addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters, annotations);
    }

    public void addPainlessMethod(Class targetClass, Class augmentedClass,
            String methodName, Class returnType, List> typeParameters, Map, 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> 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) + "]");
        }
    }

    public void addPainlessField(String targetCanonicalClassName, String fieldName, String canonicalTypeNameParameter) {
        Objects.requireNonNull(targetCanonicalClassName);
        Objects.requireNonNull(fieldName);
        Objects.requireNonNull(canonicalTypeNameParameter);

        Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName);

        if (targetClass == null) {
            throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for field " +
                    "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" + canonicalTypeNameParameter + "]]");
        }

        Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);

        if (typeParameter == null) {
            throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found " +
                    "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]");
        }


        addPainlessField(targetClass, fieldName, typeParameter);
    }

    public void addPainlessField(Class targetClass, String fieldName, Class typeParameter) {
        Objects.requireNonNull(targetClass);
        Objects.requireNonNull(fieldName);
        Objects.requireNonNull(typeParameter);

        if (targetClass == def.class) {
            throw new IllegalArgumentException("cannot add field to reserved class [" + DEF_CLASS_NAME + "]");
        }

        String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);

        if (FIELD_NAME_PATTERN.matcher(fieldName).matches() == false) {
            throw new IllegalArgumentException(
                    "invalid field name [" + fieldName + "] for target class [" + targetCanonicalClassName + "].");
        }


        PainlessClassBuilder painlessClassBuilder = classesToPainlessClassBuilders.get(targetClass);

        if (painlessClassBuilder == null) {
            throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for field " +
                    "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" + typeToCanonicalTypeName(typeParameter) + "]]");
        }

        if (isValidType(typeParameter) == false) {
            throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
                    "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]");
        }

        Field javaField;

        try {
            javaField = targetClass.getField(fieldName);
        } catch (NoSuchFieldException nsme) {
            throw new IllegalArgumentException(
                    "reflection object not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]", nsme);
        }

        if (javaField.getType() != typeToJavaType(typeParameter)) {
            throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(javaField.getType()) + "] " +
                    "does not match the specified type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
                    "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]");
        }

        MethodHandle methodHandleGetter;

        try {
            methodHandleGetter = MethodHandles.publicLookup().unreflectGetter(javaField);
        } catch (IllegalAccessException iae) {
            throw new IllegalArgumentException(
                    "getter method handle not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]");
        }

        String painlessFieldKey = buildPainlessFieldKey(fieldName);

        if (Modifier.isStatic(javaField.getModifiers())) {
            if (Modifier.isFinal(javaField.getModifiers()) == false) {
                throw new IllegalArgumentException("static field [[" + targetCanonicalClassName + "], [" + fieldName + "]] must be final");
            }

            PainlessField existingPainlessField = painlessClassBuilder.staticFields.get(painlessFieldKey);
            PainlessField newPainlessField = new PainlessField(javaField, typeParameter, methodHandleGetter, null);

            if (existingPainlessField == null) {
                newPainlessField = painlessFieldCache.computeIfAbsent(newPainlessField, key -> key);
                painlessClassBuilder.staticFields.put(painlessFieldKey.intern(), newPainlessField);
            } else if (newPainlessField.equals(existingPainlessField) == false) {
                throw new IllegalArgumentException("cannot add fields with the same name but are not equivalent for fields " +
                        "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" +
                        typeToCanonicalTypeName(typeParameter) + "] and " +
                        "[[" + targetCanonicalClassName + "], [" + existingPainlessField.javaField.getName() + "], " +
                        typeToCanonicalTypeName(existingPainlessField.typeParameter) + "] " +
                        "with the same name and different type parameters");
            }
        } else {
            MethodHandle methodHandleSetter;

            try {
                methodHandleSetter = MethodHandles.publicLookup().unreflectSetter(javaField);
            } catch (IllegalAccessException iae) {
                throw new IllegalArgumentException(
                        "setter method handle not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]");
            }

            PainlessField existingPainlessField = painlessClassBuilder.fields.get(painlessFieldKey);
            PainlessField newPainlessField = new PainlessField(javaField, typeParameter, methodHandleGetter, methodHandleSetter);

            if (existingPainlessField == null) {
                newPainlessField = painlessFieldCache.computeIfAbsent(newPainlessField, key -> key);
                painlessClassBuilder.fields.put(painlessFieldKey.intern(), newPainlessField);
            } else if (newPainlessField.equals(existingPainlessField) == false) {
                throw new IllegalArgumentException("cannot add fields with the same name but are not equivalent for fields " +
                        "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" +
                        typeToCanonicalTypeName(typeParameter) + "] and " +
                        "[[" + targetCanonicalClassName + "], [" + existingPainlessField.javaField.getName() + "], " +
                        typeToCanonicalTypeName(existingPainlessField.typeParameter) + "] " +
                        "with the same name and different type parameters");
            }
        }
    }

    public void addImportedPainlessMethod(ClassLoader classLoader, String targetJavaClassName,
            String methodName, String returnCanonicalTypeName, List canonicalTypeNameParameters,
            Map, Object> annotations) {

        Objects.requireNonNull(classLoader);
        Objects.requireNonNull(targetJavaClassName);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnCanonicalTypeName);
        Objects.requireNonNull(canonicalTypeNameParameters);

        Class targetClass;

        try {
            targetClass = Class.forName(targetJavaClassName, true, classLoader);
        } catch (ClassNotFoundException cnfe) {
            throw new IllegalArgumentException("class [" + targetJavaClassName + "] not found", cnfe);
        }

        String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);

        if (targetClass == null) {
            throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for imported method " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
        }

        List> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());

        for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
            Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);

            if (typeParameter == null) {
                throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for imported method " +
                        "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
            }

            typeParameters.add(typeParameter);
        }

        Class returnType = canonicalTypeNameToType(returnCanonicalTypeName);

        if (returnType == null) {
            throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for imported method " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
        }

        addImportedPainlessMethod(targetClass, methodName, returnType, typeParameters, annotations);
    }

    public void addImportedPainlessMethod(Class targetClass, String methodName, Class returnType, List> typeParameters,
            Map, Object> annotations) {
        Objects.requireNonNull(targetClass);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnType);
        Objects.requireNonNull(typeParameters);

        if (targetClass == def.class) {
            throw new IllegalArgumentException("cannot add imported method from reserved class [" + DEF_CLASS_NAME + "]");
        }

        String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
        Class existingTargetClass = javaClassNamesToClasses.get(targetClass.getName());

        if (existingTargetClass == null) {
            javaClassNamesToClasses.put(targetClass.getName().intern(), targetClass);
        } else if (existingTargetClass != targetClass) {
            throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] " +
                    "cannot represent multiple java classes with the same name from different class loaders");
        }

        if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
            throw new IllegalArgumentException(
                    "invalid imported method name [" + methodName + "] for target class [" + targetCanonicalClassName + "].");
        }

        int typeParametersSize = typeParameters.size();
        List> javaTypeParameters = new ArrayList<>(typeParametersSize);

        for (Class typeParameter : typeParameters) {
            if (isValidType(typeParameter) == false) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
                        "not found for imported method [[" + targetCanonicalClassName + "], [" + methodName + "], " +
                        typesToCanonicalTypeNames(typeParameters) + "]");
            }

            javaTypeParameters.add(typeToJavaType(typeParameter));
        }

        if (isValidType(returnType) == false) {
            throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for imported method " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
        }

        Method javaMethod;

        try {
            javaMethod = targetClass.getMethod(methodName, javaTypeParameters.toArray(new Class[typeParametersSize]));
        } catch (NoSuchMethodException nsme) {
            throw new IllegalArgumentException("imported method reflection object [[" + targetCanonicalClassName + "], " +
                    "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme);
        }

        if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
            throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
                    "does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
                    "for imported method [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "]");
        }

        if (Modifier.isStatic(javaMethod.getModifiers()) == false) {
            throw new IllegalArgumentException("imported method [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "] must be static");
        }

        String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);

        if (painlessMethodKeysToPainlessClassBindings.containsKey(painlessMethodKey)) {
            throw new IllegalArgumentException("imported method and class binding cannot have the same name [" + methodName + "]");
        }

        if (painlessMethodKeysToPainlessInstanceBindings.containsKey(painlessMethodKey)) {
            throw new IllegalArgumentException("imported method and instance binding cannot have the same name [" + methodName + "]");
        }

        MethodHandle methodHandle;

        try {
            methodHandle = MethodHandles.publicLookup().in(targetClass).unreflect(javaMethod);
        } catch (IllegalAccessException iae) {
            throw new IllegalArgumentException("imported method handle [[" + targetClass.getCanonicalName() + "], " +
                    "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", iae);
        }

        MethodType methodType = methodHandle.type();

        PainlessMethod existingImportedPainlessMethod = painlessMethodKeysToImportedPainlessMethods.get(painlessMethodKey);
        PainlessMethod newImportedPainlessMethod =
                new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType, annotations);

        if (existingImportedPainlessMethod == null) {
            newImportedPainlessMethod = painlessMethodCache.computeIfAbsent(newImportedPainlessMethod, key -> key);
            painlessMethodKeysToImportedPainlessMethods.put(painlessMethodKey.intern(), newImportedPainlessMethod);
        } else if (newImportedPainlessMethod.equals(existingImportedPainlessMethod) == false) {
            throw new IllegalArgumentException("cannot add imported methods with the same name and arity " +
                    "but do not have equivalent methods " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " +
                    "[" + typeToCanonicalTypeName(returnType) + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "] and " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " +
                    "[" + typeToCanonicalTypeName(existingImportedPainlessMethod.returnType) + "], " +
                    typesToCanonicalTypeNames(existingImportedPainlessMethod.typeParameters) + "]");
        }
    }

    public void addPainlessClassBinding(ClassLoader classLoader, String targetJavaClassName,
            String methodName, String returnCanonicalTypeName, List canonicalTypeNameParameters,
            Map, Object> annotations) {

        Objects.requireNonNull(classLoader);
        Objects.requireNonNull(targetJavaClassName);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnCanonicalTypeName);
        Objects.requireNonNull(canonicalTypeNameParameters);

        Class targetClass;

        try {
            targetClass = Class.forName(targetJavaClassName, true, classLoader);
        } catch (ClassNotFoundException cnfe) {
            throw new IllegalArgumentException("class [" + targetJavaClassName + "] not found", cnfe);
        }

        String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
        List> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());

        for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
            Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);

            if (typeParameter == null) {
                throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for class binding " +
                        "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
            }

            typeParameters.add(typeParameter);
        }

        Class returnType = canonicalTypeNameToType(returnCanonicalTypeName);

        if (returnType == null) {
            throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for class binding " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
        }

        addPainlessClassBinding(targetClass, methodName, returnType, typeParameters, annotations);
    }

    public void addPainlessClassBinding(Class targetClass, String methodName, Class returnType, List> typeParameters,
            Map, Object> annotations) {
        Objects.requireNonNull(targetClass);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnType);
        Objects.requireNonNull(typeParameters);

        if (targetClass == def.class) {
            throw new IllegalArgumentException("cannot add class binding as reserved class [" + DEF_CLASS_NAME + "]");
        }

        String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
        Class existingTargetClass = javaClassNamesToClasses.get(targetClass.getName());

        if (existingTargetClass == null) {
            javaClassNamesToClasses.put(targetClass.getName().intern(), targetClass);
        } else if (existingTargetClass != targetClass) {
            throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] " +
                    "cannot represent multiple java classes with the same name from different class loaders");
        }

        Constructor[] javaConstructors = targetClass.getConstructors();
        Constructor javaConstructor = null;

        for (Constructor eachJavaConstructor : javaConstructors) {
            if (eachJavaConstructor.getDeclaringClass() == targetClass) {
                if (javaConstructor != null) {
                    throw new IllegalArgumentException(
                            "class binding [" + targetCanonicalClassName + "] cannot have multiple constructors");
                }

                javaConstructor = eachJavaConstructor;
            }
        }

        if (javaConstructor == null) {
            throw new IllegalArgumentException("class binding [" + targetCanonicalClassName + "] must have exactly one constructor");
        }

        int constructorTypeParametersSize = javaConstructor.getParameterCount();

        for (int typeParameterIndex = 0; typeParameterIndex < constructorTypeParametersSize; ++typeParameterIndex) {
            Class typeParameter = typeParameters.get(typeParameterIndex);

            if (isValidType(typeParameter) == false) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
                        "for class binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
            }

            Class javaTypeParameter = javaConstructor.getParameterTypes()[typeParameterIndex];

            if (isValidType(javaTypeParameter) == false) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
                        "for class binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
            }

            if (javaTypeParameter != typeToJavaType(typeParameter)) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(javaTypeParameter) + "] " +
                        "does not match the specified type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
                        "for class binding [[" + targetClass.getCanonicalName() + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
            }
        }

        if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
            throw new IllegalArgumentException(
                    "invalid method name [" + methodName + "] for class binding [" + targetCanonicalClassName + "].");
        }

        Method[] javaMethods = targetClass.getMethods();
        Method javaMethod = null;

        for (Method eachJavaMethod : javaMethods) {
            if (eachJavaMethod.getDeclaringClass() == targetClass) {
                if (javaMethod != null) {
                    throw new IllegalArgumentException("class binding [" + targetCanonicalClassName + "] cannot have multiple methods");
                }

                javaMethod = eachJavaMethod;
            }
        }

        if (javaMethod == null) {
            throw new IllegalArgumentException("class binding [" + targetCanonicalClassName + "] must have exactly one method");
        }

        int methodTypeParametersSize = javaMethod.getParameterCount();

        for (int typeParameterIndex = 0; typeParameterIndex < methodTypeParametersSize; ++typeParameterIndex) {
            Class typeParameter = typeParameters.get(constructorTypeParametersSize + typeParameterIndex);

            if (isValidType(typeParameter) == false) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
                        "for class binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
            }

            Class javaTypeParameter = javaMethod.getParameterTypes()[typeParameterIndex];

            if (isValidType(javaTypeParameter) == false) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
                        "for class binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
            }

            if (javaTypeParameter != typeToJavaType(typeParameter)) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(javaTypeParameter) + "] " +
                        "does not match the specified type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
                        "for class binding [[" + targetClass.getCanonicalName() + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
            }
        }

        if (isValidType(returnType) == false) {
            throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for class binding " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
        }

        if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
            throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
                    "does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
                    "for class binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "]");
        }

        String painlessMethodKey = buildPainlessMethodKey(methodName, constructorTypeParametersSize + methodTypeParametersSize);

        if (painlessMethodKeysToImportedPainlessMethods.containsKey(painlessMethodKey)) {
            throw new IllegalArgumentException("class binding and imported method cannot have the same name [" + methodName + "]");
        }

        if (painlessMethodKeysToPainlessInstanceBindings.containsKey(painlessMethodKey)) {
            throw new IllegalArgumentException("class binding and instance binding cannot have the same name [" + methodName + "]");
        }

        if (Modifier.isStatic(javaMethod.getModifiers())) {
            throw new IllegalArgumentException("class binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "] cannot be static");
        }

        PainlessClassBinding existingPainlessClassBinding = painlessMethodKeysToPainlessClassBindings.get(painlessMethodKey);
        PainlessClassBinding newPainlessClassBinding =
                new PainlessClassBinding(javaConstructor, javaMethod, returnType, typeParameters, annotations);

        if (existingPainlessClassBinding == null) {
            newPainlessClassBinding = painlessClassBindingCache.computeIfAbsent(newPainlessClassBinding, key -> key);
            painlessMethodKeysToPainlessClassBindings.put(painlessMethodKey.intern(), newPainlessClassBinding);
        } else if (newPainlessClassBinding.equals(existingPainlessClassBinding) == false) {
            throw new IllegalArgumentException("cannot add class bindings with the same name and arity " +
                    "but do not have equivalent methods " +
                    "[[" + targetCanonicalClassName + "], " +
                    "[" + methodName + "], " +
                    "[" + typeToCanonicalTypeName(returnType) + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "] and " +
                    "[[" + targetCanonicalClassName + "], " +
                    "[" + methodName + "], " +
                    "[" + typeToCanonicalTypeName(existingPainlessClassBinding.returnType) + "], " +
                    typesToCanonicalTypeNames(existingPainlessClassBinding.typeParameters) + "]");
        }
    }

    public void addPainlessInstanceBinding(Object targetInstance,
            String methodName, String returnCanonicalTypeName, List canonicalTypeNameParameters) {

        Objects.requireNonNull(targetInstance);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnCanonicalTypeName);
        Objects.requireNonNull(canonicalTypeNameParameters);

        Class targetClass = targetInstance.getClass();
        String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
        List> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());

        for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
            Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);

            if (typeParameter == null) {
                throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for instance binding " +
                        "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
            }

            typeParameters.add(typeParameter);
        }

        Class returnType = canonicalTypeNameToType(returnCanonicalTypeName);

        if (returnType == null) {
            throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for class binding " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
        }

        addPainlessInstanceBinding(targetInstance, methodName, returnType, typeParameters);
    }

    public void addPainlessInstanceBinding(Object targetInstance, String methodName, Class returnType, List> typeParameters) {
        Objects.requireNonNull(targetInstance);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnType);
        Objects.requireNonNull(typeParameters);

        Class targetClass = targetInstance.getClass();

        if (targetClass == def.class) {
            throw new IllegalArgumentException("cannot add instance binding as reserved class [" + DEF_CLASS_NAME + "]");
        }

        String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
        Class existingTargetClass = javaClassNamesToClasses.get(targetClass.getName());

        if (existingTargetClass == null) {
            javaClassNamesToClasses.put(targetClass.getName().intern(), targetClass);
        } else if (existingTargetClass != targetClass) {
            throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] " +
                    "cannot represent multiple java classes with the same name from different class loaders");
        }

        if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
            throw new IllegalArgumentException(
                    "invalid method name [" + methodName + "] for instance binding [" + targetCanonicalClassName + "].");
        }

        int typeParametersSize = typeParameters.size();
        List> javaTypeParameters = new ArrayList<>(typeParametersSize);

        for (Class typeParameter : typeParameters) {
            if (isValidType(typeParameter) == false) {
                throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
                        "not found for instance binding [[" + targetCanonicalClassName + "], [" + methodName + "], " +
                        typesToCanonicalTypeNames(typeParameters) + "]");
            }

            javaTypeParameters.add(typeToJavaType(typeParameter));
        }

        if (isValidType(returnType) == false) {
            throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for imported method " +
                    "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
        }

        Method javaMethod;

        try {
            javaMethod = targetClass.getMethod(methodName, javaTypeParameters.toArray(new Class[typeParametersSize]));
        } catch (NoSuchMethodException nsme) {
            throw new IllegalArgumentException("instance binding reflection object [[" + targetCanonicalClassName + "], " +
                    "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme);
        }

        if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
            throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
                    "does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
                    "for instance binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "]");
        }

        if (Modifier.isStatic(javaMethod.getModifiers())) {
            throw new IllegalArgumentException("instance binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "] cannot be static");
        }

        String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);

        if (painlessMethodKeysToImportedPainlessMethods.containsKey(painlessMethodKey)) {
            throw new IllegalArgumentException("instance binding and imported method cannot have the same name [" + methodName + "]");
        }

        if (painlessMethodKeysToPainlessClassBindings.containsKey(painlessMethodKey)) {
            throw new IllegalArgumentException("instance binding and class binding cannot have the same name [" + methodName + "]");
        }

        PainlessInstanceBinding existingPainlessInstanceBinding = painlessMethodKeysToPainlessInstanceBindings.get(painlessMethodKey);
        PainlessInstanceBinding newPainlessInstanceBinding =
                new PainlessInstanceBinding(targetInstance, javaMethod, returnType, typeParameters);

        if (existingPainlessInstanceBinding == null) {
            newPainlessInstanceBinding = painlessInstanceBindingCache.computeIfAbsent(newPainlessInstanceBinding, key -> key);
            painlessMethodKeysToPainlessInstanceBindings.put(painlessMethodKey.intern(), newPainlessInstanceBinding);
        } else if (newPainlessInstanceBinding.equals(existingPainlessInstanceBinding) == false) {
            throw new IllegalArgumentException("cannot add instances bindings with the same name and arity " +
                    "but do not have equivalent methods " +
                    "[[" + targetCanonicalClassName + "], " +
                    "[" + methodName + "], " +
                    "[" + typeToCanonicalTypeName(returnType) + "], " +
                    typesToCanonicalTypeNames(typeParameters) + "] and " +
                    "[[" + targetCanonicalClassName + "], " +
                    "[" + methodName + "], " +
                    "[" + typeToCanonicalTypeName(existingPainlessInstanceBinding.returnType) + "], " +
                    typesToCanonicalTypeNames(existingPainlessInstanceBinding.typeParameters) + "]");
        }
    }

    public PainlessLookup build() {
        copyPainlessClassMembers();
        setFunctionalInterfaceMethods();
        generateRuntimeMethods();
        cacheRuntimeHandles();

        Map, PainlessClass> classesToPainlessClasses = new HashMap<>(classesToPainlessClassBuilders.size());

        for (Map.Entry, PainlessClassBuilder> painlessClassBuilderEntry : classesToPainlessClassBuilders.entrySet()) {
            classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
        }

        if (javaClassNamesToClasses.values().containsAll(canonicalClassNamesToClasses.values()) == false) {
            throw new IllegalArgumentException("the values of java class names to classes " +
                    "must be a superset of the values of canonical class names to classes");
        }

        if (javaClassNamesToClasses.values().containsAll(classesToPainlessClasses.keySet()) == false) {
            throw new IllegalArgumentException("the values of java class names to classes " +
                    "must be a superset of the keys of classes to painless classes");
        }

        if (canonicalClassNamesToClasses.values().containsAll(classesToPainlessClasses.keySet()) == false ||
                classesToPainlessClasses.keySet().containsAll(canonicalClassNamesToClasses.values()) == false) {
            throw new IllegalArgumentException("the values of canonical class names to classes "  +
                    "must have the same classes as the keys of classes to painless classes");
        }

        return new PainlessLookup(
                javaClassNamesToClasses,
                canonicalClassNamesToClasses,
                classesToPainlessClasses,
                painlessMethodKeysToImportedPainlessMethods,
                painlessMethodKeysToPainlessClassBindings,
                painlessMethodKeysToPainlessInstanceBindings);
    }

    private void copyPainlessClassMembers() {
        for (Class parentClass : classesToPainlessClassBuilders.keySet()) {
            copyPainlessInterfaceMembers(parentClass, parentClass);

            Class childClass = parentClass.getSuperclass();

            while (childClass != null) {
                if (classesToPainlessClassBuilders.containsKey(childClass)) {
                    copyPainlessClassMembers(childClass, parentClass);
                }

                copyPainlessInterfaceMembers(childClass, parentClass);
                childClass = childClass.getSuperclass();
            }
        }

        for (Class javaClass : classesToPainlessClassBuilders.keySet()) {
            if (javaClass.isInterface()) {
                copyPainlessClassMembers(Object.class, javaClass);
            }
        }
    }

    private void copyPainlessInterfaceMembers(Class parentClass, Class targetClass) {
        for (Class childClass : parentClass.getInterfaces()) {
            if (classesToPainlessClassBuilders.containsKey(childClass)) {
                copyPainlessClassMembers(childClass, targetClass);
            }

            copyPainlessInterfaceMembers(childClass, targetClass);
        }
    }

    private void copyPainlessClassMembers(Class originalClass, Class targetClass) {
        PainlessClassBuilder originalPainlessClassBuilder = classesToPainlessClassBuilders.get(originalClass);
        PainlessClassBuilder targetPainlessClassBuilder = classesToPainlessClassBuilders.get(targetClass);

        Objects.requireNonNull(originalPainlessClassBuilder);
        Objects.requireNonNull(targetPainlessClassBuilder);

        for (Map.Entry painlessMethodEntry : originalPainlessClassBuilder.methods.entrySet()) {
            String painlessMethodKey = painlessMethodEntry.getKey();
            PainlessMethod newPainlessMethod = painlessMethodEntry.getValue();
            PainlessMethod existingPainlessMethod = targetPainlessClassBuilder.methods.get(painlessMethodKey);

            if (existingPainlessMethod == null || existingPainlessMethod.targetClass != newPainlessMethod.targetClass &&
                    existingPainlessMethod.targetClass.isAssignableFrom(newPainlessMethod.targetClass)) {
                targetPainlessClassBuilder.methods.put(painlessMethodKey.intern(), newPainlessMethod);
            }
        }

        for (Map.Entry painlessFieldEntry : originalPainlessClassBuilder.fields.entrySet()) {
            String painlessFieldKey = painlessFieldEntry.getKey();
            PainlessField newPainlessField = painlessFieldEntry.getValue();
            PainlessField existingPainlessField = targetPainlessClassBuilder.fields.get(painlessFieldKey);

            if (existingPainlessField == null ||
                    existingPainlessField.javaField.getDeclaringClass() != newPainlessField.javaField.getDeclaringClass() &&
                    existingPainlessField.javaField.getDeclaringClass().isAssignableFrom(newPainlessField.javaField.getDeclaringClass())) {
                targetPainlessClassBuilder.fields.put(painlessFieldKey.intern(), newPainlessField);
            }
        }
    }

    private void setFunctionalInterfaceMethods() {
        for (Map.Entry, PainlessClassBuilder> painlessClassBuilderEntry : classesToPainlessClassBuilders.entrySet()) {
            setFunctionalInterfaceMethod(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue());
        }
    }

    private void setFunctionalInterfaceMethod(Class targetClass, PainlessClassBuilder painlessClassBuilder) {
        if (targetClass.isInterface()) {
            List javaMethods = new ArrayList<>();

            for (java.lang.reflect.Method javaMethod : targetClass.getMethods()) {
                if (javaMethod.isDefault() == false && Modifier.isStatic(javaMethod.getModifiers()) == false) {
                    try {
                        Object.class.getMethod(javaMethod.getName(), javaMethod.getParameterTypes());
                    } catch (ReflectiveOperationException roe) {
                        javaMethods.add(javaMethod);
                    }
                }
            }

            if (javaMethods.size() != 1 && targetClass.isAnnotationPresent(FunctionalInterface.class)) {
                throw new IllegalArgumentException("class [" + typeToCanonicalTypeName(targetClass) + "] " +
                        "is illegally marked as a FunctionalInterface with java methods " + javaMethods);
            } else if (javaMethods.size() == 1) {
                java.lang.reflect.Method javaMethod = javaMethods.get(0);
                String painlessMethodKey = buildPainlessMethodKey(javaMethod.getName(), javaMethod.getParameterCount());
                painlessClassBuilder.functionalInterfaceMethod = painlessClassBuilder.methods.get(painlessMethodKey);
            }
        }
    }

    /**
     * Creates a {@link Map} of PainlessMethodKeys to {@link PainlessMethod}s per {@link PainlessClass} stored as
     * {@link PainlessClass#runtimeMethods} identical to {@link PainlessClass#methods} with the exception of generated
     * bridge methods. A generated bridge method is created for each whitelisted method that has at least one parameter
     * with a boxed type to cast from other numeric primitive/boxed types in a symmetric was not handled by
     * {@link MethodHandle#asType(MethodType)}. As an example {@link MethodHandle#asType(MethodType)} legally casts
     * from {@link Integer} to long but not from int to {@link Long}. Generated bridge methods cover the latter case.
     * A generated bridge method replaces the method its a bridge to in the {@link PainlessClass#runtimeMethods}
     * {@link Map}. The {@link PainlessClass#runtimeMethods} {@link Map} is used exclusively to look up methods at
     * run-time resulting from calls with a def type value target.
     */
    private void generateRuntimeMethods() {
        for (PainlessClassBuilder painlessClassBuilder : classesToPainlessClassBuilders.values()) {
            painlessClassBuilder.runtimeMethods.putAll(painlessClassBuilder.methods);

            for (PainlessMethod painlessMethod : painlessClassBuilder.runtimeMethods.values()) {
                for (Class typeParameter : painlessMethod.typeParameters) {
                    if (
                            typeParameter == Byte.class      ||
                            typeParameter == Short.class     ||
                            typeParameter == Character.class ||
                            typeParameter == Integer.class   ||
                            typeParameter == Long.class      ||
                            typeParameter == Float.class     ||
                            typeParameter == Double.class
                    ) {
                        generateBridgeMethod(painlessClassBuilder, painlessMethod);
                    }
                }
            }
        }
    }

    private void generateBridgeMethod(PainlessClassBuilder painlessClassBuilder, PainlessMethod painlessMethod) {
        String painlessMethodKey = buildPainlessMethodKey(painlessMethod.javaMethod.getName(), painlessMethod.typeParameters.size());
        PainlessMethod bridgePainlessMethod = painlessBridgeCache.get(painlessMethod);

        if (bridgePainlessMethod == null) {
            Method javaMethod = painlessMethod.javaMethod;
            boolean isStatic = Modifier.isStatic(painlessMethod.javaMethod.getModifiers());

            int bridgeClassFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
            int bridgeClassAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
            String bridgeClassName =
                    "org/elasticsearch/painless/Bridge$" + javaMethod.getDeclaringClass().getSimpleName() + "$" + javaMethod.getName();
            ClassWriter bridgeClassWriter = new ClassWriter(bridgeClassFrames);
            bridgeClassWriter.visit(
                    WriterConstants.CLASS_VERSION, bridgeClassAccess, bridgeClassName, null, OBJECT_TYPE.getInternalName(), null);

            org.objectweb.asm.commons.Method bridgeConstructorType =
                    new org.objectweb.asm.commons.Method("", MethodType.methodType(void.class).toMethodDescriptorString());
            GeneratorAdapter bridgeConstructorWriter =
                    new GeneratorAdapter(Opcodes.ASM5, bridgeConstructorType, bridgeClassWriter.visitMethod(
                            Opcodes.ACC_PRIVATE, bridgeConstructorType.getName(), bridgeConstructorType.getDescriptor(), null, null));
            bridgeConstructorWriter.visitCode();
            bridgeConstructorWriter.loadThis();
            bridgeConstructorWriter.invokeConstructor(OBJECT_TYPE, bridgeConstructorType);
            bridgeConstructorWriter.returnValue();
            bridgeConstructorWriter.endMethod();

            int bridgeTypeParameterOffset = isStatic ? 0 : 1;
            List> bridgeTypeParameters = new ArrayList<>(javaMethod.getParameterTypes().length + bridgeTypeParameterOffset);

            if (isStatic == false) {
                bridgeTypeParameters.add(javaMethod.getDeclaringClass());
            }

            for (Class typeParameter : javaMethod.getParameterTypes()) {
                if (
                        typeParameter == Byte.class      ||
                        typeParameter == Short.class     ||
                        typeParameter == Character.class ||
                        typeParameter == Integer.class   ||
                        typeParameter == Long.class      ||
                        typeParameter == Float.class     ||
                        typeParameter == Double.class
                ) {
                    bridgeTypeParameters.add(Object.class);
                } else {
                    bridgeTypeParameters.add(typeParameter);
                }
            }

            MethodType bridgeMethodType = MethodType.methodType(painlessMethod.returnType, bridgeTypeParameters);
            MethodWriter bridgeMethodWriter =
                    new MethodWriter(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
                            new org.objectweb.asm.commons.Method(
                                    painlessMethod.javaMethod.getName(), bridgeMethodType.toMethodDescriptorString()),
                            bridgeClassWriter, null, null);
            bridgeMethodWriter.visitCode();

            if (isStatic == false) {
                bridgeMethodWriter.loadArg(0);
            }

            for (int typeParameterCount = 0; typeParameterCount < javaMethod.getParameterTypes().length; ++typeParameterCount) {
                bridgeMethodWriter.loadArg(typeParameterCount + bridgeTypeParameterOffset);
                Class typeParameter = javaMethod.getParameterTypes()[typeParameterCount];

                if      (typeParameter == Byte.class)      bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_BYTE_IMPLICIT);
                else if (typeParameter == Short.class)     bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_SHORT_IMPLICIT);
                else if (typeParameter == Character.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_CHARACTER_IMPLICIT);
                else if (typeParameter == Integer.class)   bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_INTEGER_IMPLICIT);
                else if (typeParameter == Long.class)      bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_LONG_IMPLICIT);
                else if (typeParameter == Float.class)     bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_FLOAT_IMPLICIT);
                else if (typeParameter == Double.class)    bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_DOUBLE_IMPLICIT);
            }

            bridgeMethodWriter.invokeMethodCall(painlessMethod);
            bridgeMethodWriter.returnValue();
            bridgeMethodWriter.endMethod();

            bridgeClassWriter.visitEnd();

            try {
                BridgeLoader bridgeLoader = AccessController.doPrivileged(new PrivilegedAction() {
                    @Override
                    public BridgeLoader run() {
                        return new BridgeLoader(javaMethod.getDeclaringClass().getClassLoader());
                    }
                });

                Class bridgeClass = bridgeLoader.defineBridge(bridgeClassName.replace('/', '.'), bridgeClassWriter.toByteArray());
                Method bridgeMethod = bridgeClass.getMethod(
                        painlessMethod.javaMethod.getName(), bridgeTypeParameters.toArray(new Class[0]));
                MethodHandle bridgeHandle = MethodHandles.publicLookup().in(bridgeClass).unreflect(bridgeClass.getMethods()[0]);
                bridgePainlessMethod = new PainlessMethod(bridgeMethod, bridgeClass,
                        painlessMethod.returnType, bridgeTypeParameters, bridgeHandle, bridgeMethodType, Collections.emptyMap());
                painlessClassBuilder.runtimeMethods.put(painlessMethodKey.intern(), bridgePainlessMethod);
                painlessBridgeCache.put(painlessMethod, bridgePainlessMethod);
            } catch (Exception exception) {
                throw new IllegalStateException(
                        "internal error occurred attempting to generate a bridge method [" + bridgeClassName + "]", exception);
            }
        } else {
            painlessClassBuilder.runtimeMethods.put(painlessMethodKey.intern(), bridgePainlessMethod);
        }
    }

    private void cacheRuntimeHandles() {
        for (PainlessClassBuilder painlessClassBuilder : classesToPainlessClassBuilders.values()) {
            cacheRuntimeHandles(painlessClassBuilder);
        }
    }

    private void cacheRuntimeHandles(PainlessClassBuilder painlessClassBuilder) {
        for (Map.Entry painlessMethodEntry : painlessClassBuilder.methods.entrySet()) {
            String methodKey = painlessMethodEntry.getKey();
            PainlessMethod painlessMethod = painlessMethodEntry.getValue();
            PainlessMethod bridgePainlessMethod = painlessClassBuilder.runtimeMethods.get(methodKey);
            String methodName = painlessMethod.javaMethod.getName();
            int typeParametersSize = painlessMethod.typeParameters.size();

            if (typeParametersSize == 0 && methodName.startsWith("get") && methodName.length() > 3 &&
                    Character.isUpperCase(methodName.charAt(3))) {
                painlessClassBuilder.getterMethodHandles.putIfAbsent(
                        Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), bridgePainlessMethod.methodHandle);
            } else if (typeParametersSize == 0 && methodName.startsWith("is") && methodName.length() > 2 &&
                    Character.isUpperCase(methodName.charAt(2))) {
                painlessClassBuilder.getterMethodHandles.putIfAbsent(
                        Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3), bridgePainlessMethod.methodHandle);
            } else if (typeParametersSize == 1 && methodName.startsWith("set") && methodName.length() > 3 &&
                    Character.isUpperCase(methodName.charAt(3))) {
                painlessClassBuilder.setterMethodHandles.putIfAbsent(
                        Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), bridgePainlessMethod.methodHandle);
            }
        }

        for (PainlessField painlessField : painlessClassBuilder.fields.values()) {
            painlessClassBuilder.getterMethodHandles.put(painlessField.javaField.getName().intern(), painlessField.getterMethodHandle);
            painlessClassBuilder.setterMethodHandles.put(painlessField.javaField.getName().intern(), painlessField.setterMethodHandle);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy