AnnotationMetadataGenUtils.java

/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.inject.annotation;

import io.micronaut.context.expressions.AbstractEvaluatedExpression;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationDefaultValuesProvider;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.expressions.EvaluatedExpressionReference;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.writer.GenUtils;
import io.micronaut.sourcegen.model.ClassTypeDef;
import io.micronaut.sourcegen.model.ExpressionDef;
import io.micronaut.sourcegen.model.FieldDef;
import io.micronaut.sourcegen.model.MethodDef;
import io.micronaut.sourcegen.model.StatementDef;
import io.micronaut.sourcegen.model.TypeDef;

import javax.lang.model.element.Modifier;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * Responsible for writing class files that are instances of {@link AnnotationMetadata}.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@Internal
public final class AnnotationMetadataGenUtils {

    /**
     * Field name for annotation metadata.
     */
    public static final String FIELD_ANNOTATION_METADATA_NAME = "$ANNOTATION_METADATA";
    public static final ClassTypeDef TYPE_ANNOTATION_METADATA = ClassTypeDef.of(AnnotationMetadata.class);

    public static final FieldDef FIELD_ANNOTATION_METADATA = FieldDef.builder(FIELD_ANNOTATION_METADATA_NAME, TYPE_ANNOTATION_METADATA).build();
    public static final ExpressionDef EMPTY_METADATA = TYPE_ANNOTATION_METADATA.getStaticField(
        FieldDef.builder("EMPTY_METADATA", TYPE_ANNOTATION_METADATA).build()
    );

    private static final ClassTypeDef TYPE_DEFAULT_ANNOTATION_METADATA = ClassTypeDef.of(DefaultAnnotationMetadata.class);
    private static final ClassTypeDef TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY = ClassTypeDef.of(AnnotationMetadataHierarchy.class);
    private static final ClassTypeDef TYPE_ANNOTATION_CLASS_VALUE = ClassTypeDef.of(AnnotationClassValue.class);

    private static final String LOAD_CLASS_PREFIX = "$micronaut_load_class_value_";

    private static final Method METHOD_REGISTER_ANNOTATION_DEFAULTS = ReflectionUtils.getRequiredInternalMethod(
        DefaultAnnotationMetadata.class,
        "registerAnnotationDefaults",
        AnnotationClassValue.class,
        Map.class
    );

    private static final Method METHOD_REGISTER_ANNOTATION_TYPE = ReflectionUtils.getRequiredInternalMethod(
        DefaultAnnotationMetadata.class,
        "registerAnnotationType",
        AnnotationClassValue.class
    );

    private static final Method METHOD_REGISTER_REPEATABLE_ANNOTATIONS = ReflectionUtils.getRequiredInternalMethod(
        DefaultAnnotationMetadata.class,
        "registerRepeatableAnnotations",
        Map.class
    );

    private static final Constructor<?> CONSTRUCTOR_ANNOTATION_METADATA = ReflectionUtils.getRequiredInternalConstructor(
        DefaultAnnotationMetadata.class,
        Map.class,
        Map.class,
        Map.class,
        Map.class,
        Map.class,
        boolean.class,
        boolean.class
    );

    private static final Constructor<?> CONSTRUCTOR_ANNOTATION_METADATA_HIERARCHY = ReflectionUtils.getRequiredInternalConstructor(
        AnnotationMetadataHierarchy.class,
        AnnotationMetadata[].class
    );

    private static final Constructor<?> CONSTRUCTOR_ANNOTATION_VALUE_AND_MAP = ReflectionUtils.getRequiredInternalConstructor(
        AnnotationValue.class,
        String.class,
        Map.class,
        AnnotationDefaultValuesProvider.class
    );

    private static final Constructor<?> CONSTRUCTOR_CLASS_VALUE = ReflectionUtils.getRequiredInternalConstructor(
        AnnotationClassValue.class,
        String.class
    );

    private static final Constructor<?> CONSTRUCTOR_CLASS_VALUE_WITH_CLASS = ReflectionUtils.getRequiredInternalConstructor(
        AnnotationClassValue.class,
        Class.class
    );

    private static final Constructor<?> CONSTRUCTOR_CLASS_VALUE_WITH_INSTANCE = ReflectionUtils.getRequiredInternalConstructor(
        AnnotationClassValue.class,
        Object.class
    );

    private static final Constructor<?> CONSTRUCTOR_CONTEXT_EVALUATED_EXPRESSION = ReflectionUtils.getRequiredInternalConstructor(
        AbstractEvaluatedExpression.class,
        Object.class
    );

    private static final Field ANNOTATION_DEFAULT_VALUES_PROVIDER = ReflectionUtils.getRequiredField(
        AnnotationMetadataSupport.class,
        "ANNOTATION_DEFAULT_VALUES_PROVIDER"
    );

    private AnnotationMetadataGenUtils() {
    }

    /**
     * Instantiate new metadata expression.
     *
     * @param annotationMetadata         The annotation metadata
     * @param loadClassValueExpressionFn The load type expression fn
     * @return The expression
     */
    @NonNull
    public static ExpressionDef instantiateNewMetadata(MutableAnnotationMetadata annotationMetadata,
                                                       Function<String, ExpressionDef> loadClassValueExpressionFn) {
        return instantiateInternal(annotationMetadata, loadClassValueExpressionFn);
    }

    /**
     * Instantiate new metadata hierarchy expression.
     *
     * @param hierarchy                  The annotation metadata hierarchy
     * @param loadClassValueExpressionFn The load type expression fn
     * @return The expression
     */
    @NonNull
    public static ExpressionDef instantiateNewMetadataHierarchy(AnnotationMetadataHierarchy hierarchy,
                                                                Function<String, ExpressionDef> loadClassValueExpressionFn) {

        if (hierarchy.isEmpty()) {
            return emptyMetadata();
        }
        List<AnnotationMetadata> notEmpty = CollectionUtils.iterableToList(hierarchy)
            .stream().filter(h -> !h.isEmpty()).toList();
        if (notEmpty.size() == 1) {
            return pushNewAnnotationMetadataOrReference(notEmpty.get(0), loadClassValueExpressionFn);
        }

        return TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY.instantiate(
            CONSTRUCTOR_ANNOTATION_METADATA_HIERARCHY,

            TYPE_ANNOTATION_METADATA.array().instantiate(
                pushNewAnnotationMetadataOrReference(hierarchy.getRootMetadata(), loadClassValueExpressionFn),
                pushNewAnnotationMetadataOrReference(hierarchy.getDeclaredMetadata(), loadClassValueExpressionFn)
            )
        );
    }

    /**
     * The annotation metadata reference expression.
     *
     * @param annotationMetadata The annotation metadata
     * @return The expression
     */
    @NonNull
    public static ExpressionDef annotationMetadataReference(AnnotationMetadataReference annotationMetadata) {
        return ClassTypeDef.of(annotationMetadata.getClassName()).getStaticField(FIELD_ANNOTATION_METADATA);
    }

    /**
     * The empty annotation metadata expression.
     *
     * @return The expression
     */
    @NonNull
    public static ExpressionDef emptyMetadata() {
        return TYPE_ANNOTATION_METADATA.getStaticField("EMPTY_METADATA", TYPE_ANNOTATION_METADATA);
    }

    /**
     * Create a new load class value expression function.
     *
     * @param declaringType   The declaring type
     * @param loadTypeMethods The load type methods
     * @return The function
     */
    @NonNull
    public static Function<String, ExpressionDef> createLoadClassValueExpressionFn(ClassTypeDef declaringType,
                                                                                   Map<String, MethodDef> loadTypeMethods) {
        return typeName -> invokeLoadClassValueMethod(declaringType, loadTypeMethods, typeName);
    }

    /**
     * Creates a `getAnnotationMetadata` method.
     *
     * @param owningType         The owning type
     * @param annotationMetadata The annotation metadata
     * @return The new method
     */
    @NonNull
    public static MethodDef createGetAnnotationMetadataMethodDef(ClassTypeDef owningType, AnnotationMetadata annotationMetadata) {
        return MethodDef.builder("getAnnotationMetadata").returns(TYPE_ANNOTATION_METADATA)
            .addModifiers(Modifier.PUBLIC)
            .build((aThis, methodParameters) -> {
                // in order to save memory of a method doesn't have any annotations of its own but merely references class metadata
                // then we set up an annotation metadata reference from the method to the class (or inherited method) metadata
                AnnotationMetadata targetAnnotationMetadata = annotationMetadata.getTargetAnnotationMetadata();
                if (targetAnnotationMetadata.isEmpty()) {
                    return AnnotationMetadataGenUtils.EMPTY_METADATA.returning();
                }
                if (targetAnnotationMetadata instanceof AnnotationMetadataReference reference) {
                    return annotationMetadataReference(reference).returning();
                }
                return owningType.getStaticField(FIELD_ANNOTATION_METADATA).returning();
            });
    }

    /**
     * Create annotation metadata field and initialize it to the metadata provided.
     *
     * @param annotationMetadata         The annotation metadata
     * @param loadClassValueExpressionFn The function to get the class value
     * @return The new field
     */
    @Nullable
    public static FieldDef createAnnotationMetadataFieldAndInitialize(AnnotationMetadata annotationMetadata,
                                                                      Function<String, ExpressionDef> loadClassValueExpressionFn) {
        if (annotationMetadata instanceof AnnotationMetadataReference) {
            return null;
        }
        FieldDef.FieldDefBuilder fieldDefBuilder = FieldDef.builder(FIELD_ANNOTATION_METADATA_NAME, TYPE_ANNOTATION_METADATA)
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);

        ExpressionDef initializer;
        annotationMetadata = annotationMetadata.getTargetAnnotationMetadata();
        if (annotationMetadata.isEmpty()) {
            initializer = AnnotationMetadataGenUtils.EMPTY_METADATA;
        } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) {
            initializer = AnnotationMetadataGenUtils.instantiateNewMetadata(mutableAnnotationMetadata, loadClassValueExpressionFn);
        } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) {
            initializer = AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(annotationMetadataHierarchy, loadClassValueExpressionFn);
        } else {
            throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata);
        }
        fieldDefBuilder.initializer(initializer);

        return fieldDefBuilder.build();
    }

    /**
     * Adds the annotation metadata defaults statement/s.
     *
     * @param statements                 The statements
     * @param annotationMetadata         The annotation metadata
     * @param loadClassValueExpressionFn The load type expression fn
     */
    public static void addAnnotationDefaults(List<StatementDef> statements,
                                             AnnotationMetadata annotationMetadata,
                                             Function<String, ExpressionDef> loadClassValueExpressionFn) {
        annotationMetadata = annotationMetadata.getTargetAnnotationMetadata();
        if (annotationMetadata.isEmpty()) {
            return;
        }
        if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) {
            annotationMetadata = annotationMetadataHierarchy.merge();
        }
        if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) {
            AnnotationMetadataGenUtils.addAnnotationDefaults(
                statements,
                mutableAnnotationMetadata,
                loadClassValueExpressionFn
            );
        } else {
            throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata);
        }
    }

    @NonNull
    private static ExpressionDef.InvokeStaticMethod invokeLoadClassValueMethod(ClassTypeDef declaringType,
                                                                               Map<String, MethodDef> loadTypeMethods,
                                                                               String typeName) {
        final MethodDef loadTypeGeneratorMethod = loadTypeMethods.computeIfAbsent(typeName, type -> {

            final String methodName = LOAD_CLASS_PREFIX + loadTypeMethods.size();

            // This logic will generate a method such as the following, allowing non-dynamic classloading:
            //
            // AnnotationClassValue $micronaut_load_class_value_0() {
            //     try {
            //          return new AnnotationClassValue(test.MyClass.class);
            //     } catch(Throwable e) {
            //          return new AnnotationClassValue("test.MyClass");
            //     }
            // }

            return MethodDef.builder(methodName)
                .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC)
                .returns(TYPE_ANNOTATION_CLASS_VALUE)
                .buildStatic(methodParameters -> StatementDef.doTry(
                    TYPE_ANNOTATION_CLASS_VALUE.instantiate(
                        CONSTRUCTOR_CLASS_VALUE_WITH_CLASS,
                        ExpressionDef.constant(TypeDef.of(typeName))
                    ).returning()
                ).doCatch(Throwable.class, exceptionVar -> TYPE_ANNOTATION_CLASS_VALUE.instantiate(
                    CONSTRUCTOR_CLASS_VALUE,
                    ExpressionDef.constant(typeName)
                ).returning()));
        });

        return declaringType.invokeStatic(loadTypeGeneratorMethod);
    }

    private static void addAnnotationDefaults(List<StatementDef> statements,
                                              MutableAnnotationMetadata annotationMetadata,
                                              Function<String, ExpressionDef> loadClassValueExpressionFn) {
        final Map<String, Map<CharSequence, Object>> annotationDefaultValues = annotationMetadata.annotationDefaultValues;

        if (CollectionUtils.isNotEmpty(annotationDefaultValues)) {
            addAnnotationDefaultsInternal(statements, annotationDefaultValues, new HashSet<>(), loadClassValueExpressionFn);
        }
        if (annotationMetadata.annotationRepeatableContainer != null && !annotationMetadata.annotationRepeatableContainer.isEmpty()) {
            Map<String, String> annotationRepeatableContainer = new LinkedHashMap<>(annotationMetadata.annotationRepeatableContainer);
            AnnotationMetadataSupport.getCoreRepeatableAnnotationsContainers().forEach(annotationRepeatableContainer::remove);
            AnnotationMetadataSupport.registerRepeatableAnnotations(annotationRepeatableContainer);
            if (!annotationRepeatableContainer.isEmpty()) {
                statements.add(
                    TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic(
                        METHOD_REGISTER_REPEATABLE_ANNOTATIONS,
                        stringMapOf(annotationRepeatableContainer, loadClassValueExpressionFn)
                    )
                );
            }
        }
    }

    private static void addAnnotationDefaultsInternal(List<StatementDef> statements,
                                                      Map<String, Map<CharSequence, Object>> annotationDefaultValues,
                                                      Set<String> writtenAnnotations,
                                                      Function<String, ExpressionDef> loadClassValueExpressionFn) {
        for (Map.Entry<String, Map<CharSequence, Object>> entry : annotationDefaultValues.entrySet()) {
            addAnnotationDefaultsInternal(statements,
                writtenAnnotations,
                entry.getKey(),
                entry.getValue(),
                loadClassValueExpressionFn);
        }
    }

    @NonNull
    private static void addAnnotationDefaultsInternal(List<StatementDef> statements,
                                                      Set<String> writtenAnnotations,
                                                      String annotationName,
                                                      Map<CharSequence, Object> annotationValues,
                                                      Function<String, ExpressionDef> loadClassValueExpressionFn) {
        final boolean typeOnly = CollectionUtils.isEmpty(annotationValues);

        // skip already registered
        if (typeOnly && AnnotationMetadataSupport.getRegisteredAnnotationType(annotationName).isPresent()
            || AnnotationMetadataSupport.getCoreAnnotationDefaults().containsKey(annotationName)) {
            return;
        }

        if (!writtenAnnotations.add(annotationName)) {
            return;
        }

        for (Map.Entry<CharSequence, Object> values : annotationValues.entrySet()) {
            Object value = values.getValue();
            if (value instanceof AnnotationValue<?> annotationValue && CollectionUtils.isNotEmpty(annotationValue.getDefaultValues())) {
                addAnnotationDefaultsInternal(
                    statements,
                    writtenAnnotations,
                    annotationValue.getAnnotationName(),
                    annotationValue.getDefaultValues(),
                    loadClassValueExpressionFn
                );
            }
        }

        if (!typeOnly) {
            statements.add(
                TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic(
                    METHOD_REGISTER_ANNOTATION_DEFAULTS,
                    loadClassValueExpressionFn.apply(annotationName),
                    stringMapOf(annotationValues, loadClassValueExpressionFn)
                )
            );
        } else {
            statements.add(
                TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic(
                    METHOD_REGISTER_ANNOTATION_TYPE,
                    loadClassValueExpressionFn.apply(annotationName)
                )
            );
        }
        writtenAnnotations.add(annotationName);
    }

    @NonNull
    private static ExpressionDef instantiateInternal(MutableAnnotationMetadata annotationMetadata,
                                                     Function<String, ExpressionDef> loadClassValueExpressionFn) {
        Map<String, List<String>> annotationsByStereotype = annotationMetadata.annotationsByStereotype;
        if (annotationMetadata.getSourceRetentionAnnotations() != null && annotationsByStereotype != null) {
            annotationsByStereotype = new LinkedHashMap<>(annotationsByStereotype);
            for (String sourceRetentionAnnotation : annotationMetadata.getSourceRetentionAnnotations()) {
                annotationsByStereotype.remove(sourceRetentionAnnotation);
            }
        }
        return TYPE_DEFAULT_ANNOTATION_METADATA
            .instantiate(
                CONSTRUCTOR_ANNOTATION_METADATA,

                // 1st argument: the declared annotations
                pushCreateAnnotationData(annotationMetadata.declaredAnnotations, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn),
                // 2nd argument: the declared stereotypes
                pushCreateAnnotationData(annotationMetadata.declaredStereotypes, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn),
                // 3rd argument: all stereotypes
                pushCreateAnnotationData(annotationMetadata.allStereotypes, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn),
                // 4th argument: all annotations
                pushCreateAnnotationData(annotationMetadata.allAnnotations, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn),
                // 5th argument: annotations by stereotype,
                GenUtils.stringMapOf(annotationsByStereotype, false, Collections.emptyList(), GenUtils::listOfString),
                // 6th argument: has property expressions,
                ExpressionDef.constant(annotationMetadata.hasPropertyExpressions()),
                // 7th argument: has evaluated expressions
                ExpressionDef.constant(annotationMetadata.hasEvaluatedExpressions())
            );
    }

    @NonNull
    private static ExpressionDef pushCreateAnnotationData(Map<String, Map<CharSequence, Object>> annotationData,
                                                          Set<String> sourceRetentionAnnotations,
                                                          Function<String, ExpressionDef> loadClassValueExpressionFn) {
        if (annotationData != null) {
            annotationData = new LinkedHashMap<>(annotationData);
            for (String sourceRetentionAnnotation : sourceRetentionAnnotations) {
                annotationData.remove(sourceRetentionAnnotation);
            }
        }

        return GenUtils.stringMapOf(annotationData, false, Collections.emptyMap(),
            attributes -> GenUtils.stringMapOf(attributes, true, null,
                value -> asValueExpression(value, loadClassValueExpressionFn)));
    }

    @NonNull
    private static ExpressionDef asValueExpression(Object value,
                                                   Function<String, ExpressionDef> loadClassValueExpressionFn) {
        if (value == null) {
            throw new IllegalStateException("Cannot map null value");
        }
        if (value instanceof Enum<?> anEnum) {
            return ExpressionDef.constant(anEnum.name());
        }
        if (value instanceof Boolean || value instanceof String || value instanceof Number || value instanceof Character) {
            return ExpressionDef.constant(value);
        }
        if (value instanceof AnnotationClassValue<?> acv) {
            if (acv.isInstantiated()) {
                return TYPE_ANNOTATION_CLASS_VALUE
                    .instantiate(CONSTRUCTOR_CLASS_VALUE_WITH_INSTANCE,
                        ClassTypeDef.of(acv.getName()).instantiate()
                    );
            } else {
                return loadClassValueExpressionFn.apply(acv.getName());
            }
        }
        if (value.getClass().isArray()) {
            Class<?> arrayComponentType = value.getClass().getComponentType();
            if (Enum.class.isAssignableFrom(arrayComponentType)) {
                // Express enums as strings
                arrayComponentType = String.class;
            }
            return TypeDef.of(arrayComponentType).array().instantiate(Arrays.stream(getArray(value))
                .map(v -> asValueExpression(v, loadClassValueExpressionFn))
                .toList());
        }
        if (value instanceof Collection<?> collection) {
            if (collection.isEmpty()) {
                return ExpressionDef.constant(new Object[0]);
            }
            Class<?> componentType = null;
            for (Object o : collection) {
                if (componentType == null) {
                    componentType = o.getClass();
                } else if (!o.getClass().equals(componentType)) {
                    componentType = Object.class;
                    break;
                }
            }
            if (Enum.class.isAssignableFrom(componentType)) {
                // Express enums as strings
                componentType = String.class;
            }
            return TypeDef.of(componentType).array()
                .instantiate(collection.stream().map(i -> asValueExpression(i, loadClassValueExpressionFn)).toList());
        }
        if (value instanceof AnnotationValue<?> data) {
            return ClassTypeDef.of(AnnotationValue.class)
                .instantiate(
                    CONSTRUCTOR_ANNOTATION_VALUE_AND_MAP,
                    ExpressionDef.constant(data.getAnnotationName()),
                    stringMapOf(data.getValues(), loadClassValueExpressionFn),
                    ClassTypeDef.of(AnnotationMetadataSupport.class).getStaticField(ANNOTATION_DEFAULT_VALUES_PROVIDER)
                );
        }
        if (value instanceof EvaluatedExpressionReference expressionReference) {
            Object annotationValue = expressionReference.annotationValue();
            if (annotationValue instanceof String || annotationValue instanceof String[]) {
                return ClassTypeDef.of(expressionReference.expressionClassName())
                    .instantiate(
                        CONSTRUCTOR_CONTEXT_EVALUATED_EXPRESSION,

                        ExpressionDef.constant(annotationValue)
                    );
            } else {
                throw new IllegalStateException();
            }
        }
        throw new IllegalStateException("Unsupported Map value:  " + value + " " + value.getClass().getName());
    }

    @NonNull
    private static <T> ExpressionDef stringMapOf(Map<? extends CharSequence, T> annotationData,
                                                 Function<String, ExpressionDef> loadClassValueExpressionFn) {
        return GenUtils.stringMapOf(
            annotationData,
            true,
            null,
            AnnotationMetadataGenUtils::isSupportedMapValue,
            o -> asValueExpression(o, loadClassValueExpressionFn)
        );
    }

    @NonNull
    private static ExpressionDef pushNewAnnotationMetadataOrReference(AnnotationMetadata annotationMetadata,
                                                                      Function<String, ExpressionDef> loadClassValueExpressionFn) {
        annotationMetadata = annotationMetadata.getTargetAnnotationMetadata();
        if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) {
            // Synthetic property getters / setters can consist of field + (setter / getter) annotation hierarchy
            annotationMetadata = MutableAnnotationMetadata.of(annotationMetadataHierarchy);
        }
        if (annotationMetadata.isEmpty()) {
            return emptyMetadata();
        } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) {
            return instantiateNewMetadata(mutableAnnotationMetadata, loadClassValueExpressionFn);
        } else if (annotationMetadata instanceof AnnotationMetadataReference reference) {
            return annotationMetadataReference(reference);
        } else {
            throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata);
        }
    }

    private static Object[] getArray(Object val) {
        if (val instanceof Object[]) {
            return (Object[]) val;
        }
        Object[] outputArray = new Object[Array.getLength(val)];
        for (int i = 0; i < outputArray.length; ++i) {
            outputArray[i] = Array.get(val, i);
        }
        return outputArray;
    }

    private static boolean isSupportedMapValue(Object value) {
        if (value == null) {
            return false;
        } else if (value instanceof Boolean) {
            return true;
        } else if (value instanceof String) {
            return true;
        } else if (value instanceof AnnotationClassValue<?>) {
            return true;
        } else if (value instanceof Enum<?>) {
            return true;
        } else if (value.getClass().isArray()) {
            return true;
        } else if (value instanceof Collection<?>) {
            return true;
        } else if (value instanceof Map) {
            return true;
        } else if (value instanceof Long) {
            return true;
        } else if (value instanceof Double) {
            return true;
        } else if (value instanceof Float) {
            return true;
        } else if (value instanceof Byte) {
            return true;
        } else if (value instanceof Short) {
            return true;
        } else if (value instanceof Character) {
            return true;
        } else if (value instanceof Number) {
            return true;
        } else if (value instanceof AnnotationValue<?>) {
            return true;
        } else if (value instanceof EvaluatedExpressionReference) {
            return true;
        } else if (value instanceof Class<?>) {
            // The class should be added as AnnotationClassValue
            return false;
        }
        return false;
    }

}