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

io.helidon.codegen.apt.AptProcessor Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
/*
 * Copyright (c) 2023, 2024 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.codegen.apt;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

import io.helidon.codegen.Codegen;
import io.helidon.codegen.CodegenEvent;
import io.helidon.codegen.Option;
import io.helidon.common.types.TypeInfo;
import io.helidon.common.types.TypeName;

import static java.lang.System.Logger.Level.TRACE;
import static java.lang.System.Logger.Level.WARNING;

/**
 * Annotation processor that maps APT types to Helidon types, and invokes {@link io.helidon.codegen.Codegen}.
 */
public final class AptProcessor extends AbstractProcessor {
    private static final TypeName GENERATOR = TypeName.create(AptProcessor.class);

    private AptContext ctx;
    private Codegen codegen;

    /**
     * Only for {@link java.util.ServiceLoader}, to be loaded by compiler.
     */
    @Deprecated
    public AptProcessor() {
        super();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set getSupportedAnnotationTypes() {
        return Stream.concat(codegen.supportedAnnotations()
                                     .stream()
                                     .map(TypeName::fqName),
                             codegen.supportedAnnotationPackagePrefixes()
                                     .stream()
                                     .map(it -> it + "*"))
                .collect(Collectors.toSet());
    }

    @Override
    public Set getSupportedOptions() {
        return Codegen.supportedOptions()
                .stream()
                .map(Option::name)
                .collect(Collectors.toUnmodifiableSet());
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        this.ctx = AptContext.create(processingEnv, Codegen.supportedOptions());
        this.codegen = Codegen.create(ctx, GENERATOR);
    }

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        Thread thread = Thread.currentThread();
        ClassLoader previousClassloader = thread.getContextClassLoader();
        thread.setContextClassLoader(AptProcessor.class.getClassLoader());

        // we want everything to execute in the classloader of this type, so service loaders
        // use the classpath of the annotation processor, and not some "random" classloader, such as a maven one
        try {
            doProcess(annotations, roundEnv);
            return true;
        } finally {
            thread.setContextClassLoader(previousClassloader);
        }
    }

    private void doProcess(Set annotations, RoundEnvironment roundEnv) {
        ctx.logger().log(TRACE, "Process annotations: " + annotations + ", processing over: " + roundEnv.processingOver());

        if (roundEnv.processingOver()) {
            codegen.processingOver();
            return;
        }

        if (annotations.isEmpty()) {
            // no annotations, no types, still call the codegen, maybe it has something to do
            codegen.process(List.of());
            return;
        }

        List allTypes = discoverTypes(annotations, roundEnv);
        codegen.process(allTypes);
    }

    private List discoverTypes(Set annotations, RoundEnvironment roundEnv) {
        // we must discover all types that should be handled, create TypeInfo and only then check if these should be processed
        // as we may replace annotations, elements, and whole types.

        // first collect all types (group by type name, so we do not have duplicity)
        Map types = new HashMap<>();

        for (TypeElement annotation : annotations) {
            Set elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(annotation);
            for (Element element : elementsAnnotatedWith) {
                ElementKind kind = element.getKind();
                switch (kind) {
                case ENUM, INTERFACE, CLASS, ANNOTATION_TYPE, RECORD -> addType(types, element, element, annotation);
                case ENUM_CONSTANT, CONSTRUCTOR, METHOD, FIELD, STATIC_INIT, INSTANCE_INIT, RECORD_COMPONENT ->
                        addType(types, element.getEnclosingElement(), element, annotation);
                case PARAMETER -> addType(types, element.getEnclosingElement().getEnclosingElement(), element, annotation);
                default -> ctx.logger().log(TRACE, "Ignoring annotated element, not supported: " + element + ", kind: " + kind);
                }
            }
        }

        return types.values()
                .stream()
                .flatMap(element -> {
                    Optional typeInfo = AptTypeInfoFactory.create(ctx, element);

                    if (typeInfo.isEmpty()) {
                        ctx.logger().log(CodegenEvent.builder()
                                                 .level(WARNING)
                                                 .message("Could not create TypeInfo for annotated type.")
                                                 .addObject(element)
                                                 .build());
                    }
                    return typeInfo.stream();
                })
                .toList();
    }

    private void addType(Map types,
                         Element typeElement,
                         Element processedElement,
                         TypeElement annotation) {
        Optional typeName = AptTypeFactory.createTypeName(typeElement);
        if (typeName.isPresent()) {
            types.putIfAbsent(typeName.get(), (TypeElement) typeElement);
        } else {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING,
                                                     "Could not create TypeName for annotated type."
                                                             + " Annotation: " + annotation,
                                                     processedElement);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy