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

io.micronaut.serde.processor.SerdeAnnotationVisitor Maven / Gradle / Ivy

/*
 * Copyright 2017-2021 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.serde.processor;

import io.micronaut.context.annotation.Executable;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.Order;
import io.micronaut.core.bind.annotation.Bindable;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.exceptions.ConversionErrorException;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.InstantiationUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.ElementModifier;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.ast.TypedElement;
import io.micronaut.inject.beans.visitor.IntrospectedTypeElementVisitor;
import io.micronaut.inject.processing.ProcessingException;
import io.micronaut.inject.visitor.TypeElementVisitor;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.serde.annotation.SerdeImport;
import io.micronaut.serde.annotation.Serdeable;
import io.micronaut.serde.config.annotation.SerdeConfig;
import io.micronaut.serde.config.naming.PropertyNamingStrategy;

import java.lang.annotation.Annotation;
import java.text.DecimalFormat;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * A visitor that provides validation and extended handling for JSON annotations.
 */
public class SerdeAnnotationVisitor implements TypeElementVisitor {

    private static final String DEFAULT_REF_ALIAS_NAME = "defaultReference";

    private boolean failOnError = true;
    private ClassElement currentClass;
    private MethodElement anyGetterMethod;
    private MethodElement anySetterMethod;
    private FieldElement anyGetterField;
    private FieldElement anySetterField;
    private MethodElement jsonValueMethod;
    private FieldElement jsonValueField;
    private final Set readMethods = new HashSet<>(20);
    private final Set writeMethods = new HashSet<>(20);
    private final Set elementVisitedAsSubtype = new HashSet<>(10);
    private SerdeConfig.SerCreatorMode creatorMode = SerdeConfig.SerCreatorMode.PROPERTIES;

    @Override
    public Set getSupportedAnnotationNames() {
        return CollectionUtils.setOf(
                "com.fasterxml.jackson.annotation.*",
                "jakarta.json.bind.annotation.*",
                "io.micronaut.serde.annotation.*",
                "org.bson.codecs.pojo.annotations.*",
                "io.micronaut.serde.config.annotation.*",
                "com.fasterxml.jackson.databind.annotation.*"
        );
    }

    private Set getUnsupportedJacksonAnnotations() {
        return CollectionUtils.setOf(
                "com.fasterxml.jackson.annotation.JsonKey",
                "com.fasterxml.jackson.annotation.JsonAutoDetect",
                "com.fasterxml.jackson.annotation.JsonMerge",
                "com.fasterxml.jackson.annotation.JsonIdentityInfo",
                "com.fasterxml.jackson.annotation.JsonIdentityReference"
        );
    }

    @Override
    public void visitField(FieldElement element, VisitorContext context) {
        checkForErrors(element, context);
        checkForFieldErrors(element);
    }

    private void checkForFieldErrors(FieldElement element) {
        if (!failOnError) {
            return;
        }
        if (element.hasDeclaredAnnotation(SerdeConfig.SerAnyGetter.class)) {
            if (element.hasDeclaredAnnotation(SerdeConfig.SerUnwrapped.class)) {
                throw new ProcessingException(element, "A field annotated with AnyGetter cannot be unwrapped");
            } else if (element.hasDeclaredAnnotation(SerdeConfig.SerValue.class)) {
                throw new ProcessingException(element, "A field annotated with AnyGetter cannot be a JsonValue");
            } else if (!element.getGenericField().isAssignable(Map.class)) {
                throw new ProcessingException(element, "A field annotated with AnyGetter must be a Map");
            } else {
                if (anyGetterField != null) {
                    throw new ProcessingException(element, "Only a single AnyGetter field is supported, another defined: " + anyGetterField.getDescription(true));
                } else if (anyGetterMethod != null) {
                    throw new ProcessingException(element, "Cannot define both an AnyGetter field and an AnyGetter method: " + anyGetterMethod.getDescription(true));
                } else {
                    this.anyGetterField = element;
                }
            }
        } else if (element.hasDeclaredAnnotation(SerdeConfig.SerAnySetter.class)) {
            if (creatorMode == SerdeConfig.SerCreatorMode.DELEGATING) {
                throw new ProcessingException(element, "A field annotated with AnySetter cannot use DELEGATING creation");
            } else if (element.hasDeclaredAnnotation(SerdeConfig.SerUnwrapped.class)) {
                throw new ProcessingException(element, "A field annotated with AnySetter cannot be unwrapped");
            } else if (!element.getGenericField().isAssignable(Map.class)) {
                throw new ProcessingException(element, "A field annotated with AnySetter must be a Map");
            } else {
                if (anySetterField != null) {
                    throw new ProcessingException(element, "Only a single AnySetter field is supported, another defined: " + anySetterField.getDescription(true));
                } else if (anySetterMethod != null) {
                    throw new ProcessingException(element, "Cannot define both an AnySetter field and an AnySetter method: " + anySetterMethod.getDescription(true));
                } else {
                    this.anySetterField = element;
                }
            }
        } else if (element.hasDeclaredAnnotation(SerdeConfig.SerValue.class)) {
            if (jsonValueField != null) {
                throw new ProcessingException(element, "A JsonValue field is already defined: " + jsonValueField);
            } else if (jsonValueMethod != null) {
                throw new ProcessingException(element, "A JsonValue method is already defined: " + jsonValueMethod);
            } else {
                this.jsonValueField = element;
            }
        }
    }

    @Override
    public void visitConstructor(ConstructorElement element, VisitorContext context) {
        checkForErrors(element, context);
    }

