io.helidon.codegen.apt.AptProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-codegen-apt Show documentation
Show all versions of helidon-codegen-apt Show documentation
Tools for annotation processing
/*
* 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 extends TypeElement> 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 extends TypeElement> 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 extends TypeElement> 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 extends Element> 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);
}
}
}