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

io.helidon.microprofile.graphql.server.SchemaGeneratorHelper Maven / Gradle / Ivy

/*
 * Copyright (c) 2020, 2023 Oracle and/or its affiliates.
 *
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.microprofile.graphql.server;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.System.Logger.Level;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import graphql.scalars.ExtendedScalars;
import jakarta.json.bind.annotation.JsonbProperty;
import jakarta.json.bind.annotation.JsonbTransient;
import org.eclipse.microprofile.graphql.DefaultValue;
import org.eclipse.microprofile.graphql.Description;
import org.eclipse.microprofile.graphql.Enum;
import org.eclipse.microprofile.graphql.Id;
import org.eclipse.microprofile.graphql.Ignore;
import org.eclipse.microprofile.graphql.Input;
import org.eclipse.microprofile.graphql.Interface;
import org.eclipse.microprofile.graphql.Mutation;
import org.eclipse.microprofile.graphql.Name;
import org.eclipse.microprofile.graphql.Query;
import org.eclipse.microprofile.graphql.Source;
import org.eclipse.microprofile.graphql.Type;

import static io.helidon.microprofile.graphql.server.CustomScalars.CUSTOM_BIGDECIMAL_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.CUSTOM_BIGINTEGER_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.CUSTOM_FLOAT_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.CUSTOM_INT_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.CUSTOM_OFFSET_DATE_TIME_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.CUSTOM_ZONED_DATE_TIME_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.FORMATTED_CUSTOM_DATE_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.FORMATTED_CUSTOM_DATE_TIME_SCALAR;
import static io.helidon.microprofile.graphql.server.CustomScalars.FORMATTED_CUSTOM_TIME_SCALAR;
import static io.helidon.microprofile.graphql.server.ElementGenerator.OPEN_SQUARE;
import static io.helidon.microprofile.graphql.server.FormattingHelper.getDefaultDateTimeFormat;
import static io.helidon.microprofile.graphql.server.SchemaGenerator.GET;
import static io.helidon.microprofile.graphql.server.SchemaGenerator.IS;
import static io.helidon.microprofile.graphql.server.SchemaGenerator.SET;

/**
 * Helper class for {@link SchemaGenerator}.
 */
final class SchemaGeneratorHelper {

    /**
     * {@link OffsetTime} class name.
     */
    protected static final String OFFSET_TIME_CLASS = OffsetTime.class.getName();

    /**
     * {@link LocalTime} class name.
     */
    protected static final String LOCAL_TIME_CLASS = LocalTime.class.getName();

    /**
     * {@link OffsetDateTime} class name.
     */
    protected static final String OFFSET_DATE_TIME_CLASS = OffsetDateTime.class.getName();

    /**
     * {@link ZonedDateTime} class name.
     */
    protected static final String ZONED_DATE_TIME_CLASS = ZonedDateTime.class.getName();

    /**
     * {@link LocalDateTime} class name.
     */
    protected static final String LOCAL_DATE_TIME_CLASS = LocalDateTime.class.getName();

    /**
     * {@link LocalDate} class name.
     */
    protected static final String LOCAL_DATE_CLASS = LocalDate.class.getName();

    /**
     * {@link LocalDate} class name.
     */
    protected static final String LEGACY_DATE_CLASS = Date.class.getName();

    /**
     * {@link BigDecimal} class name.
     */
    protected static final String BIG_DECIMAL_CLASS = BigDecimal.class.getName();

    /**
     * {@link Long} class name.
     */
    protected static final String LONG_CLASS = Long.class.getName();

    /**
     * Class name for long primitive.
     */
    protected static final String LONG_PRIMITIVE_CLASS = long.class.getName();

    /**
     * {@link Float} class name.
     */
    protected static final String FLOAT_CLASS = Float.class.getName();

    /**
     * Class name for float primitive.
     */
    protected static final String FLOAT_PRIMITIVE_CLASS = float.class.getName();

    /**
     * {@link Double} class name.
     */
    protected static final String DOUBLE_CLASS = Double.class.getName();

    /**
     * Class name for double primitive.
     */
    protected static final String DOUBLE_PRIMITIVE_CLASS = double.class.getName();

    /**
     * Class name for {@link BigInteger}.
     */
    protected static final String BIG_INTEGER_CLASS = BigInteger.class.getName();

    /**
     * Class name for {@link Integer}.
     */
    protected static final String INTEGER_CLASS = Integer.class.getName();

    /**
     * Class name for int.
     */
    protected static final String INTEGER_PRIMITIVE_CLASS = int.class.getName();

    /**
     * Class name for {@link Byte}.
     */
    protected static final String BYTE_CLASS = Byte.class.getName();

    /**
     * Class name for byte.
     */
    protected static final String BYTE_PRIMITIVE_CLASS = byte.class.getName();

    /**
     * Class name for {@link Short}.
     */
    protected static final String SHORT_CLASS = Short.class.getName();

    /**
     * Class name for short.
     */
    protected static final String SHORT_PRIMITIVE_CLASS = short.class.getName();

    /**
     * Formatted Date scalar.
     */
    public static final String FORMATTED_DATE_SCALAR = "FormattedDate";

    /**
     * Formatted DateTime scalar.
     */
    public static final String FORMATTED_DATETIME_SCALAR = "FormattedDateTime";

    /**
     * Formatted DateTime scalar.
     */
    public static final String FORMATTED_OFFSET_DATETIME_SCALAR = "FormattedOffsetDateTime";

    /**
     * Formatted DateTime scalar.
     */
    public static final String FORMATTED_ZONED_DATETIME_SCALAR = "FormattedZonedDateTime";

    /**
     * Formatted Time Scalar.
     */
    public static final String FORMATTED_TIME_SCALAR = "FormattedTime";

    /**
     * Formatted Int.
     */

    /**
     * Date scalar (with default formatting).
     */
    public static final String DATE_SCALAR = "Date";

    /**
     * DateTime scalar (with default formatting).
     */
    public static final String DATETIME_SCALAR = "DateTime";

    /**
     * Time Scalar (with default formatting).
     */
    public static final String TIME_SCALAR = "Time";

    /**
     * Defines a {@link BigDecimal} type.
     */
    static final String BIG_DECIMAL = "BigDecimal";

    /**
     * Defines a {@link BigInteger} type.
     */
    static final String BIG_INTEGER = "BigInteger";

    /**
     * Value that indicates that default {@link java.util.Locale}.
     */
    static final String DEFAULT_LOCALE = "##default";

