protected void findComponentClassProperties()

in tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java [917:1123]


    protected void findComponentClassProperties(
            ComponentModel componentModel, Class<?> classElement,
            String prefix, String nestedTypeName, String nestedFieldName) {
        final Class<?> orgClassElement = classElement;
        Set<String> excludes = new HashSet<>();
        while (true) {
            processMetadataClassAnnotation(componentModel, classElement, excludes);

            List<Method> methods = findCandidateClassMethods(classElement);

            // if the component has options with annotations then we only want to generate options that are annotated
            // as ideally components should favour doing this, so we can control what is an option and what is not
            List<Field> fields = Stream.of(classElement.getDeclaredFields()).toList();
            boolean annotationBasedOptions = fields.stream().anyMatch(f -> f.getAnnotation(Metadata.class) != null)
                    || methods.stream().anyMatch(m -> m.getAnnotation(Metadata.class) != null);

            if (!methods.isEmpty() && !annotationBasedOptions) {
                getLog().warn("Component class " + classElement.getName() + " has not been marked up with @Metadata for "
                              + methods.size() + " options.");
            }

            for (Method method : methods) {
                String methodName = method.getName();
                Metadata metadata = method.getAnnotation(Metadata.class);
                boolean deprecated = method.getAnnotation(Deprecated.class) != null;
                String deprecationNote = null;
                if (metadata != null) {
                    deprecationNote = metadata.deprecationNote();
                }

                // we usually favor putting the @Metadata annotation on the
                // field instead of the setter, so try to use it if its there
                String fieldName = methodName.substring(3);
                fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
                Field fieldElement = getFieldElement(classElement, fieldName);
                if (fieldElement != null && metadata == null) {
                    metadata = fieldElement.getAnnotation(Metadata.class);
                }
                if (metadata != null && metadata.skip()) {
                    continue;
                }

                // skip methods/fields which has no annotation if we only look for annotation based
                if (annotationBasedOptions && metadata == null) {
                    continue;
                }

                // if the field type is a nested parameter then iterate
                // through its fields
                if (fieldElement != null) {
                    Class<?> fieldTypeElement = fieldElement.getType();
                    String fieldTypeName = getTypeName(GenericsUtil.resolveType(orgClassElement, fieldElement));
                    UriParams fieldParams = fieldTypeElement.getAnnotation(UriParams.class);
                    if (fieldParams != null) {
                        String nestedPrefix = prefix;
                        String extraPrefix = fieldParams.prefix();
                        if (!Strings.isNullOrEmpty(extraPrefix)) {
                            nestedPrefix += extraPrefix;
                        }
                        nestedTypeName = fieldTypeName;
                        nestedFieldName = fieldElement.getName();
                        findClassProperties(componentModel, fieldTypeElement, Collections.emptySet(), nestedPrefix,
                                nestedTypeName, nestedFieldName, true);
                        nestedTypeName = null;
                        nestedFieldName = null;
                        // we also want to include the configuration itself so continue and add ourselves
                    }
                }

                boolean required = metadata != null && metadata.required();
                String label = metadata != null ? metadata.label() : null;
                boolean secret = metadata != null && metadata.secret();
                boolean autowired = metadata != null && metadata.autowired();
                boolean supportFileReference = metadata != null && metadata.supportFileReference();
                boolean largeInput = metadata != null && metadata.largeInput();
                String inputLanguage = metadata != null ? metadata.inputLanguage() : null;

                // we do not yet have default values / notes / as no annotation
                // support yet
                // String defaultValueNote = param.defaultValueNote();
                Object defaultValue = metadata != null ? metadata.defaultValue() : "";
                String defaultValueNote = null;

                String name = prefix + fieldName;
                String displayName = metadata != null ? metadata.displayName() : null;
                // compute a display name if we don't have anything
                if (Strings.isNullOrEmpty(displayName)) {
                    displayName = Strings.asTitle(name);
                }

                Class<?> fieldType = method.getParameters()[0].getType();
                String fieldTypeName = getTypeName(GenericsUtil.resolveParameterTypes(orgClassElement, method)[0]);

                String docComment = findJavaDoc(method, fieldName, name, classElement, false);
                if (Strings.isNullOrEmpty(docComment)) {
                    docComment = metadata != null ? metadata.description() : null;
                }
                if (Strings.isNullOrEmpty(docComment)) {
                    // apt cannot grab javadoc from camel-core, only from
                    // annotations
                    if ("setHeaderFilterStrategy".equals(methodName)) {
                        docComment = HEADER_FILTER_STRATEGY_JAVADOC;
                    } else {
                        docComment = "";
                    }
                }

                // gather enums
                List<String> enums = getEnums(metadata, fieldType);

                // the field type may be overloaded by another type
                boolean isDuration = false;
                if (metadata != null && !Strings.isNullOrEmpty(metadata.javaType())) {
                    String mjt = metadata.javaType();
                    if ("java.time.Duration".equals(mjt)) {
                        isDuration = true;
                    } else {
                        fieldTypeName = mjt;
                    }
                }

                // generics for collection types
                String nestedType = null;
                String desc = fieldTypeName;
                if (desc.contains("<") && desc.contains(">")) {
                    desc = Strings.between(desc, "<", ">");
                    // if it has additional nested types, then we only want the outer type
                    int pos = desc.indexOf('<');
                    if (pos != -1) {
                        desc = desc.substring(0, pos);
                    }
                    // if its a map then it has a key/value, so we only want the last part
                    pos = desc.indexOf(',');
                    if (pos != -1) {
                        desc = desc.substring(pos + 1);
                    }
                    desc = desc.replace('$', '.');
                    desc = desc.trim();
                    // skip if the type is generic or a wildcard
                    if (!desc.isEmpty() && desc.indexOf('?') == -1 && !desc.contains(" extends ")) {
                        nestedType = desc;
                    }
                }

                // prepare default value so its value is correct according to its type
                defaultValue = getDefaultValue(defaultValue, fieldTypeName, isDuration);

                String group = EndpointHelper.labelAsGroupName(label, componentModel.isConsumerOnly(),
                        componentModel.isProducerOnly());
                // filter out consumer/producer only
                boolean accept = !excludes.contains(name);
                if (componentModel.isConsumerOnly() && "producer".equals(group)) {
                    accept = false;
                } else if (componentModel.isProducerOnly() && "consumer".equals(group)) {
                    accept = false;
                }
                if (accept) {
                    Optional<ComponentOptionModel> prev = componentModel.getComponentOptions().stream()
                            .filter(opt -> name.equals(opt.getName())).findAny();
                    if (prev.isPresent()) {
                        String prv = prev.get().getJavaType();
                        String cur = fieldTypeName;
                        if (prv.equals("java.lang.String")
                                || prv.equals("java.lang.String[]") && cur.equals("java.util.Collection<java.lang.String>")) {
                            componentModel.getComponentOptions().remove(prev.get());
                        } else {
                            accept = false;
                        }
                    }
                }
                if (accept) {
                    ComponentOptionModel option = new ComponentOptionModel();
                    option.setKind("property");
                    option.setName(name);
                    option.setDisplayName(displayName);
                    option.setType(MojoHelper.getType(fieldTypeName, false, isDuration));
                    option.setJavaType(fieldTypeName);
                    option.setRequired(required);
                    option.setDefaultValue(defaultValue);
                    option.setDefaultValueNote(defaultValueNote);
                    option.setDescription(docComment.trim());
                    option.setDeprecated(deprecated);
                    option.setDeprecationNote(deprecationNote);
                    option.setSecret(secret);
                    option.setAutowired(autowired);
                    option.setGroup(group);
                    option.setLabel(label);
                    option.setEnums(enums);
                    option.setNestedType(nestedType);
                    option.setConfigurationClass(nestedTypeName);
                    option.setConfigurationField(nestedFieldName);
                    option.setSupportFileReference(supportFileReference);
                    option.setLargeInput(largeInput);
                    option.setInputLanguage(inputLanguage);
                    componentModel.addComponentOption(option);
                }
            }

            // check super classes which may also have fields
            Class<?> superclass = classElement.getSuperclass();
            if (superclass != null && superclass != Object.class) {
                classElement = superclass;
            } else {
                break;
            }
        }
    }