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

io.micronaut.openapi.visitor.SchemaDefinitionUtils Maven / Gradle / Ivy

/*
 * Copyright 2017-2024 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.openapi.visitor;

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.micronaut.context.exceptions.ConfigurationException;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanMap;
import io.micronaut.core.bind.annotation.Bindable;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.EnumConstantElement;
import io.micronaut.inject.ast.EnumElement;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.GenericElement;
import io.micronaut.inject.ast.GenericPlaceholderElement;
import io.micronaut.inject.ast.MemberElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.ast.PropertyElementQuery;
import io.micronaut.inject.ast.TypedElement;
import io.micronaut.inject.ast.WildcardElement;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.openapi.OpenApiUtils;
import io.micronaut.openapi.javadoc.JavadocDescription;
import io.micronaut.openapi.swagger.core.util.PrimitiveType;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.enums.ParameterStyle;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.links.Link;
import io.swagger.v3.oas.annotations.links.LinkParameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
import io.swagger.v3.oas.annotations.media.Encoding;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.OAuthScope;
import io.swagger.v3.oas.annotations.servers.Server;
import io.swagger.v3.oas.annotations.servers.ServerVariable;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Discriminator;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.security.SecurityRequirement;

import javax.xml.datatype.XMLGregorianCalendar;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Function;

import static io.micronaut.core.util.StringUtils.EMPTY_STRING;
import static io.micronaut.openapi.visitor.ConfigUtils.getConfigProperty;
import static io.micronaut.openapi.visitor.ConfigUtils.getCustomSchema;
import static io.micronaut.openapi.visitor.ConfigUtils.getExpandableProperties;
import static io.micronaut.openapi.visitor.ConfigUtils.getGenericSeparator;
import static io.micronaut.openapi.visitor.ConfigUtils.getInnerClassSeparator;
import static io.micronaut.openapi.visitor.ConfigUtils.getSchemaDecoration;
import static io.micronaut.openapi.visitor.ConfigUtils.getSchemaDuplicateResolution;
import static io.micronaut.openapi.visitor.ConfigUtils.isJsonViewDefaultInclusion;
import static io.micronaut.openapi.visitor.ContextUtils.warn;
import static io.micronaut.openapi.visitor.ConvertUtils.parseJsonString;
import static io.micronaut.openapi.visitor.ConvertUtils.setDefaultValueObject;
import static io.micronaut.openapi.visitor.ConvertUtils.toTupleSubMap;
import static io.micronaut.openapi.visitor.ElementUtils.findAnnotation;
import static io.micronaut.openapi.visitor.ElementUtils.getAnnotation;
import static io.micronaut.openapi.visitor.ElementUtils.getAnnotationMetadata;
import static io.micronaut.openapi.visitor.ElementUtils.isAnnotationPresent;
import static io.micronaut.openapi.visitor.ElementUtils.isDeprecated;
import static io.micronaut.openapi.visitor.ElementUtils.isEnum;
import static io.micronaut.openapi.visitor.ElementUtils.isFileUpload;
import static io.micronaut.openapi.visitor.ElementUtils.isNotNullable;
import static io.micronaut.openapi.visitor.ElementUtils.isNullable;
import static io.micronaut.openapi.visitor.ElementUtils.isTypeWithGenericNullable;
import static io.micronaut.openapi.visitor.ElementUtils.stringValue;
import static io.micronaut.openapi.visitor.GeneratorExt.MAX_MESSAGE;
import static io.micronaut.openapi.visitor.GeneratorExt.MIN_MESSAGE;
import static io.micronaut.openapi.visitor.GeneratorExt.NOT_NULL_MESSAGE;
import static io.micronaut.openapi.visitor.GeneratorExt.PATTERN_MESSAGE;
import static io.micronaut.openapi.visitor.GeneratorExt.SIZE_MESSAGE;
import static io.micronaut.openapi.visitor.GeneratorUtils.addEnumExtensions;
import static io.micronaut.openapi.visitor.GeneratorUtils.addSchemaDeprecatedExtension;
import static io.micronaut.openapi.visitor.GeneratorUtils.addValidationMessages;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.expandProperties;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.replacePlaceholders;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.resolvePlaceholders;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL;
import static io.micronaut.openapi.visitor.OpenApiModelProp.DISCRIMINATOR;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ACCESS;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ACCESS_MODE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ADDITIONAL_PROPERTIES;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ALLOWABLE_VALUES;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ALL_OF;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ANY_OF;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ARRAY_SCHEMA;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DEFAULT;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DEFAULT_VALUE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DEPRECATED;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DESCRIPTION;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DISCRIMINATOR_MAPPING;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DISCRIMINATOR_PROPERTY;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ENUM;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLES;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLE_SET_FLAG;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXPRESSION;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXTENSIONS;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXTERNAL_DOCS;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_HIDDEN;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_IMPLEMENTATION;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_IN;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_MAPPING;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_MEDIA_TYPE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_NAME;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_NOT;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_NULLABLE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ONE_FORMAT;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ONE_OF;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ONE_TYPES;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_PROPERTY_NAME;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_READ_ONLY;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REF;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REF_DOLLAR;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REQUIRED;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REQUIRED_MODE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_REQUIRED_PROPERTIES;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_RESPONSE_CODE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_SCHEMA;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_STYLE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_TITLE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_TYPE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_VALUE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_WRITE_ONLY;
import static io.micronaut.openapi.visitor.ProtoUtils.filterProtobufProperties;
import static io.micronaut.openapi.visitor.ProtoUtils.isProtobufGenerated;
import static io.micronaut.openapi.visitor.ProtoUtils.isProtobufMessageClass;
import static io.micronaut.openapi.visitor.ProtoUtils.normalizePropertyName;
import static io.micronaut.openapi.visitor.ProtoUtils.normalizeProtobufClassName;
import static io.micronaut.openapi.visitor.ProtoUtils.protobufTypeSchema;
import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_ARRAY;
import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT;
import static io.micronaut.openapi.visitor.SchemaUtils.appendSchema;
import static io.micronaut.openapi.visitor.SchemaUtils.getSchemaByRef;
import static io.micronaut.openapi.visitor.SchemaUtils.isEmptySchema;
import static io.micronaut.openapi.visitor.SchemaUtils.processExtensions;
import static io.micronaut.openapi.visitor.SchemaUtils.setAllowableValues;
import static io.micronaut.openapi.visitor.SchemaUtils.setSpecVersion;
import static io.micronaut.openapi.visitor.StringUtil.DOLLAR;
import static io.micronaut.openapi.visitor.StringUtil.DOT;
import static io.micronaut.openapi.visitor.Utils.isOpenapi31;
import static io.micronaut.openapi.visitor.Utils.resolveOpenApi;

/**
 * Methods to construct OpenPI schema definition.
 *
 * @since 6.12.0
 */
@Internal
public final class SchemaDefinitionUtils {

    /**
     * Stores the current in progress type.
     */
    private static List inProgressSchemas = new ArrayList<>(10);
    /**
     * Stores relations between schema names and class names.
     */
    private static Map schemaNameToClassNameMap = new HashMap<>();
    /**
     * Stores class name counters for schema suffix, when found classes with same name in different packages.
     */
    private static Map shemaNameSuffixCounterMap = new HashMap<>();
    /**
     * {@link PropertyNamingStrategy} instances cache.
     */
    private static Map propertyNamingStrategyInstances = new HashMap<>();

    private SchemaDefinitionUtils() {
    }

    /**
     * Cleanup context.
     */
    public static void clean() {
        inProgressSchemas = new ArrayList<>(10);
        schemaNameToClassNameMap = new HashMap<>();
        shemaNameSuffixCounterMap = new HashMap<>();
        propertyNamingStrategyInstances = new HashMap<>();
    }

    /**
     * Reads schema.
     *
     * @param schemaValue annotation value
     * @param openAPI The OpenApi
     * @param context The VisitorContext
     * @param type type element
     * @param typeArgs type arguments
     * @param definingElement defining element
     * @param mediaTypes The media types of schema
     * @param jsonViewClass Class from JsonView annotation
     * @return New schema instance
     */
    public static Schema readSchema(AnnotationValue schemaValue, OpenAPI openAPI, VisitorContext context,
                                       @Nullable Element type, Map typeArgs,
                                       @Nullable Element definingElement, List mediaTypes,
                                       @Nullable ClassElement jsonViewClass) {

        var schema = setSpecVersion(new Schema<>());
        processSchemaAnn(schema, context, definingElement, (ClassElement) type, schemaValue);

        String elType = SchemaUtils.getType(schema);
        String elFormat = schema.getFormat();
        if (elType == null && type instanceof ClassElement classEl) {
            Pair typeAndFormat;
            if (classEl instanceof EnumElement enumEl && isEnum(enumEl)) {
                typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat);
            } else {
                typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classEl.getName(), classEl.isArray(), classEl);
            }
            elType = typeAndFormat.getFirst();
            schema.setType(elType);
            if (elFormat == null) {
                elFormat = typeAndFormat.getSecond();
                schema.setFormat(elFormat);
            }
        }

        if (type instanceof EnumElement enumEl && isEnum(enumEl)) {
            if (CollectionUtils.isEmpty(schema.getEnum())) {
                schema.setEnum(getEnumValues(enumEl, schema.getType(), schema.getFormat(), context));
                addEnumExtensions(enumEl, schema, context);
            }
        } else {
            JavadocDescription javadoc = type != null ? Utils.getJavadocParser().parse(type.getDescription()) : null;
            populateSchemaProperties(openAPI, context, type, typeArgs, schema, mediaTypes, javadoc, jsonViewClass);
            checkAllOf(schema);
        }
        return schema;
    }

    @Nullable
    public static Schema getSchemaDefinition(OpenAPI openAPI,
                                                VisitorContext context,
                                                ClassElement type,
                                                Map typeArgs,
                                                @Nullable Element definingElement,
                                                List mediaTypes,
                                                @Nullable ClassElement jsonViewClass
    ) {

        // Here we need to skip Schema annotation on field level, because with micronaut 3.x method getDeclaredAnnotation
        // returned always null and found Schema annotation only on getters and setters
        var schemaAnnOnField = false;
        AnnotationValue schemaValue = null;
        if (definingElement != null) {
            if (definingElement instanceof PropertyElement propertyEl) {
                var getterOpt = propertyEl.getReadMethod();
                if (getterOpt.isPresent()) {
                    schemaValue = getterOpt.get().getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
                    schemaAnnOnField = schemaValue != null;
                }
                if (schemaValue == null) {
                    var setterOpt = propertyEl.getWriteMethod();
                    if (setterOpt.isPresent()) {
                        schemaValue = setterOpt.get().getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
                        schemaAnnOnField = schemaValue != null;
                    }
                }
            } else {
                schemaValue = definingElement.getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
                schemaAnnOnField = schemaValue != null && definingElement instanceof FieldElement;
            }
        }
        if (schemaValue == null) {
            schemaValue = type.getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
        }

        Schema schema;
        Map schemas = SchemaUtils.resolveSchemas(openAPI);
        if (schemaValue == null) {
            final boolean isBasicType = ElementUtils.isJavaBasicType(type.getName());
            final PrimitiveType primitiveType;
            if (isBasicType) {
                primitiveType = ClassUtils.forName(type.getName(), SchemaDefinitionUtils.class.getClassLoader())
                    .map(PrimitiveType::fromType)
                    .orElse(null);
            } else {
                primitiveType = null;
            }
            if (primitiveType == null) {
                String schemaName = computeDefaultSchemaName(null, definingElement, type, typeArgs, context, jsonViewClass);
                schema = schemas.get(schemaName);
                JavadocDescription javadoc = Utils.getJavadocParser().parse(type.getDocumentation().orElse(null));
                if (schema == null) {

                    if (type instanceof EnumElement enumEl && isEnum(enumEl)) {
                        schema = setSpecVersion(new Schema<>());
                        schema.setName(schemaName);
                        processJacksonDescription(enumEl, schema);
                        if (schema.getDescription() == null && javadoc != null && StringUtils.hasText(javadoc.getMethodDescription())) {
                            schema.setDescription(javadoc.getMethodDescription());
                        }
                        schemas.put(schemaName, schema);

                        Pair typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, schema.getType(), schema.getFormat());
                        schema.setType(typeAndFormat.getFirst());
                        schema.setFormat(typeAndFormat.getSecond());
                        if (CollectionUtils.isEmpty(schema.getEnum())) {
                            schema.setEnum(getEnumValues(enumEl, schema.getType(), schema.getFormat(), context));
                            addEnumExtensions(enumEl, schema, context);
                        }
                    } else {
                        Schema schemaWithSuperTypes = processSuperTypes(null, schemaName, type, definingElement, openAPI, mediaTypes, schemas, context, jsonViewClass);
                        if (schemaWithSuperTypes != null) {
                            schema = schemaWithSuperTypes;
                        }
                        processJacksonDescription(type, schema);
                        if (schema != null && schema.getDescription() == null && javadoc != null && StringUtils.hasText(javadoc.getMethodDescription())) {
                            schema.setDescription(javadoc.getMethodDescription());
                        }

                        populateSchemaProperties(openAPI, context, type, typeArgs, schema, mediaTypes, javadoc, jsonViewClass);
                        checkAllOf(schema);
                    }
                    if (isDeprecated(type) && schema != null) {
                        schema.setDeprecated(true);
                        addSchemaDeprecatedExtension(type, schema, context);
                    }
                }
            } else {
                return setSpecVersion(primitiveType.createProperty());
            }
        } else {
            // Schema annotation property `name` on field level means, that this property must be with this name.
            // This is not a schema name
            var schemaName = computeDefaultSchemaName(!schemaAnnOnField ? schemaValue.stringValue(PROP_NAME).orElse(null) : null,
                definingElement, type, typeArgs, context, jsonViewClass);
            schema = schemas.get(schemaName);
            if (schema == null) {
                if (inProgressSchemas.contains(schemaName)) {
                    // Break recursion
                    return setSpecVersion(new Schema<>().$ref(SchemaUtils.schemaRef(schemaName)));
                }
                inProgressSchemas.add(schemaName);
                try {
                    schema = readSchema(schemaValue, openAPI, context, type, typeArgs, schemaAnnOnField ? definingElement : type, mediaTypes, jsonViewClass);
                    var typeSchema = type.getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
                    if (typeSchema != null) {
                        Schema originalTypeSchema = readSchema(typeSchema, openAPI, context, type, typeArgs, schemaAnnOnField ? definingElement : type, mediaTypes, jsonViewClass);
                        if (originalTypeSchema != null && schema != null) {
                            if (StringUtils.isNotEmpty(originalTypeSchema.getDescription())) {
                                schema.setDescription(originalTypeSchema.getDescription());
                            }
                            if ((originalTypeSchema.getNullable() != null && originalTypeSchema.getNullable())
                                || (isOpenapi31()
                                && CollectionUtils.isNotEmpty(originalTypeSchema.getTypes())
                                && originalTypeSchema.getTypes().contains(SchemaUtils.TYPE_NULL))
                            ) {
                                SchemaUtils.setNullable(schema);
                            }
                            schema.setRequired(originalTypeSchema.getRequired());
                        }
                    }

                    if (schema != null) {
                        processSuperTypes(schema, schemaName, type, definingElement, openAPI, mediaTypes, schemas, context, jsonViewClass);
                    }
                } finally {
                    inProgressSchemas.remove(schemaName);
                }
            }
        }
        if (schema != null) {
            var externalDocsValue = type.getDeclaredAnnotation(io.swagger.v3.oas.annotations.ExternalDocumentation.class);
            ExternalDocumentation externalDocs = null;
            if (externalDocsValue != null) {
                externalDocs = toValue(externalDocsValue.getValues(), context, ExternalDocumentation.class, null);
            }
            if (externalDocs != null) {
                schema.setExternalDocs(externalDocs);
            }
            setSchemaDescription(type, schema);
            var schemaRef = setSpecVersion(new Schema<>());
            schemaRef.set$ref(SchemaUtils.schemaRef(schema.getName()));
            if (definingElement instanceof ClassElement classEl && classEl.isIterable()) {
                schemaRef.setDescription(schema.getDescription());
            }
            return schemaRef;
        }
        return null;
    }

    public static String computeDefaultSchemaName(String defaultSchemaName, Element definingElement, Element type, Map typeArgs, VisitorContext context,
                                                  @Nullable ClassElement jsonViewClass) {

        var genericSeparator = getGenericSeparator(context);
        var innerClassSeparator = getInnerClassSeparator(context);

        String jsonViewPostfix = EMPTY_STRING;
        if (jsonViewClass != null) {
            String jsonViewClassName = jsonViewClass.getName();
            jsonViewClassName = jsonViewClassName.replace(DOLLAR, DOT);
            jsonViewPostfix = genericSeparator + (jsonViewClassName.contains(DOT) ? jsonViewClassName.substring(jsonViewClassName.lastIndexOf(DOT) + 1) : jsonViewClassName);
        }

        var pair = computeFullClassNameWithGenerics(type, typeArgs, jsonViewPostfix, context);
        var fullClassNameWithGenerics = pair.getSecond();

        String resultSchemaName;
        if (defaultSchemaName != null) {
            resultSchemaName = defaultSchemaName;
        } else {

            String metaAnnName = null;
            if (definingElement != null) {
                metaAnnName = definingElement.getAnnotationNameByStereotype(io.swagger.v3.oas.annotations.media.Schema.class).orElse(null);
            }
            if (metaAnnName != null && !io.swagger.v3.oas.annotations.media.Schema.class.getName().equals(metaAnnName)) {
                resultSchemaName = NameUtils.getSimpleName(metaAnnName) + jsonViewPostfix;
                if (!DOT.equals(innerClassSeparator)) {
                    resultSchemaName = resultSchemaName.replace(DOT, innerClassSeparator);
                }
            } else {
                resultSchemaName = pair.getFirst();
            }
        }

        String storedClassName = schemaNameToClassNameMap.get(resultSchemaName);
        // Check if the class exists in other packages. If so, you need to add a suffix,
        // because there are two classes in different packages, but with the same class name.
        if (storedClassName != null && !storedClassName.equals(fullClassNameWithGenerics)) {
            if (getSchemaDuplicateResolution(context) == ConfigUtils.DuplicateResolution.ERROR) {
                throw new ConfigurationException("Found 2 schemas with same name \"" + resultSchemaName + "\" for classes " + storedClassName + " and " + fullClassNameWithGenerics);
            }
            int index = shemaNameSuffixCounterMap.getOrDefault(resultSchemaName, 0);
            index++;
            shemaNameSuffixCounterMap.put(resultSchemaName, index);
            resultSchemaName += genericSeparator + index;
        }
        schemaNameToClassNameMap.put(resultSchemaName, fullClassNameWithGenerics);

        return resultSchemaName;
    }

    /**
     * @return pair of package name and full className with generics
     */
    private static Pair computeFullClassNameWithGenerics(Element type, Map typeArgs, String jsonViewPostfix, VisitorContext context) {

        var innerClassSeparator = getInnerClassSeparator(context);

        String resultSchemaName;
        String packageName;
        if (type instanceof TypedElement typedEl && !(type instanceof EnumElement)) {
            ClassElement typeType = typedEl.getType();
            var isProtobufGenerated = isProtobufGenerated(typeType);
            packageName = typeType.getPackageName();
            if (CollectionUtils.isNotEmpty(typeType.getTypeArguments())) {
                resultSchemaName = computeNameWithGenerics(typeType, typeArgs, context, isProtobufGenerated);
            } else {
                resultSchemaName = computeNameWithGenerics(typeType, Collections.emptyMap(), context, isProtobufGenerated);
            }
        } else {
            resultSchemaName = type.getSimpleName();
            packageName = NameUtils.getPackageName(type.getName());
        }

        ConfigUtils.SchemaDecorator schemaDecorator = getSchemaDecoration(packageName, context);
        resultSchemaName = resultSchemaName.replace(DOLLAR, innerClassSeparator) + jsonViewPostfix;
        if (schemaDecorator != null) {
            resultSchemaName = (StringUtils.hasText(schemaDecorator.getPrefix()) ? schemaDecorator.getPrefix() : EMPTY_STRING)
                + resultSchemaName
                + (StringUtils.hasText(schemaDecorator.getPostfix()) ? schemaDecorator.getPostfix() : EMPTY_STRING);
        }

        return Pair.of(resultSchemaName, packageName + DOT + resultSchemaName);
    }

    public static List getEnumValues(EnumElement type, String schemaType, String schemaFormat, VisitorContext context) {
        var isProtobufGenerated = isProtobufGenerated(type);
        var enumValues = new ArrayList<>();
        for (EnumConstantElement element : type.elements()) {

            if (isProtobufGenerated) {
                Integer protoValue = null;
                for (var field : type.getFields()) {
                    if (field.getName().equals(element.getSimpleName() + "_VALUE")) {
                        try {
                            protoValue = (Integer) field.getConstantValue();
                        } catch (Exception e) {
                            // do nothing
                        }
                        break;
                    }
                }
                if (protoValue != null) {
                    enumValues.add(protoValue);
                }
                continue;
            }

            var schemaAnn = getAnnotation(element, io.swagger.v3.oas.annotations.media.Schema.class);
            boolean isHidden = schemaAnn != null && schemaAnn.booleanValue(PROP_HIDDEN).orElse(false);

            if (isHidden
                || isAnnotationPresent(element, Hidden.class)
                || isAnnotationPresent(element, JsonIgnore.class)) {
                continue;
            }
            var jsonPropertyAnn = getAnnotation(element, JsonProperty.class);
            String jacksonValue = jsonPropertyAnn != null ? jsonPropertyAnn.stringValue(PROP_VALUE).orElse(null) : null;
            if (StringUtils.hasText(jacksonValue)) {
                try {
                    enumValues.add(ConvertUtils.normalizeValue(jacksonValue, schemaType, schemaFormat, context));
                } catch (JsonProcessingException e) {
                    warn("Error converting jacksonValue " + jacksonValue + " : to " + type + ":\n" + Utils.printStackTrace(e), context, element);
                    enumValues.add(element.getSimpleName());
                }
            } else {
                enumValues.add(element.getSimpleName());
            }
        }
        return !enumValues.isEmpty() ? enumValues : null;
    }

    /**
     * Resolves the schema for the given type element.
     *
     * @param definingElement The defining element
     * @param type The type element
     * @param context The context
     * @param mediaTypes An optional media type
     * @param jsonViewClass Class from JsonView annotation
     * @return The schema or null if it cannot be resolved
     */
    @Nullable
    public static Schema resolveSchema(@Nullable Element definingElement, @Nullable ClassElement type, VisitorContext context, List mediaTypes, @Nullable ClassElement jsonViewClass) {
        return resolveSchema(Utils.resolveOpenApi(context), definingElement, type, context, mediaTypes, jsonViewClass, null, null);
    }

    /**
     * Resolves the schema for the given type element.
     *
     * @param openApi The OpenAPI object
     * @param definingElement The defining element
     * @param type The type element
     * @param context The context
     * @param mediaTypes An optional media type
     * @param fieldJavadoc Field-level java doc
     * @param classJavadoc Class-level java doc
     * @param jsonViewClass Class from JsonView annotation
     * @return The schema or null if it cannot be resolved
     */
    @Nullable
    public static Schema resolveSchema(OpenAPI openApi, @Nullable Element definingElement, @Nullable ClassElement type, VisitorContext context,
                                          List mediaTypes, @Nullable ClassElement jsonViewClass,
                                          JavadocDescription fieldJavadoc, JavadocDescription classJavadoc) {

        AnnotationValue schemaAnnotationValue = null;
        if (definingElement != null) {
            schemaAnnotationValue = getAnnotation(definingElement, io.swagger.v3.oas.annotations.media.Schema.class);
        }
        if (type != null && schemaAnnotationValue == null) {
            schemaAnnotationValue = getAnnotation(type, io.swagger.v3.oas.annotations.media.Schema.class);
        }
        boolean isSubstitutedType = false;
        if (schemaAnnotationValue != null) {
            String impl = schemaAnnotationValue.stringValue(PROP_IMPLEMENTATION).orElse(null);
            if (StringUtils.isNotEmpty(impl)) {
                var typeEl = ContextUtils.getClassElement(impl, context);
                if (typeEl != null) {
                    type = typeEl;
                }
                isSubstitutedType = true;
            } else {
                String typeFromAnn = schemaAnnotationValue.stringValue(PROP_TYPE).orElse(null);
                List schemaTypes;
                if (isOpenapi31() && StringUtils.isEmpty(typeFromAnn)) {
                    schemaTypes = Arrays.asList(schemaAnnotationValue.stringValues(PROP_ONE_TYPES));
                } else {
                    schemaTypes = Collections.singletonList(typeFromAnn);
                }
                for (var schemaType : schemaTypes) {
                    if (StringUtils.isNotEmpty(schemaType) && !(type instanceof EnumElement)) {
                        PrimitiveType primitiveType = PrimitiveType.fromName(schemaType);
                        if (primitiveType != null && primitiveType != PrimitiveType.OBJECT) {
                            var typeEl = ContextUtils.getClassElement(primitiveType.getKeyClass().getName(), context);
                            if (typeEl != null) {
                                type = typeEl;
                            }
                            isSubstitutedType = true;
                            break;
                        }
                    }
                }
            }
        }

        if (type != null && isProtobufMessageClass(type)) {
            type = type.getInterfaces().iterator().next();
        }

        Boolean isArray = null;
        Boolean isIterable = null;

        ClassElement componentType = type != null ? type.getFirstTypeArgument().orElse(null) : null;
        if (type instanceof WildcardElement wildcardEl) {
            type = CollectionUtils.isNotEmpty(wildcardEl.getUpperBounds()) ? wildcardEl.getUpperBounds().get(0) : null;
        } else if (type instanceof GenericPlaceholderElement placeholderEl) {
            isArray = type.isArray();
            isIterable = type.isIterable();
            if (!isArray) {
                type = placeholderEl.getResolved().orElse(CollectionUtils.isNotEmpty(placeholderEl.getBounds()) ? placeholderEl.getBounds().get(0) : null);
            }
        } else if (type instanceof GenericElement genericEl) {
            isArray = type.isArray();
            isIterable = type.isIterable();
            type = genericEl.getResolved().orElse(null);
        }
        Map typeArgs = type != null ? type.getTypeArguments() : null;

        Schema schema = null;

        if (type instanceof EnumElement enumEl && isEnum(enumEl)) {
            schema = getSchemaDefinition(openApi, context, enumEl, typeArgs, definingElement, mediaTypes, jsonViewClass);
            if (isArray != null && isArray) {
                schema = SchemaUtils.arraySchema(schema);
            }
        } else if (type != null) {

            boolean isPublisher = false;
            boolean isObservable = false;
            boolean isNullable = false;
            // MultipartBody implements Publisher (Issue : #907)
            if (type.isAssignable("io.micronaut.http.server.multipart.MultipartBody")) {
                isPublisher = true;
                type = type.getInterfaces()
                    .stream()
                    .filter(i -> i.isAssignable("org.reactivestreams.Publisher"))
                    .findFirst()
                    .flatMap(ClassElement::getFirstTypeArgument)
                    .orElse(null);
                // StreamingFileUpload implements Publisher, but it should be not considered as a Publisher in the spec file
            } else if (!type.isAssignable("io.micronaut.http.multipart.StreamingFileUpload") && ElementUtils.isContainerType(type)) {
                isPublisher = (type.isAssignable("org.reactivestreams.Publisher") || type.isAssignable("kotlinx.coroutines.flow.Flow"))
                    && !type.isAssignable("reactor.core.publisher.Mono");
                isObservable = (type.isAssignable("io.reactivex.Observable") || type.isAssignable("io.reactivex.rxjava3.core.Observable"))
                    && !type.isAssignable("reactor.core.publisher.Mono");
                type = componentType;
                if (componentType != null) {
                    typeArgs = componentType.getTypeArguments();
                    componentType = componentType.getFirstTypeArgument().orElse(null);
                }
            } else if (isTypeWithGenericNullable(type)) {
                isNullable = true;
                type = componentType;
                if (componentType != null) {
                    typeArgs = componentType.getTypeArguments();
                    componentType = componentType.getFirstTypeArgument().orElse(null);
                }
            }

            if (type != null) {

                String typeName = type.getName();
                ClassElement customTypeSchema = getCustomSchema(typeName, typeArgs, context);
                if (customTypeSchema != null) {
                    Map customTypeArgs = customTypeSchema.getTypeArguments();
                    if (customTypeArgs.isEmpty()) {
                        type = customTypeSchema;
                    } else {
                        var inheritedTypeArgs = new HashMap<>(customTypeArgs);
                        for (String generic : customTypeArgs.keySet()) {
                            ClassElement element = typeArgs.get(generic);
                            if (element != null) {
                                inheritedTypeArgs.put(generic, element);
                            }
                        }
                        type = customTypeSchema.withTypeArguments(inheritedTypeArgs);
                    }
                }

                if (isArray == null) {
                    isArray = type.isArray();
                }
                if (isIterable == null) {
                    isIterable = type.isIterable();
                }

                // File upload case
                if (isFileUpload(type)) {
                    isPublisher = isPublisher && !"io.micronaut.http.multipart.PartData".equals(typeName);
                    // For file upload, we use PrimitiveType.BINARY
                    typeName = PrimitiveType.BINARY.name();
                }
                PrimitiveType primitiveType = PrimitiveType.fromName(typeName);
                schema = protobufTypeSchema(type);
                if (schema != null) {
                    return schema;
                }
                if (!isArray && ClassUtils.isJavaLangType(typeName)) {
                    schema = getPrimitiveType(type, typeName, context);
                } else if (!isArray && primitiveType != null) {
                    schema = setSpecVersion(primitiveType.createProperty());
                } else if (type.isAssignable(Map.class)) {
                    schema = processMapSchema(type, typeArgs, mediaTypes, openApi, jsonViewClass, classJavadoc, context);
                } else if (isIterable) {
                    if (isArray) {
                        schema = resolveSchema(openApi, type, type.fromArray(), context, mediaTypes, jsonViewClass, null, classJavadoc);
                        if (schema != null) {
                            schema = SchemaUtils.arraySchema(schema);
                        }
                    } else {
                        if (componentType != null) {
                            schema = resolveSchema(openApi, type, componentType, context, mediaTypes, jsonViewClass, null, classJavadoc);
                        } else {
                            schema = getPrimitiveType(null, Object.class.getName(), context);
                        }
                        List fields = type.getPackageName().startsWith("java.util") ? Collections.emptyList() : type.getFields();
                        if (schema != null && fields.isEmpty()) {
                            schema = processGenericAnnotations(schema, componentType, context);
                            schema = SchemaUtils.arraySchema(schema);
                        } else {
                            schema = getSchemaDefinition(openApi, context, type, typeArgs, definingElement, mediaTypes, jsonViewClass);
                        }
                    }
                } else if (ElementUtils.isReturnTypeFile(type)) {
                    schema = setSpecVersion(PrimitiveType.FILE.createProperty());
                } else if (type.isAssignable(Boolean.class) || type.isAssignable(boolean.class)) {
                    schema = setSpecVersion(PrimitiveType.BOOLEAN.createProperty());
                } else if (type.isAssignable(Byte.class) || type.isAssignable(byte.class)) {
                    schema = setSpecVersion(PrimitiveType.BYTE.createProperty());
                } else if (type.isAssignable(UUID.class)) {
                    schema = setSpecVersion(PrimitiveType.UUID.createProperty());
                } else if (type.isAssignable(URL.class)) {
                    schema = setSpecVersion(PrimitiveType.URL.createProperty());
                } else if (type.isAssignable(URI.class)) {
                    schema = setSpecVersion(PrimitiveType.URI.createProperty());
                } else if (type.isAssignable(Character.class) || type.isAssignable(char.class)) {
                    schema = setSpecVersion(PrimitiveType.STRING.createProperty());
                } else if (type.isAssignable(Integer.class) || type.isAssignable(int.class)
                    || type.isAssignable(Short.class) || type.isAssignable(short.class)
                    || type.isAssignable(OptionalInt.class)
                ) {
                    schema = setSpecVersion(PrimitiveType.INT.createProperty());
                } else if (type.isAssignable(Long.class) || type.isAssignable(long.class)
                    || type.isAssignable(OptionalLong.class)
                ) {
                    schema = setSpecVersion(PrimitiveType.LONG.createProperty());
                } else if (type.isAssignable(Float.class) || type.isAssignable(float.class)) {
                    schema = setSpecVersion(PrimitiveType.FLOAT.createProperty());
                } else if (type.isAssignable(Double.class) || type.isAssignable(double.class)
                    || type.isAssignable(OptionalDouble.class)
                ) {
                    schema = setSpecVersion(PrimitiveType.DOUBLE.createProperty());
                } else if (type.isAssignable(BigInteger.class)) {
                    schema = setSpecVersion(PrimitiveType.INTEGER.createProperty());
                } else if (type.isAssignable(BigDecimal.class)) {
                    schema = setSpecVersion(PrimitiveType.DECIMAL.createProperty());
                } else if (type.isAssignable(Date.class)
                    || type.isAssignable(Calendar.class)
                    || type.isAssignable(LocalDateTime.class)
                    || type.isAssignable(ZonedDateTime.class)
                    || type.isAssignable(OffsetDateTime.class)
                    || type.isAssignable(Instant.class)
                    || type.isAssignable(XMLGregorianCalendar.class)) {
                    schema = setSpecVersion(new StringSchema().format("date-time"));
                } else if (type.isAssignable(LocalDate.class)) {
                    schema = setSpecVersion(new StringSchema().format("date"));
                } else if (type.isAssignable(LocalTime.class)) {
                    schema = setSpecVersion(new StringSchema().format("partial-time"));
                } else if (type.isAssignable(Number.class)) {
                    schema = setSpecVersion(PrimitiveType.NUMBER.createProperty());
                } else if (type.getName().equals(Object.class.getName())) {
                    schema = setSpecVersion(PrimitiveType.OBJECT.createProperty());
                } else {
                    schema = getSchemaDefinition(openApi, context, type, typeArgs, definingElement, mediaTypes, jsonViewClass);
                    schema = processGenericAnnotations(schema, componentType, context);
                }
            }

            if (schema != null) {

                if (isSubstitutedType) {
                    processSchemaAnn(schema, context, definingElement, type, schemaAnnotationValue);
                }

                processJacksonDescription(definingElement, schema);

                if (definingElement != null && StringUtils.isEmpty(schema.getDescription())) {
                    if (fieldJavadoc != null) {
                        if (StringUtils.hasText(fieldJavadoc.getMethodDescription())) {
                            schema.setDescription(fieldJavadoc.getMethodDescription());
                        }
                    } else if (classJavadoc != null) {
                        String paramJavadoc = classJavadoc.getParameters().get(definingElement.getName());
                        if (StringUtils.hasText(paramJavadoc)) {
                            schema.setDescription(paramJavadoc);
                        }
                    }
                }

                boolean isStream = false;
                if (CollectionUtils.isNotEmpty(mediaTypes)) {
                    for (MediaType mediaType : mediaTypes) {
                        if (MediaType.TEXT_EVENT_STREAM_TYPE.equals(mediaType) || MediaType.APPLICATION_JSON_STREAM_TYPE.equals(mediaType)) {
                            isStream = true;
                            break;
                        }
                    }
                }

                if (!isStream && (isPublisher || isObservable)) {
                    schema = SchemaUtils.arraySchema(schema);
                } else if (isNullable) {
                    SchemaUtils.setNullable(schema);
                }
            }
        }
        return schema;
    }

    /**
     * Binds the schema for the given element.
     *
     * @param context The context
     * @param element The element
     * @param elementType The element type
     * @param schemaToBind The schema to bind
     * @param jsonViewClass Class from JsonView annotation
     * @param withProcessDeprecated Is need to process deprecates annotation
     * @return The bound schema
     */
    public static Schema bindSchemaForElement(VisitorContext context, TypedElement element, ClassElement elementType, Schema schemaToBind,
                                                 @Nullable ClassElement jsonViewClass, boolean withProcessDeprecated) {
        var schemaAnn = getAnnotation(element, io.swagger.v3.oas.annotations.media.Schema.class);
        Schema originalSchema = schemaToBind != null ? schemaToBind : new Schema<>();

        if (originalSchema.get$ref() != null) {
            Schema schemaFromAnn = schemaFromAnnotation(context, element, elementType, schemaAnn);
            if (schemaFromAnn != null) {
                schemaToBind = schemaFromAnn;
            }
        }
        if (originalSchema.get$ref() == null && schemaAnn != null) {
            // Apply @Schema annotation only if not $ref since for $ref schemas
            // we already populated values from right @Schema annotation in previous steps
            schemaToBind = bindSchemaAnnotationValue(context, element, schemaToBind, schemaAnn, jsonViewClass);
            var schemaName = schemaAnn.stringValue(PROP_NAME).orElse(null);
            if (schemaName != null) {
                schemaToBind.setName(schemaName);
            }

            var impl = schemaAnn.stringValue(PROP_IMPLEMENTATION).orElse(null);
            if (StringUtils.isNotEmpty(impl)) {
                var implEl = ContextUtils.getClassElement(impl, context);
                if (implEl != null) {
                    elementType = implEl;
                }
            }
        }
        var arraySchemaAnn = getAnnotation(element, io.swagger.v3.oas.annotations.media.ArraySchema.class);
        if (arraySchemaAnn != null) {
            schemaToBind = bindArraySchemaAnnotationValue(context, element, schemaToBind, arraySchemaAnn, jsonViewClass);
            arraySchemaAnn.stringValue(PROP_NAME).ifPresent(schemaToBind::setName);
        }

        processJakartaValidationAnnotations(element, elementType, schemaToBind, context);

        final ComposedSchema composedSchema;
        final Schema topLevelSchema;
        if (originalSchema.get$ref() != null) {
            composedSchema = setSpecVersion(new ComposedSchema());
            topLevelSchema = composedSchema;
        } else {
            composedSchema = setSpecVersion(new ComposedSchema());
            topLevelSchema = schemaToBind;
        }

        boolean notOnlyRef = false;
        setSchemaDescription(element, topLevelSchema);
        if (StringUtils.isNotEmpty(topLevelSchema.getDescription())) {
            notOnlyRef = true;
        }
        if (withProcessDeprecated
            && (isDeprecated(element) || isAnnotationPresent(element, Deprecated.class) || isAnnotationPresent(element, "kotlin.Deprecated"))
            && !(element instanceof PropertyElement propertyEl && isProtobufGenerated(propertyEl.getOwningType()) && elementType.getName().equals(Map.class.getName())
        )) {
            addSchemaDeprecatedExtension(element, topLevelSchema, context);
            topLevelSchema.setDeprecated(true);
            notOnlyRef = true;
        }
        final String defaultValue = stringValue(element, Bindable.class, PROP_DEFAULT_VALUE).orElse(null);
        if (defaultValue != null && schemaToBind != null && schemaToBind.getDefault() == null) {
            setDefaultValueObject(schemaToBind, defaultValue, elementType, schemaToBind.getType(), schemaToBind.getFormat(), true, context);
            notOnlyRef = true;
        }
        // @Schema annotation takes priority over nullability annotations
        Boolean isSchemaNullable = element.booleanValue(io.swagger.v3.oas.annotations.media.Schema.class, PROP_NULLABLE).orElse(null);
        boolean isNullable = (isSchemaNullable == null && isNullable(element) && !isNotNullable(element)) || Boolean.TRUE.equals(isSchemaNullable);
        if (isNullable) {
            SchemaUtils.setNullable(topLevelSchema);
            notOnlyRef = true;
        }
        if (processJacksonPropertyAnn(element, elementType, topLevelSchema, schemaAnn, context)) {
            notOnlyRef = true;
        }
//        final String defaultJacksonValue = stringValue(element, JsonProperty.class, PROP_DEFAULT_VALUE).orElse(null);
//        if (defaultJacksonValue != null && schemaToBind.getDefault() == null) {
//            setDefaultValueObject(topLevelSchema, defaultJacksonValue, elementType, schemaToBind.getType(), schemaToBind.getFormat(), false, context);
//            notOnlyRef = true;
//        }

        boolean addSchemaToBind = !SchemaUtils.isEmptySchema(schemaToBind);

        if (addSchemaToBind) {
            if (TYPE_OBJECT.equals(originalSchema.getType()) && !(originalSchema instanceof MapSchema)) {
                if (composedSchema.getType() == null) {
                    composedSchema.setType(TYPE_OBJECT);
                }
                originalSchema.setType(null);
            }
            if (!SchemaUtils.isEmptySchema(originalSchema)) {
                composedSchema.addAllOfItem(originalSchema);
            }
        } else if (isNullable && CollectionUtils.isEmpty(composedSchema.getAllOf())) {
            composedSchema.addAllOfItem(originalSchema);
        }
        if (addSchemaToBind && schemaToBind != null && !schemaToBind.equals(originalSchema)) {
            if (TYPE_OBJECT.equals(schemaToBind.getType()) && !(originalSchema instanceof MapSchema)) {
                if (composedSchema.getType() == null) {
                    composedSchema.setType(TYPE_OBJECT);
                }
                originalSchema.setType(null);
            }
            composedSchema.addAllOfItem(schemaToBind);
        }

        if (!SchemaUtils.isEmptySchema(composedSchema)
            && ((CollectionUtils.isNotEmpty(composedSchema.getAllOf()) && composedSchema.getAllOf().size() > 1)
            || CollectionUtils.isNotEmpty(composedSchema.getOneOf())
            || CollectionUtils.isNotEmpty(composedSchema.getAnyOf())
            || notOnlyRef)) {
            return composedSchema;
        }
        if (CollectionUtils.isNotEmpty(composedSchema.getAllOf()) && composedSchema.getAllOf().size() == 1) {
            return composedSchema.getAllOf().get(0);
        }

        return originalSchema;
    }

    /**
     * Binds the array schema for the given element.
     *
     * @param context The context
     * @param element The element
     * @param schemaToBind The schema to bind
     * @param schemaAnn The schema annotation
     * @param jsonViewClass Class from JsonView annotation
     * @return The bound schema
     */
    public static Schema bindArraySchemaAnnotationValue(VisitorContext context, TypedElement element, Schema schemaToBind,
                                                           AnnotationValue schemaAnn,
                                                           @Nullable ClassElement jsonViewClass) {
        JsonNode schemaJson = toJson(schemaAnn.getValues(), context, jsonViewClass);
        if (schemaJson.isObject()) {
            ObjectNode objNode = (ObjectNode) schemaJson;
            JsonNode arraySchema = objNode.remove(PROP_ARRAY_SCHEMA);
            // flatten
            if (arraySchema != null && arraySchema.isObject()) {
                ((ObjectNode) arraySchema).remove(PROP_IMPLEMENTATION);
                objNode.setAll((ObjectNode) arraySchema);
            }
            // remove schema that maps to 'items'
            JsonNode items = objNode.remove(PROP_SCHEMA);
            if (items != null && schemaToBind != null && (schemaToBind.getType() != null && schemaToBind.getType().equals(TYPE_ARRAY))) {
                try {
                    Schema updatedSchema = Utils.getJsonMapper().readerForUpdating(schemaToBind.getItems()).readValue(items);
                    if (updatedSchema != null) {
                        schemaToBind.items(updatedSchema);
                    } else {
                        warn("Error reading Swagger Schema for element [" + element + "] (items schema): " + items, context, element);
                    }
                } catch (IOException e) {
                    warn("Error reading Swagger Schema for element [" + element + "] (items schema): " + e.getMessage(), context, element);
                }
            }
        }

        String elType = schemaJson.has(PROP_TYPE) ? schemaJson.get(PROP_TYPE).textValue() : null;
        String elFormat = schemaJson.has(PROP_ONE_FORMAT) ? schemaJson.get(PROP_ONE_FORMAT).textValue() : null;
        return doBindSchemaAnnotationValue(context, element, schemaToBind, schemaJson, elType, elFormat, null, jsonViewClass);
    }

    /**
     * Convert the values to a map.
     *
     * @param values The values
     * @param context The visitor context
     * @param jsonViewClass Class from JsonView annotation
     * @return The map
     */
    public static Map toValueMap(Map values, VisitorContext context, @Nullable ClassElement jsonViewClass) {
        if (CollectionUtils.isEmpty(values)) {
            return Collections.emptyMap();
        }
        var newValues = new HashMap(values.size());
        for (Map.Entry entry : values.entrySet()) {
            CharSequence key = entry.getKey();
            Object value = entry.getValue();

            if (value instanceof AnnotationValue av) {
                if (av.getAnnotationName().equals(io.swagger.v3.oas.annotations.media.ArraySchema.class.getName())) {
                    final Map valueMap = resolveArraySchemaAnnotationValues(context, av, jsonViewClass);
                    newValues.put(PROP_SCHEMA, valueMap);
                } else {
                    final Map valueMap = resolveAnnotationValues(context, av, jsonViewClass);
                    newValues.put(key, valueMap);
                }
            } else if (value instanceof AnnotationClassValue acv) {
                acv.getType().ifPresent(aClass -> newValues.put(key, aClass));
            } else if (value != null) {
                if (value.getClass().isArray()) {
                    var a = (Object[]) value;
                    if (ArrayUtils.isNotEmpty(a)) {
                        Object first = a[0];

                        // are class values
                        if (first instanceof AnnotationClassValue) {
                            var classes = new ArrayList>(a.length);
                            for (Object o : a) {
                                var acv = (AnnotationClassValue) o;
                                acv.getType().ifPresent(classes::add);
                            }
                            newValues.put(key, classes);
                        } else if (first instanceof AnnotationValue annValue) {
                            String annotationName = annValue.getAnnotationName();
                            if (io.swagger.v3.oas.annotations.security.SecurityRequirement.class.getName().equals(annotationName)) {
                                var securityRequirements = new ArrayList(a.length);
                                for (Object o : a) {
                                    securityRequirements.add(ConvertUtils.mapToSecurityRequirement((AnnotationValue) o));
                                }
                                newValues.put(key, securityRequirements);
                            } else if (Extension.class.getName().equals(annotationName)) {
                                var extensions = new HashMap();
                                for (Object o : a) {
                                    processExtensions(extensions, (AnnotationValue) o);
                                }
                                newValues.put(PROP_EXTENSIONS, extensions);
                            } else if (Encoding.class.getName().equals(annotationName)) {
                                Map encodings = annotationValueArrayToSubmap(a, PROP_NAME, context, null);
                                newValues.put(key, encodings);
                            } else if (Content.class.getName().equals(annotationName)) {
                                Map mediaTypes = annotationValueArrayToSubmap(a, PROP_MEDIA_TYPE, context, jsonViewClass);
                                newValues.put(key, mediaTypes);
                            } else if (Link.class.getName().equals(annotationName) || Header.class.getName().equals(annotationName)) {
                                Map linksOrHeaders = annotationValueArrayToSubmap(a, PROP_NAME, context, jsonViewClass);
                                var newLinksOrHeaders = new HashMap(linksOrHeaders.size());
                                for (var linkOrHeaderEntry : linksOrHeaders.entrySet()) {
                                    var linkOrHeaderMap = (Map) linkOrHeaderEntry.getValue();
                                    if (linkOrHeaderMap.containsKey(PROP_HIDDEN) && (Boolean) linkOrHeaderMap.get(PROP_HIDDEN)) {
                                        continue;
                                    }
                                    if (linkOrHeaderMap.containsKey(PROP_REF)) {
                                        linkOrHeaderMap.put(PROP_REF_DOLLAR, linkOrHeaderMap.remove(PROP_REF));
                                    }
                                    if (linkOrHeaderMap.containsKey(PROP_SCHEMA)) {
                                        var schemaMap = (Map) linkOrHeaderMap.get(PROP_SCHEMA);
                                        if (schemaMap.containsKey(PROP_REF)) {
                                            Object ref = schemaMap.get(PROP_REF);
                                            schemaMap.clear();
                                            schemaMap.put(PROP_REF_DOLLAR, ref);
                                        }
                                        if (schemaMap.containsKey(PROP_DEFAULT_VALUE)) {
                                            schemaMap.put(PROP_DEFAULT, schemaMap.remove(PROP_DEFAULT_VALUE));
                                        }
                                        if (schemaMap.containsKey(PROP_ALLOWABLE_VALUES)) {
                                            // The key in the generated openapi needs to be "enum"
                                            schemaMap.put(PROP_ENUM, schemaMap.remove(PROP_ALLOWABLE_VALUES));
                                        }
                                    }
                                    if (linkOrHeaderMap.containsKey(PROP_EXAMPLE)) {
                                        var headerExample = linkOrHeaderMap.get(PROP_EXAMPLE);
                                        if (headerExample != null) {
                                            try {
                                                var headerSchema = (Map) linkOrHeaderMap.get(PROP_SCHEMA);
                                                String type = null;
                                                String format = null;
                                                if (headerSchema != null) {
                                                    type = (String) headerSchema.get(PROP_TYPE);
                                                    format = (String) headerSchema.get(PROP_ONE_FORMAT);
                                                    if (type == null) {
                                                        type = SchemaUtils.getType(type, (Collection) headerSchema.get(PROP_ONE_TYPES));
                                                    }
                                                }
                                                var headerExampleStr = OpenApiUtils.getConvertJsonMapper().writeValueAsString(headerExample);
                                                // need to set placeholders to set correct values and types to example field
                                                headerExampleStr = replacePlaceholders(headerExampleStr, context);
                                                linkOrHeaderMap.put(PROP_EXAMPLE, ConvertUtils.parseByTypeAndFormat(headerExampleStr, type, format, context, false));
                                            } catch (JsonProcessingException e) {
                                                // do nothing
                                            }
                                        }
                                    }
                                    newLinksOrHeaders.put(linkOrHeaderEntry.getKey(), linkOrHeaderEntry.getValue());
                                }
                                newValues.put(key, newLinksOrHeaders);
                            } else if (LinkParameter.class.getName().equals(annotationName)) {
                                Map params = toTupleSubMap(a, PROP_NAME, PROP_EXPRESSION);
                                newValues.put(key, params);
                            } else if (OAuthScope.class.getName().equals(annotationName)) {
                                Map params = toTupleSubMap(a, PROP_NAME, PROP_DESCRIPTION);
                                newValues.put(key, params);
                            } else if (ApiResponse.class.getName().equals(annotationName)) {
                                var responses = new LinkedHashMap>();
                                for (Object o : a) {
                                    var sv = (AnnotationValue) o;
                                    String name = sv.stringValue(PROP_RESPONSE_CODE).orElse(PROP_DEFAULT);
                                    Map map = toValueMap(sv.getValues(), context, jsonViewClass);
                                    if (map.containsKey(PROP_REF)) {
                                        Object ref = map.get(PROP_REF);
                                        map.clear();
                                        map.put(PROP_REF_DOLLAR, ref);
                                    }

                                    try {
                                        if (!map.containsKey(PROP_DESCRIPTION)) {
                                            map.put(PROP_DESCRIPTION, name.equals(PROP_DEFAULT) ? "OK response" : HttpStatus.valueOf(Integer.parseInt(name)).getReason());
                                        }
                                    } catch (Exception e) {
                                        map.put(PROP_DESCRIPTION, "Response " + name);
                                    }

                                    responses.put(name, map);
                                }
                                newValues.put(key, responses);
                            } else if (ExampleObject.class.getName().equals(annotationName)) {
                                var examples = new LinkedHashMap>();
                                for (Object o : a) {
                                    var sv = (AnnotationValue) o;
                                    String name = sv.stringValue(PROP_NAME).orElse(PROP_EXAMPLE);
                                    Map map = toValueMap(sv.getValues(), context, null);
                                    if (map.containsKey(PROP_REF)) {
                                        Object ref = map.get(PROP_REF);
                                        map.clear();
                                        map.put(PROP_REF_DOLLAR, ref);
                                    }
                                    examples.put(name, map);
                                }
                                newValues.put(key, examples);
                            } else if (Server.class.getName().equals(annotationName)) {
                                var servers = new ArrayList>();
                                for (Object o : a) {
                                    var sv = (AnnotationValue) o;
                                    var variables = new LinkedHashMap<>(toValueMap(sv.getValues(), context, null));
                                    servers.add(variables);
                                }
                                newValues.put(key, servers);
                            } else if (ServerVariable.class.getName().equals(annotationName)) {
                                var variables = new LinkedHashMap>();
                                for (Object o : a) {
                                    var sv = (AnnotationValue) o;
                                    sv.stringValue(PROP_NAME)
                                        .ifPresent(name -> {
                                            Map map = toValueMap(sv.getValues(), context, null);
                                            Object dv = map.get(PROP_DEFAULT_VALUE);
                                            if (dv != null) {
                                                map.put(PROP_DEFAULT, dv);
                                            }
                                            if (map.containsKey(PROP_ALLOWABLE_VALUES)) {
                                                // The key in the generated openapi needs to be "enum"
                                                map.put(PROP_ENUM, map.remove(PROP_ALLOWABLE_VALUES));
                                            }
                                            variables.put(name, map);
                                        });
                                }
                                newValues.put(key, variables);
                            } else if (DiscriminatorMapping.class.getName().equals(annotationName)) {
                                var extensions = new HashMap();
                                var mappings = new HashMap();
                                for (Object o : a) {
                                    var dv = (AnnotationValue) o;
                                    final Map valueMap = resolveAnnotationValues(context, dv, null);
                                    mappings.put(valueMap.get(PROP_VALUE).toString(), valueMap.get(PROP_REF_DOLLAR).toString());
                                    var extValue = (Map) valueMap.get(PROP_EXTENSIONS);
                                    if (extValue != null) {
                                        extensions.putAll(extValue);
                                    }
                                }
                                final Map discriminatorMap = getDiscriminatorMap(newValues);
                                discriminatorMap.put(PROP_MAPPING, mappings);
                                if (CollectionUtils.isNotEmpty(extensions)) {
                                    discriminatorMap.put(PROP_EXTENSIONS, extensions);
                                }
                                newValues.put(DISCRIMINATOR, discriminatorMap);
                            } else {
                                if (a.length == 1) {
                                    var av = (AnnotationValue) a[0];
                                    final Map valueMap = resolveAnnotationValues(context, av, jsonViewClass);
                                    newValues.put(key, toValueMap(valueMap, context, jsonViewClass));
                                } else {

                                    var list = new ArrayList<>();
                                    for (Object o : a) {
                                        if (o instanceof AnnotationValue av) {
                                            final Map valueMap = resolveAnnotationValues(context, av, jsonViewClass);
                                            list.add(valueMap);
                                        } else {
                                            list.add(o);
                                        }
                                    }
                                    newValues.put(key, list);
                                }
                            }
                        } else if (key.equals(PROP_ONE_TYPES) && isOpenapi31()) {
                            newValues.put(PROP_TYPE, value);
                        } else {
                            newValues.put(key, value);
                        }
                    } else {
                        newValues.put(key, a);
                    }
                } else if (key.equals(PROP_ADDITIONAL_PROPERTIES)) {
                    if (io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.TRUE.toString().equals(value.toString())) {
                        newValues.put(PROP_ADDITIONAL_PROPERTIES, true);
                    } else if (io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.FALSE.toString().equals(value.toString())) {
                        newValues.put(PROP_ADDITIONAL_PROPERTIES, false);
                    }
                    // TODO
//                    } else if (AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION.toString().equals(value.toString())) {
                } else if (key.equals(PROP_ONE_TYPES) && isOpenapi31()) {
                    newValues.put(PROP_TYPE, value);
                } else if (key.equals(PROP_DISCRIMINATOR_PROPERTY)) {
                    final Map discriminatorMap = getDiscriminatorMap(newValues);
                    var parsedJsonValue = parseJsonString(value);
                    discriminatorMap.put(PROP_PROPERTY_NAME, parsedJsonValue != null ? parsedJsonValue : value);
                    newValues.put(DISCRIMINATOR, discriminatorMap);
                } else if (key.equals(PROP_STYLE)) {
                    io.swagger.v3.oas.models.parameters.Parameter.StyleEnum paramStyle = null;
                    try {
                        paramStyle = io.swagger.v3.oas.models.parameters.Parameter.StyleEnum.valueOf((String) value);
                    } catch (Exception e) {
                        // ignore
                    }
                    if (paramStyle == null) {
                        for (var styleValue : io.swagger.v3.oas.models.parameters.Parameter.StyleEnum.values()) {
                            if (styleValue.toString().equals(value)) {
                                paramStyle = styleValue;
                                newValues.put(key, styleValue.toString());
                                break;
                            }
                        }
                    } else {
                        newValues.put(key, paramStyle.toString());
                    }

                    if (paramStyle == null) {
                        io.swagger.v3.oas.models.media.Encoding.StyleEnum encodingStyle = null;
                        try {
                            encodingStyle = io.swagger.v3.oas.models.media.Encoding.StyleEnum.valueOf((String) value);
                        } catch (Exception e) {
                            // ignore
                        }
                        if (encodingStyle == null) {
                            for (var styleValue : io.swagger.v3.oas.models.media.Encoding.StyleEnum.values()) {
                                if (styleValue.toString().equals(value)) {
                                    encodingStyle = styleValue;
                                    break;
                                }
                            }
                        }
                        if (encodingStyle != null) {
                            newValues.put(key, encodingStyle.toString());
                        }
                    }
                } else if (key.equals(PROP_REF)) {
                    newValues.put(PROP_REF_DOLLAR, value);
                } else if (key.equals(PROP_ACCESS_MODE)) {
                    if (io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY.toString().equals(value)) {
                        newValues.put(PROP_READ_ONLY, Boolean.TRUE);
                        newValues.remove(PROP_WRITE_ONLY);
                    } else if (io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY.toString().equals(value)) {
                        newValues.remove(PROP_READ_ONLY);
                        newValues.put(PROP_WRITE_ONLY, Boolean.TRUE);
                    } else if (io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE.toString().equals(value)) {
                        newValues.remove(PROP_READ_ONLY);
                        newValues.remove(PROP_WRITE_ONLY);
                    }
                } else {
                    if (key.equals(PROP_EXAMPLE)) {
                        newValues.put(PROP_EXAMPLE_SET_FLAG, true);
                    }
                    var parsedJsonValue = parseJsonString(value);
                    newValues.put(key, parsedJsonValue != null ? parsedJsonValue : value);
                }
            }
        }
        return newValues;
    }

    public static Map resolveArraySchemaAnnotationValues(VisitorContext context, AnnotationValue av, @Nullable ClassElement jsonViewClass) {
        var arraySchemaMap = new HashMap(10);
        // properties
        av.get(PROP_ARRAY_SCHEMA, AnnotationValue.class).ifPresent(annotationValue ->
            processAnnotationValue(context, (AnnotationValue) annotationValue, arraySchemaMap, List.of(PROP_REF, PROP_IMPLEMENTATION), Schema.class, null)
        );
        // items
        av.get(PROP_SCHEMA, AnnotationValue.class).ifPresent(annotationValue -> {
            Optional impl = annotationValue.stringValue(PROP_IMPLEMENTATION);
            Optional type = annotationValue.stringValue(PROP_TYPE);
            Optional format = annotationValue.stringValue(PROP_ONE_FORMAT);
            ClassElement classEl = null;
            PrimitiveType primitiveType = null;
            if (impl.isPresent()) {
                classEl = ContextUtils.getClassElement(impl.get(), context);
            } else if (type.isPresent()) {
                // if format is "binary", we want PrimitiveType.BINARY
                primitiveType = PrimitiveType.fromName(format.isPresent() && format.get().equals("binary") ? format.get() : type.get());
                if (primitiveType == null) {
                    classEl = ContextUtils.getClassElement(type.get(), context);
                } else {
                    classEl = ContextUtils.getClassElement(primitiveType.getKeyClass().getName(), context);
                }
            }
            if (classEl != null) {
                if (primitiveType == null) {
                    final ArraySchema schema = SchemaUtils.arraySchema(resolveSchema(null, classEl, context, Collections.emptyList(), jsonViewClass));
                    schemaToValueMap(arraySchemaMap, schema);
                } else {
                    // For primitive type, just copy description field is present.
                    final Schema items = setSpecVersion(primitiveType.createProperty());
                    items.setDescription((String) annotationValue.stringValue(PROP_DESCRIPTION).orElse(null));
                    final ArraySchema schema = SchemaUtils.arraySchema(items);
                    schemaToValueMap(arraySchemaMap, schema);
                }
            } else {
                arraySchemaMap.putAll(resolveAnnotationValues(context, annotationValue, jsonViewClass));
            }
        });
        // other properties (minItems,...)
        processAnnotationValue(context, av, arraySchemaMap, List.of(PROP_SCHEMA, PROP_ARRAY_SCHEMA), ArraySchema.class, null);
        return arraySchemaMap;
    }

    /**
     * Binds the schema for the given element.
     *
     * @param context The context
     * @param element The element
     * @param schemaToBind The schema to bind
     * @param schemaAnn The schema annotation
     * @param jsonViewClass Class from JsonView annotation
     * @return The bound schema
     */
    public static Schema bindSchemaAnnotationValue(VisitorContext context, TypedElement element, Schema schemaToBind,
                                                      AnnotationValue schemaAnn,
                                                      @Nullable ClassElement jsonViewClass) {

        ClassElement classEl = element.getType();
        if (ElementUtils.isContainerType(classEl)) {
            classEl = classEl.getFirstTypeArgument().orElse(context.getClassElement("java.lang.Object").orElse(classEl));
        }
        Pair typeAndFormat;
        if (classEl.isIterable()) {
            typeAndFormat = Pair.of(TYPE_ARRAY, null);
        } else if (classEl instanceof EnumElement enumEl && isEnum(enumEl)) {
            typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null);
        } else {
            typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classEl.getName(), classEl.isArray(), classEl);
        }

        JsonNode schemaJson = toJson(schemaAnn.getValues(), context, jsonViewClass);
        return doBindSchemaAnnotationValue(context, element, schemaToBind, schemaJson,
            schemaAnn.stringValue(PROP_TYPE).orElse(typeAndFormat.getFirst()),
            schemaAnn.stringValue(PROP_ONE_FORMAT).orElse(typeAndFormat.getSecond()),
            schemaAnn, jsonViewClass);
    }

    /**
     * Convert the given Map to a JSON node and then to the specified type.
     *
     * @param  The output class type
     * @param values The values
     * @param context The visitor context
     * @param type The class
     * @param jsonViewClass Class from JsonView annotation
     * @return The converted instance
     */
    public static  T toValue(Map values, VisitorContext context, Class type, @Nullable ClassElement jsonViewClass) {
        JsonNode node = toJson(values, context, jsonViewClass);
        try {
            return ConvertUtils.treeToValue(node, type, context);
        } catch (JsonProcessingException e) {
            warn("Error converting  [" + node + "]: to " + type + ":\n" + Utils.printStackTrace(e), context);
        }
        return null;
    }

    /**
     * Convert the given map to a JSON node.
     *
     * @param values The values
     * @param context The visitor context
     * @param jsonViewClass Class from JsonView annotation
     * @return The node
     */
    public static JsonNode toJson(Map values, VisitorContext context, @Nullable ClassElement jsonViewClass) {
        Map newValues = toValueMap(values, context, jsonViewClass);
        return Utils.getJsonMapper().valueToTree(newValues);
    }

    /**
     * Processes a schema property.
     *
     * @param context The visitor context
     * @param element The element
     * @param elementType The element type
     * @param classElement The class element
     * @param parentSchema The parent schema
     * @param propertySchema The property schema
     */
    public static void processSchemaProperty(VisitorContext context, TypedElement element, ClassElement elementType, @Nullable ClassElement classElement,
                                             Schema parentSchema, Schema propertySchema) {
        if (propertySchema == null) {
            return;
        }
        var jsonUnwrappedAnn = getAnnotation(element, JsonUnwrapped.class);
        if (jsonUnwrappedAnn != null && jsonUnwrappedAnn.booleanValue("enabled").orElse(Boolean.TRUE)) {
            handleUnwrapped(context, element, elementType, parentSchema, jsonUnwrappedAnn);
        } else {
            // check schema required flag
            var schemaAnn = getAnnotation(element, io.swagger.v3.oas.annotations.media.Schema.class);
            Boolean elementSchemaRequired = null;
            boolean isAutoRequiredMode = true;
            boolean isRequiredDefaultValueSet = false;
            if (schemaAnn != null) {
                elementSchemaRequired = schemaAnn.get(PROP_REQUIRED, Argument.BOOLEAN).orElse(null);
                isRequiredDefaultValueSet = !schemaAnn.contains(PROP_REQUIRED);
                var requiredMode = schemaAnn.enumValue(PROP_REQUIRED_MODE, io.swagger.v3.oas.annotations.media.Schema.RequiredMode.class)
                    .orElse(null);
                if (requiredMode == io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED) {
                    elementSchemaRequired = true;
                    isAutoRequiredMode = false;
                } else if (requiredMode == io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED) {
                    elementSchemaRequired = false;
                    isAutoRequiredMode = false;
                }
            }

            // check field annotations (@NonNull, @Nullable, etc.)
            boolean isNotNullable = isNotNullable(element);
            // check as mandatory in constructor
            boolean isMandatoryInConstructor = doesParamExistsMandatoryInConstructor(element, classElement);
            boolean required = elementSchemaRequired != null ? elementSchemaRequired : isNotNullable || isMandatoryInConstructor;

            if (isRequiredDefaultValueSet && isAutoRequiredMode && isNotNullable) {
                required = true;
            }

            propertySchema = bindSchemaForElement(context, element, elementType, propertySchema, null, true);
            String propertyName = resolvePropertyName(element, classElement, propertySchema);
            propertyName = normalizePropertyName(propertyName, classElement, elementType);
            propertySchema.setRequired(null);
            Schema propertySchemaFinal = propertySchema;
            addProperty(parentSchema, propertyName, propertySchema, required);
            if (schemaAnn != null) {
                schemaAnn.stringValue(PROP_DEFAULT_VALUE)
                    .ifPresent(value -> {
                        String elType = schemaAnn.stringValue(PROP_TYPE).orElse(null);
                        String elFormat = schemaAnn.stringValue(PROP_ONE_FORMAT).orElse(null);
                        if (elType == null && elementType != null) {
                            Pair typeAndFormat;
                            if (elementType instanceof EnumElement enumEl && isEnum(enumEl)) {
                                typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat);
                            } else {
                                typeAndFormat = ConvertUtils.getTypeAndFormatByClass(elementType.getName(), elementType.isArray(), elementType);
                            }
                            elType = typeAndFormat.getFirst();
                            if (elFormat == null) {
                                elFormat = typeAndFormat.getSecond();
                            }
                        }
                        setDefaultValueObject(propertySchemaFinal, value, element, elType, elFormat, false, context);
                    });
            }
        }
    }

    private static void populateSchemaProperties(OpenAPI openAPI, VisitorContext context, Element type, Map typeArgs, Schema schema,
                                                 List mediaTypes, JavadocDescription classJavadoc, @Nullable ClassElement jsonViewClass) {
        ClassElement classElement = null;
        if (type instanceof ClassElement classEl) {
            classElement = classEl;
        } else if (type instanceof TypedElement typedEl) {
            classElement = typedEl.getType();
        }

        if (classElement != null && !ClassUtils.isJavaLangType(classElement.getName())) {
            List beanProperties;
            try {
                beanProperties = classElement.getBeanProperties(
                        PropertyElementQuery.of(classElement)
                            .excludedAnnotations(Set.of(
                                Hidden.class.getName(),
                                JsonBackReference.class.getName(),
                                JsonIgnore.class.getName()
                            ))
                    ).stream()
                    .filter(p ->
                        !"groovy.lang.MetaClass".equals(p.getType().getName())
                            && !"java.lang.Class".equals(p.getType().getName())
                            && !getAnnotationMetadata(p).booleanValue(io.swagger.v3.oas.annotations.media.Schema.class, PROP_HIDDEN).orElse(false)
                    )
                    .toList();
            } catch (Exception e) {
                warn("Error with getting properties for class " + classElement.getName() + ": " + e + "\n" + Utils.printStackTrace(e), context, classElement);
                // Workaround for https://github.com/micronaut-projects/micronaut-openapi/issues/313
                beanProperties = Collections.emptyList();
            }
            beanProperties = filterProtobufProperties(classElement, beanProperties);
            processPropertyElements(openAPI, context, type, typeArgs, schema, beanProperties, mediaTypes, classJavadoc, jsonViewClass);

            String visibilityLevelProp = getConfigProperty(MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL, context);
            var visibilityLevel = VisibilityLevel.PUBLIC;
            if (StringUtils.hasText(visibilityLevelProp)) {
                try {
                    visibilityLevel = VisibilityLevel.valueOf(visibilityLevelProp.toUpperCase());
                } catch (Exception e) {
                    throw new IllegalStateException("Wrong value for visibility level property: " + getConfigProperty(MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL, context));
                }
            }

            var publicFields = new ArrayList();

            for (FieldElement field : classElement.getFields()) {
                if (field.isStatic()) {
                    continue;
                }
                if (visibilityLevel == VisibilityLevel.PUBLIC
                    && !field.isPublic()) {
                    continue;
                } else if (visibilityLevel == VisibilityLevel.PROTECTED
                    && (!field.isPublic() && !field.isProtected())) {
                    continue;
                } else if (visibilityLevel == VisibilityLevel.PACKAGE
                    && (!field.isPublic() && !field.isProtected() && !field.isPackagePrivate())) {
                    continue;
                }
                boolean alreadyProcessed = false;
                for (PropertyElement prop : beanProperties) {
                    if (prop.getName().equals(field.getName())) {
                        alreadyProcessed = true;
                        break;
                    }
                }
                if (alreadyProcessed) {
                    continue;
                }
                publicFields.add(field);
            }

            processPropertyElements(openAPI, context, type, typeArgs, schema, publicFields, mediaTypes, classJavadoc, jsonViewClass);
        }
    }

    private static Schema processSuperTypes(Schema schema,
                                               String schemaName,
                                               ClassElement type, @Nullable Element definingElement,
                                               OpenAPI openAPI,
                                               List mediaTypes,
                                               Map schemas,
                                               VisitorContext context,
                                               @Nullable ClassElement jsonViewClass) {

        if (type == null || type.getName().equals(Object.class.getName())) {
            return null;
        }

        var classElement = type.getType();
        var superTypes = new ArrayList();
        Collection parentInterfaces = classElement.getInterfaces();
        if (classElement.isInterface() && !parentInterfaces.isEmpty()) {
            for (ClassElement parentInterface : parentInterfaces) {
                if (ClassUtils.isJavaLangType(parentInterface.getName())
                    || isProtobufGenerated(parentInterface)
                    || parentInterface.getBeanProperties().isEmpty()) {
                    continue;
                }
                superTypes.add(parentInterface);
            }
        } else {
            var superType = classElement.getSuperType().orElse(null);
            if (superType != null
                && !Enum.class.getName().equals(superType.getName())
                // check protobuf generated classes
                && !isProtobufMessageClass(superType)) {
                superTypes.add(superType);
            }
        }
        if (!type.isRecord() && !superTypes.isEmpty()) {
            // skip if it is Enum or Object super class
            String firstSuperTypeName = superTypes.get(0).getName();
            if (superTypes.size() == 1
                && (firstSuperTypeName.equals(Enum.class.getName()) || firstSuperTypeName.equals(Object.class.getName()))) {
                if (schema != null) {
                    schema.setName(schemaName);
                    schemas.put(schemaName, schema);
                }

                return null;
            }

            if (schema == null) {
                schema = setSpecVersion(new ComposedSchema());
                schema.setType(TYPE_OBJECT);
            }
            for (ClassElement sType : superTypes) {
                Map sTypeArgs = sType.getTypeArguments();
                ClassElement customStype = getCustomSchema(sType.getName(), sTypeArgs, context);
                if (customStype != null) {
                    sType = customStype;
                }
                readAllInterfaces(openAPI, context, definingElement, mediaTypes, schema, sType, schemas, sTypeArgs, jsonViewClass);
            }
        } else {
            if (schema == null) {
                schema = setSpecVersion(new Schema<>());
                schema.setType(TYPE_OBJECT);
            }
        }

        schema.setName(schemaName);
        schemas.put(schemaName, schema);

        return schema;
    }

    @SuppressWarnings("java:S3655") // false positive
    private static void readAllInterfaces(OpenAPI openAPI, VisitorContext context, @Nullable Element definingElement, List mediaTypes,
                                          Schema schema, ClassElement superType, Map schemas, Map superTypeArgs,
                                          @Nullable ClassElement jsonViewClass) {
        String parentSchemaName = computeDefaultSchemaName(superType.stringValue(io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME).orElse(null),
            definingElement, superType, superTypeArgs, context, jsonViewClass);

        if (schemas.get(parentSchemaName) != null
            || getSchemaDefinition(openAPI, context, superType, superTypeArgs, null, mediaTypes, jsonViewClass) != null) {
            var parentSchema = setSpecVersion(new Schema<>());
            parentSchema.set$ref(SchemaUtils.schemaRef(parentSchemaName));
            if (schema.getAllOf() == null || !schema.getAllOf().contains(parentSchema)) {
                schema.addAllOfItem(parentSchema);
            }
        }
        if (superType.isInterface()) {
            for (var interfaceEl : superType.getInterfaces()) {
                if (ClassUtils.isJavaLangType(interfaceEl.getName())
                    || interfaceEl.getBeanProperties().isEmpty()) {
                    continue;
                }

                Map interfaceTypeArgs = interfaceEl.getTypeArguments();
                ClassElement customInterfaceType = getCustomSchema(interfaceEl.getName(), interfaceTypeArgs, context);
                if (customInterfaceType != null) {
                    interfaceEl = customInterfaceType;
                }

                readAllInterfaces(openAPI, context, definingElement, mediaTypes, schema, interfaceEl, schemas, interfaceTypeArgs, jsonViewClass);
            }
        } else if (superType.getSuperType().isPresent()) {
            ClassElement superSuperType = superType.getSuperType().get();
            Map superSuperTypeArgs = superSuperType.getTypeArguments();
            ClassElement customSuperSuperType = getCustomSchema(superSuperType.getName(), superSuperTypeArgs, context);
            if (customSuperSuperType != null) {
                superSuperType = customSuperSuperType;
            }
            readAllInterfaces(openAPI, context, definingElement, mediaTypes, schema, superSuperType, schemas, superSuperTypeArgs, jsonViewClass);
        }
    }

    private static void checkAllOf(Schema composedSchema) {
        if (composedSchema == null || CollectionUtils.isEmpty(composedSchema.getAllOf()) || CollectionUtils.isEmpty(composedSchema.getProperties())) {
            return;
        }
        if (composedSchema.getType() == null) {
            composedSchema.setType(TYPE_OBJECT);
        }
        // put all properties as siblings of allOf
        var propSchema = setSpecVersion(new Schema<>());
        propSchema.properties(composedSchema.getProperties());
        propSchema.setDescription(composedSchema.getDescription());
        propSchema.setRequired(composedSchema.getRequired());
        propSchema.setType(null);
        composedSchema.setProperties(null);
        composedSchema.setDescription(null);
        composedSchema.setRequired(null);
        composedSchema.addAllOfItem(propSchema);
    }

    private static void processJacksonDescription(@Nullable Element element, @Nullable Schema schemaToBind) {
        if (element == null || schemaToBind == null || StringUtils.isNotEmpty(schemaToBind.getDescription())) {
            return;
        }
        findAnnotation(element, element instanceof ClassElement
            ? "com.fasterxml.jackson.annotation.JsonClassDescription"
            : "com.fasterxml.jackson.annotation.JsonPropertyDescription"
        )
            .flatMap(ann -> ann.stringValue(PROP_VALUE))
            .ifPresent(schemaToBind::setDescription);
    }

    private static void setSchemaDescription(Element element, Schema schemaToBind) {
        if (StringUtils.isNotEmpty(schemaToBind.getDescription())) {
            return;
        }

        processJacksonDescription(element, schemaToBind);
        if (StringUtils.isNotEmpty(schemaToBind.getDescription())) {
            return;
        }

        // First, find getter method javadoc
        String doc = element.getDocumentation().orElse(null);
        if (StringUtils.isEmpty(doc)) {
            // next, find field javadoc
            if (element instanceof MemberElement memberEl) {
                List fields = memberEl.getDeclaringType().getFields();
                if (CollectionUtils.isNotEmpty(fields)) {
                    for (FieldElement field : fields) {
                        if (field.getName().equals(element.getName())) {
                            doc = field.getDocumentation().orElse(null);
                            break;
                        }
                    }
                }
            }
        }
        if (doc != null) {
            JavadocDescription desc = Utils.getJavadocParser().parse(doc);
            if (StringUtils.hasText(desc.getMethodDescription())) {
                schemaToBind.setDescription(desc.getMethodDescription());
            }
        }
    }

    private static void processSchemaAnn(Schema schemaToBind, VisitorContext context, Element element,
                                         @Nullable ClassElement classEl,
                                         @Nullable AnnotationValue schemaAnn) {

        if (schemaAnn == null) {
            return;
        }
        Map annValues = schemaAnn.getValues();
        if (annValues.containsKey(PROP_NAME)) {
            schemaToBind.setName((String) annValues.get(PROP_NAME));
        }
        if (annValues.containsKey(PROP_TITLE)) {
            schemaToBind.setTitle((String) annValues.get(PROP_TITLE));
        }
        if (annValues.containsKey(PROP_REF)) {
            schemaToBind.set$ref((String) annValues.get(PROP_REF));
        }
        var schemaMultipleOf = (Double) annValues.get("multipleOf");
        if (schemaMultipleOf != null) {
            schemaToBind.setMultipleOf(BigDecimal.valueOf(schemaMultipleOf));
        }
        var schemaMaximum = (String) annValues.get("maximum");
        if (NumberUtils.isCreatable(schemaMaximum)) {
            schemaToBind.setMaximum(new BigDecimal(schemaMaximum));
        }
        if (!isOpenapi31()) {
            var schemaExclusiveMaximum = (Boolean) annValues.get("exclusiveMaximum");
            if (schemaExclusiveMaximum != null && schemaExclusiveMaximum) {
                schemaToBind.setExclusiveMaximum(true);
            }
        }
        var schemaMinimum = (String) annValues.get("minimum");
        if (NumberUtils.isCreatable(schemaMinimum)) {
            schemaToBind.setMinimum(new BigDecimal(schemaMinimum));
        }
        if (!isOpenapi31()) {
            var schemaExclusiveMinimum = (Boolean) annValues.get("exclusiveMinimum");
            if (schemaExclusiveMinimum != null && schemaExclusiveMinimum) {
                schemaToBind.setExclusiveMinimum(true);
            }
        }
        if (annValues.containsKey("maxLength")) {
            schemaToBind.setMaxLength((Integer) annValues.get("maxLength"));
        }
        if (annValues.containsKey("minLength")) {
            schemaToBind.setMinLength((Integer) annValues.get("minLength"));
        }
        if (annValues.containsKey("pattern")) {
            schemaToBind.setPattern((String) annValues.get("pattern"));
        }
        if (annValues.containsKey("maxProperties")) {
            schemaToBind.setMaxProperties((Integer) annValues.get("maxProperties"));
        }
        if (annValues.containsKey("minProperties")) {
            schemaToBind.setMinProperties((Integer) annValues.get("minProperties"));
        }
        if (annValues.containsKey(PROP_REQUIRED_PROPERTIES)) {
            var requiredProperties = (String[]) annValues.get(PROP_REQUIRED_PROPERTIES);
            schemaToBind.setRequired(new ArrayList<>(Arrays.asList(requiredProperties)));
        }
        if (annValues.containsKey(PROP_DESCRIPTION)) {
            schemaToBind.setDescription((String) annValues.get(PROP_DESCRIPTION));
        }
        String format = null;
        if (annValues.containsKey(PROP_ONE_FORMAT)) {
            format = (String) annValues.get(PROP_ONE_FORMAT);
            schemaToBind.setFormat(format);
        }
        if (annValues.containsKey(PROP_NULLABLE)) {
            if (!(element instanceof MemberElement)) {
                SchemaUtils.setNullable(schemaToBind);
            }
        }
        var accessModeStr = (String) annValues.get(PROP_ACCESS_MODE);
        if (StringUtils.isNotEmpty(accessModeStr)) {
            var schemaAccessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.valueOf(accessModeStr);
            if (schemaAccessMode != io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO) {
                if (schemaAccessMode == io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY) {
                    schemaToBind.setReadOnly(true);
                    schemaToBind.setWriteOnly(null);
                } else if (schemaAccessMode == io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY) {
                    schemaToBind.setReadOnly(null);
                    schemaToBind.setWriteOnly(true);
                } else if (schemaAccessMode == io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE) {
                    schemaToBind.setReadOnly(null);
                    schemaToBind.setWriteOnly(null);
                }
            }
        }
        var schemaExtDocs = (AnnotationValue) annValues.get(PROP_EXTERNAL_DOCS);
        ExternalDocumentation externalDocs = null;
        if (schemaExtDocs != null) {
            externalDocs = toValue(schemaExtDocs.getValues(), context, ExternalDocumentation.class, null);
        }
        if (externalDocs != null) {
            schemaToBind.setExternalDocs(externalDocs);
        }
        var schemaDeprecated = (Boolean) annValues.get(PROP_DEPRECATED);
        if (schemaDeprecated != null && schemaDeprecated) {
            schemaToBind.setDeprecated(true);
        }
        String type = null;
        if (annValues.containsKey(PROP_TYPE)) {
            type = (String) annValues.get(PROP_TYPE);
            schemaToBind.setType(type);
        }

        Pair typeAndFormat = null;
        if (element instanceof ClassElement classElement) {
            if (classElement.isIterable()) {
                typeAndFormat = Pair.of(TYPE_ARRAY, null);
            } else if (element instanceof EnumElement enumEl && isEnum(enumEl)) {
                typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null);
            } else {
                typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classElement.getName(), classElement.isArray(), classElement);
            }
        }
        var elType = type != null ? type : typeAndFormat != null ? typeAndFormat.getFirst() : null;
        var elFormat = format != null ? format : typeAndFormat != null ? typeAndFormat.getSecond() : null;

        var allowableValues = schemaAnn.stringValues(PROP_ALLOWABLE_VALUES);
        setAllowableValues(schemaToBind, allowableValues, element, elType, elFormat, context);

        if (isOpenapi31()) {
            var schemaExamples = (String[]) annValues.get(PROP_EXAMPLES);
            if (ArrayUtils.isNotEmpty(schemaExamples)) {
                for (var schemaExample : schemaExamples) {
                    // need to set placeholders to set correct values and types to example field
                    schemaExample = replacePlaceholders(schemaExample, context);
                    schemaToBind.addExample(ConvertUtils.parseByTypeAndFormat(schemaExample, elType, elFormat, context, false));
                }
            }
        } else {
            var schemaExample = (String) annValues.get(PROP_EXAMPLE);
            if (StringUtils.isNotEmpty(schemaExample)) {
                // need to set placeholders to set correct values and types to example field
                schemaExample = replacePlaceholders(schemaExample, context);
                schemaToBind.setExample(ConvertUtils.parseByTypeAndFormat(schemaExample, elType, elFormat, context, false));
            }
        }

        var schemaDefaultValue = (String) annValues.get(PROP_DEFAULT_VALUE);
        if (schemaDefaultValue != null) {
            setDefaultValueObject(schemaToBind, schemaDefaultValue, classEl, schemaToBind.getType(), schemaToBind.getFormat(), false, context);
        }
        if (annValues.containsKey(PROP_DISCRIMINATOR_PROPERTY)) {
            var discriminator = new Discriminator();
            schemaToBind.setDiscriminator(discriminator);
            discriminator.setPropertyName(annValues.get(PROP_DISCRIMINATOR_PROPERTY).toString());

            if (annValues.containsKey(PROP_DISCRIMINATOR_MAPPING)) {
                var discriminatorMapping = (AnnotationValue[]) annValues.get(PROP_DISCRIMINATOR_MAPPING);
                var extensions = new HashMap();
                var mappings = new HashMap();
                for (var discriminatorMappingAnn : discriminatorMapping) {
                    final Map valueMap = resolveAnnotationValues(context, discriminatorMappingAnn, null);
                    mappings.put(valueMap.get(PROP_VALUE).toString(), valueMap.get(PROP_REF_DOLLAR).toString());
                    var extValue = (Map) valueMap.get(PROP_EXTENSIONS);
                    if (extValue != null) {
                        extensions.putAll(extValue);
                    }
                }
                discriminator.setMapping(mappings);
                if (CollectionUtils.isNotEmpty(extensions)) {
                    extensions.forEach(discriminator::addExtension);
                }
            }
        }

        if (annValues.containsKey(PROP_EXTENSIONS)) {
            var extensionAnns = (AnnotationValue[]) annValues.get(PROP_EXTENSIONS);
            var extensions = new HashMap();
            for (var extensionAnn : extensionAnns) {
                processExtensions(extensions, extensionAnn);
            }
            if (!extensions.isEmpty()) {
                extensions.forEach(schemaToBind::addExtension);
            }
        }

        var addProps = (String) annValues.get(PROP_ADDITIONAL_PROPERTIES);
        if (StringUtils.isNotEmpty(addProps)) {
            var schemaAdditionalProperties = io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.valueOf(addProps);
            if (schemaAdditionalProperties == io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.TRUE) {
                schemaToBind.additionalProperties(true);
            } else if (schemaAdditionalProperties == io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.FALSE) {
                schemaToBind.additionalProperties(false);
            }
        }

        if (isOpenapi31()) {

            if (annValues.containsKey("contains")) {
                if (annValues.containsKey("minContains")) {
                    schemaToBind.setMinContains((Integer) annValues.get("minContains"));
                }
                if (annValues.containsKey("maxContains")) {
                    schemaToBind.setMaxContains((Integer) annValues.get("maxContains"));
                }
            }

            if (annValues.containsKey(PROP_ONE_TYPES)) {
                schemaToBind.setTypes(new HashSet<>((Collection) annValues.get(PROP_ONE_TYPES)));
            }
            if (annValues.containsKey("$id")) {
                schemaToBind.set$id((String) annValues.get("$id"));
            }
            if (annValues.containsKey("$schema")) {
                schemaToBind.set$schema((String) annValues.get("$schema"));
            }
            if (annValues.containsKey("$anchor")) {
                schemaToBind.set$anchor((String) annValues.get("$anchor"));
            }
            if (annValues.containsKey("$vocabulary")) {
                schemaToBind.set$vocabulary((String) annValues.get("$vocabulary"));
            }
            if (annValues.containsKey("$dynamicAnchor")) {
                schemaToBind.set$dynamicAnchor((String) annValues.get("$dynamicAnchor"));
            }
            if (annValues.containsKey("contentEncoding")) {
                schemaToBind.setContentEncoding((String) annValues.get("contentEncoding"));
            }
            if (annValues.containsKey("contentMediaType")) {
                schemaToBind.setContentMediaType((String) annValues.get("contentMediaType"));
            }
            if (annValues.containsKey("$comment")) {
                schemaToBind.set$comment((String) annValues.get("$comment"));
            }
            if (annValues.containsKey("_const")) {
                schemaToBind.setConst(annValues.get("_const"));
            }
            String exclusiveMinimum = (String) annValues.get("exclusiveMinimumValue");
            if (NumberUtils.isCreatable(exclusiveMinimum)) {
                schemaToBind.setExclusiveMinimumValue(new BigDecimal(exclusiveMinimum));
            }
            String exclusiveMaximum = (String) annValues.get("exclusiveMaximumValue");
            if (NumberUtils.isCreatable(exclusiveMaximum)) {
                schemaToBind.setExclusiveMaximumValue(new BigDecimal(exclusiveMaximum));
            }
            parseAndSetClassValue("contains", Schema::contains, annValues, schemaToBind, context);
            parseAndSetClassValue("contentSchema", Schema::contentSchema, annValues, schemaToBind, context);
            parseAndSetClassValue("propertyNames", Schema::propertyNames, annValues, schemaToBind, context);
            parseAndSetClassValue("unevaluatedProperties", Schema::unevaluatedProperties, annValues, schemaToBind, context);
            parseAndSetClassValue("additionalItems", Schema::additionalItems, annValues, schemaToBind, context);
            parseAndSetClassValue("unevaluatedItems", Schema::unevaluatedItems, annValues, schemaToBind, context);
            parseAndSetClassValue("_if", Schema::_if, annValues, schemaToBind, context);
            parseAndSetClassValue("_else", Schema::_else, annValues, schemaToBind, context);
            parseAndSetClassValue("then", Schema::then, annValues, schemaToBind, context);
        }

        processClassValues(schemaToBind, annValues, Collections.emptyList(), context, null);
    }

    private static void processClassValues(Schema schemaToBind, Map annValues, List mediaTypes, VisitorContext context, @Nullable ClassElement jsonViewClass) {
        var openApi = Utils.resolveOpenApi(context);
        parseAndSetClassValue(PROP_NOT, Schema::not, annValues, schemaToBind, context);
        processSchemasArray(schemaToBind, openApi, PROP_ALL_OF, annValues, mediaTypes, jsonViewClass, Schema::getAllOf, Schema::addAllOfItem, context);
        processSchemasArray(schemaToBind, openApi, PROP_ANY_OF, annValues, mediaTypes, jsonViewClass, Schema::getAnyOf, Schema::addAnyOfItem, context);
        processSchemasArray(schemaToBind, openApi, PROP_ONE_OF, annValues, mediaTypes, jsonViewClass, Schema::getOneOf, Schema::addOneOfItem, context);
    }

    private static void parseAndSetClassValue(String propName,
                                              BiConsumer setter,
                                              Map annValues, Schema schema,
                                              VisitorContext context) {
        if (annValues.containsKey(propName)) {
            var classEl = ContextUtils.getClassElement(annValues.get(propName).toString(), context);
            var resolvedSchema = resolveSchema(null, classEl, context, Collections.emptyList(), null);
            setter.accept(schema, resolvedSchema);
        }
    }

    private static void processSchemasArray(Schema schemaToBind, OpenAPI openApi,
                                            String propName,
                                            Map annValues,
                                            List mediaTypes,
                                            @Nullable ClassElement jsonViewClass,
                                            Function> getter,
                                            BiConsumer methodAdd,
                                            VisitorContext context) {

        var oneOf = (AnnotationClassValue[]) annValues.get(propName);
        if (ArrayUtils.isEmpty(oneOf)) {
            return;
        }

        for (var classAnn : oneOf) {
            var classEl = ContextUtils.getClassElement(classAnn.getName(), context);
            if (classEl == null) {
                continue;
            }
            Map classElementTypeArgs = classEl.getTypeArguments();
            var customClassEl = getCustomSchema(classEl.getName(), classElementTypeArgs, context);
            if (customClassEl != null) {
                classEl = customClassEl;
            }
            final Schema schema = getSchemaDefinition(openApi, context, classEl, classElementTypeArgs, null, mediaTypes, jsonViewClass);
            if (schema == null) {
                continue;
            }
            if (TYPE_OBJECT.equals(schema.getType())) {
                if (schemaToBind.getType() == null) {
                    schemaToBind.setType(TYPE_OBJECT);
                }
                schema.setType(null);
            }
            var schemas = getter.apply(schemaToBind);
            if (CollectionUtils.isEmpty(schemas) || !schemas.contains(schema)) {
                methodAdd.accept(schemaToBind, schema);
            }
        }
    }

    private static Map annotationValueArrayToSubmap(Object[] a, String classifier, VisitorContext context, @Nullable ClassElement jsonViewClass) {
        var mediaTypes = new LinkedHashMap();
        for (Object o : a) {
            AnnotationValue sv = (AnnotationValue) o;
            String name = sv.stringValue(classifier).orElse(null);
            if (name == null && classifier.equals(PROP_MEDIA_TYPE)) {
                name = MediaType.APPLICATION_JSON;
            }
            if (name != null) {
                Map map = toValueMap(sv.getValues(), context, jsonViewClass);
                mediaTypes.put(name, map);
            }
        }
        return mediaTypes;
    }

    private static Schema getPrimitiveType(ClassElement type, String typeName, VisitorContext context) {
        Schema schema = null;
        Class aClass = ClassUtils.getPrimitiveType(typeName).orElse(null);
        if (aClass == null) {
            aClass = ClassUtils.forName(typeName, SchemaDefinitionUtils.class.getClassLoader()).orElse(null);
        }

        if (aClass != null) {
            Class wrapperType = ReflectionUtils.getWrapperType(aClass);

            var primitiveType = PrimitiveType.fromType(wrapperType);
            if (primitiveType != null) {
                schema = setSpecVersion(primitiveType.createProperty());
            }
        }

        processArgTypeAnnotations(type, schema, context);

        return schema;
    }

    private static Schema processGenericAnnotations(Schema schema, ClassElement componentType, VisitorContext context) {
        if (componentType == null) {
            return schema;
        }
        var primitiveComponentType = getPrimitiveType(componentType, componentType.getName(), context);
        if (primitiveComponentType == null) {
            var schemaFromTypeArgAnnotations = setSpecVersion(new Schema<>());
            processArgTypeAnnotations(componentType, schemaFromTypeArgAnnotations, context);
            if (isEmptySchema(schemaFromTypeArgAnnotations)) {
                return schema;
            }
            var composedSchema = setSpecVersion(new ComposedSchema());
            composedSchema.addAllOfItem(schema);
            composedSchema.addAllOfItem(schemaFromTypeArgAnnotations);
            return composedSchema;
        }

        return schema;
    }

    private static void processArgTypeAnnotations(ClassElement type, @Nullable Schema schema, VisitorContext context) {
        if (schema == null || type == null || type.getAnnotationNames().isEmpty()) {
            return;
        }
        if (isNullable(type) && !isNotNullable(type)) {
            SchemaUtils.setNullable(schema);
        }
        processJakartaValidationAnnotations(type, type, schema, context);
    }

    private static boolean processJacksonPropertyAnn(Element element, ClassElement elType, Schema schemaToBind,
                                                     @Nullable AnnotationValue schemaAnn,
                                                     VisitorContext context) {

        var swaggerAccessMode = schemaAnn != null ? schemaAnn.stringValue(PROP_ACCESS_MODE).orElse(null) : null;
        var swaggerDefaultValue = schemaAnn != null ? schemaAnn.stringValue(PROP_DEFAULT_VALUE).orElse(null) : null;

        var reference = new AtomicReference<>(false);
        findAnnotation(element, "com.fasterxml.jackson.annotation.JsonProperty")
            .ifPresent(ann -> {
                if (swaggerAccessMode == null) {
                    ann.get(PROP_ACCESS, JsonProperty.Access.class).ifPresent(access -> {
                        switch (access) {
                            case READ_ONLY:
                                schemaToBind.setWriteOnly(null);
                                schemaToBind.setReadOnly(true);
                                reference.set(true);
                                break;
                            case WRITE_ONLY:
                                schemaToBind.setWriteOnly(true);
                                schemaToBind.setReadOnly(null);
                                reference.set(true);
                                break;
                            case READ_WRITE:
                                schemaToBind.setWriteOnly(null);
                                schemaToBind.setReadOnly(null);
                                break;
                            default:
                                break;
                        }
                    });
                }
                if (swaggerDefaultValue == null) {
                    ann.stringValue(PROP_DEFAULT_VALUE).ifPresent(defaultValue -> {
                        Pair typeAndFormat;
                        if (elType.isIterable()) {
                            typeAndFormat = Pair.of(TYPE_ARRAY, null);
                        } else if (elType instanceof EnumElement enumEl) {
                            typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null);
                        } else {
                            typeAndFormat = ConvertUtils.getTypeAndFormatByClass(elType.getName(), elType.isArray(), elType);
                        }
                        setDefaultValueObject(schemaToBind, defaultValue, elType, typeAndFormat.getFirst(), typeAndFormat.getSecond(), false, context);
                        if (schemaToBind.getDefault() != null) {
                            reference.set(true);
                        }
                    });
                }
            });

        return reference.get();
    }

    private static void processJakartaValidationAnnotations(Element element, ClassElement elementType, Schema schemaToBind, VisitorContext context) {

        final boolean isIterableOrMap = elementType.isIterable() || elementType.isAssignable(Map.class);

        var messages = new HashMap();

        addValidationAnnMessage(element, "javax.validation.constraints.NotNull$List", NOT_NULL_MESSAGE, messages, context);
        addValidationAnnMessage(element, "jakarta.validation.constraints.NotNull$List", NOT_NULL_MESSAGE, messages, context);

        if (isIterableOrMap) {
            if (isAnnotationPresent(element, "javax.validation.constraints.NotEmpty$List")
                || isAnnotationPresent(element, "jakarta.validation.constraints.NotEmpty$List")) {
                schemaToBind.setMinItems(1);

                addValidationAnnMessage(element, "javax.validation.constraints.NotEmpty$List", SIZE_MESSAGE, messages, context);
                addValidationAnnMessage(element, "jakarta.validation.constraints.NotEmpty$List", SIZE_MESSAGE, messages, context);
            }

            addValidationAnnMessage(element, "javax.validation.constraints.Size$List", SIZE_MESSAGE, messages, context);
            addValidationAnnMessage(element, "jakarta.validation.constraints.Size$List", SIZE_MESSAGE, messages, context);

            findAnnotation(element, "javax.validation.constraints.Size$List")
                .ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
                    .ifPresent(ann -> ann.intValue("min")
                        .ifPresent(schemaToBind::setMinItems)));
            findAnnotation(element, "jakarta.validation.constraints.Size$List")
                .ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
                    .ifPresent(ann -> ann.intValue("min")
                        .ifPresent(schemaToBind::setMinItems)));

            findAnnotation(element, "javax.validation.constraints.Size$List")
                .ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
                    .ifPresent(ann -> ann.intValue("max")
                        .ifPresent(schemaToBind::setMaxItems)));
            findAnnotation(element, "jakarta.validation.constraints.Size$List")
                .ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
                    .ifPresent(ann -> ann.intValue("max")
                        .ifPresent(schemaToBind::setMaxItems)));

        } else {
            if (PrimitiveType.STRING.getCommonName().equals(schemaToBind.getType())) {
                if (isAnnotationPresent(element, "javax.validation.constraints.NotEmpty$List")
                    || isAnnotationPresent(element, "jakarta.validation.constraints.NotEmpty$List")
                    || isAnnotationPresent(element, "javax.validation.constraints.NotBlank$List")
                    || isAnnotationPresent(element, "jakarta.validation.constraints.NotBlank$List")) {
                    schemaToBind.setMinLength(1);

                    addValidationAnnMessage(element, "javax.validation.constraints.NotEmpty$List", SIZE_MESSAGE, messages, context);
                    addValidationAnnMessage(element, "jakarta.validation.constraints.NotEmpty$List", SIZE_MESSAGE, messages, context);

                    addValidationAnnMessage(element, "javax.validation.constraints.NotBlank$List", SIZE_MESSAGE, messages, context);
                    addValidationAnnMessage(element, "jakarta.validation.constraints.NotBlank$List", SIZE_MESSAGE, messages, context);
                }

                findAnnotation(element, "javax.validation.constraints.Size$List")
                    .ifPresent(listAnn -> {
                        for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                            ann.intValue("min").ifPresent(schemaToBind::setMinLength);
                            ann.intValue("max").ifPresent(schemaToBind::setMaxLength);
                        }
                    });
                findAnnotation(element, "jakarta.validation.constraints.Size$List")
                    .ifPresent(listAnn -> {
                        for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                            ann.intValue("min").ifPresent(schemaToBind::setMinLength);
                            ann.intValue("max").ifPresent(schemaToBind::setMaxLength);
                        }
                    });

                addValidationAnnMessage(element, "javax.validation.constraints.Size$List", SIZE_MESSAGE, messages, context);
                addValidationAnnMessage(element, "jakarta.validation.constraints.Size$List", SIZE_MESSAGE, messages, context);
            }

            if (isAnnotationPresent(element, "javax.validation.constraints.Negative$List")
                || isAnnotationPresent(element, "jakarta.validation.constraints.Negative$List")) {
                schemaToBind.setMaximum(BigDecimal.ZERO);
                schemaToBind.exclusiveMaximum(true);

                addValidationAnnMessage(element, "javax.validation.constraints.Negative$List", MAX_MESSAGE, messages, context);
                addValidationAnnMessage(element, "jakarta.validation.constraints.Negative$List", MAX_MESSAGE, messages, context);
            }
            if (isAnnotationPresent(element, "javax.validation.constraints.NegativeOrZero$List")
                || isAnnotationPresent(element, "jakarta.validation.constraints.NegativeOrZero$List")) {
                schemaToBind.setMaximum(BigDecimal.ZERO);

                addValidationAnnMessage(element, "javax.validation.constraints.NegativeOrZero$List", MAX_MESSAGE, messages, context);
                addValidationAnnMessage(element, "jakarta.validation.constraints.NegativeOrZero$List", MAX_MESSAGE, messages, context);
            }
            if (isAnnotationPresent(element, "javax.validation.constraints.Positive$List")
                || isAnnotationPresent(element, "jakarta.validation.constraints.Positive$List")) {
                schemaToBind.setMinimum(BigDecimal.ZERO);
                schemaToBind.exclusiveMinimum(true);

                addValidationAnnMessage(element, "javax.validation.constraints.Positive$List", MIN_MESSAGE, messages, context);
                addValidationAnnMessage(element, "jakarta.validation.constraints.Positive$List", MIN_MESSAGE, messages, context);
            }
            if (isAnnotationPresent(element, "javax.validation.constraints.PositiveOrZero$List")
                || isAnnotationPresent(element, "jakarta.validation.constraints.PositiveOrZero$List")) {
                schemaToBind.setMinimum(BigDecimal.ZERO);

                addValidationAnnMessage(element, "javax.validation.constraints.PositiveOrZero$List", MIN_MESSAGE, messages, context);
                addValidationAnnMessage(element, "jakarta.validation.constraints.PositiveOrZero$List", MIN_MESSAGE, messages, context);
            }

            findAnnotation(element, "javax.validation.constraints.Min$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMinimum);
                    }
                });
            findAnnotation(element, "jakarta.validation.constraints.Min$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMinimum);
                    }
                });
            addValidationAnnMessage(element, "javax.validation.constraints.Min$List", MIN_MESSAGE, messages, context);
            addValidationAnnMessage(element, "jakarta.validation.constraints.Min$List", MIN_MESSAGE, messages, context);

            findAnnotation(element, "javax.validation.constraints.Max$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMaximum);
                    }
                });
            findAnnotation(element, "jakarta.validation.constraints.Max$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMaximum);
                    }
                });
            addValidationAnnMessage(element, "javax.validation.constraints.Max$List", MAX_MESSAGE, messages, context);
            addValidationAnnMessage(element, "jakarta.validation.constraints.Max$List", MAX_MESSAGE, messages, context);

            findAnnotation(element, "javax.validation.constraints.DecimalMin$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMinimum);
                    }
                });
            findAnnotation(element, "jakarta.validation.constraints.DecimalMin$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMinimum);
                    }
                });
            addValidationAnnMessage(element, "javax.validation.constraints.DecimalMin$List", MIN_MESSAGE, messages, context);
            addValidationAnnMessage(element, "jakarta.validation.constraints.DecimalMin$List", MIN_MESSAGE, messages, context);

            findAnnotation(element, "javax.validation.constraints.DecimalMax$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMaximum);
                    }
                });
            findAnnotation(element, "jakarta.validation.constraints.DecimalMax$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.getValue(BigDecimal.class).ifPresent(schemaToBind::setMaximum);
                    }
                });
            addValidationAnnMessage(element, "javax.validation.constraints.DecimalMax$List", MAX_MESSAGE, messages, context);
            addValidationAnnMessage(element, "jakarta.validation.constraints.DecimalMax$List", MAX_MESSAGE, messages, context);

            findAnnotation(element, "javax.validation.constraints.Email$List")
                .ifPresent(listAnn -> {
                    schemaToBind.setFormat(PrimitiveType.EMAIL.getCommonName());
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.stringValue("regexp").ifPresent(schemaToBind::setPattern);
                    }
                });
            findAnnotation(element, "jakarta.validation.constraints.Email$List")
                .ifPresent(listAnn -> {
                    schemaToBind.setFormat(PrimitiveType.EMAIL.getCommonName());
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.stringValue("regexp").ifPresent(schemaToBind::setPattern);
                    }
                });

            findAnnotation(element, "javax.validation.constraints.Pattern$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.stringValue("regexp").ifPresent(schemaToBind::setPattern);
                    }
                });
            findAnnotation(element, "jakarta.validation.constraints.Pattern$List")
                .ifPresent(listAnn -> {
                    for (AnnotationValue ann : listAnn.getAnnotations(PROP_VALUE)) {
                        ann.stringValue("regexp").ifPresent(schemaToBind::setPattern);
                    }
                });

            addValidationAnnMessage(element, "javax.validation.constraints.Email$List", PATTERN_MESSAGE, messages, context);
            addValidationAnnMessage(element, "jakarta.validation.constraints.Email$List", PATTERN_MESSAGE, messages, context);
            addValidationAnnMessage(element, "javax.validation.constraints.Pattern$List", PATTERN_MESSAGE, messages, context);
            addValidationAnnMessage(element, "jakarta.validation.constraints.Pattern$List", PATTERN_MESSAGE, messages, context);

            element.getValue("io.micronaut.http.annotation.Part", String.class)
                .ifPresent(schemaToBind::setName);

        }
        addValidationMessages(element, schemaToBind, messages, context);
    }

    private static void addValidationAnnMessage(Element element, String annName, String extName, Map messages, VisitorContext context) {
        if (!ConfigUtils.isGeneratorExtensionsEnabled(context)) {
            return;
        }

        findAnnotation(element, annName)
            .ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
                .ifPresent(ann -> ann.stringValue("message")
                    .ifPresent(message -> {
                        var strMessage = message != null ? message.toString() : null;
                        if (StringUtils.isEmpty(strMessage)) {
                            return;
                        }
                        messages.put(extName, strMessage);
                    })));
    }

    private static Schema processMapSchema(ClassElement type, Map typeArgs,
                                              List mediaTypes,
                                              OpenAPI openApi, ClassElement jsonViewClass,
                                              JavadocDescription classJavadoc,
                                              VisitorContext context) {
        var schema = setSpecVersion(new Schema<>());
        if (CollectionUtils.isEmpty(typeArgs)) {
            schema.setAdditionalProperties(true);
            return schema;
        }
        // Case, when map key is enumeration
        ClassElement keyType = typeArgs.get("K");
        ClassElement valueType = typeArgs.get("V");
        if (isEnum(keyType)) {
            var enumSchema = getSchemaDefinition(openApi, context, keyType, keyType.getTypeArguments(), null, mediaTypes, null);
            if (enumSchema != null && enumSchema.get$ref() != null) {
                enumSchema = getSchemaByRef(enumSchema, openApi);
                var values = enumSchema.getEnum();
                if (CollectionUtils.isNotEmpty(values)) {
                    var valueSchema = getSchemaDefinition(openApi, context, valueType, valueType.getTypeArguments(), null, mediaTypes, jsonViewClass);
                    for (var value : values) {
                        schema.addProperty(value.toString(), valueSchema);
                    }
                }
                return schema;
            }
        }
        if (valueType.getName().equals(Object.class.getName())) {
            schema.setAdditionalProperties(true);
        } else {
            schema.setAdditionalProperties(resolveSchema(openApi, type, valueType, context, mediaTypes, jsonViewClass, null, classJavadoc));
        }
        return schema;
    }

    @SuppressWarnings("java:S3776")
    private static void processPropertyElements(OpenAPI openAPI, VisitorContext context, Element type, Map typeArgs, Schema schema,
                                                List publicFields, List mediaTypes, JavadocDescription classJavadoc,
                                                @Nullable ClassElement jsonViewClass) {

        ClassElement classElement = null;
        if (type instanceof ClassElement classEl) {
            classElement = classEl;
        } else if (type instanceof TypedElement typedEl) {
            classElement = typedEl.getType();
        }

        boolean withJsonView = jsonViewClass != null;
        String[] classLvlJsonViewClasses = null;
        if (withJsonView && classElement != null) {
            classLvlJsonViewClasses = classElement.getAnnotationMetadata().stringValues(JsonView.class);
        }

        for (TypedElement publicField : publicFields) {
            if (isHiddenElement(publicField)) {
                continue;
            }

            var isGetterOverridden = false;
            JavadocDescription fieldJavadoc = null;
            if (classElement != null) {
                for (FieldElement field : classElement.getFields()) {
                    if (field.getName().equals(publicField.getName())) {
                        fieldJavadoc = Utils.getJavadocParser().parse(publicField.getDocumentation().orElse(field.getDocumentation().orElse(null)));
                        break;
                    }
                }

                // checking if the getter is overridden and has javadoc and other annotations
                if (publicField instanceof PropertyElement propertyEl) {
                    var readerMethod = propertyEl.getReadMethod().orElse(null);
                    if (readerMethod != null) {
                        var methods = classElement.getEnclosedElements(ElementQuery.ALL_METHODS.includeOverriddenMethods());
                        for (var method : methods) {
                            if (readerMethod.overrides(method)) {
                                isGetterOverridden = CollectionUtils.isNotEmpty(readerMethod.getAnnotationNames()) || fieldJavadoc != null;
                                break;
                            }
                        }
                    }
                }
            }

            if (publicField instanceof MemberElement memberEl && (memberEl.getDeclaringType().getType().getName().equals(type.getName()) || isGetterOverridden)) {

                if (withJsonView && !allowedByJsonView(publicField, classLvlJsonViewClasses, jsonViewClass, context)) {
                    continue;
                }
                ClassElement fieldType = publicField.getGenericType();

                Schema propertySchema = resolveSchema(openAPI, publicField, fieldType, context, mediaTypes, jsonViewClass, fieldJavadoc, classJavadoc);

                processSchemaProperty(
                    context,
                    publicField,
                    fieldType,
                    classElement,
                    schema,
                    propertySchema
                );
            }
        }
    }

    private static boolean isHiddenElement(TypedElement elementType) {
        boolean isHidden = getAnnotationMetadata(elementType)
            .booleanValue(io.swagger.v3.oas.annotations.media.Schema.class, PROP_HIDDEN).orElse(false);
        var jsonAnySetterAnn = getAnnotation(elementType, JsonAnySetter.class);
        return elementType.getType().isAssignable(Class.class)
            || isAnnotationPresent(elementType, JsonIgnore.class)
            || isAnnotationPresent(elementType, JsonBackReference.class)
            || isAnnotationPresent(elementType, Hidden.class)
            || (jsonAnySetterAnn != null && jsonAnySetterAnn.booleanValue("enabled").orElse(true))
            || isHidden;
    }

    private static boolean allowedByJsonView(TypedElement publicField, String[] classLvlJsonViewClasses, ClassElement jsonViewClassEl, VisitorContext context) {
        String[] fieldJsonViewClasses = getAnnotationMetadata(publicField).stringValues(JsonView.class);
        if (ArrayUtils.isEmpty(fieldJsonViewClasses)) {
            fieldJsonViewClasses = classLvlJsonViewClasses;
        }
        if (ArrayUtils.isEmpty(fieldJsonViewClasses)) {
            return isJsonViewDefaultInclusion(context);
        }

        for (String fieldJsonViewClass : fieldJsonViewClasses) {
            var classEl = ContextUtils.getClassElement(fieldJsonViewClass, context);
            if (classEl != null && jsonViewClassEl.isAssignable(classEl)) {
                return true;
            }
        }

        return false;
    }

    private static Schema schemaFromAnnotation(VisitorContext context, Element element, ClassElement type, AnnotationValue schemaAnn) {
        if (schemaAnn == null) {
            return null;
        }

        var schemaToBind = setSpecVersion(new Schema<>());
        processSchemaAnn(schemaToBind, context, element, type, schemaAnn);

        return schemaToBind;
    }

    private static Schema doBindSchemaAnnotationValue(VisitorContext context, TypedElement element, Schema schemaToBind,
                                                         JsonNode schemaJson, String elType, String elFormat,
                                                         AnnotationValue schemaAnn,
                                                         @Nullable ClassElement jsonViewClass) {

        if (schemaAnn != null && schemaAnn.annotationClassValue(PROP_IMPLEMENTATION).isEmpty()) {
            var resolvedSchema = resolveSchema(element, element != null ? element.getType() : null, context, List.of(), null);
            schemaToBind = appendSchema(schemaToBind, resolvedSchema);
        }

        // need to set placeholders to set correct values and types to example field
        schemaJson = resolvePlaceholders(schemaJson, s -> expandProperties(s, getExpandableProperties(context), context));
        try {
            Schema updatedSchema = Utils.getJsonMapper().readerForUpdating(schemaToBind).readValue(schemaJson);
            if (updatedSchema != null) {
                schemaToBind = updatedSchema;
            } else {
                warn("Error reading Swagger Schema for element [" + element + "]: " + schemaJson, context, element);
            }
        } catch (IOException e) {
            warn("Error reading Swagger Schema for element [" + element + "]: " + e.getMessage(), context, element);
        }
        // fix for example = "null"
        if (schemaToBind.getExample() == null
            && !schemaToBind.getExampleSetFlag()
            && schemaJson.get(PROP_EXAMPLE_SET_FLAG) != null) {
            schemaToBind.setExampleSetFlag(true);
        }

        String defaultValue = null;
        String[] allowableValues = null;
        if (schemaAnn != null) {
            defaultValue = schemaAnn.stringValue(PROP_DEFAULT_VALUE).orElse(null);
            allowableValues = schemaAnn.stringValues(PROP_ALLOWABLE_VALUES);
            Map annValues = schemaAnn.getValues();
            Map valueMap = toValueMap(annValues, context, jsonViewClass);
            bindSchemaIfNecessary(context, schemaAnn, valueMap, jsonViewClass);
            processClassValues(schemaToBind, annValues, Collections.emptyList(), context, jsonViewClass);
        }

        if (elType == null && element != null) {
            ClassElement typeEl = element.getType();
            Pair typeAndFormat;
            if (typeEl instanceof EnumElement enumEl && isEnum(enumEl)) {
                typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat);
            } else {
                typeAndFormat = ConvertUtils.getTypeAndFormatByClass(typeEl.getName(), typeEl.isArray(), typeEl);
            }
            elType = typeAndFormat.getFirst();
            if (elFormat == null) {
                elFormat = typeAndFormat.getSecond();
            }
        }

        if (StringUtils.isNotEmpty(defaultValue)) {
            setDefaultValueObject(schemaToBind, defaultValue, element, elType, elFormat, false, context);
        }
        setAllowableValues(schemaToBind, allowableValues, element, elType, elFormat, context);

        return schemaToBind;
    }

    private static void bindSchemaIfNecessary(VisitorContext context, AnnotationValue av, Map valueMap, @Nullable ClassElement jsonViewClass) {
        final Optional impl = av.stringValue(PROP_IMPLEMENTATION);
        final Optional not = av.stringValue(PROP_NOT);
        final Optional schema = av.stringValue(PROP_SCHEMA);
        var anyOfList = av.stringValues(PROP_ANY_OF);
        var oneOfList = av.stringValues(PROP_ONE_OF);
        var allOfList = av.stringValues(PROP_ALL_OF);
        // remap keys.
        Object o = valueMap.remove(PROP_DEFAULT_VALUE);
        if (o != null) {
            valueMap.put(PROP_DEFAULT, o);
        }
        o = valueMap.remove(PROP_ALLOWABLE_VALUES);
        if (o != null) {
            valueMap.put(PROP_ENUM, o);
        }
        boolean isSchema = io.swagger.v3.oas.annotations.media.Schema.class.getName().equals(av.getAnnotationName());
        if (isSchema) {
            if (impl.isPresent()) {
                final String className = impl.get();
                bindSchemaForClassName(context, valueMap, className, jsonViewClass);
            }
            if (not.isPresent()) {
                final Schema schemaNot = resolveSchema(null, ContextUtils.getClassElement(not.get(), context), context, Collections.emptyList(), jsonViewClass);
                var schemaMap = new HashMap();
                schemaToValueMap(schemaMap, schemaNot);
                valueMap.put(PROP_NOT, schemaMap);
            }
            if (anyOfList.length > 0) {
                bindSchemaForComposite(context, valueMap, anyOfList, PROP_ANY_OF, jsonViewClass);
            }
            if (oneOfList.length > 0) {
                bindSchemaForComposite(context, valueMap, oneOfList, PROP_ONE_OF, jsonViewClass);
            }
            if (allOfList.length > 0) {
                bindSchemaForComposite(context, valueMap, allOfList, PROP_ALL_OF, jsonViewClass);
            }
        }
        if (DiscriminatorMapping.class.getName().equals(av.getAnnotationName()) && schema.isPresent()) {
            final String className = schema.get();
            bindSchemaForClassName(context, valueMap, className, jsonViewClass);
        }
    }

    private static void bindSchemaForComposite(VisitorContext context, Map valueMap, String[] classNames, String key, @Nullable ClassElement jsonViewClass) {
        var namesToSchemas = new ArrayList>();
        for (var className : classNames) {
            ClassElement classEl = ContextUtils.getClassElement(className, context);
            var schemaMap = new HashMap();
            if (classEl != null) {
                final Schema schema = resolveSchema(null, classEl, context, Collections.emptyList(), jsonViewClass);
                schemaToValueMap(schemaMap, schema);
            }
            namesToSchemas.add(schemaMap);
        }
        valueMap.put(key, namesToSchemas);
    }

    private static void bindSchemaForClassName(VisitorContext context, Map valueMap, String className, @Nullable ClassElement jsonViewClass) {
        ClassElement classEl = ContextUtils.getClassElement(className, context);
        if (classEl != null) {
            final Schema schema = resolveSchema(null, classEl, context, Collections.emptyList(), jsonViewClass);
            schemaToValueMap(valueMap, schema);
        }
    }

    private static void schemaToValueMap(Map valueMap, Schema schema) {
        if (schema == null) {
            return;
        }
        var beanMap = BeanMap.of(schema);
        for (var entry : beanMap.entrySet()) {
            var value = entry.getValue();
            if (value == null) {
                continue;
            }
            if (entry.getKey().equals("specVersion")) {
                continue;
            }
            if (entry.getKey().equals(PROP_EXAMPLE_SET_FLAG) && StringUtils.FALSE.equals(value.toString())) {
                continue;
            }
            valueMap.put(entry.getKey(), value);
        }
        if (schema.get$ref() != null) {
            valueMap.put(PROP_REF_DOLLAR, schema.get$ref());
        }
    }

    private static String computeNameWithGenerics(ClassElement classElement, Map typeArgs, VisitorContext context, boolean isProtobufGenerated) {
        var className = classElement.getSimpleName();
        if (isProtobufGenerated) {
            className = normalizeProtobufClassName(className);
        }
        var builder = new StringBuilder(className);
        computeNameWithGenerics(classElement, builder, new HashSet<>(), typeArgs, context, isProtobufGenerated);
        return builder.toString();
    }

    private static void computeNameWithGenerics(ClassElement classElement, StringBuilder builder, Set computed, Map typeArgs, VisitorContext context, boolean isProtobufGenerated) {

        var genericSeparator = getGenericSeparator(context);
        var innerClassSeparator = getInnerClassSeparator(context);

        computed.add(classElement.getName());
        final Iterator i = typeArgs.values().iterator();
        if (i.hasNext()) {

            builder.append(genericSeparator);
            while (i.hasNext()) {
                ClassElement ce = i.next();
                if (ClassUtils.isJavaLangType(ce.getName())) {
                    var typeArgAnnotations = ce.getAnnotationNames();
                    if (CollectionUtils.isNotEmpty(typeArgAnnotations)) {
                        for (var typeArgAnnName : typeArgAnnotations) {
                            var annValue = ce.getAnnotation(typeArgAnnName);
                            builder.append(addTypeArgsAnnotations(null, typeArgAnnName.endsWith("$List") ? annValue.getValues().get(PROP_VALUE) : annValue, context));
                        }
                    }
                }
                var className = ce.getSimpleName();
                if (isProtobufGenerated) {
                    className = normalizeProtobufClassName(className);
                }
                builder.append(className);
                Map ceTypeArgs = ce.getTypeArguments();
                ClassElement customElement = getCustomSchema(ce.getName(), ceTypeArgs, context);
                if (customElement != null) {
                    ce = customElement;
                }
                if (!computed.contains(ce.getName())) {
                    computeNameWithGenerics(ce, builder, computed, ceTypeArgs, context, isProtobufGenerated);
                } else if (CollectionUtils.isNotEmpty(ceTypeArgs)) {
                    ce = ceTypeArgs.values().iterator().next();
                    className = ce.getSimpleName();
                    if (isProtobufGenerated) {
                        className = normalizeProtobufClassName(className);
                    }
                    builder.append(genericSeparator).append(className).append(genericSeparator);
                }
                if (i.hasNext()) {
                    builder.append(innerClassSeparator);
                }
            }
            builder.append(genericSeparator);
        }
    }

    private static String addTypeArgsAnnotations(String memberName, Object annValue, VisitorContext context) {

        var genericSeparator = getGenericSeparator(context);
        var innerClassSeparator = getInnerClassSeparator(context);

        var result = new StringBuilder();

        if (annValue instanceof AnnotationValue aValue) {
            var annName = aValue.getAnnotationName();
            var values = ((Map) aValue.getValues());
            var endPos = annName.contains(DOLLAR) ? annName.lastIndexOf(DOLLAR) : annName.length();
            result.append(annName, annName.lastIndexOf(DOT) + 1, endPos);
            if (CollectionUtils.isNotEmpty(values)) {
                result.append(genericSeparator);
                for (var entry : values.entrySet()) {
                    result.append(addTypeArgsAnnotations(entry.getKey(), entry.getValue(), context));
                }
                result.append(genericSeparator);
            }
        } else if (annValue instanceof Iterable iterable) {
            var isFirst = true;
            for (var item : iterable) {
                if (!isFirst) {
                    result.append(genericSeparator);
                }
                if (memberName != null) {
                    result.append(memberName).append(genericSeparator);
                }
                result.append(addTypeArgsAnnotations(null, item, context));
                isFirst = false;
            }
        } else {
            if (memberName != null && !memberName.equals(PROP_VALUE)) {
                result.append(memberName).append(genericSeparator);
            }
            result.append(annValue);
        }
        var resultTypeName = result.toString();
        resultTypeName = resultTypeName.replace(DOT, innerClassSeparator);
        return resultTypeName;
    }

    private static String resolvePropertyName(Element element, Element classElement, Schema propertySchema) {
        String name = propertySchema.getName() != null ? propertySchema.getName() : element.getName();

        if (isAnnotationPresent(element, io.swagger.v3.oas.annotations.media.Schema.class)) {
            var nameFromSchema = stringValue(element, io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME).orElse(null);
            if (nameFromSchema != null) {
                return nameFromSchema;
            }
        }
        if (isAnnotationPresent(element, JsonProperty.class)) {
            return stringValue(element, JsonProperty.class, PROP_VALUE).orElse(name);
        }
        if (classElement != null && classElement.hasAnnotation(JsonNaming.class)) {
            // INVESTIGATE: "classValue" doesn't work in this case
            Optional propertyNamingStrategyClass = classElement.stringValue(JsonNaming.class);
            if (propertyNamingStrategyClass.isEmpty()) {
                return name;
            }
            PropertyNamingStrategy strategy = propertyNamingStrategyInstances.computeIfAbsent(propertyNamingStrategyClass.get(), clazz -> {
                try {
                    return (PropertyNamingStrategy) Class.forName(propertyNamingStrategyClass.get()).getConstructor().newInstance();
                } catch (Exception e) {
                    throw new RuntimeException("Cannot instantiate: " + clazz);
                }
            });
            if (strategy instanceof PropertyNamingStrategies.NamingBase namingBase) {
                return namingBase.translate(name);
            }
        }
        return name;
    }

    private static void handleUnwrapped(VisitorContext context, Element element, ClassElement elementType, Schema parentSchema, AnnotationValue uw) {
        Map schemas = SchemaUtils.resolveSchemas(Utils.resolveOpenApi(context));
        ClassElement customElementType = getCustomSchema(elementType.getName(), elementType.getTypeArguments(), context);
        var elType = customElementType != null ? customElementType : elementType;

        // check schema annotation
        var schemaAnn = element.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
        if (schemaAnn != null) {
            var implClass = schemaAnn.annotationClassValue(PROP_IMPLEMENTATION).orElse(null);
            if (implClass != null) {
                elType = context.getClassElement(implClass.getName()).orElse(elType);
            }
        }

        String schemaName = computeDefaultSchemaName(stringValue(element, io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME).orElse(null),
            null, elType, elementType.getTypeArguments(), context, null);
        Schema wrappedPropertySchema = schemas.get(schemaName);
        if (wrappedPropertySchema == null) {
            getSchemaDefinition(resolveOpenApi(context), context, elType, elType.getTypeArguments(), element, Collections.emptyList(), null);
            wrappedPropertySchema = schemas.get(schemaName);
        }
        Map properties = wrappedPropertySchema != null ? wrappedPropertySchema.getProperties() : null;
        if (CollectionUtils.isEmpty(properties)) {
            return;
        }
        String prefix = uw.stringValue("prefix").orElse(EMPTY_STRING);
        String suffix = uw.stringValue("suffix").orElse(EMPTY_STRING);
        for (Map.Entry prop : properties.entrySet()) {
            try {
                String propertyName = prop.getKey();
                Schema propertySchema = prop.getValue();
                boolean isRequired = wrappedPropertySchema.getRequired() != null && wrappedPropertySchema.getRequired().contains(propertyName);
                if (StringUtils.isNotEmpty(suffix) || StringUtils.isNotEmpty(prefix)) {
                    propertyName = prefix + propertyName + suffix;
                    propertySchema = Utils.getJsonMapper().readValue(Utils.getJsonMapper().writeValueAsString(prop.getValue()), Schema.class);
                    propertySchema.setName(propertyName);
                }
                addProperty(parentSchema, propertyName, propertySchema, isRequired);
            } catch (IOException e) {
                warn("Exception cloning property " + e.getMessage(), context);
            }
        }
    }

    private static boolean doesParamExistsMandatoryInConstructor(Element element, @Nullable Element classElement) {
        if (classElement instanceof ClassElement classEl) {
            if (classEl.isEnum()) {
                return true;
            }
            return classEl.getPrimaryConstructor()
                .flatMap(methodElement -> Arrays.stream(methodElement.getParameters())
                    .filter(parameterElement -> parameterElement.getName().equals(element.getName()))
                    .map(parameterElement -> !parameterElement.isNullable())
                    .findFirst())
                .orElse(false);
        }

        return false;
    }

    private static void addProperty(Schema parentSchema, String name, Schema propertySchema, boolean required) {
        parentSchema.addProperty(name, propertySchema);
        if (required) {
            List requiredList = parentSchema.getRequired();
            // Check for duplicates
            if (requiredList == null || !requiredList.contains(name)) {
                parentSchema.addRequiredItem(name);
            }
        }
    }

    private static Map getDiscriminatorMap(Map newValues) {
        return newValues.containsKey(DISCRIMINATOR) ? (Map) newValues.get(DISCRIMINATOR) : new HashMap<>();
    }

    private static > void processAnnotationValue(VisitorContext context, AnnotationValue annotationValue,
                                                                     Map arraySchemaMap, List filters,
                                                                     Class type, @Nullable ClassElement jsonViewClass) {

        var values = new LinkedHashMap();
        for (var entry : annotationValue.getValues().entrySet()) {
            var key = entry.getKey();
            if (filters == null || !filters.contains((String) key)) {
                values.put(key.equals(PROP_REQUIRED_PROPERTIES) ? PROP_REQUIRED : key, entry.getValue());
            }
        }

        var s = toValue(values, context, type, jsonViewClass);
        if (s != null) {
            schemaToValueMap(arraySchemaMap, s);
        }
    }

    private static Map resolveAnnotationValues(VisitorContext context, AnnotationValue av, @Nullable ClassElement jsonViewClass) {
        final Map valueMap = toValueMap(av.getValues(), context, jsonViewClass);
        bindSchemaIfNecessary(context, av, valueMap, jsonViewClass);
        final String annotationName = av.getAnnotationName();
        if (Parameter.class.getName().equals(annotationName)) {
            Utils.normalizeEnumValues(valueMap, CollectionUtils.mapOf(
                PROP_IN, ParameterIn.class,
                PROP_STYLE, ParameterStyle.class
            ));
        }
        return valueMap;
    }

    public static Map getSchemaNameToClassNameMap() {
        return schemaNameToClassNameMap;
    }
}