    /**
     * GraphQL Int.
     */
    public static final String INT = "Int";

    /**
     * GraphQL Float.
     */
    public static final String FLOAT = "Float";

    /**
     * GraphQL String.
     */
    public static final String STRING = "String";

    /**
     * GraphQL ID.
     */
    public static final String ID = "ID";

    /**
     * GraphQL Boolean.
     */
    public static final String BOOLEAN = "Boolean";

    /**
     * Logger.
     */
    private static final System.Logger LOGGER = System.getLogger(SchemaGeneratorHelper.class.getName());

    /**
     * Indicates empty annotations.
     */
    private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];

    /**
     * List of supported scalars keyed by the full class name.
     */
    static final Map SUPPORTED_SCALARS = new HashMap<>() {{
        // Object Scalar
        put(Object.class.getName(), new SchemaScalar("Object", Object.class.getName(), ExtendedScalars.Object, null));

        // Time scalars
        put(OffsetTime.class.getName(),
            new SchemaScalar(FORMATTED_TIME_SCALAR, OFFSET_TIME_CLASS, FORMATTED_CUSTOM_TIME_SCALAR, "HH[:mm][:ss]Z"));
        put(LocalTime.class.getName(),
            new SchemaScalar(FORMATTED_TIME_SCALAR, LOCAL_TIME_CLASS, FORMATTED_CUSTOM_TIME_SCALAR, "HH[:mm][:ss]"));

        // DateTime scalars
        put(OFFSET_DATE_TIME_CLASS,
            new SchemaScalar(FORMATTED_OFFSET_DATETIME_SCALAR, OFFSET_DATE_TIME_CLASS, CUSTOM_OFFSET_DATE_TIME_SCALAR,
                             "yyyy-MM-dd'T'HH[:mm][:ss]Z"));
        put(ZONED_DATE_TIME_CLASS,
            new SchemaScalar(FORMATTED_ZONED_DATETIME_SCALAR, ZONED_DATE_TIME_CLASS, CUSTOM_ZONED_DATE_TIME_SCALAR,
                             "yyyy-MM-dd'T'HH[:mm][:ss]Z'['VV']'"));
        put(LOCAL_DATE_TIME_CLASS,
            new SchemaScalar(FORMATTED_DATETIME_SCALAR, LOCAL_DATE_TIME_CLASS, FORMATTED_CUSTOM_DATE_TIME_SCALAR,
                             "yyyy-MM-dd'T'HH[:mm][:ss]"));

        // Date scalar
        put(LOCAL_DATE_CLASS, new SchemaScalar(FORMATTED_DATE_SCALAR, LOCAL_DATE_CLASS, FORMATTED_CUSTOM_DATE_SCALAR,
                                               "yyyy-MM-dd"));

        // Date scalar
        put(LEGACY_DATE_CLASS, new SchemaScalar(FORMATTED_DATE_SCALAR, LEGACY_DATE_CLASS, FORMATTED_CUSTOM_DATE_SCALAR,
                                               "yyyy-MM-dd"));

        // BigDecimal scalars
        put(BIG_DECIMAL_CLASS, new SchemaScalar(BIG_DECIMAL, BIG_DECIMAL_CLASS, CUSTOM_BIGDECIMAL_SCALAR, null));

        // BigInteger scalars
        put(BIG_INTEGER_CLASS, new SchemaScalar(BIG_INTEGER, BIG_INTEGER_CLASS, CUSTOM_BIGINTEGER_SCALAR, null));
        put(LONG_PRIMITIVE_CLASS, new SchemaScalar(BIG_INTEGER, LONG_CLASS, CUSTOM_BIGINTEGER_SCALAR, null));
        put(LONG_CLASS, new SchemaScalar(BIG_INTEGER, LONG_CLASS, CUSTOM_BIGINTEGER_SCALAR, null));

        // Int scalars
        put(INTEGER_CLASS, new SchemaScalar(INT, INTEGER_CLASS, CUSTOM_INT_SCALAR, null));
        put(INTEGER_PRIMITIVE_CLASS, new SchemaScalar(INT, INTEGER_PRIMITIVE_CLASS, CUSTOM_INT_SCALAR, null));
        put(BYTE_CLASS, new SchemaScalar(INT, BYTE_CLASS, CUSTOM_INT_SCALAR, null));
        put(BYTE_PRIMITIVE_CLASS, new SchemaScalar(INT, BYTE_PRIMITIVE_CLASS, CUSTOM_INT_SCALAR, null));
        put(SHORT_CLASS, new SchemaScalar(INT, SHORT_CLASS, CUSTOM_INT_SCALAR, null));
        put(SHORT_PRIMITIVE_CLASS, new SchemaScalar(INT, SHORT_PRIMITIVE_CLASS, CUSTOM_INT_SCALAR, null));

        // Float scalars
        put(FLOAT_CLASS, new SchemaScalar(FLOAT, FLOAT_CLASS, CUSTOM_FLOAT_SCALAR, null));
        put(FLOAT_PRIMITIVE_CLASS, new SchemaScalar(FLOAT, FLOAT_PRIMITIVE_CLASS, CUSTOM_FLOAT_SCALAR, null));
        put(DOUBLE_CLASS, new SchemaScalar(FLOAT, DOUBLE_CLASS, CUSTOM_FLOAT_SCALAR, null));
        put(DOUBLE_PRIMITIVE_CLASS, new SchemaScalar(FLOAT, DOUBLE_PRIMITIVE_CLASS, CUSTOM_FLOAT_SCALAR, null));
    }};

    /**
     * List of types that should map to a GraphQL Boolean.
     */
    static final List BOOLEAN_LIST = new ArrayList<>() {{
        add("boolean");
        add("java.lang.Boolean");
    }};

    /**
     * List of types that should map to a GraphQL String.
     */
    static final List STRING_LIST = new ArrayList<>() {{
        add("java.lang.String");
        add("java.lang.Character");
        add("char");
    }};

    /**
     * List of array primitive types and their array mapping. See https://docs.oracle.com/javase/6/docs/api/java/lang/Class
     * .html#getName%28%29
     */
    static final Map PRIMITIVE_ARRAY_MAP = new HashMap<>() {{
        put("[Z", "boolean");
        put("[B", "byte");
        put("[C", "char");
        put("[D", "double");
        put("[F", "float");
        put("[I", "int");
        put("[J", "long");
        put("[S", "short");
    }};

    /**
     * List of all Java primitives.
     */
    static final List JAVA_PRIMITIVE_TYPES = new ArrayList<>() {{
        add("byte");
        add("short");
        add("int");
        add("long");
        add("float");
        add("double");
        add("boolean");
        add("char");
    }};

    /**
     * Private no-args constructor.
     */
    private SchemaGeneratorHelper() {
    }

    /**
     * Return the simple name from a given class as a String. This takes into account any annotations that may be present.
     *
     * @param className class name
     * @return the simple class name
     * @throws ClassNotFoundException if invalid class name
     */
    protected static String getSimpleName(String className)
            throws ClassNotFoundException {
        return getSimpleName(className, false);
    }

    /**
     * Return true of the {@link Class} is a primitive or array of primitives.
     *
     * @param clazz {@link Class} to check
     * @return true of the {@link Class} is a primitive or array of primitives.
     */
    protected static boolean isPrimitive(Class clazz) {
        return isPrimitive(clazz.getName());
    }

    /**
     * Return true of the class name is a primitive or array of primitives.
     *
     * @param clazz class name to check
     * @return true if the class name is a primitive or array of primitives.
     */
    protected static boolean isPrimitive(String clazz) {
        return JAVA_PRIMITIVE_TYPES.contains(clazz) || PRIMITIVE_ARRAY_MAP.containsValue(clazz);
    }

    /**
     * Return true of the class name is an array of primitives.
     *
     * @param clazz class name to check
     * @return true true of the class name is an array of primitives.
     */
    protected static boolean isPrimitiveArray(String clazz) {
        return PRIMITIVE_ARRAY_MAP.containsValue(clazz);
    }

    /**
     * Return true of the class name is an array of primitives.
     *
     * @param clazz {@link Class} to check
     * @return true true of the class name is an array of primitives.
     */
    protected static boolean isPrimitiveArray(Class clazz) {
        String className = clazz.getName();
        for (String key : PRIMITIVE_ARRAY_MAP.keySet()) {
            if (className.contains(key)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return the simple name from a given class as a String. This takes into account any annotations that may be present.
     *
     * @param className                 class name
     * @param ignoreInputNameAnnotation indicates if we should ignore the name from {@link Input} annotation as we should not
     *                                  change the name of a type if it as and {@link Input} annotation
     * @return the simple class name
     * @throws ClassNotFoundException if invalid class name
     */
    protected static String getSimpleName(String className, boolean ignoreInputNameAnnotation)
            throws ClassNotFoundException {
        if (ID.equals(className)
                || STRING.equals(className) || BOOLEAN.equalsIgnoreCase(className)
                || getScalar(className) != null) {
            return className;
        }
        // return the type name taking into account any annotations
        Class clazz = Class.forName(className);
        return getTypeName(clazz, ignoreInputNameAnnotation);
    }

    /**
     * Return a {@link SchemaScalar} if one matches the known list of scalars available from the {@link ExtendedScalars} helper.
     *
     * @param clazzName class name to check for
     * @return a {@link SchemaScalar} if one matches the known list of scalars or null if none found
     */
    protected static SchemaScalar getScalar(String clazzName) {
        return SUPPORTED_SCALARS.get(clazzName);
    }

    /**
     * Return true if the give name is a scalar with that name.
     *
     * @param scalarName the scalae name to check
     * @return true if the give name is a scalar with that name
     */
    protected static boolean isScalar(String scalarName) {
        return SUPPORTED_SCALARS.values().stream().anyMatch((s -> s.name().equals(scalarName)));
    }

    /**
     * Return the GraphQL type for the given Java type.
     *
     * @param className fully qualified class name
     * @return the GraphQL type
     */
    protected static String getGraphQLType(String className) {
        if (BOOLEAN_LIST.contains(className)) {
            return BOOLEAN;
        } else if (STRING_LIST.contains(className)) {
            return STRING;
        } else if (java.util.UUID.class.getName().equals(className)) {
            return ID;
        }

        return className;
    }

    /**
     * Return true of the name is a Date, DateTime, or Time scalar.
     *
     * @param scalarName scalar name
     * @return true of the name is a Date, DateTime, or Time scalar
     */
    protected static boolean isDateTimeScalar(String scalarName) {
        return FORMATTED_DATE_SCALAR.equals(scalarName)
                || FORMATTED_TIME_SCALAR.equals(scalarName)
                || FORMATTED_OFFSET_DATETIME_SCALAR.equals(scalarName)
                || FORMATTED_ZONED_DATETIME_SCALAR.equals(scalarName)
                || FORMATTED_DATETIME_SCALAR.equals(scalarName);
    }

    /**
     * Return true if the type type is a native GraphQLType.
     *
     * @param type the type to check
     * @return true if the type type is a GraphQLType
     */
    protected static boolean isGraphQLType(String type) {
        return BOOLEAN.equals(type) || STRING.equals(type) || ID.equals(type);
    }

    /**
     * Return the field name after checking both the {@link Name} and {@link JsonbProperty} annotations are present on the field
     * name.

Name will take precedence if both are specified. * * @param clazz {@link Class} to check * @param fieldName field name to check * @return the field name or null if none exist */ protected static String getFieldName(Class clazz, String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); Name nameAnnotation = field.getAnnotation(Name.class); // Name annotation is specified so use this and don't bother checking JsonbProperty if (nameAnnotation != null && !nameAnnotation.value().isBlank()) { return nameAnnotation.value(); } // check for JsonbProperty JsonbProperty jsonbPropertyAnnotation = field.getAnnotation(JsonbProperty.class); return jsonbPropertyAnnotation != null && !jsonbPropertyAnnotation.value().isBlank() ? jsonbPropertyAnnotation.value() : null; } catch (NoSuchFieldException e) { return null; } } /** * Return the field name after checking both the {@link Name} and {@link JsonbProperty} annotations are present on the {@link * Method}. Name will take precedence if both are specified. * * @param method {@link Method} to check * @return the field name or null if non exist */ protected static String getMethodName(Method method) { if (method != null) { Query queryAnnotation = method.getAnnotation(Query.class); Mutation mutationAnnotation = method.getAnnotation(Mutation.class); Name nameAnnotation = method.getAnnotation(Name.class); JsonbProperty jsonbPropertyAnnotation = method.getAnnotation(JsonbProperty.class); if (queryAnnotation != null && !queryAnnotation.value().isBlank()) { return queryAnnotation.value(); } if (mutationAnnotation != null && !mutationAnnotation.value().isBlank()) { return mutationAnnotation.value(); } if (nameAnnotation != null && !nameAnnotation.value().isBlank()) { // Name annotation is specified so use this and don't bother checking JsonbProperty return nameAnnotation.value(); } if (jsonbPropertyAnnotation != null && !jsonbPropertyAnnotation.value().isBlank()) { return jsonbPropertyAnnotation.value(); } } return null; } /** * Return a Class from a class name and ignore any exceptions. * * @param clazzName the class name as a String * @return a Class name */ protected static Class getSafeClass(String clazzName) { try { return Class.forName(clazzName); } catch (ClassNotFoundException e) { return null; } } /** * Return true if the class is a date, time or date/time. * * @param clazz the {@link Class} to check * @return true if the class is a date, time or date/time */ protected static boolean isDateTimeClass(Class clazz) { return clazz != null && ( clazz.equals(LocalDate.class) || clazz.equals(Date.class) || clazz.equals(LocalTime.class) || clazz.equals(LocalDateTime.class) || clazz.equals(OffsetTime.class) || clazz.equals(ZonedDateTime.class) || clazz.equals(OffsetDateTime.class)); } /** * Return true if the {@link Class} is a valid {@link Class} to apply the {@link Id} annotation. * * @param clazz {@link Class} to check * @return true if the {@link Class} is a valid {@link Class} to apply the {@link Id} annotation */ protected static boolean isValidIDType(Class clazz) { return clazz.equals(Long.class) || clazz.equals(Integer.class) || clazz.equals(java.util.UUID.class) || clazz.equals(int.class) || clazz.equals(String.class) || clazz.equals(long.class); } /** * Return true if the fully qualified class is an enum. * * @param clazz class to check * @return true if the fully qualified class is an enum. */ protected static boolean isEnumClass(String clazz) { try { return (Class.forName(clazz)).isEnum(); } catch (ClassNotFoundException e) { return false; } } /** * Return the {@link Name} annotation value if it exists or null. * * @param clazz {@link Class} to check * @return the {@link Name} annotation value if it exists or null */ protected static String getNameAnnotationValue(Class clazz) { Name nameAnnotation = clazz.getAnnotation(Name.class); if (nameAnnotation != null && !"".equals(nameAnnotation.value())) { return nameAnnotation.value(); } return null; } /** * Return the {@link DefaultValue} annotation value if it exists for a {@link Parameter} or null. * * @param annotatedElement {@link AnnotatedElement} to check * @return the {@link DefaultValue} annotation value if it exists or null */ protected static String getDefaultValueAnnotationValue(AnnotatedElement annotatedElement) { DefaultValue defaultValueAnnotation = annotatedElement.getAnnotation(DefaultValue.class); if (defaultValueAnnotation != null && !"".equals(defaultValueAnnotation.value())) { return defaultValueAnnotation.value(); } return null; } /** * Return the default description based upon the format and description. * * @param format format * @param description description * @return the default description */ protected static String getDefaultDescription(String[] format, String description) { String fmt = format == null || format.length != 2 || (format[0] == null || format[1] == null) ? null : format[0] + (DEFAULT_LOCALE.equals(format[1]) ? "" : " " + format[1]); if (description == null && fmt == null) { return null; } // for the format display replace all [] (optionals) with null so TCK works if (fmt != null) { fmt = fmt.replaceAll("\\[", "").replaceAll("]", ""); } return description == null ? fmt.trim() : fmt == null ? description : description + " (" + fmt.trim() + ")"; } /** * Return the type method name taking into account the is/set/get prefix. * * @param method {@link Method} to check * @param isStrictTest indicates if a strict test for setters and getters should be carried out. * @return the method name */ protected static String stripMethodName(Method method, boolean isStrictTest) { String name = method.getName(); boolean isPublic = Modifier.isPublic(method.getModifiers()); boolean isSetterName = name.matches("^set[A-Z].*"); boolean isGetterName = name.matches("^get[A-Z].*") || name.matches("^is[A-Z].*"); Parameter[] parameters = method.getParameters(); if (isStrictTest) { boolean isSetter = isPublic && method.getReturnType().equals(void.class) && parameters.length == 1 && isSetterName; boolean isGetter = isPublic && !method.getReturnType().equals(void.class) && parameters.length == 0 && isGetterName; if (!isGetter && !isSetter) { return name; } } else { // non-strict test so just check names if (!isPublic || !isGetterName && !isSetterName) { return name; } } String varName; if (name.startsWith(IS) || name.startsWith(GET) || name.startsWith(SET)) { String prefix; if (name.startsWith(IS)) { prefix = IS; } else if (name.startsWith(GET)) { prefix = GET; } else if (name.startsWith(SET)) { prefix = SET; } else { prefix = ""; } // remove the prefix and make first letter lowercase varName = name.replaceAll(prefix, ""); varName = varName.substring(0, 1).toLowerCase() + varName.substring(1); } else { // may be any method, e.g. from GraphQLApi annotated class varName = name; } return varName; } /** * Return correct name for a Type or Enum based upon the value of the annotation or the {@link Name}. * * @param clazz {@link Class} to introspect. * @return the correct name */ protected static String getTypeName(Class clazz) { return getTypeName(clazz, false); } /** * Return the array of {@link Annotation}s on a {@link Parameter} that are parameterized types. * * @param field {@link Field} to introspect * @param index index of type generic type. 0 = List/Collection 1 = Map * @return the array of {@link Annotation}s on a {@link Parameter} */ protected static Annotation[] getFieldAnnotations(Field field, int index) { if (field.getAnnotatedType() instanceof AnnotatedParameterizedType) { return getAnnotationsFromType((AnnotatedParameterizedType) field.getAnnotatedType(), index); } return EMPTY_ANNOTATIONS; } /** * Return the array of {@link Annotation}s on a {@link Method} that are parameterized types. * * @param method {@link Method} to introspect * @param index index of type generic type. 0 = List/Collection 1 = Map * @return the array of {@link Annotation}s on a {@link Parameter} */ protected static Annotation[] getMethodAnnotations(Method method, int index) { if (method.getAnnotatedReturnType() instanceof AnnotatedParameterizedType) { return getAnnotationsFromType((AnnotatedParameterizedType) method.getAnnotatedReturnType(), index); } return EMPTY_ANNOTATIONS; } /** * Return the array of {@link Annotation}s on a {@link Parameter} that are parameterized types. * * @param parameter {@link Parameter} to introspect * @param index index of type generic type. 0 = List/Collection 1 = Map * @return the array of {@link Annotation}s on a {@link Parameter} */ protected static Annotation[] getParameterAnnotations(Parameter parameter, int index) { if (parameter.getAnnotatedType() instanceof AnnotatedParameterizedType) { return getAnnotationsFromType((AnnotatedParameterizedType) parameter.getAnnotatedType(), index); } return EMPTY_ANNOTATIONS; } /** * Return the annotations from the given {@link AnnotatedParameterizedType}. * * @param apt {@link AnnotatedParameterizedType} * @param index index of type generic type. 0 = List/Collection 1 = Map * @return the annotations from the given {@link AnnotatedParameterizedType} */ private static Annotation[] getAnnotationsFromType(AnnotatedParameterizedType apt, int index) { if (apt != null) { // loop until we find the root annotated type AnnotatedType annotatedActualTypeArgument = apt.getAnnotatedActualTypeArguments()[index]; while (annotatedActualTypeArgument instanceof AnnotatedParameterizedType) { AnnotatedParameterizedType parameterizedType2 = (AnnotatedParameterizedType) annotatedActualTypeArgument; annotatedActualTypeArgument = parameterizedType2.getAnnotatedActualTypeArguments()[index]; } if (annotatedActualTypeArgument != null) { return annotatedActualTypeArgument.getAnnotations(); } } return EMPTY_ANNOTATIONS; } /** * Return the annotation that matches the type. * * @param annotations array of {@link Annotation}s to search * @param type the {@link Type} to find * @return the annotation that matches the type */ protected static Annotation getAnnotationValue(Annotation[] annotations, java.lang.reflect.Type type) { if (annotations != null) { for (Annotation annotation : annotations) { if (annotation.annotationType().equals(type)) { return annotation; } } } return null; } /** * Return correct name for a Type or Enum based upon the value of the annotation or the {@link Name}. * * @param clazz {@link Class} to introspect. * @param ignoreInputNameAnnotation indicates if we should ignore the name from {@link Input} annotation as we should not * change the name of a type if it as and {@link Input} annotation * @return the correct name */ protected static String getTypeName(Class clazz, boolean ignoreInputNameAnnotation) { Type typeAnnotation = clazz.getAnnotation(Type.class); Interface interfaceAnnotation = clazz.getAnnotation(Interface.class); Input inputAnnotation = ignoreInputNameAnnotation ? null : clazz.getAnnotation(Input.class); Enum enumAnnotation = clazz.getAnnotation(Enum.class); Query queryAnnotation = clazz.getAnnotation(Query.class); Mutation mutationAnnotation = clazz.getAnnotation(Mutation.class); String name = ""; if (typeAnnotation != null) { name = typeAnnotation.value(); } else if (interfaceAnnotation != null) { name = interfaceAnnotation.value(); } else if (inputAnnotation != null) { name = inputAnnotation.value(); } else if (enumAnnotation != null) { name = enumAnnotation.value(); } else if (queryAnnotation != null) { name = queryAnnotation.value(); } else if (mutationAnnotation != null) { name = mutationAnnotation.value(); } if ("".equals(name)) { name = getNameAnnotationValue(clazz); } ensureValidName(LOGGER, name); return name == null || name.isBlank() ? clazz.getSimpleName() : name; } /** * Ensure the provided name is a valid GraphQL name. * * @param logger {@link System.Logger} to log to * @param name to validate */ protected static void ensureValidName(System.Logger logger, String name) { if (name != null && !isValidGraphQLName(name)) { ensureConfigurationException(LOGGER, "The name '" + name + "' is not a valid " + "GraphQL name and cannot be used."); } } /** * Checks a {@link SchemaType} for {@link SchemaFieldDefinition}s which contain known scalars and replace them with the scalar * name. * * @param schema {@link Schema} to check scalars for. * @param type {@link SchemaType} to check */ protected static void checkScalars(Schema schema, SchemaType type) { type.fieldDefinitions().forEach(fd -> { SchemaScalar scalar = getScalar(fd.returnType()); if (scalar != null) { fd.returnType(scalar.name()); if (!schema.containsScalarWithName(scalar.name())) { schema.addScalar(scalar); } } }); } /** * Return current format or if none exists, then the default if it exists for the scalar. * * @param scalarName scalar name to check * @param clazzName class name to check * @param existingFormat the existing format * @return current format or if none exists, then the default if it exists for the scalar */ protected static String[] ensureFormat(String scalarName, String clazzName, String[] existingFormat) { if (existingFormat == null || (existingFormat[0] == null && existingFormat[1] == null && isScalar(scalarName))) { String[] defaultFormat = getDefaultDateTimeFormat(scalarName, clazzName); if (defaultFormat != null) { return defaultFormat; } } return existingFormat; } /** * Return the number of array levels in the class. * * @param clazz the class name retrieved via Class.getName() * @return the number of array levels in the class */ protected static int getArrayLevels(String clazz) { int c = 0; for (int i = 0; i < clazz.length(); i++) { if (clazz.charAt(i) == '[') { c++; } } return c; } /** * Indicates if the class is an array type. * * @param clazz the class name retrieved via Class.getName() * @return true if the class is an array type */ protected static boolean isArrayType(String clazz) { return clazz.startsWith(OPEN_SQUARE); } /** * Return the root array class from the given class. * * @param clazz the class name retrieved via Class.getName() * @return the root class name */ protected static String getRootArrayClass(String clazz) { if (clazz == null || "".equals(clazz.trim()) || clazz.length() < 2) { throw new IllegalArgumentException("Class must be not null"); } // check to see if it is a primitive array String type = PRIMITIVE_ARRAY_MAP.get(clazz.substring(clazz.length() - 2)); if (type != null) { return type; } // must be an object return clazz.replaceAll("\\[", "").replaceAll(";", "").replaceAll("^L", ""); } /** * Indicates if the method should be ignored. * * @param method {@link Method} to check * @param isInputType indicates if this is an input type * @return true if the method should be ignored */ protected static boolean shouldIgnoreMethod(Method method, boolean isInputType) { Ignore ignore = method.getAnnotation(Ignore.class); JsonbTransient jsonbTransient = method.getAnnotation(JsonbTransient.class); // default case if (ignore == null && jsonbTransient == null) { return false; } // at least one of the annotations is present on the method String methodName = method.getName(); String prefix = methodName.startsWith(SET) ? SET : methodName.startsWith(GET) ? GET : null; // if @Ignore or @JsonbTransient is on getter method then exclude from output type // if @Ignore or @JsonbTransient is on setter methods then excludes from input type if (GET.equals(prefix) && !isInputType) { return true; } else { return SET.equals(prefix) && isInputType; } } /** * Return true if the provided field should be ignored. * * @param clazz {@link Class} to check field on * @param fieldName field name to check * @return true if the {@link Field} should be ignored */ protected static boolean shouldIgnoreField(Class clazz, String fieldName) { Field field = null; try { field = clazz.getDeclaredField(fieldName); return field != null && (field.getAnnotation(Ignore.class) != null || field.getAnnotation(JsonbTransient.class) != null); } catch (NoSuchFieldException e) { return false; } } /** * Safely return the value of the {@link Description} annotation. * * @param description {@link Description} annotation * @return the description or null */ protected static String getDescription(Description description) { return description == null || "".equals(description.value()) ? null : description.value(); } /** * Validates that a name is valid according to the graphql spec at. Ref: http://spec.graphql.org/June2018/#sec-Names * * @param name name to validate * @return true if the name is valid */ protected static boolean isValidGraphQLName(String name) { return name != null && name.matches("[_A-Za-z][_0-9A-Za-z]*") && !name.startsWith("__"); } /** * Ensures a {@link RuntimeException} with the message supplied is thrown and logged. * * @param message message to throw * @param logger the {@link System.Logger} to use */ protected static void ensureRuntimeException(System.Logger logger, String message) { ensureRuntimeException(logger, message, null); } /** * Ensures a {@link RuntimeException} with the message supplied is thrown and logged. * * @param message message to throw * @param cause cause of the erro * @param logger the {@link System.Logger} to use */ protected static void ensureRuntimeException(System.Logger logger, String message, Throwable cause) { logger.log(Level.WARNING, message); if (cause != null) { logger.log(Level.WARNING, getStackTrace(cause)); } throw new RuntimeException(message, cause); } /** * Ensures a {@link GraphQlConfigurationException} with the message suppleid is thrown and logged. * * @param message message to throw * @param logger the {@link System.Logger} to use */ protected static void ensureConfigurationException(System.Logger logger, String message) { ensureConfigurationException(logger, message, null); } /** * Ensures a {@link GraphQlConfigurationException} with the message supplied is thrown and logged. * * @param message message to throw * @param cause cause of the erro * @param logger the {@link System.Logger} to use */ protected static void ensureConfigurationException(System.Logger logger, String message, Throwable cause) { logger.log(Level.WARNING, message); if (cause != null) { logger.log(Level.WARNING, getStackTrace(cause)); } throw new GraphQlConfigurationException(message, cause); } /** * Return the stacktrace of the given {@link Throwable}. * * @param throwable {@link Throwable} to get stack tracek for * @return the stacktrace of the given {@link Throwable} */ protected static String getStackTrace(Throwable throwable) { StringWriter stack = new StringWriter(); throwable.printStackTrace(new PrintWriter(stack)); return stack.toString(); } /** * Validate that a {@link Class} annotated with ID is a valid type. * * @param returnClazz {@link Class} to check */ protected static void validateIDClass(Class returnClazz) { if (!isValidIDType(returnClazz)) { ensureConfigurationException(LOGGER, "A class of type " + returnClazz + " is not allowed to be an @Id"); } } /** * Validate that a class annotated with ID is a valid type. * * @param returnClazz class to check */ protected static void validateIDClass(String returnClazz) { try { validateIDClass(Class.forName(returnClazz)); } catch (ClassNotFoundException e) { // ignore } } /** * Defines discovered methods for a class. */ public static class DiscoveredMethod { /** * Indicates query Type. */ public static final int QUERY_TYPE = 0; /** * Indicates write method. */ public static final int MUTATION_TYPE = 1; /** * Name of the discovered method. */ private String name; /** * Return type of the method. */ private String returnType; /** * type of method. */ private int methodType; /** * If the return type is a {@link Collection} then this is the type of {@link Collection} and the returnType will be * return type for the collection. */ private String collectionType; /** * Indicates if the return type is an array. */ private boolean isArrayReturnType; /** * Indicates if the return type is a {@link Map}. Note: In the 1.0.1 microprofile spec the behaviour of {@link Map} is * undefined. */ private boolean isMap; /** * The {@link List} of {@link SchemaArgument}s for this method. */ private List listArguments = new ArrayList<>(); /** * The actual method. */ private Method method; /** * Number of levels in the Array. */ private int arrayLevels = 0; /** * The source on which the method should be added. */ private String source; /** * The property name if the method is a getter. */ private String propertyName; /** * Indicates if the method containing the {@link Source} annotation was also annotated with the {@link Query} annotation. * If true, then this indicates that a top level query should also be created as well as the field in the type. */ private boolean isQueryAnnotated = false; /** * Defines the format for a number or date. */ private String[] format = new String[0]; /** * A description for a method. */ private String description; /** * Indicates id the return type is mandatory. */ private boolean isReturnTypeMandatory; /** * The default value for this discovered method. */ private Object defaultValue; /** * If the return type is an array then indicates if the value in the array is mandatory. */ private boolean isArrayReturnTypeMandatory; /** * Original array inner type if it is array type. */ private Class originalArrayType; /** * Indicates if the format is of type Jsonb. */ private boolean isJsonbFormat; /** * Indicates if the property name is of type Jsonb. */ private boolean isJsonbProperty; /** * Construct a {@link DiscoveredMethod}. * * @param builder the {@link Builder} to construct from */ private DiscoveredMethod(Builder builder) { this.name = builder.name; this.returnType = builder.returnType; this.methodType = builder.methodType; this.collectionType = builder.collectionType; this.isArrayReturnType = builder.isArrayReturnType; this.isMap = builder.isMap; this.listArguments = builder.listArguments; this.method = builder.method; this.arrayLevels = builder.arrayLevels; this.source = builder.source; this.propertyName = builder.propertyName; this.isQueryAnnotated = builder.isQueryAnnotated; this.format = builder.format; this.description = builder.description; this.isArrayReturnTypeMandatory = builder.isReturnTypeMandatory; this.defaultValue = builder.defaultValue; this.originalArrayType = builder.originalArrayType; this.isJsonbFormat = builder.isJsonbFormat; this.isJsonbProperty = builder.isJsonbProperty; } /** * Fluent API builder to create {@link DiscoveredMethod}. * * @return new builder instance */ public static Builder builder() { return new Builder(); } /** * Return the name. * * @return the name */ public String name() { return name; } /** * Set the name. * * @param name the name */ public void name(String name) { this.name = name; } /** * Return the return type. * * @return the return type */ public String returnType() { return returnType; } /** * Set the return type. * * @param returnType the return type */ public void returnType(String returnType) { this.returnType = returnType; } /** * Return the collection type. * * @return the collection */ public String collectionType() { return collectionType; } /** * Set the collection type. * * @param collectionType the collection type */ public void collectionType(String collectionType) { this.collectionType = collectionType; } /** * Sets if the property has a JsonbProperty annotation. * * @param isJsonbProperty if the property has a JsonbProperty annotation */ public void jsonbProperty(boolean isJsonbProperty) { this.isJsonbProperty = isJsonbProperty; } /** * Indicates if the property has a JsonbProperty annotation. * * @return true if the property has a JsonbProperty annotation */ public boolean isJsonbProperty() { return isJsonbProperty; } /** * Indicates if the method is a map return type. * * @return if the method is a map return type */ public boolean isMap() { return isMap; } /** * Set if the method is a map return type. * * @param map if the method is a map return type */ public void map(boolean map) { isMap = map; } /** * Return the method type. * * @return the method type */ public int methodType() { return methodType; } /** * Set the method type either READ or WRITE. * * @param methodType the method type */ public void methodType(int methodType) { this.methodType = methodType; } /** * Indicates if the method returns an array. * * @return if the method returns an array. */ public boolean isArrayReturnType() { return isArrayReturnType; } /** * Indicates if the method returns an array. * * @param arrayReturnType if the method returns an array */ public void arrayReturnType(boolean arrayReturnType) { isArrayReturnType = arrayReturnType; } /** * Indicates if the method is a collection type. * * @return if the method is a collection type */ public boolean isCollectionType() { return collectionType != null; } /** * Return the {@link Method}. * * @return the {@link Method} */ public Method method() { return method; } /** * Sets the {@link Method}. * * @param method the {@link Method} */ public void method(Method method) { this.method = method; } /** * Return the {@link List} of {@link SchemaArgument}s. * * @return the {@link List} of {@link SchemaArgument} */ public List arguments() { return this.listArguments; } /** * Return the number of levels in the Array. * * @return Return the number of levels in the Array */ public int arrayLevels() { return arrayLevels; } /** * Sets the number of levels in the Array. * * @param arrayLevels the number of levels in the Array */ public void arrayLevels(int arrayLevels) { this.arrayLevels = arrayLevels; } /** * Add a {@link SchemaArgument}. * * @param argument a {@link SchemaArgument} */ public void addArgument(SchemaArgument argument) { listArguments.add(argument); } /** * Return the source on which the method should be added. * * @return source on which the method should be added */ public String source() { return source; } /** * Set the source on which the method should be added. * * @param source source on which the method should be added */ public void source(String source) { this.source = source; } /** * Indicates if the method containing the {@link Source} annotation was also annotated with the {@link Query} annotation. * * @return true if the {@link Query} annotation was present */ public boolean isQueryAnnotated() { return isQueryAnnotated; } /** * Set if the method containing the {@link Source} annotation was * also annotated with the {@link Query} annotation. * * @param queryAnnotated true if the {@link Query} annotation was present */ public void queryAnnotated(boolean queryAnnotated) { isQueryAnnotated = queryAnnotated; } /** * Return the format for a number or date. * * @return the format for a number or date */ public String[] format() { if (format == null) { return null; } String[] copy = new String[format.length]; System.arraycopy(format, 0, copy, 0, copy.length); return copy; } /** * Set the format for a number or date. * * @param format the format for a number or date */ public void format(String[] format) { if (format == null) { this.format = null; } else { this.format = new String[format.length]; System.arraycopy(format, 0, this.format, 0, this.format.length); } } /** * Return the property name if the method is a getter. * * @return property name if the method is a getter */ public String propertyName() { return propertyName; } /** * Set the property name if the method is a getter. * * @param propertyName property name */ public void propertyName(String propertyName) { this.propertyName = propertyName; } /** * Return the description for a method. * * @return the description for a method */ public String description() { return description; } /** * Set the description for a method. * * @param description the description for a method */ public void description(String description) { this.description = description; } /** * Indicates if the return type is mandatory. * * @return if the return type is mandatory */ public boolean isReturnTypeMandatory() { return isReturnTypeMandatory; } /** * Set if the return type is mandatory. * * @param returnTypeMandatory if the return type is mandatory */ public void returnTypeMandatory(boolean returnTypeMandatory) { isReturnTypeMandatory = returnTypeMandatory; } /** * Return the default value for this method. * * @return the default value for this method */ public Object defaultValue() { return defaultValue; } /** * Set the default value for this method. * * @param defaultValue the default value for this method */ public void defaultValue(Object defaultValue) { this.defaultValue = defaultValue; } /** * Return if the array return type is mandatory. * * @return if the array return type is mandatory */ public boolean isArrayReturnTypeMandatory() { return isArrayReturnTypeMandatory; } /** * Sets if the array return type is mandatory. * * @param arrayReturnTypeMandatory if the array return type is mandatory */ public void arrayReturnTypeMandatory(boolean arrayReturnTypeMandatory) { isArrayReturnTypeMandatory = arrayReturnTypeMandatory; } /** * Sets the original array type. * * @param originalArrayType the original array type */ public void originalArrayType(Class originalArrayType) { this.originalArrayType = originalArrayType; } /** * Return the original array type. * * @return the original array type */ public Class originalArrayType() { return originalArrayType; } /** * Set if the format is of type JsonB. * * @param isJsonbFormat if the format is of type JsonB */ public void jsonbFormat(boolean isJsonbFormat) { this.isJsonbFormat = isJsonbFormat; } /** * Return true if the format is of type JsonB. * * @return true if the format is of type JsonB */ public boolean isJsonbFormat() { return isJsonbFormat; } @Override public String toString() { return "DiscoveredMethod{" + "name='" + name + '\'' + ", returnType='" + returnType + '\'' + ", methodType=" + methodType + ", collectionType='" + collectionType + '\'' + ", isArrayReturnType=" + isArrayReturnType + ", isMap=" + isMap + ", listArguments=" + listArguments + ", arrayLevels=" + arrayLevels + ", source=" + source + ", isQueryAnnotated=" + isQueryAnnotated + ", isReturnTypeMandatory=" + isReturnTypeMandatory + ", isArrayReturnTypeMandatory=" + isArrayReturnTypeMandatory + ", description=" + description + ", originalArrayType=" + originalArrayType + ", defaultValue=" + defaultValue + ", isJsonbFormat=" + isJsonbFormat + ", isJsonbProperty=" + isJsonbProperty + ", method=" + method + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DiscoveredMethod that = (DiscoveredMethod) o; return methodType == that.methodType && isArrayReturnType == that.isArrayReturnType && isMap == that.isMap && arrayLevels == that.arrayLevels && Objects.equals(name, that.name) && Objects.equals(returnType, that.returnType) && Objects.equals(source, that.source) && Objects.equals(isQueryAnnotated, that.isQueryAnnotated) && Objects.equals(method, that.method) && Objects.equals(description, that.description) && Objects.equals(isReturnTypeMandatory, that.isReturnTypeMandatory) && Objects.equals(isArrayReturnTypeMandatory, that.isArrayReturnTypeMandatory) && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(isJsonbFormat, that.isJsonbFormat) && Objects.equals(collectionType, that.collectionType); } @Override public int hashCode() { return Objects.hash(name, returnType, methodType, method, arrayLevels, isQueryAnnotated, collectionType, isArrayReturnType, isMap, source, description, isReturnTypeMandatory, defaultValue, isArrayReturnTypeMandatory, isJsonbFormat, isJsonbProperty); } /** * A fluent API {@link io.helidon.common.Builder} to build instances of {@link DiscoveredMethod}. */ public static class Builder implements io.helidon.common.Builder { private String name; private String returnType; private int methodType; private String collectionType; private boolean isArrayReturnType; private boolean isMap; private List listArguments = new ArrayList<>(); private Method method; private int arrayLevels = 0; private String source; private String propertyName; private boolean isQueryAnnotated = false; private String[] format = new String[0]; private String description; private boolean isReturnTypeMandatory; private Object defaultValue; private Class originalArrayType; private boolean isJsonbFormat; private boolean isJsonbProperty; /** * Set the name of the {@link DiscoveredMethod}. * * @param name the name of the {@link DiscoveredMethod} * @return updated builder instance */ public Builder name(String name) { this.name = name; return this; } /** * Set the returnType. * * @param returnType the returnType * @return updated builder instance */ public Builder returnType(String returnType) { this.returnType = returnType; return this; } /** * Set the method type. * * @param methodType the method type * @return updated builder instance */ public Builder methodType(int methodType) { this.methodType = methodType; return this; } /** * Set the collection type. * * @param collectionType the collection type * @return updated builder instance */ public Builder collectionType(String collectionType) { this.collectionType = collectionType; return this; } /** * Set if the return type is an array type such as a native array([]) or a List, Collection. * * @param isArrayReturnType true if the return type is an array type * @return updated builder instance */ public Builder arrayReturnType(boolean isArrayReturnType) { this.isArrayReturnType = isArrayReturnType; return this; } /** * Indicates if the return type is a {@link Map}. * @param isMap if the return type is a {@link Map} * @return updated builder instance */ public Builder map(boolean isMap) { this.isMap = isMap; return this; } /** * Add an argument to the {@link DiscoveredMethod}. * * @param argument the argument to add to the {@link DiscoveredMethod} * @return updated builder instance */ public Builder addArgument(SchemaArgument argument) { listArguments.add(argument); return this; } /** * Set the actual method. * @param method the actual method * @return updated builder instance */ public Builder method(Method method) { this.method = method; return this; } /** * Set the number of array levels if return type is an array. * * @param arrayLevels the number of array levels if return type is an array * @return updated builder instance */ public Builder arrayLevels(int arrayLevels) { this.arrayLevels = arrayLevels; return this; } /** * Set the source on which the method should be added. * @param source the source on which the method should be added * @return updated builder instance */ public Builder source(String source) { this.source = source; return this; } /** * The property name if this property is a getter. * @param propertyName property name if this property is a getter * @return updated builder instance */ public Builder propertyName(String propertyName) { this.propertyName = propertyName; return this; } /** * Indicates if the method containing the {@link Source} annotation was also annotated with the {@link Query} * annotation. * * @param isQueryAnnotated if the method containing the {@link Source} annotation was also annotated * @return updated builder instance */ public Builder queryAnnotated(boolean isQueryAnnotated) { this.isQueryAnnotated = isQueryAnnotated; return this; } /** * Set the format for a number or date. * * @param format the format for a number or date * @return updated builder instance */ public Builder format(String[] format) { if (format == null) { this.format = null; } else { this.format = new String[format.length]; System.arraycopy(format, 0, this.format, 0, this.format.length); } return this; } /** * Set the description. * * @param description the description of the {@link DiscoveredMethod} * @return updated builder instance */ public Builder description(String description) { this.description = description; return this; } /** * Set if the return type is mandatory. * * @param isReturnTypeMandatory true if the return type is mandatory. * @return updated builder instance */ public Builder returnTypeMandatory(boolean isReturnTypeMandatory) { this.isReturnTypeMandatory = isReturnTypeMandatory; return this; } /** * Set the default value for this {@link DiscoveredMethod}. * * @param defaultValue the default value for this field definition * @return updated builder instance */ public Builder defaultValue(Object defaultValue) { this.defaultValue = defaultValue; return this; } /** * Set the original array inner type if it is array type. * * @param originalArrayType the original array inner type if it is array type * @return updated builder instance */ public Builder originalArrayType(Class originalArrayType) { this.originalArrayType = originalArrayType; return this; } /** * Set if the format is of type Jsonb. * * @param isJsonbFormat if the format is of type Jsonb. * @return updated builder instance */ public Builder jsonbFormat(boolean isJsonbFormat) { this.isJsonbFormat = isJsonbFormat; return this; } /** * Set if the property name is of type Jsonb. * * @param isJsonbProperty if the property name is of type Jsonb. * @return updated builder instance */ public Builder jsonbProperty(boolean isJsonbProperty) { this.isJsonbProperty = isJsonbProperty; return this; } @Override public DiscoveredMethod build() { return new DiscoveredMethod(this); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy