/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF 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.codehaus.groovy.vmplugin.v8;

import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.reflection.ReflectionUtils;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.vmplugin.VMPlugin;
import org.codehaus.groovy.vmplugin.VMPluginFactory;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedArrayType;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.AnnotatedWildcardType;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericSignatureFormatError;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.List;

/**
 * Java 8 based functions.
 *
 * @since 2.5.0
 * @deprecated Use {@link org.codehaus.groovy.vmplugin.v17.Java17} instead. Groovy 6.0 requires JDK 17+.
 */
@Deprecated(since = "6.0.0", forRemoval = true)
public class Java8 implements VMPlugin {

    private static final Method[] EMPTY_METHOD_ARRAY = new Method[0];
    private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];


    /**
     * Configures a type-variable definition node from its bounds.
     *
     * @param base the placeholder class node
     * @param bounds the upper bounds of the type variable
     * @return the configured generics type
     */
    public static GenericsType configureTypeVariableDefinition(final ClassNode base, final ClassNode[] bounds) {
        ClassNode redirect = base.redirect();
        base.setRedirect(null);
        GenericsType gt;
        if (bounds == null || bounds.length == 0) {
            gt = new GenericsType(base);
        } else {
            // GROOVY-10756: fix erasure -- ResolveVisitor#resolveGenericsHeader
            if (!ClassHelper.isObjectType(bounds[0])) redirect = bounds[0];
            gt = new GenericsType(base, bounds, null);
            gt.setName(base.getName());
            gt.setPlaceholder(true);
        }
        base.setRedirect(redirect);
        return gt;
    }

    /**
     * Creates a placeholder class node that references a type variable by name.
     *
     * @param name the type-variable name
     * @return the placeholder class node
     */
    public static ClassNode configureTypeVariableReference(final String name) {
        ClassNode cn = ClassHelper.makeWithoutCaching(name);
        cn.setGenericsPlaceHolder(true);
        ClassNode cn2 = ClassHelper.makeWithoutCaching(name);
        cn2.setGenericsPlaceHolder(true);

        cn.setGenericsTypes(new GenericsType[]{new GenericsType(cn2)});
        cn.setRedirect(ClassHelper.OBJECT_TYPE);
        return cn;
    }

    private static ClassNode configureClass(final Class<?> c) {
        if (c.isPrimitive()) {
            return ClassHelper.make(c);
        } else {
            return ClassHelper.makeWithoutCaching(c, false);
        }
    }

    //--------------------------------------------------------------------------

    /** {@inheritDoc} */
    @Override
    public Class<?>[] getPluginDefaultGroovyMethods() {
        return new Class[]{PluginDefaultGroovyMethods.class};
    }

    /** {@inheritDoc} */
    @Override
    public Class<?>[] getPluginStaticGroovyMethods() {
        return MetaClassHelper.EMPTY_TYPE_ARRAY;
    }

    /** {@inheritDoc} */
    @Override
    public int getVersion() {
        return 8;
    }

    /** {@inheritDoc} */
    @Override
    public void setAdditionalClassInformation(final ClassNode cn) {
        cn.setGenericsTypes(configureTypeParameters(cn.getTypeClass().getTypeParameters(), cn));
    }

    private ClassNode[] configureTypes(final Type[] types) {
        final int n = types.length;
        if (n == 0) return null;
        ClassNode[] nodes = new ClassNode[n];
        for (int i = 0; i < n; i += 1) {
            nodes[i] = configureType(types[i]);
        }
        return nodes;
    }

    private ClassNode configureType(final Type type) {
        if (type instanceof WildcardType) {
            return configureWildcardType((WildcardType) type);
        } else if (type instanceof ParameterizedType) {
            return configureParameterizedType((ParameterizedType) type);
        } else if (type instanceof GenericArrayType) {
            return configureGenericArray((GenericArrayType) type);
        } else if (type instanceof TypeVariable) {
            return configureTypeVariableReference(((TypeVariable<?>) type).getName());
        } else if (type instanceof Class) {
            return configureClass((Class<?>) type);
        } else if (type == null) {
            throw new GroovyBugError("Type is null. Most probably you let a transform reuse existing ClassNodes with generics information, that is now used in a wrong context.");
        } else {
            throw new GroovyBugError("unknown type: " + type + " := " + type.getClass());
        }
    }

    private ClassNode configureGenericArray(final GenericArrayType genericArrayType) {
        Type component = genericArrayType.getGenericComponentType();
        ClassNode node = configureType(component);
        return node.makeArray();
    }

    private ClassNode configureWildcardType(final WildcardType wildcardType) {
        ClassNode base = ClassHelper.makeWithoutCaching("?");
        base.setRedirect(ClassHelper.OBJECT_TYPE);

        ClassNode[] lowers = configureTypes(wildcardType.getLowerBounds());
        ClassNode[] uppers = configureTypes(wildcardType.getUpperBounds());
        // beware of [Object] upper bounds; often it's <?> or <? super T>
        if (lowers != null || "?".equals(wildcardType.getTypeName())) {
            uppers = null;
        }

        GenericsType gt = new GenericsType(base, uppers, lowers != null ? lowers[0] : null);
        gt.setWildcard(true);

        ClassNode wt = ClassHelper.makeWithoutCaching(Object.class, false);
        wt.setGenericsTypes(new GenericsType[]{gt});
        return wt;
    }

    private ClassNode configureParameterizedType(final ParameterizedType parameterizedType) {
        ClassNode type = configureType(parameterizedType.getRawType());
        GenericsType[] tas = configureTypeArguments(parameterizedType.getActualTypeArguments());
        // fix erasure : ResolveVisitor#resolveWildcardBounding
        final int n; if (tas != null && (n = tas.length) > 0) {
            GenericsType[] tps = type.redirect().getGenericsTypes();
            for (int i = 0; i < n; i += 1) { GenericsType ta = tas[i];
                if (!ta.isWildcard() || ta.getUpperBounds() != null) continue;
                ClassNode[] ubs = (tps != null && tps.length > i ? tps[i].getUpperBounds() : null);
                if (ubs != null && !ClassHelper.isObjectType(ubs[0])) ta.getType().setRedirect(ubs[0]);
            }
        }
        type.setGenericsTypes(tas);
        return type;
    }

    private GenericsType[] configureTypeArguments(final Type[] ta) {
        final int n = ta.length;
        if (n == 0) return null;
        GenericsType[] gts = new GenericsType[n];
        for (int i = 0; i < n; i += 1) {
            ClassNode t = configureType(ta[i]);
            if (ta[i] instanceof WildcardType) {
                GenericsType[] gen = t.getGenericsTypes();
                gts[i] = gen[0];
            } else {
                gts[i] = new GenericsType(t);
            }
        }
        return gts;
    }

    private GenericsType[] configureTypeParameters(final TypeVariable<?>[] tp, final AnnotatedNode declaration) {
        final int n = tp.length;
        if (n == 0) return null;
        GenericsType[] gt = new GenericsType[n];
        for (int i = 0; i < n; i += 1) {
            ClassNode t = configureTypeVariableReference(tp[i].getName());
            ClassNode[] bounds = configureTypes(tp[i].getBounds());
            gt[i] = configureTypeVariableDefinition(t, bounds);
            gt[i].setGenericDeclaration(declaration);
            for (Annotation annotation : tp[i].getAnnotations()) {
                gt[i].setType(addTypeAnnotation(gt[i].getType(), annotation));
            }
        }
        return gt;
    }

    //

    /** {@inheritDoc} */
    @Override
    public void configureAnnotation(final AnnotationNode node) {
        ClassNode type = node.getClassNode();
        VMPlugin plugin = VMPluginFactory.getPlugin();
        List<AnnotationNode> annotations = type.getAnnotations();
        for (AnnotationNode an : annotations) {
            plugin.configureAnnotationNodeFromDefinition(an, node);
        }
        if (!"java.lang.annotation.Retention".equals(node.getClassNode().getName())) {
            plugin.configureAnnotationNodeFromDefinition(node, node);
        }
    }

    private void configureAnnotation(final AnnotationNode node, final Annotation annotation) {
        if (annotation instanceof Retention r) {
            final ClassNode retentionPolicy = ClassHelper.makeWithoutCaching(RetentionPolicy.class, false);
            node.setMember("value", new PropertyExpression(new ClassExpression(retentionPolicy), r.value().toString()));
        } else if (annotation instanceof Target t) {
            var elementExprs = new ListExpression();
            for (ElementType elementTypes : t.value()) {
                elementExprs.addExpression(new PropertyExpression(new ClassExpression(ClassHelper.ELEMENT_TYPE_TYPE), elementTypes.name()));
            }
            node.setMember("value", elementExprs);
        } else {
            Method[] declaredMethods;
            try {
                declaredMethods = ReflectionUtils.getDeclaredMethodsSorted(annotation.annotationType());
            } catch (SecurityException se) {
                declaredMethods = EMPTY_METHOD_ARRAY;
            }
            for (Method declaredMethod : declaredMethods) {
                try {
                    Object value = declaredMethod.invoke(annotation);
                    Expression valueExpression = toAnnotationValueExpression(value);
                    if (valueExpression != null) node.setMember(declaredMethod.getName(), valueExpression);
                } catch (IllegalAccessException | InvocationTargetException ignore) {
                }
            }
        }
    }

    private void setAnnotationMetaData(final Annotation[] annotations, final AnnotatedNode target) {
        for (Annotation annotation : annotations) {
            target.addAnnotation(toAnnotationNode(annotation));
        }
    }

    /**
     * Converts a runtime annotation instance into a Groovy AST annotation node.
     *
     * @param annotation the runtime annotation
     * @return the corresponding annotation node
     */
    protected AnnotationNode toAnnotationNode(final Annotation annotation) {
        ClassNode type = ClassHelper.make(annotation.annotationType());
        AnnotationNode node = new AnnotationNode(type);
        configureAnnotation(node, annotation);
        return node;
    }

    private Expression toAnnotationValueExpression(final Object value) {
        if (value == null || value instanceof String || value instanceof Number || value instanceof Character || value instanceof Boolean)
            return new ConstantExpression(value);

        if (value instanceof Class)
            return new ClassExpression(ClassHelper.makeWithoutCaching((Class<?>)value));

        if (value instanceof Annotation)
            return new AnnotationConstantExpression(toAnnotationNode((Annotation)value));

        if (value instanceof Enum)
            return new PropertyExpression(new ClassExpression(ClassHelper.makeWithoutCaching(value.getClass())), value.toString());

        if (value.getClass().isArray()) {
            ListExpression list = new ListExpression();
            for (int i = 0, n = Array.getLength(value); i < n; i += 1)
                list.addExpression(toAnnotationValueExpression(Array.get(value, i)));
            return list;
        }

        return null;
    }

    /** {@inheritDoc} */
    @Override
    public void configureClassNode(final CompileUnit compileUnit, final ClassNode classNode) {
        try {
            Class<?> clazz = classNode.getTypeClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field f : fields) {
                ClassNode rt = makeClassNode(compileUnit, f.getGenericType(), f.getType());
                rt = applyTypeAnnotations(f.getAnnotatedType(), rt);
                FieldNode fn = new FieldNode(f.getName(), f.getModifiers(), rt, classNode, getValue(f));
                setAnnotationMetaData(f.getAnnotations(), fn);
                classNode.addField(fn);
            }
            Method[] methods = ReflectionUtils.getDeclaredMethodsSorted(clazz);
            for (Method m : methods) {
                ClassNode rt = makeClassNode(compileUnit, m.getGenericReturnType(), m.getReturnType());
                rt = applyTypeAnnotations(m.getAnnotatedReturnType(), rt);
                Parameter[] params = makeParameters(compileUnit, m.getGenericParameterTypes(), m.getParameterTypes(), m.getParameterAnnotations(), m);
                ClassNode[] exceptions = makeClassNodes(compileUnit, m.getGenericExceptionTypes(), m.getExceptionTypes());
                applyExceptionTypeAnnotations(m, exceptions);
                MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), rt, params, exceptions, null);
                setAnnotationMetaData(m.getAnnotations(), mn);
                if (m.getDefaultValue() != null) {
                    mn.setAnnotationDefault(true); // GROOVY-10862
                    mn.setCode(new ReturnStatement(new ConstantExpression(m.getDefaultValue(), true)));
                }
                mn.setGenericsTypes(configureTypeParameters(m.getTypeParameters(), mn));
                mn.setSynthetic(m.isSynthetic());
                classNode.addMethod(mn);
            }
            Constructor<?>[] constructors = ReflectionUtils.getDeclaredConstructorsSorted(clazz);
            for (Constructor<?> c : constructors) {
                Parameter[] params = makeParameters(compileUnit, c.getGenericParameterTypes(), c.getParameterTypes(), getConstructorParameterAnnotations(c), c);
                ClassNode[] exceptions = makeClassNodes(compileUnit, c.getGenericExceptionTypes(), c.getExceptionTypes());
                applyExceptionTypeAnnotations(c, exceptions);
                ConstructorNode cn = classNode.addConstructor(c.getModifiers(), params, exceptions, null);
                setAnnotationMetaData(c.getAnnotations(), cn);
                GenericsType[] constructorTypeParameters = configureTypeParameters(c.getTypeParameters(), cn);
                if (constructorTypeParameters != null) {
                    cn.setGenericsTypes(constructorTypeParameters);
                }
            }

            Class<?> sc = clazz.getSuperclass();
            if (sc != null) {
                ClassNode superClass = makeClassNode(compileUnit, clazz.getGenericSuperclass(), sc);
                AnnotatedType annotatedSuperclass = clazz.getAnnotatedSuperclass();
                if (annotatedSuperclass != null) superClass = applyTypeAnnotations(annotatedSuperclass, superClass);
                classNode.setUnresolvedSuperClass(superClass);
            }
            makeInterfaceTypes(compileUnit, classNode, clazz);
            makePermittedSubclasses(compileUnit, classNode, clazz);
            makeRecordComponents(compileUnit, classNode, clazz);
            setAnnotationMetaData(clazz.getAnnotations(), classNode);

            PackageNode packageNode = classNode.getPackage();
            if (packageNode != null) {
                setAnnotationMetaData(clazz.getPackage().getAnnotations(), packageNode);
            }
        } catch (NoClassDefFoundError e) {
            throw new NoClassDefFoundError("Unable to configure " + classNode.getName() + " due to missing dependency " + e.getMessage());
        } catch (TypeNotPresentException e) {
            throw new NoClassDefFoundError("Unable to configure " + classNode.getName() + " due to missing dependency " + e.typeName());
        } catch (GenericSignatureFormatError | MalformedParameterizedTypeException e) {
            throw new RuntimeException(    "Unable to configure " + classNode.getName() + " due to malformed type info" , e);
        }
    }

    /**
     * Returns the initial expression for given field.
     *
     * @return value expression or null
     * @since 5.0.0
     */
    protected Expression getValue(final Field field) {
        int modifiers = field.getModifiers();
        // TODO: read ConstantValue from field attributes
        if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)
                && (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers))
                && (field.getType().isPrimitive() || field.getType().equals(String.class))) {
            try {
                return new ConstantExpression(field.get(null), true);
            } catch (ReflectiveOperationException | LinkageError e) {
            }
        }
        return null;
    }

    /**
     * Synthetic parameters such as those added for inner class constructors may
     * not be included in the parameter annotations array. This is the case when
     * at least one parameter of an inner class constructor has an annotation with
     * a RUNTIME retention (this occurs for JDK8 and below). This method will
     * normalize the annotations array so that it contains the same number of
     * elements as the array returned from {@link Constructor#getParameterTypes()}.
     *
     * If adjustment is required, the adjusted array will be prepended with a
     * zero-length element. If no adjustment is required, the original array
     * from {@link Constructor#getParameterAnnotations()} will be returned.
     *
     * @param constructor the Constructor for which to return parameter annotations
     * @return array of arrays containing the annotations on the parameters of the given Constructor
     */
    private Annotation[][] getConstructorParameterAnnotations(final Constructor<?> constructor) {
        /*
         * TODO: Remove after JDK9 is the minimum JDK supported
         *
         * JDK9+ correctly accounts for the synthetic parameter and when it becomes
         * the minimum version this method should no longer be required.
         */
        int parameterCount = constructor.getParameterTypes().length;
        Annotation[][] annotations = constructor.getParameterAnnotations();
        int diff = parameterCount - annotations.length;
        if (diff > 0) {
            // May happen on JDK8 and below. We add elements to the front of the array to account for the synthetic params:
            // - for an inner class we expect one param to account for the synthetic outer reference
            // - for an enum we expect two params to account for the synthetic name and ordinal
            if ((!constructor.getDeclaringClass().isEnum() && diff > 1) || diff > 2) {
                throw new GroovyBugError(
                        "Constructor parameter annotations length [" + annotations.length + "] " +
                        "does not match the parameter length: " + constructor
                );
            }
            Annotation[][] adjusted = new Annotation[parameterCount][];
            for (int i = 0; i < diff; i += 1) {
                adjusted[i] = EMPTY_ANNOTATION_ARRAY;
            }
            System.arraycopy(annotations, 0, adjusted, diff, annotations.length);
            return adjusted;
        }
        return annotations;
    }

    private void makePermittedSubclasses(final CompileUnit cu, final ClassNode classNode, final Class<?> clazz) {
        if (!clazz.isSealed()) return;
        List<ClassNode> permittedSubclasses =
            Arrays.stream(clazz.getPermittedSubclasses())
                .map(c -> makeClassNode(cu, c, c))
                .toList();
        classNode.setPermittedSubclasses(permittedSubclasses);
    }

    /**
     * Adds record components to the class node when supported by the runtime.
     *
     * @param cu the owning compile unit
     * @param classNode the class node to update
     * @param clazz the runtime class
     */
    protected void makeRecordComponents(final CompileUnit cu, final ClassNode classNode, final Class<?> clazz) {
    }

    private void makeInterfaceTypes(final CompileUnit cu, final ClassNode classNode, final Class<?> clazz) {
        Type[] interfaceTypes = clazz.getGenericInterfaces();
        final int n = interfaceTypes.length;
        if (n == 0) {
            classNode.setInterfaces(ClassNode.EMPTY_ARRAY);
        } else {
            ClassNode[] ret = new ClassNode[n];
            for (int i = 0; i < n; i += 1) {
                Type type = interfaceTypes[i];
                while (!(type instanceof Class)) {
                    ParameterizedType pt = (ParameterizedType) type;
                    Type t2 = pt.getRawType();
                    if (t2 == type) {
                        throw new GroovyBugError("Cannot transform generic signature of " + clazz + " with generic interface " + interfaceTypes[i] + " to a class.");
                    }
                    type = t2;
                }
                ret[i] = makeClassNode(cu, interfaceTypes[i], (Class<?>) type);
            }
            AnnotatedType[] annotatedInterfaces = clazz.getAnnotatedInterfaces();
            for (int i = 0, m = Math.min(annotatedInterfaces.length, n); i < m; i += 1) {
                ret[i] = applyTypeAnnotations(annotatedInterfaces[i], ret[i]);
            }
            classNode.setInterfaces(ret);
        }
    }

    private ClassNode[] makeClassNodes(final CompileUnit cu, final Type[] types, final Class<?>[] cls) {
        final int n = types.length;
        ClassNode[] nodes = new ClassNode[n];
        for (int i = 0; i < n; i += 1) {
            nodes[i] = makeClassNode(cu, types[i], cls[i]);
        }
        return nodes;
    }

    /**
     * Creates or reuses a class node for the supplied runtime and generic type information.
     *
     * @param cu the owning compile unit
     * @param t the reflective type
     * @param c the erased runtime class
     * @return the resolved class node
     */
    protected ClassNode makeClassNode(final CompileUnit cu, final Type t, final Class<?> c) {
        ClassNode back = null;
        if (cu != null) back = cu.getClass(c.getName());
        if (back == null) back = ClassHelper.make(c);
        if (!(t instanceof Class)) {
            ClassNode front = configureType(t);
            front.setRedirect(back);
            return front;
        }
        return back.getPlainNodeReference();
    }

    /**
     * Applies type-use annotations (JSR 308) of the given annotated type to the given class node,
     * recursing into type arguments, wildcard bounds and array component types. The returned node
     * may differ from the input node: type annotations may only be attached to per-use redirect
     * nodes, so shared (cached) nodes are replaced by an annotated plain node reference.
     *
     * @param annotatedType the reflective annotated type at some position (e.g. a method return type)
     * @param classNode the class node created for that position
     * @return the annotated class node: either the input node or a per-use replacement for it
     * @since 6.0.0
     */
    protected ClassNode applyTypeAnnotations(final AnnotatedType annotatedType, ClassNode classNode) {
        for (Annotation annotation : annotatedType.getAnnotations()) {
            classNode = addTypeAnnotation(classNode, annotation);
        }
        if (annotatedType instanceof AnnotatedParameterizedType) {
            AnnotatedType[] typeArguments = ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
            GenericsType[] genericsTypes = classNode.getGenericsTypes();
            if (genericsTypes != null) {
                for (int i = 0, n = Math.min(typeArguments.length, genericsTypes.length); i < n; i += 1) {
                    if (typeArguments[i] instanceof AnnotatedWildcardType) {
                        applyWildcardTypeAnnotations((AnnotatedWildcardType) typeArguments[i], genericsTypes, i);
                    } else {
                        genericsTypes[i].setType(applyTypeAnnotations(typeArguments[i], genericsTypes[i].getType()));
                    }
                }
            }
        } else if (annotatedType instanceof AnnotatedArrayType && classNode.isArray()) {
            ClassNode componentType = classNode.getComponentType();
            ClassNode newComponentType = applyTypeAnnotations(((AnnotatedArrayType) annotatedType).getAnnotatedGenericComponentType(), componentType);
            if (newComponentType != componentType) {
                ClassNode newClassNode = newComponentType.makeArray();
                for (AnnotationNode annotationNode : classNode.getTypeAnnotations()) {
                    if (!newClassNode.isRedirectNode()) newClassNode = newClassNode.getPlainNodeReference(false);
                    newClassNode.addTypeAnnotation(annotationNode);
                }
                classNode = newClassNode;
            }
        }
        return classNode;
    }

    private void applyWildcardTypeAnnotations(final AnnotatedWildcardType wildcardType, final GenericsType[] genericsTypes, final int i) {
        GenericsType genericsType = genericsTypes[i];
        for (Annotation annotation : wildcardType.getAnnotations()) {
            genericsType.setType(addTypeAnnotation(genericsType.getType(), annotation));
        }
        AnnotatedType[] annotatedLowerBounds = wildcardType.getAnnotatedLowerBounds();
        ClassNode lowerBound = genericsType.getLowerBound();
        if (annotatedLowerBounds.length > 0 && lowerBound != null) {
            ClassNode newLowerBound = applyTypeAnnotations(annotatedLowerBounds[0], lowerBound);
            if (newLowerBound != lowerBound) {
                GenericsType newGenericsType = new GenericsType(genericsType.getType(), null, newLowerBound);
                newGenericsType.setWildcard(true);
                genericsTypes[i] = newGenericsType;
            }
        } else {
            ClassNode[] upperBounds = genericsType.getUpperBounds();
            AnnotatedType[] annotatedUpperBounds = wildcardType.getAnnotatedUpperBounds();
            if (upperBounds != null) {
                for (int j = 0, m = Math.min(annotatedUpperBounds.length, upperBounds.length); j < m; j += 1) {
                    upperBounds[j] = applyTypeAnnotations(annotatedUpperBounds[j], upperBounds[j]);
                }
            }
        }
    }

    private ClassNode addTypeAnnotation(ClassNode classNode, final Annotation annotation) {
        // shared/cached nodes must not be mutated; substitute a per-use proxy,
        // which is where type annotations belong
        if (!classNode.isRedirectNode()) classNode = classNode.getPlainNodeReference(false);
        classNode.addTypeAnnotation(toAnnotationNode(annotation));
        return classNode;
    }

    private Parameter[] makeParameters(final CompileUnit cu, final Type[] types, final Class<?>[] cls, final Annotation[][] parameterAnnotations, final Member member) {
        Parameter[] params = Parameter.EMPTY_ARRAY;
        final int n = types.length;
        if (n > 0) {
            params = new Parameter[n];
            String[] names = new String[n];
            fillParameterNames(names, member);
            for (int i = 0; i < n; i += 1) {
                setAnnotationMetaData(parameterAnnotations[i],
                        params[i] = new Parameter(makeClassNode(cu, types[i], cls[i]), names[i]));
            }
            // synthetic parameters (e.g. of inner class constructors) may not be
            // included in the annotated parameter types; skip on length mismatch
            AnnotatedType[] annotatedTypes = ((java.lang.reflect.Executable) member).getAnnotatedParameterTypes();
            if (annotatedTypes.length == n) {
                for (int i = 0; i < n; i += 1) {
                    params[i].setType(applyTypeAnnotations(annotatedTypes[i], params[i].getType()));
                }
            }
        }
        return params;
    }

    /**
     * Applies type-use annotations on the {@code throws} clause of the given executable
     * to the corresponding exception class nodes.
     *
     * @param member the reflective executable member
     * @param exceptions the exception class nodes created for the member
     */
    private void applyExceptionTypeAnnotations(final java.lang.reflect.Executable member, final ClassNode[] exceptions) {
        AnnotatedType[] annotatedTypes = member.getAnnotatedExceptionTypes();
        for (int i = 0, n = Math.min(annotatedTypes.length, exceptions.length); i < n; i += 1) {
            exceptions[i] = applyTypeAnnotations(annotatedTypes[i], exceptions[i]);
        }
    }

    /**
     * Populates parameter names from the supplied reflective executable member.
     *
     * @param names the destination array for parameter names
     * @param member the reflective member providing parameter metadata
     */
    protected void fillParameterNames(final String[] names, final Member member) {
        try {
            java.lang.reflect.Parameter[] parameters = ((java.lang.reflect.Executable) member).getParameters();
            for (int i = 0, n = names.length; i < n; i += 1) {
                names[i] = parameters[i].getName();
            }
        } catch (RuntimeException e) {
            throw new GroovyBugError(e);
        }
    }

    //--------------------------------------------------------------------------

    /**
     * The following scenarios can not set accessible, i.e. the return value is false
     * 1) SecurityException occurred
     * 2) the accessible object is a Constructor object for the Class class
     *
     * @param accessibleObject the accessible object to check
     * @param callerClass the callerClass to invoke {@code setAccessible}
     * @return the check result
     */
    @Override
    public boolean checkCanSetAccessible(final AccessibleObject accessibleObject, final Class<?> callerClass) {
        if (accessibleObject instanceof Constructor<?> c) {
            if (c.getDeclaringClass() == Class.class) {
                return false; // Cannot make a java.lang.Class constructor accessible
            }
        }

        return true;
    }

    /** {@inheritDoc} */
    @Override
    public boolean checkAccessible(final Class<?> callerClass, final Class<?> declaringClass, final int memberModifiers, final boolean allowIllegalAccess) {
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public boolean trySetAccessible(final AccessibleObject ao) {
        try {
            ao.setAccessible(true);
            return true;
        } catch (SecurityException e) {
            throw e;
        } catch (Throwable t) {
            return false;
        }
    }

    /** {@inheritDoc} */
    @Override
    public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod) {
        return transformMetaMethod(metaClass, metaMethod, null);
    }

    /** {@inheritDoc} */
    @Override
    public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod, final Class<?> caller) {
        return metaMethod;
    }

    /** {@inheritDoc} */
    @Override
    public void invalidateCallSites() {
        IndyInterface.invalidateSwitchPoints();
    }

    /** {@inheritDoc} */
    @Override
    public Object getInvokeSpecialHandle(final Method method, final Object receiver) {
        final Class<?> receiverClass = receiver.getClass();
        try {
            return newLookup(receiverClass).unreflectSpecial(method, receiverClass).bindTo(receiver);
        } catch (ReflectiveOperationException e1) {
            ReflectionUtils.trySetAccessible(method);
            final Class<?> declaringClass = method.getDeclaringClass();
            try {
                return newLookup(declaringClass).unreflectSpecial(method, declaringClass).bindTo(receiver);
            } catch (ReflectiveOperationException e2) {
                var e3 = new GroovyBugError(e1);
                e3.addSuppressed(e2);
                throw e3;
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    public Object invokeHandle(final Object handle, final Object[] arguments) throws Throwable {
        return ((MethodHandle) handle).invokeWithArguments(arguments);
    }

    //--------------------------------------------------------------------------

    /**
     * Returns a private lookup for the supplied target class using the active VM plugin.
     *
     * @param targetClass the class to create a lookup for
     * @return a lookup with private access to {@code targetClass}
     */
    @Deprecated(since = "5.0.0")
    @SuppressWarnings("removal")
    public static MethodHandles.Lookup of(final Class<?> targetClass) {
        return ((Java8) VMPluginFactory.getPlugin()).newLookup(targetClass);
    }

    /**
     * Creates a lookup capable of accessing members declared by the target class.
     *
     * @param targetClass the lookup target
     * @return a lookup for the target class
     */
    protected MethodHandles.Lookup newLookup(final Class<?> targetClass) {
        throw new IllegalStateException();
    }

}