    @Override
    public void visitMethod(MethodElement element, VisitorContext context) {
        checkForErrors(element, context);
        AnnotationMetadata methodMetadata = element.getMethodAnnotationMetadata();
        if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.META_ANNOTATION_PROPERTY) ||
            methodMetadata.stringValue(SerdeConfig.class, SerdeConfig.PROPERTY).isPresent()) {
            ParameterElement[] parameters = element.getParameters();
            if (element.isStatic()) {
                throw new ProcessingException(element, "A method annotated with JsonProperty cannot be static");
            } else if (parameters.length == 0) {
                if (element.getReturnType().getName().equals("void")) {
                    throw new ProcessingException(element, "A method annotated with JsonProperty cannot return void");
                } else if (!readMethods.contains(element)) {
                    element.annotate(Executable.class);
                    element.annotate(SerdeConfig.SerGetter.class);
                }
            } else if (parameters.length == 1) {
                if (!writeMethods.contains(element)) {
                    element.annotate(Executable.class);
                    element.annotate(SerdeConfig.SerSetter.class);
                }
            } else {
                throw new ProcessingException(element, "A method annotated with JsonProperty must specify at most 1 argument");
            }
        } else if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.SerGetter.class)) {
            if (element.isStatic()) {
                throw new ProcessingException(element, "A method annotated with JsonGetter cannot be static");
            } else if (element.getReturnType().getName().equals("void")) {
                throw new ProcessingException(element, "A method annotated with JsonGetter cannot return void");
            } else if (element.hasParameters()) {
                throw new ProcessingException(element, "A method annotated with JsonGetter cannot define arguments");
            }
        } else if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.SerSetter.class)) {
            if (element.isStatic()) {
                throw new ProcessingException(element, "A method annotated with JsonSetter cannot be static");
            } else {
                final ParameterElement[] parameters = element.getParameters();
                if (parameters.length != 1) {
                    throw new ProcessingException(element, "A method annotated with JsonSetter must specify exactly 1 argument");
                }
            }
        } else if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.SerAnyGetter.class)) {
            if (this.anyGetterMethod == null) {
                this.anyGetterMethod = element;
            } else {
                throw new ProcessingException(element, "Type already defines a method annotated with JsonAnyGetter: " + anyGetterMethod.getDescription(true));
            }

            if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.SerUnwrapped.class)) {
                throw new ProcessingException(element, "A method annotated with AnyGetter cannot be unwrapped");
            } else if (element.isStatic()) {
                throw new ProcessingException(element, "A method annotated with AnyGetter cannot be static");
            } else if (!element.getGenericReturnType().isAssignable(Map.class)) {
                throw new ProcessingException(element, "A method annotated with AnyGetter must return a Map");
            } else if (element.hasParameters()) {
                throw new ProcessingException(element, "A method annotated with AnyGetter cannot define arguments");
            }
        } else if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.SerAnySetter.class)) {
            if (this.anySetterMethod == null) {
                this.anySetterMethod = element;
            } else {
                throw new ProcessingException(element, "Type already defines a method annotated with JsonAnySetter: " + anySetterMethod.getDescription(true));
            }
            if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.SerUnwrapped.class)) {
                throw new ProcessingException(element, "A method annotated with AnyGetter cannot be unwrapped");
            } else if (element.isStatic()) {
                throw new ProcessingException(element, "A method annotated with AnySetter cannot be static");
            } else {
                final ParameterElement[] parameters = element.getParameters();
                if (parameters.length == 1) {
                   if (!parameters[0].getGenericType().isAssignable(Map.class)) {
                       throw new ProcessingException(element, "A method annotated with AnySetter must either define a single parameter of type Map or define exactly 2 parameters, the first of which should be of type String");
                   }
                } else if (parameters.length != 2 || !parameters[0].getGenericType().isAssignable(String.class)) {
                    throw new ProcessingException(element, "A method annotated with AnySetter must either define a single parameter of type Map or define exactly 2 parameters, the first of which should be of type String");
                }
            }
        } else if (methodMetadata.hasDeclaredAnnotation(SerdeConfig.SerValue.class)) {
            if (jsonValueField != null) {
                throw new ProcessingException(element, "A JsonValue field is already defined: " + jsonValueField);
            } else if (jsonValueMethod != null) {
                throw new ProcessingException(element, "A JsonValue method is already defined: " + jsonValueMethod);
            } else {
                this.jsonValueMethod = element;
            }
        }
    }

    private void checkForErrors(Element element, VisitorContext context) {
        if (!failOnError) {
            return;
        }
        if (element instanceof MethodElement methodElement) {
            if (readMethods.contains(methodElement) && !methodElement.hasParameters()) {
                // handled by PropertyElement
                return;
            } else if (writeMethods.contains(methodElement) && methodElement.getParameters().length == 1) {
                // handled by PropertyElement
                return;
            }
        }

        if (element instanceof MethodElement && element.hasDeclaredAnnotation(SerdeConfig.class) && element.isPrivate()) {
            throw new ProcessingException(element, "JSON annotations cannot be used on private methods and constructors");
        }
        for (String annotation : getUnsupportedJacksonAnnotations()) {
            if (element.hasDeclaredAnnotation(annotation)) {
                throw new ProcessingException(element, "Annotation @" + NameUtils.getSimpleName(annotation) + " is not supported");
            }
        }
        final String error = element.stringValue(SerdeConfig.SerError.class).orElse(null);
        if (error != null) {
            throw new ProcessingException(element, error);
        }
        ClassElement propertyType = resolvePropertyType(element);
        if (propertyType == null) {
            return;
        }
        final boolean isBasicType = isBasicType(propertyType);
        if (isBasicType) {
            String defaultValue = element.stringValue(Bindable.class, "defaultValue").orElse(null);
            if (defaultValue != null) {
                Class t;
                if (propertyType.isPrimitive()) {
                    t = ClassUtils.getPrimitiveType(propertyType.getName())
                            .map(ReflectionUtils::getWrapperType)
                            .orElse(null);
                } else {
                    t =  ClassUtils.forName(propertyType.getName(), getClass().getClassLoader()).orElse(null);
                }
                if (t != null) {
                    try {
                        if (ConversionService.SHARED.canConvert(String.class, t)) {
                            ConversionService.SHARED.convertRequired(defaultValue, t);
                        }
                    } catch (ConversionErrorException e) {
                        throw new ProcessingException(element, "Invalid defaultValue [" + defaultValue + "] specified: " + e.getConversionError().getCause().getMessage());
                    }
                }
            }
        }
        final String pattern = element.stringValue(SerdeConfig.class, SerdeConfig.PATTERN).orElse(null);
        if (pattern != null && failOnError) {
            if (isNumberType(propertyType)) {
                try {
                    new DecimalFormat(pattern);
                } catch (Exception e) {
                    throw new ProcessingException(element, "Specified pattern [" + pattern + "] is not a valid decimal format. See the javadoc for DecimalFormat: " + e.getMessage());
                }
            } else if (propertyType.isAssignable(Temporal.class)) {
                try {
                    DateTimeFormatter.ofPattern(pattern);
                } catch (Exception e) {
                    throw new ProcessingException(element, "Specified pattern [" + pattern + "] is not a valid date format. See the javadoc for DateTimeFormatter: " + e.getMessage());
                }
            }
        }

        handleReferences(element, context, propertyType, isBasicType);

        if (hasAnnotationOnElement(element, SerdeConfig.SerUnwrapped.class)) {
            if (isBasicType(propertyType)) {
                throw new ProcessingException(element, "Unwrapped cannot be declared on basic types");
            }
            final List thatProperties = resolvePropertyNames(context, propertyType, element);
            final List thisProperties = resolvePropertyNames(context, currentClass, null);
            for (String thisProperty : thisProperties) {
                for (String thatProperty : thatProperties) {
                    if (thisProperty.equals(thatProperty)) {
                        throw new ProcessingException(element, "Unwrapped property contains a property [" + thatProperty + "] that conflicts with an existing property of the outer type: " + currentClass.getName() + ". Consider specifying a prefix or suffix to disambiguate this conflict.");
                    }
                }
            }
        }
    }

    private void handleReferences(Element element, VisitorContext context, ClassElement propertyType, boolean isBasicType) {
        handleReferenceProperty(
            element,
            context,
            propertyType,
            isBasicType,
            SerdeConfig.SerManagedRef.class,
            SerdeConfig.SerBackRef.class,
            SerdeConfig.SerManagedRef.ALIAS);
        handleReferenceProperty(
            element,
            context,
            propertyType,
            isBasicType,
            SerdeConfig.SerBackRef.class,
            SerdeConfig.SerManagedRef.class,
            SerdeConfig.SerBackRef.ALIAS);
    }

    private void handleReferenceProperty(Element element,
                                           VisitorContext context,
                                           ClassElement propertyType,
                                           boolean isBasicType,
                                           Class refClass,
                                           Class inverseRefClass,
                                           String aliasProperty) {
        if (hasAnnotationOnElement(element, refClass)) {
            if (element.stringValue(refClass).isPresent()) {
                // Already managed
                return;
            }
            if (hasAnnotationOnElement(element, SerdeConfig.SerUnwrapped.class)) {
                throw new ProcessingException(element, "Managed references cannot be unwrapped");
            }
            if (isBasicType) {
                throw new ProcessingException(element, "Managed references cannot be declared on basic types");
            }

            final String refName = element.stringValue(refClass, aliasProperty).orElse(DEFAULT_REF_ALIAS_NAME);
            final List inverseElements = resolveInverseElements(
                context,
                resolveRefType(propertyType),
                inverseRefClass,
                aliasProperty,
                refName);

            final int i = inverseElements.size();
            if (i == 0) {
                throw new ProcessingException(element, "No inverse property found for reference of type " + propertyType.getName() + " and reference: " + refName);
            } else if (i > 1) {
                throw new ProcessingException(element, "More than one potential inverse property found " + inverseElements + ", consider specifying a value to the reference to configure the association");
            } else {
                final TypedElement otherElement = inverseElements.iterator().next();
                if (!isCompatibleInverseSide(otherElement.getGenericType(), currentClass)) {
                    throw new ProcessingException(element, "Managed reference declares an incompatible inverse property [" + otherElement +
                        "]. The inverse side should be a map, collection, bean or array of the same type as the property.");
                } else {
                    element.annotate(refClass, (builder) ->
                        builder.value(otherElement.getName())
                    );
                }
            }
        }
    }

    private boolean hasAnnotationOnElement(Element element, Class managedRefClass) {
        return element.hasDeclaredAnnotation(managedRefClass) || (
                element instanceof PropertyElement && element.hasAnnotation(managedRefClass));
    }

    private String resolvePropertyName(TypedElement thisProperty) {
        return thisProperty.stringValue(SerdeConfig.class, SerdeConfig.PROPERTY)
                                      .orElseGet(() -> {
                                          if (thisProperty instanceof MethodElement) {
                                              return NameUtils.getPropertyNameForGetter(thisProperty.getName());
                                          }
                                          return thisProperty.getName();
                                      });
    }

    private ClassElement resolveRefType(ClassElement propertyType) {
        if (propertyType.isArray()) {
            return propertyType.fromArray();
        } else if (propertyType.isAssignable(Iterable.class)) {
            return propertyType.getFirstTypeArgument().orElse(propertyType);
        } else if (propertyType.isAssignable(Map.class)) {
            final List boundGenericTypes = propertyType.getBoundGenericTypes();
            if (boundGenericTypes.size() == 2) {
                return boundGenericTypes.get(1);
            } else {
                return propertyType;
            }
        }
        return propertyType;
    }

    private List resolveInverseElements(VisitorContext context,
                                                      ClassElement propertyType,
                                                      Class refType,
                                                      String aliasPropertyName,
                                                      String ref) {
        Set accessKindSet = resolveAccessSet(context, propertyType);
        final List otherElements = new ArrayList<>();
        if (accessKindSet.contains(Introspected.AccessKind.METHOD)) {
            propertyType.getBeanProperties()
                    .stream()
                    .filter(p ->
                       isMappedCandidate(refType, aliasPropertyName, ref, p) &&
                        p.hasAnnotation(refType) &&
                        isCompatibleInverseSide(p.getGenericType(), currentClass)
                    ).forEach(otherElements::add);
        }
        if (accessKindSet.contains(Introspected.AccessKind.FIELD)) {
            final List fields = propertyType
                    .getEnclosedElements(ElementQuery.ALL_FIELDS
                                                 .onlyInstance()
                                                 .annotated(ann -> isMappedCandidate(refType, aliasPropertyName, ref, ann)
                                                     && ann.hasDeclaredAnnotation(refType))
                                                 .modifiers(m -> m.contains(ElementModifier.PUBLIC))
                                                 .typed(t -> isCompatibleInverseSide(t.getGenericType(),
                                                                                     currentClass)));
            otherElements.addAll(fields);

        }
        return otherElements;
    }

    private List resolvePropertyNames(VisitorContext context,
                                              ClassElement propertyType,
                                              @Nullable Element annotationSource) {
        Set includeSet;
        String[] includedSource = annotationSource == null ? null : annotationSource.stringValues(SerdeConfig.SerIncluded.class);
        String[] includedType = propertyType.stringValues(SerdeConfig.SerIncluded.class);
        if (ArrayUtils.isEmpty(includedSource)) {
            if (ArrayUtils.isEmpty(includedType)) {
                includeSet = null;
            } else {
                includeSet = CollectionUtils.setOf(includedType);
            }
        } else {
            includeSet = CollectionUtils.setOf(includedSource);
        }

        Set ignoreSet;
        String[] ignoredSource = annotationSource == null ? null : annotationSource.stringValues(SerdeConfig.SerIgnored.class);
        String[] ignoredType = propertyType.stringValues(SerdeConfig.SerIgnored.class);
        if (ArrayUtils.isEmpty(ignoredSource)) {
            if (ArrayUtils.isEmpty(ignoredType)) {
                ignoreSet = null;
            } else {
                ignoreSet = CollectionUtils.setOf(ignoredType);
            }
        } else {
            ignoreSet = CollectionUtils.setOf(ignoredSource);
        }

        Stream typeElements;
        Set accessKindSet = resolveAccessSet(context, propertyType);
        if (accessKindSet.contains(Introspected.AccessKind.METHOD)) {
            typeElements = propertyType.getBeanProperties().stream()
                .filter(p -> !p.hasDeclaredAnnotation(SerdeConfig.SerIgnored.class));
        } else if (accessKindSet.contains(Introspected.AccessKind.FIELD)) {
            final List fields = propertyType
                    .getEnclosedElements(ElementQuery.ALL_FIELDS
                                                 .onlyInstance()
                                                 .annotated(ann -> !ann.hasDeclaredAnnotation(SerdeConfig.SerIgnored.class))
                                                 .modifiers(m -> m.contains(ElementModifier.PUBLIC)));
            typeElements = fields.stream();
        } else {
            typeElements = Stream.empty();
        }

        return typeElements
            .map(this::resolvePropertyName)
            .filter(s -> (ignoreSet == null || !ignoreSet.contains(s)) && (includeSet == null || includeSet.contains(s)))
            .toList();
    }

    private boolean isMappedCandidate(Class refType,
                                      String aliasPropertyName,
                                      String ref,
                                      AnnotationMetadata p) {
        return p.stringValue(refType, aliasPropertyName).orElse(DEFAULT_REF_ALIAS_NAME).equals(ref);
    }

    private Set resolveAccessSet(VisitorContext context, ClassElement propertyType) {
        final Introspected.AccessKind[] accessKinds = context.getClassElement(propertyType.getName())
                .map(t -> t.enumValues(Introspected.class,
                                                  "accessKind",
                                                  Introspected.AccessKind.class)).orElse(null);
        return ArrayUtils.isNotEmpty(accessKinds) ? CollectionUtils.setOf(accessKinds) : Collections.singleton(Introspected.AccessKind.METHOD);
    }

    private boolean isCompatibleInverseSide(ClassElement genericType, ClassElement propertyType) {
        if (genericType.isAssignable(propertyType)) {
            return true;
        } else if (genericType.isArray() && genericType.fromArray().isAssignable(propertyType)) {
            return true;
        } else if (genericType.isAssignable(Iterable.class) && genericType.getFirstTypeArgument().map(t -> t.isAssignable(propertyType)).orElse(false)) {
            return true;
        } else if (genericType.isAssignable(Map.class)) {
            final List types = genericType.getBoundGenericTypes();
            return types.size() == 2 && types.get(1).isAssignable(propertyType);
        }
        return false;
    }

    private boolean isNumberType(ClassElement type) {
        if (type == null) {
            return false;
        }
        return type.isAssignable(Number.class) ||
                (type.isPrimitive() && ClassUtils.getPrimitiveType(type.getName())
                        .map(ReflectionUtils::getWrapperType)
                        .map(Number.class::isAssignableFrom).orElse(false));
    }

    private @Nullable ClassElement resolvePropertyType(Element element) {
        ClassElement type = null;
        if (element instanceof FieldElement fieldElement) {
            type = fieldElement.getGenericField().getType();
        } else if (element instanceof MethodElement methodElement) {
            if (!methodElement.hasParameters()) {
                type = methodElement.getGenericReturnType();
            } else {
                type = methodElement.getParameters()[0].getGenericType();
            }
        } else if (element instanceof PropertyElement propertyElement) {
            return propertyElement.getGenericType();
        }
        return type;
    }

    @Override
    public void visitClass(ClassElement element, VisitorContext context) {
        // reset
        resetForNewClass(element);
        checkForErrors(element, context);
        visitClassInternal(element, context, false);
    }

    private void visitClassSubtypes(ClassElement supertype, VisitorContext context) {
        List> subtypes =
            supertype.getDeclaredAnnotationValuesByType(SerdeConfig.SerSubtyped.SerSubtype.class);

        for (AnnotationValue subtypeAnn : subtypes) {
            subtypeAnn.stringValue()
                .flatMap(context::getClassElement)
                .ifPresent(subtype -> {
                    if (!subtype.hasStereotype(SerdeConfig.class)) {
                        subtype.annotate(Serdeable.class);
                        visitSubtype(supertype, subtype, context);
                    }
                });
        }
    }

    private void visitSubtype(ClassElement supertype, ClassElement subtype, VisitorContext context) {
        if (elementVisitedAsSubtype.contains(subtype.getName())) {
            return;
        }
        elementVisitedAsSubtype.add(subtype.getName());

        if (failOnError && creatorMode == SerdeConfig.SerCreatorMode.DELEGATING) {
            throw new ProcessingException(subtype, "Inheritance cannot be combined with DELEGATING creation");
        }

        if (!subtype.hasAnnotation(SerdeConfig.SerIgnored.class)) {
            // Replicate the Jackson behaviour of using the ignore annotation from supertype but allow to override it
            AnnotationValue serIgnored = supertype.getAnnotation(SerdeConfig.SerIgnored.class);
            if (serIgnored != null) {
                subtype.annotate(serIgnored);
            }
            // Re-evaluate ignore properties
            visitProperties(subtype, context);
        }

        final SerdeConfig.SerSubtyped.DiscriminatorValueKind discriminatorValueKind =
            getDiscriminatorValueKind(supertype);
        final SerdeConfig.SerSubtyped.DiscriminatorType discriminatorType =
            getDiscriminatorType(supertype);
        if (discriminatorType == SerdeConfig.SerSubtyped.DiscriminatorType.EXTERNAL_PROPERTY) {
            throw new ProcessingException(subtype, "EXTERNAL_PROPERTY can only be used for properties. " +
                "Trying to use it for classes will result in inclusion strategy of basic PROPERTY instead.");
        }
        final String typeProperty = resolveTypeProperty(supertype).orElseThrow();

        List allNames = new ArrayList<>();

        switch (discriminatorValueKind) {
            case NAME -> {
                subtype.stringValue(SerdeConfig.class, SerdeConfig.TYPE_NAME)
                        .ifPresent(allNames::add);
                for (AnnotationValue parentSubtype :
                        supertype.getDeclaredAnnotationValuesByType(SerdeConfig.SerSubtyped.SerSubtype.class)) {
                    String parentSubtypeName = parentSubtype.stringValue().orElse(null);
                    if (subtype.getName().equals(parentSubtypeName)) {
                        parentSubtype.stringValue("name")
                                .ifPresent(allNames::add);
                        Collections.addAll(allNames, parentSubtype.stringValues("names"));
                    }
                }
                if (allNames.isEmpty()) {
                    // Fallback to class name
                    allNames.add(subtype.getSimpleName());
                }
            }
            case CLASS_SIMPLE_NAME -> allNames.add(subtype.getSimpleName());
            case MINIMAL_CLASS -> {
                String superPackage = supertype.getPackage().getName();
                String name = subtype.getName();
                String typeName;
                if (name.startsWith(superPackage)) {
                    typeName = name.substring(superPackage.length());
                } else {
                    typeName = name;
                }
                allNames.add(typeName);
            }
            default -> allNames.add(subtype.getName());
        }

        subtype.annotate(SerdeConfig.class, builder -> {
            builder.member(SerdeConfig.TYPE_NAME, allNames.get(0));
            builder.member(SerdeConfig.TYPE_NAMES, allNames.toArray(new String[0]));

            switch (discriminatorType) {
                case WRAPPER_OBJECT -> builder.member(SerdeConfig.WRAPPER_PROPERTY, allNames.get(0));
                case WRAPPER_ARRAY -> builder.member(SerdeConfig.ARRAY_WRAPPER_PROPERTY, allNames.get(0));
                case PROPERTY -> builder.member(SerdeConfig.TYPE_PROPERTY, typeProperty);
                case EXISTING_PROPERTY -> builder.member(SerdeConfig.TYPE_DISCRIMINATOR_TYPE, discriminatorType);
            }

            if (supertype.booleanValue(SerdeConfig.SerSubtyped.class, SerdeConfig.SerSubtyped.DISCRIMINATOR_VISIBLE).orElse(false)) {
                builder.member(SerdeConfig.TYPE_PROPERTY_VISIBLE, true);
            }
        });
    }

    private void visitClassInternal(ClassElement element, VisitorContext context, boolean isImport) {
        visitClassSubtypes(element, context);

        if (element.hasDeclaredAnnotation(SerdeImport.Repeated.class) && !isImport) {
            final List> values = element.getDeclaredAnnotationValuesByType(SerdeImport.class);
            List> classValues = new ArrayList<>();
            for (AnnotationValue value : values) {
                value.annotationClassValue(AnnotationMetadata.VALUE_MEMBER)
                        .flatMap(acv -> context.getClassElement(acv.getName()))
                        .ifPresent(c -> {
                            if (!c.isPublic()) {
                                throw new ProcessingException(element, "Cannot mixin non-public type: " + c.getName());
                            } else {
                                handleClassImport(context, value, c, classValues);
                            }
                        });
                 value.stringValue("packageName").ifPresent(packageName -> {
                     @NonNull ClassElement[] classElements = context.getClassElements(packageName, "*");
                     for (ClassElement c : classElements) {
                         if (c.isPublic()) {
                             handleClassImport(context, value, c, classValues);
                         }
                     }
                 });
            }
            element.annotate(Introspected.class, builder ->
                builder.member("classes", classValues.toArray(new AnnotationClassValue[0]))
            );
        } else if (isJsonAnnotated(element) || isImport) {
            if (!element.hasStereotype(Serdeable.Serializable.class) &&
                    !element.hasStereotype(Serdeable.Deserializable.class) && !isImport) {
                element.annotate(Serdeable.class);
                element.annotate(Introspected.class, i -> {
                    i.member("accessKind", Introspected.AccessKind.METHOD, Introspected.AccessKind.FIELD);
                    i.member("visibility", "PUBLIC");
                });
            }

            AnnotationValue declaredAnnotation = element.getDeclaredAnnotation(SerdeConfig.class);
            if (failOnError && declaredAnnotation != null) {
                String serializeAs = declaredAnnotation.stringValue(SerdeConfig.SERIALIZE_AS).orElse(null);
                if (serializeAs != null) {
                    ClassElement thatType = context.getClassElement(serializeAs).orElse(null);
                    if (thatType != null && !thatType.isAssignable(element) && failOnError) {
                        throw new ProcessingException(element, "Type to serialize as [" + serializeAs + "], must be a subtype of the annotated type: " + element.getName());
                    }
                }

                String deserializeAs = declaredAnnotation.stringValue(SerdeConfig.DESERIALIZE_AS).orElse(null);
                if (deserializeAs != null) {
                    ClassElement thatType = context.getClassElement(deserializeAs).orElse(null);
                    if (thatType != null && !thatType.isAssignable(element) && failOnError) {
                        throw new ProcessingException(element, "Type to deserialize as [" + deserializeAs + "], must be a subtype of the annotated type: " + element.getName());
                    }
                }
            }

            final MethodElement primaryConstructor = element.getPrimaryConstructor().orElse(null);
            if (primaryConstructor != null) {

                this.creatorMode = primaryConstructor.enumValue(Creator.class, "mode", SerdeConfig.SerCreatorMode.class).orElse(null);
                if (creatorMode == SerdeConfig.SerCreatorMode.DELEGATING) {
                    if (failOnError && primaryConstructor.getParameters().length != 1) {
                        throw new ProcessingException(element, "DELEGATING creator mode requires exactly one Creator parameter, but more were defined.");
                    }
                }
            }

            visitProperties(element, context);

            findTypeInfo(element, false)
                .ifPresent(superType -> visitSubtype(superType, element, context));

            if (failOnError && element.hasDeclaredAnnotation(SerdeConfig.SerSubtyped.class) && creatorMode == SerdeConfig.SerCreatorMode.DELEGATING) {
                throw new ProcessingException(element, "Inheritance cannot be combined with DELEGATING creation");
            }
        }
    }

    private void visitProperties(ClassElement classElement, VisitorContext context) {
        final List beanProperties = classElement.getBeanProperties();
        final List order = Arrays.asList(classElement.stringValues(SerdeConfig.META_ANNOTATION_PROPERTY_ORDER));
        Collections.reverse(order);
        final Set access = CollectionUtils.setOf(classElement.enumValues(Introspected.class,
                                                                                             "accessKind",
                                                                                             Introspected.AccessKind.class));
        boolean supportFields = access.contains(Introspected.AccessKind.FIELD);
        final String[] ignoresProperties = classElement.stringValues(SerdeConfig.SerIgnored.class);
        final String[] includeProperties = classElement.stringValues(SerdeConfig.SerIncluded.class);

        final boolean ignoreOnlyDeserialization = classElement.booleanValue(SerdeConfig.SerIgnored.class, SerdeConfig.SerIgnored.ALLOW_SERIALIZE).orElse(false);
        final boolean ignoreOnlySerialization = classElement.booleanValue(SerdeConfig.SerIgnored.class, SerdeConfig.SerIgnored.ALLOW_DESERIALIZE).orElse(false);
        PropertyNamingStrategy propertyNamingStrategy = getPropertyNamingStrategy(classElement, null);
        processProperties(
            context,
                beanProperties,
                order,
                ignoresProperties,
                includeProperties,
                ignoreOnlyDeserialization,
                ignoreOnlySerialization,
                propertyNamingStrategy
        );
        if (supportFields) {
            final List fields = classElement.getEnclosedElements(ElementQuery.ALL_FIELDS.onlyInstance()
                                                                                            .onlyAccessible());
            processProperties(
                context,
                    fields,
                    order,
                    ignoresProperties,
                    includeProperties,
                    ignoreOnlyDeserialization,
                    ignoreOnlySerialization,
                    propertyNamingStrategy
            );
        }
    }

    private void handleClassImport(VisitorContext context, AnnotationValue value, ClassElement c, List> classValues) {
        classValues.add(new AnnotationClassValue<>(c.getName()));
        final ClassElement mixinType = value.stringValue("mixin").flatMap(context::getClassElement)
                .orElse(null);
        if (mixinType != null) {
            visitMixin(mixinType, c);
        } else {
            visitClassInternal(c, context, true);
        }
        c.annotate(value);
        AnnotationValue jsonPojoAnn = c.getAnnotation("com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder");
        if (jsonPojoAnn != null) {
            String buildMethod = jsonPojoAnn.stringValue("buildMethodName").orElse("build");
            c.getEnclosedElement(ElementQuery.ALL_METHODS.named(n -> n.equals(buildMethod)))
                .ifPresent(m -> m.annotate(Executable.class));
        }
    }

    private void visitMixin(ClassElement mixinType, ClassElement type) {
        mixinType.getAnnotationNames()
                .stream().filter(n -> n.startsWith("io.micronaut.serde"))
                .forEach(n -> {
                    final AnnotationValue ann = mixinType.getAnnotation(n);
                    if (ann != null) {
                        type.annotate(ann);
                    }
                });
        mixinType.findAnnotation(SerdeConfig.class).ifPresent(type::annotate);
        final Map serdeFields = mixinType.getEnclosedElements(
                ElementQuery.ALL_FIELDS
                        .onlyInstance()
                        .onlyDeclared()
                        .annotated((ann) -> ann.hasAnnotation(SerdeConfig.class))
        ).stream().collect(Collectors.toMap(
                FieldElement::getName,
                (e) -> e
        ));

        final MethodElement mixinCtor = mixinType.getPrimaryConstructor().orElse(null);
        MethodElement targetCtor = type.getPrimaryConstructor().orElse(null);
        if (mixinCtor != null && targetCtor != null) {
            if (!argumentsMatch(mixinCtor, targetCtor)) {
                // The mixin constructor and the primary constructor doesn't match,
                // lets try to find a matching one and mark it as a primary
                MethodElement prevCtor = targetCtor;
                targetCtor = type.getAccessibleConstructors().stream().filter(c -> argumentsMatch(mixinCtor, c)).findFirst().orElse(null);
                if (targetCtor != null) {
                    targetCtor.annotate(Creator.class);
                    prevCtor.removeAnnotation(Creator.class);
                }
            }
            if (targetCtor != null) {
                replicateAnnotations(mixinCtor, targetCtor);
                ParameterElement[] mixinCtorParameters = mixinCtor.getParameters();
                ParameterElement[] targetCtorParameters = targetCtor.getParameters();
                for (int i = 0; i < mixinCtorParameters.length; i++) {
                    ParameterElement mixinCtorParameter = mixinCtorParameters[i];
                    ParameterElement targetCtorParameter = targetCtorParameters[i];
                    replicateAnnotations(mixinCtorParameter, targetCtorParameter);
                }
            }
        }

        final List serdeMethods = mixinType.isRecord() ? Collections.emptyList() : new ArrayList<>(mixinType.getEnclosedElements(
                ElementQuery.ALL_METHODS
                        .onlyInstance()
                        .onlyDeclared()
                        .annotated((ann) -> ann.getAnnotationNames().stream().anyMatch(n ->
                            n.startsWith("io.micronaut.serde.config.annotation")
                        ))
        ));

        final List beanProperties = type.getBeanProperties();
        for (PropertyElement beanProperty : beanProperties) {
            final FieldElement f = serdeFields.get(beanProperty.getName());
            if (f != null && f.getType().equals(beanProperty.getType())) {
                replicateAnnotations(f, beanProperty);
                continue;
            }

            if (CollectionUtils.isNotEmpty(serdeMethods)) {
                final MethodElement readMethod = beanProperty.getReadMethod().orElse(null);
                final MethodElement writeMethod = beanProperty.getWriteMethod().orElse(null);
                final Iterator i = serdeMethods.iterator();
                while (i.hasNext()) {
                    MethodElement serdeMethod = i.next();
                    if (readMethod != null) {
                        if (serdeMethod.getName().equals(readMethod.getName())) {
                            if (argumentsMatch(serdeMethod, readMethod)) {
                                i.remove();
                                replicateAnnotations(serdeMethod, beanProperty);
                                replicateAnnotations(serdeMethod, readMethod);
                            }
                        }
                    }
                    if (writeMethod != null) {
                        if (serdeMethod.getName().equals(writeMethod.getName())) {
                            if (argumentsMatch(serdeMethod, writeMethod)) {
                                i.remove();
                                replicateAnnotations(serdeMethod, beanProperty);
                                replicateAnnotations(serdeMethod, writeMethod);
                            }
                        }
                    }
                }
            }
        }

        if (!serdeMethods.isEmpty()) {
            for (MethodElement serdeMethod : serdeMethods) {
                type.getEnclosedElement(
                        ElementQuery.ALL_METHODS
                                .onlyInstance()
                                .onlyAccessible()
                                .named(n -> n.equals(serdeMethod.getName()))
                                .filter(left -> left.getReturnType().equals(serdeMethod.getReturnType())
                                        && argumentsMatch(left, serdeMethod))
                ).ifPresent(m -> {
                    m.annotate(Executable.class);
                    replicateAnnotations(serdeMethod, m);
                });
            }
        }
    }

    private boolean argumentsMatch(MethodElement left, MethodElement right) {
        final ParameterElement[] lp = left.getParameters();
        final ParameterElement[] rp = right.getParameters();
        if (lp.length == rp.length) {
            if (lp.length == 0) {
                return true;
            }
            for (int i = 0; i < lp.length; i++) {
                ParameterElement p1 = lp[i];
                ParameterElement p2 = rp[i];
                if (!p1.getType().equals(p2.getType())) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    private void replicateAnnotations(Element source, Element target) {
        final Set annotationNames = source.getAnnotationNames();
        for (String annotationName : annotationNames) {
            final AnnotationValue config = source.getAnnotation(annotationName);
            if (config != null) {
                target.annotate(config);
            }
        }
    }

    @Nullable
    private PropertyNamingStrategy getPropertyNamingStrategy(@NonNull  TypedElement element, @Nullable PropertyNamingStrategy defaultValue) {
        String namingStrategy = element.stringValue(SerdeConfig.class, SerdeConfig.NAMING)
                .filter(val -> !val.equals(PropertyNamingStrategy.IDENTITY.getClass().getName()))
                .orElse(null);
        if (namingStrategy != null) {
            PropertyNamingStrategy propertyNamingStrategy = PropertyNamingStrategy.forName(namingStrategy).orElse(null);
            if (propertyNamingStrategy == null) {
                Object o = InstantiationUtils.tryInstantiate(
                        namingStrategy,
                        getClass().getClassLoader()
                ).orElse(null);
                if (o instanceof PropertyNamingStrategy) {
                    return (PropertyNamingStrategy) o;
                } else {
                    element.annotate(SerdeConfig.class, builder -> builder.member(SerdeConfig.RUNTIME_NAMING, namingStrategy));
                }
            }
            return propertyNamingStrategy;
        }
        return defaultValue;
    }

    private void processProperties(VisitorContext context,
                                   List beanProperties,
                                   List orderDef,
                                   String[] ignoresProperties,
                                   String[] includeProperties,
                                   boolean ignoreOnlyDeserialization,
                                   boolean ignoreOnlySerialization,
                                   @Nullable PropertyNamingStrategy namingStrategy) {
        final Set ignoredSet = CollectionUtils.setOf(ignoresProperties);
        final Set includeSet = CollectionUtils.setOf(includeProperties);
        final List order = new ArrayList<>(orderDef);
        for (TypedElement beanProperty : beanProperties) {
            checkForErrors(beanProperty, context);

            PropertyNamingStrategy propertyNamingStrategy = getPropertyNamingStrategy(beanProperty, namingStrategy);

            if (beanProperty instanceof PropertyElement pm) {
                pm.getReadMethod().ifPresent(readMethods::add);
                pm.getWriteMethod().ifPresent(writeMethods::add);
            }
            if (!beanProperty.isPrimitive() && !beanProperty.isArray()) {
                final ClassElement t = beanProperty.getGenericType();
                handleJsonIgnoreType(context, beanProperty, t);
            }

            final String propertyName = resolvePropertyName(beanProperty);
            if (propertyNamingStrategy != null) {
                beanProperty.annotate(SerdeConfig.class, (builder) ->
                    builder.member(SerdeConfig.PROPERTY, propertyNamingStrategy.translate(beanProperty))
                );
            }
            if (CollectionUtils.isNotEmpty(order)) {
                int index = order.indexOf(propertyName);
                if (index == -1) {
                    // Try to find the order defined by the original name of the property
                    index = order.indexOf(beanProperty.getName());
                }
                if (index > -1) {
                    // Set as used
                    order.set(index, "");
                    int finalIndex = index;
                    beanProperty.annotate(Order.class, (builder) ->
                        builder.value(-(finalIndex + 1))
                    );
                }
            }

            if (ignoredSet.contains(propertyName)) {
                ignoreProperty(ignoreOnlyDeserialization, ignoreOnlySerialization, beanProperty);
            } else if (!includeSet.isEmpty() && !includeSet.contains(propertyName)) {
                ignoreProperty(false, false, beanProperty);
            }
        }
    }

    private void ignoreProperty(boolean ignoreOnlyDeserialization,
                                boolean ignoreOnlySerialization,
                                TypedElement beanProperty) {
        if (beanProperty instanceof PropertyElement propertyElement) {
            if (ignoreOnlySerialization) {
                propertyElement.getReadMethod().ifPresent(e -> e.annotate(SerdeConfig.class, (builder) ->
                    builder.member(SerdeConfig.IGNORED_SERIALIZATION, true)
                ));
            } else if (ignoreOnlyDeserialization) {
                propertyElement.getWriteMethod().ifPresent(e -> e.annotate(SerdeConfig.class, (builder) ->
                    builder.member(SerdeConfig.IGNORED_DESERIALIZATION, true)
                ));
            } else {
                propertyElement.annotate(SerdeConfig.class, (builder) ->
                    builder.member(SerdeConfig.IGNORED, true)
                );
            }
        } else {
            if (ignoreOnlySerialization) {
                beanProperty.annotate(SerdeConfig.class, (builder) ->
                    builder.member(SerdeConfig.IGNORED_SERIALIZATION, true)
                );
            } else if (ignoreOnlyDeserialization) {
                beanProperty.annotate(SerdeConfig.class, (builder) ->
                    builder.member(SerdeConfig.IGNORED_DESERIALIZATION, true)
                );
            } else {
                beanProperty.annotate(SerdeConfig.class, (builder) ->
                    builder.member(SerdeConfig.IGNORED, true)
                );
            }
        }
    }

    private void handleJsonIgnoreType(VisitorContext context, TypedElement beanProperty, ClassElement t) {
        final String typeName = t.getName();
        if (!ClassUtils.isJavaBasicType(typeName)) {
            final boolean ignoredType = context.getClassElement(typeName)
                    .map((c) -> c.hasAnnotation(SerdeConfig.SerIgnored.SerType.class)).orElse(false);
            if (ignoredType) {
                beanProperty.annotate(SerdeConfig.class, (builder) ->
                        builder.member(SerdeConfig.IGNORED, true)
                );
            }
        }
    }

    private void resetForNewClass(ClassElement element) {
        this.currentClass = element;
        // TODO: Investigate why the `AliasFor` doesn't work here
        this.failOnError = element.booleanValue(SerdeConfig.class, SerdeConfig.VALIDATE)
            .or(() -> element.booleanValue(Serdeable.class, SerdeConfig.VALIDATE))
            .orElse(true);
        this.creatorMode = SerdeConfig.SerCreatorMode.PROPERTIES;
        this.anyGetterMethod = null;
        this.anySetterMethod = null;
        this.anyGetterField = null;
        this.anySetterField = null;
        this.jsonValueField = null;
        this.jsonValueMethod = null;
        this.readMethods.clear();
        this.writeMethods.clear();
    }

    private SerdeConfig.SerSubtyped.DiscriminatorValueKind getDiscriminatorValueKind(Element typeInfo) {
        return typeInfo.enumValue(
                        SerdeConfig.SerSubtyped.class,
                        SerdeConfig.SerSubtyped.DISCRIMINATOR_VALUE,
                        SerdeConfig.SerSubtyped.DiscriminatorValueKind.class)
                .orElse(SerdeConfig.SerSubtyped.DiscriminatorValueKind.CLASS_NAME);
    }

    private Optional findTypeInfo(ClassElement element, boolean includeElement) {
        if (element.hasDeclaredAnnotation(SerdeConfig.SerSubtyped.class) && includeElement) {
            return Optional.of(element);
        }

        final ClassElement superElement = element.getSuperType().orElse(null);

        if (superElement == null) {
            ClassElement itfe = findInDeclaredInterfaces(element);
            if (itfe != null) {
                return Optional.of(itfe);
            } else {
                return Optional.empty();
            }
        }

        if (superElement.hasDeclaredAnnotation(SerdeConfig.SerSubtyped.class)) {
            return Optional.of(superElement);
        } else {
            ClassElement itfe = findInDeclaredInterfaces(element);
            if (itfe == null) {
                itfe = findInDeclaredInterfaces(superElement);
            }

            if (itfe != null) {
                return Optional.of(itfe);
            } else {
                return findTypeInfo(superElement, true);
            }
        }
    }

    private ClassElement findInDeclaredInterfaces(@NonNull ClassElement superElement) {
        Collection interfaces = superElement.getInterfaces();
        if (CollectionUtils.isNotEmpty(interfaces)) {
            for (ClassElement anInterface : interfaces) {
                if (anInterface.hasDeclaredAnnotation(SerdeConfig.SerSubtyped.class)) {
                    return anInterface;
                }
                ClassElement e = findInDeclaredInterfaces(anInterface);
                if (e != null) {
                    return e;
                }
            }
        }
        return null;
    }

    private Optional resolveTypeProperty(@NonNull ClassElement superType) {
        ClassElement typeInfo = findTypeInfo(superType, true).orElse(null);
        if (typeInfo != null) {
            return typeInfo.stringValue(SerdeConfig.SerSubtyped.class, SerdeConfig.SerSubtyped.DISCRIMINATOR_PROP);
        }
        return Optional.empty();
    }

    private SerdeConfig.SerSubtyped.DiscriminatorType getDiscriminatorType(Element element) {
        return element.enumValue(
                SerdeConfig.SerSubtyped.class,
                SerdeConfig.SerSubtyped.DISCRIMINATOR_TYPE,
                SerdeConfig.SerSubtyped.DiscriminatorType.class
            ).orElse(SerdeConfig.SerSubtyped.DiscriminatorType.PROPERTY);
    }

    @Override
    public int getOrder() {
        return IntrospectedTypeElementVisitor.POSITION + 100;
    }

    private boolean isJsonAnnotated(ClassElement element) {
        return Stream.of(
                        "com.fasterxml.jackson.annotation.JsonClassDescription",
                        "com.fasterxml.jackson.databind.annotation.JsonNaming",
                        "com.fasterxml.jackson.databind.annotation.JsonSerialize",
                        "com.fasterxml.jackson.databind.annotation.JsonDeserialize",
                        "com.fasterxml.jackson.annotation.JsonTypeInfo",
                        "com.fasterxml.jackson.annotation.JsonRootName",
                        "com.fasterxml.jackson.annotation.JsonTypeName",
                        "com.fasterxml.jackson.annotation.JsonTypeId",
                        "com.fasterxml.jackson.annotation.JsonAutoDetect",
                        "com.fasterxml.jackson.annotation.JsonIgnoreProperties",
                        "com.fasterxml.jackson.annotation.JsonIncludeProperties"
            ).anyMatch(element::hasDeclaredAnnotation) ||
            (element.hasStereotype(Serdeable.Serializable.class) || element.hasStereotype(Serdeable.Deserializable.class));
    }

    private static boolean isBasicType(ClassElement propertyType) {
        if (propertyType == null) {
            return false;
        }
        String name = propertyType.getName();
        return ClassUtils.isJavaBasicType(name) || (propertyType.isPrimitive() && !propertyType.isArray());
    }

    @Override
    public VisitorKind getVisitorKind() {
        return VisitorKind.ISOLATING;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy