Skip to content

Commit fc2e0bb

Browse files
authored
4.x: Udpates to types and annotation processing (helidon-io#9168)
* APT codegen - removing public methods from package private class - deprecating all public types, as these should not be used outside of this module (only the processor is public, as it is a ServiceLoader provider implmentation) - caching TypeInfo within a single annotation processing round - now handles all processed types, to be able to trigger based on "meta" annotations Class model: Bugfix in class model, where content support used deprecated (and failing) method. Added support for volatile fields Types: Updated with latest behavior of builder (mutated lists) * Fix metadata codegen, to only store the file if there are modules available (i.e. never store empty array).
1 parent 6665d7c commit fc2e0bb

17 files changed

Lines changed: 742 additions & 117 deletions

File tree

codegen/apt/src/main/java/io/helidon/codegen/apt/AptAnnotationFactory.java

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,10 @@
1717
package io.helidon.codegen.apt;
1818

1919
import java.util.LinkedHashMap;
20-
import java.util.LinkedHashSet;
21-
import java.util.List;
2220
import java.util.Map;
23-
import java.util.Set;
24-
import java.util.stream.Collectors;
2521

2622
import javax.lang.model.element.AnnotationMirror;
2723
import javax.lang.model.element.AnnotationValue;
28-
import javax.lang.model.element.Element;
2924
import javax.lang.model.element.ExecutableElement;
3025
import javax.lang.model.util.Elements;
3126

@@ -35,34 +30,11 @@
3530
/**
3631
* Factory for annotations.
3732
*/
33+
@SuppressWarnings("removal")
3834
final class AptAnnotationFactory {
3935
private AptAnnotationFactory() {
4036
}
4137

42-
/**
43-
* Creates a set of annotations using annotation processor.
44-
*
45-
* @param annoMirrors the annotation type mirrors
46-
* @param elements annotation processing element utils
47-
* @return the annotation value set
48-
*/
49-
public static Set<Annotation> createAnnotations(List<? extends AnnotationMirror> annoMirrors, Elements elements) {
50-
return annoMirrors.stream()
51-
.map(it -> createAnnotation(it, elements))
52-
.collect(Collectors.toCollection(LinkedHashSet::new));
53-
}
54-
55-
/**
56-
* Creates a set of annotations based using annotation processor.
57-
*
58-
* @param type the enclosing/owing type element
59-
* @param elements annotation processing element utils
60-
* @return the annotation value set
61-
*/
62-
public static Set<Annotation> createAnnotations(Element type, Elements elements) {
63-
return createAnnotations(type.getAnnotationMirrors(), elements);
64-
}
65-
6638
/**
6739
* Creates an instance from an annotation mirror during annotation processing.
6840
*

codegen/apt/src/main/java/io/helidon/codegen/apt/AptContext.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616

1717
package io.helidon.codegen.apt;
1818

19+
import java.util.Optional;
1920
import java.util.Set;
21+
import java.util.function.Supplier;
2022

2123
import javax.annotation.processing.ProcessingEnvironment;
2224

2325
import io.helidon.codegen.CodegenContext;
2426
import io.helidon.codegen.Option;
27+
import io.helidon.common.types.TypeInfo;
28+
import io.helidon.common.types.TypeName;
2529

2630
/**
2731
* Annotation processing code generation context.
32+
* @deprecated this API will be package local in the future, use through Helidon codegen only
2833
*/
34+
@Deprecated(forRemoval = true, since = "4.1.0")
2935
public interface AptContext extends CodegenContext {
3036
/**
3137
* Create context from the processing environment, and a set of additional supported options.
@@ -44,4 +50,14 @@ static AptContext create(ProcessingEnvironment env, Set<Option<?>> options) {
4450
* @return environment
4551
*/
4652
ProcessingEnvironment aptEnv();
53+
54+
/**
55+
* Get a cached instance of the type info, and if not cached, cache the provided one.
56+
* Only type infos known not to be modified during this build are cached.
57+
*
58+
* @param typeName type name
59+
* @param typeInfoSupplier supplier of value if it is not yet cached
60+
* @return type info for that name, in case the type info cannot be created, an empty optional
61+
*/
62+
Optional<TypeInfo> cache(TypeName typeName, Supplier<Optional<TypeInfo>> typeInfoSupplier);
4763
}

codegen/apt/src/main/java/io/helidon/codegen/apt/AptContextImpl.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import java.io.IOException;
2020
import java.io.InputStream;
2121
import java.net.URI;
22+
import java.util.HashMap;
23+
import java.util.Map;
2224
import java.util.Optional;
2325
import java.util.Set;
2426
import java.util.function.Predicate;
27+
import java.util.function.Supplier;
2528
import java.util.regex.Matcher;
2629
import java.util.regex.Pattern;
2730

@@ -40,11 +43,14 @@
4043
import io.helidon.common.types.TypeName;
4144
import io.helidon.common.types.TypedElementInfo;
4245

46+
@SuppressWarnings("removal")
4347
class AptContextImpl extends CodegenContextBase implements AptContext {
4448
private static final Pattern SCOPE_PATTERN = Pattern.compile("(\\w+).*classes");
4549

4650
private final ProcessingEnvironment env;
4751
private final ModuleInfo moduleInfo;
52+
private final Map<TypeName, Optional<TypeInfo>> safeTypeCache = new HashMap<>();
53+
private final Map<TypeName, Optional<TypeInfo>> typeCache = new HashMap<>();
4854

4955
AptContextImpl(ProcessingEnvironment env,
5056
CodegenOptions options,
@@ -59,7 +65,7 @@ class AptContextImpl extends CodegenContextBase implements AptContext {
5965
this.moduleInfo = moduleInfo;
6066
}
6167

62-
static AptContext create(ProcessingEnvironment env, Set<Option<?>> supportedOptions) {
68+
static AptContextImpl create(ProcessingEnvironment env, Set<Option<?>> supportedOptions) {
6369
CodegenOptions options = AptOptions.create(env);
6470

6571
CodegenScope scope = guessScope(env, options);
@@ -81,11 +87,16 @@ public ProcessingEnvironment aptEnv() {
8187

8288
@Override
8389
public Optional<TypeInfo> typeInfo(TypeName typeName) {
90+
if (typeCache.containsKey(typeName)) {
91+
return typeCache.get(typeName);
92+
}
93+
// cached by the factory
8494
return AptTypeInfoFactory.create(this, typeName);
8595
}
8696

8797
@Override
8898
public Optional<TypeInfo> typeInfo(TypeName typeName, Predicate<TypedElementInfo> elementPredicate) {
99+
// cannot be cached
89100
return AptTypeInfoFactory.create(this, typeName, elementPredicate);
90101
}
91102

@@ -94,6 +105,39 @@ public Optional<ModuleInfo> module() {
94105
return Optional.ofNullable(moduleInfo);
95106
}
96107

108+
@Override
109+
public Optional<TypeInfo> cache(TypeName typeName, Supplier<Optional<TypeInfo>> typeInfoSupplier) {
110+
if (typeName.generic() || !typeName.typeArguments().isEmpty() || !typeName.typeParameters().isEmpty()) {
111+
// generic types cannot be cached
112+
return typeInfoSupplier.get();
113+
}
114+
115+
if (typeName.packageName().startsWith("java.")
116+
|| typeName.packageName().startsWith("javax.")
117+
|| typeName.packageName().startsWith("sun.")
118+
|| typeName.packageName().startsWith("com.sun")) {
119+
Optional<TypeInfo> typeInfo = safeTypeCache.get(typeName);
120+
if (typeInfo != null) {
121+
return typeInfo;
122+
}
123+
typeInfo = typeInfoSupplier.get();
124+
safeTypeCache.put(typeName, typeInfo);
125+
return typeInfo;
126+
}
127+
128+
Optional<TypeInfo> typeInfo = typeCache.get(typeName);
129+
if (typeInfo != null) {
130+
return typeInfo;
131+
}
132+
typeInfo = typeInfoSupplier.get();
133+
typeCache.put(typeName, typeInfo);
134+
return typeInfo;
135+
}
136+
137+
void resetCache() {
138+
typeCache.clear();
139+
}
140+
97141
private static Optional<ModuleInfo> findModule(Filer filer) {
98142
// expected is source location
99143
try {

codegen/apt/src/main/java/io/helidon/codegen/apt/AptProcessor.java

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
package io.helidon.codegen.apt;
1818

1919
import java.util.HashMap;
20+
import java.util.HashSet;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.Optional;
2324
import java.util.Set;
2425
import java.util.stream.Collectors;
25-
import java.util.stream.Stream;
2626

2727
import javax.annotation.processing.AbstractProcessor;
2828
import javax.annotation.processing.ProcessingEnvironment;
@@ -35,7 +35,9 @@
3535

3636
import io.helidon.codegen.Codegen;
3737
import io.helidon.codegen.CodegenEvent;
38+
import io.helidon.codegen.CodegenException;
3839
import io.helidon.codegen.Option;
40+
import io.helidon.common.types.Annotation;
3941
import io.helidon.common.types.TypeInfo;
4042
import io.helidon.common.types.TypeName;
4143

@@ -45,10 +47,11 @@
4547
/**
4648
* Annotation processor that maps APT types to Helidon types, and invokes {@link io.helidon.codegen.Codegen}.
4749
*/
50+
@SuppressWarnings("removal")
4851
public final class AptProcessor extends AbstractProcessor {
4952
private static final TypeName GENERATOR = TypeName.create(AptProcessor.class);
5053

51-
private AptContext ctx;
54+
private AptContextImpl ctx;
5255
private Codegen codegen;
5356

5457
/**
@@ -66,13 +69,8 @@ public SourceVersion getSupportedSourceVersion() {
6669

6770
@Override
6871
public Set<String> getSupportedAnnotationTypes() {
69-
return Stream.concat(codegen.supportedAnnotations()
70-
.stream()
71-
.map(TypeName::fqName),
72-
codegen.supportedAnnotationPackagePrefixes()
73-
.stream()
74-
.map(it -> it + "*"))
75-
.collect(Collectors.toSet());
72+
// we need to support all annotations, to be able to use meta-annotations
73+
return Set.of("*");
7674
}
7775

7876
@Override
@@ -87,12 +85,14 @@ public Set<String> getSupportedOptions() {
8785
public synchronized void init(ProcessingEnvironment processingEnv) {
8886
super.init(processingEnv);
8987

90-
this.ctx = AptContext.create(processingEnv, Codegen.supportedOptions());
88+
this.ctx = AptContextImpl.create(processingEnv, Codegen.supportedOptions());
9189
this.codegen = Codegen.create(ctx, GENERATOR);
9290
}
9391

9492
@Override
9593
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
94+
this.ctx.resetCache();
95+
9696
Thread thread = Thread.currentThread();
9797
ClassLoader previousClassloader = thread.getContextClassLoader();
9898
thread.setContextClassLoader(AptProcessor.class.getClassLoader());
@@ -102,6 +102,19 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
102102
try {
103103
doProcess(annotations, roundEnv);
104104
return true;
105+
} catch (CodegenException e) {
106+
Object originatingElement = e.originatingElement()
107+
.orElse(null);
108+
if (originatingElement instanceof Element element) {
109+
processingEnv.getMessager().printError(e.getMessage(), element);
110+
} else if (originatingElement instanceof TypeName typeName) {
111+
processingEnv.getMessager().printError(e.getMessage() + ", source: " + typeName.fqName());
112+
} else {
113+
if (originatingElement != null) {
114+
processingEnv.getMessager().printError(e.getMessage() + ", source: " + originatingElement);
115+
}
116+
}
117+
throw e;
105118
} finally {
106119
thread.setContextClassLoader(previousClassloader);
107120
}
@@ -115,32 +128,96 @@ private void doProcess(Set<? extends TypeElement> annotations, RoundEnvironment
115128
return;
116129
}
117130

118-
if (annotations.isEmpty()) {
131+
Set<UsedAnnotation> usedAnnotations = usedAnnotations(annotations);
132+
133+
if (usedAnnotations.isEmpty()) {
119134
// no annotations, no types, still call the codegen, maybe it has something to do
120135
codegen.process(List.of());
121136
return;
122137
}
123138

124-
List<TypeInfo> allTypes = discoverTypes(annotations, roundEnv);
139+
List<TypeInfo> allTypes = discoverTypes(usedAnnotations, roundEnv);
125140
codegen.process(allTypes);
126141
}
127142

128-
private List<TypeInfo> discoverTypes(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
143+
private Set<UsedAnnotation> usedAnnotations(Set<? extends TypeElement> annotations) {
144+
var exactTypes = codegen.supportedAnnotations()
145+
.stream()
146+
.map(TypeName::fqName)
147+
.collect(Collectors.toSet());
148+
var prefixes = codegen.supportedAnnotationPackagePrefixes();
149+
150+
Set<UsedAnnotation> result = new HashSet<>();
151+
152+
for (TypeElement annotation : annotations) {
153+
TypeName typeName = TypeName.create(annotation.getQualifiedName().toString());
154+
155+
/*
156+
find meta annotations that are supported:
157+
- annotation that annotates the current annotation
158+
*/
159+
Set<TypeName> supportedAnnotations = new HashSet<>();
160+
if (supportedAnnotation(exactTypes, prefixes, typeName)) {
161+
supportedAnnotations.add(typeName);
162+
}
163+
addSupportedAnnotations(exactTypes, prefixes, supportedAnnotations, typeName);
164+
if (!supportedAnnotations.isEmpty()) {
165+
result.add(new UsedAnnotation(typeName, annotation, supportedAnnotations));
166+
}
167+
}
168+
169+
return result;
170+
}
171+
172+
private boolean supportedAnnotation(Set<String> exactTypes, Set<String> prefixes, TypeName annotationType) {
173+
if (exactTypes.contains(annotationType.fqName())) {
174+
return true;
175+
}
176+
String packagePrefix = annotationType.packageName() + ".";
177+
for (String prefix : prefixes) {
178+
if (packagePrefix.startsWith(prefix)) {
179+
return true;
180+
}
181+
}
182+
return false;
183+
}
184+
185+
private void addSupportedAnnotations(Set<String> exactTypes,
186+
Set<String> prefixes,
187+
Set<TypeName> supportedAnnotations,
188+
TypeName annotationType) {
189+
Optional<TypeInfo> foundInfo = AptTypeInfoFactory.create(ctx, annotationType);
190+
if (foundInfo.isPresent()) {
191+
TypeInfo annotationInfo = foundInfo.get();
192+
List<Annotation> annotations = annotationInfo.annotations();
193+
for (Annotation annotation : annotations) {
194+
TypeName typeName = annotation.typeName();
195+
if (supportedAnnotation(exactTypes, prefixes, typeName)) {
196+
if (supportedAnnotations.add(typeName)) {
197+
addSupportedAnnotations(exactTypes, prefixes, supportedAnnotations, typeName);
198+
}
199+
}
200+
}
201+
}
202+
}
203+
204+
private List<TypeInfo> discoverTypes(Set<UsedAnnotation> annotations, RoundEnvironment roundEnv) {
129205
// we must discover all types that should be handled, create TypeInfo and only then check if these should be processed
130206
// as we may replace annotations, elements, and whole types.
131207

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

135-
for (TypeElement annotation : annotations) {
136-
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(annotation);
211+
for (UsedAnnotation annotation : annotations) {
212+
TypeElement annotationElement = annotation.annotationElement();
213+
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(annotationElement);
137214
for (Element element : elementsAnnotatedWith) {
138215
ElementKind kind = element.getKind();
139216
switch (kind) {
140-
case ENUM, INTERFACE, CLASS, ANNOTATION_TYPE, RECORD -> addType(types, element, element, annotation);
217+
case ENUM, INTERFACE, CLASS, ANNOTATION_TYPE, RECORD -> addType(types, element, element, annotationElement);
141218
case ENUM_CONSTANT, CONSTRUCTOR, METHOD, FIELD, STATIC_INIT, INSTANCE_INIT, RECORD_COMPONENT ->
142-
addType(types, element.getEnclosingElement(), element, annotation);
143-
case PARAMETER -> addType(types, element.getEnclosingElement().getEnclosingElement(), element, annotation);
219+
addType(types, element.getEnclosingElement(), element, annotationElement);
220+
case PARAMETER -> addType(types, element.getEnclosingElement().getEnclosingElement(), element, annotationElement);
144221
default -> ctx.logger().log(TRACE, "Ignoring annotated element, not supported: " + element + ", kind: " + kind);
145222
}
146223
}
@@ -177,4 +254,16 @@ private void addType(Map<TypeName, TypeElement> types,
177254
processedElement);
178255
}
179256
}
257+
258+
/**
259+
* Annotation that annotates a processed type and that must be processed.
260+
*
261+
* @param annotationType annotation on processed type
262+
* @param annotationElement element of the annotation
263+
* @param supportedAnnotations annotations that are supported (either the actual annotation, or meta-annotations)
264+
*/
265+
private record UsedAnnotation(TypeName annotationType,
266+
TypeElement annotationElement,
267+
Set<TypeName> supportedAnnotations) {
268+
}
180269
}

0 commit comments

Comments
 (0)