if relevant: {@link io.helidon.common.types.TypeName#enclosingNames()}
+ *
+ *
+ * @return raw type of this type info
+ */
+ TypeName rawType();
+
+ /**
+ * The declared type name, including type parameters.
+ *
+ * @return type name with declared type parameters
+ */
+ TypeName declaredType();
+
/**
* Description, such as javadoc, if available.
*
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoSupport.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoSupport.java
index a054c002053..952cd31cc0e 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoSupport.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Oracle and/or its affiliates.
+ * 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.
@@ -68,6 +68,18 @@ public void decorate(TypeInfo.BuilderBase, ?> target) {
target.addModifier(typeModifier.modifierName());
}
target.addModifier(target.accessModifier().get().modifierName());
+
+ // new methods, simplify for tests
+ if (target.rawType().isEmpty()) {
+ target.typeName()
+ .map(TypeName::genericTypeName)
+ .ifPresent(target::rawType);
+ }
+ if (target.declaredType().isEmpty()) {
+ // this may not be correct, but is correct for all types that do not have any declaration of generics
+ // so it simplifies a lot of use cases
+ target.rawType().ifPresent(target::declaredType);
+ }
}
}
}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java
index 1d9d5fa7118..d712863b5b4 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java
@@ -44,7 +44,7 @@
*
{@link #declaredName()} and {@link #resolvedName()}.
*
*/
-@Prototype.Blueprint
+@Prototype.Blueprint(decorator = TypeNameSupport.Decorator.class)
@Prototype.CustomMethods(TypeNameSupport.class)
@Prototype.Implement("java.lang.Comparable")
interface TypeNameBlueprint {
@@ -137,11 +137,39 @@ default String classNameWithEnclosingNames() {
* if {@link #typeArguments()} exist, this list MUST exist and have the same size and order (it maps the name to the type).
*
* @return type parameter names as declared on this type, or names that represent the {@link #typeArguments()}
+ * @deprecated the {@link io.helidon.common.types.TypeName#typeArguments()} will contain all required information
*/
@Option.Singular
@Option.Redundant
+ @Deprecated(forRemoval = true, since = "4.2.0")
List typeParameters();
+ /**
+ * Generic types that provide keyword {@code extends} will have a lower bound defined.
+ * Each lower bound may be a real type, or another generic type.
+ *
+ * This list may only have value if this is a generic type.
+ *
+ * @return list of lower bounds of this type
+ * @see io.helidon.common.types.TypeName#generic()
+ */
+ @Option.Singular
+ @Option.Redundant
+ List lowerBounds();
+
+ /**
+ * Generic types that provide keyword {@code super} will have an upper bound defined.
+ * Upper bound may be a real type, or another generic type.
+ *
+ * This list may only have value if this is a generic type.
+ *
+ * @return list of upper bounds of this type
+ * @see io.helidon.common.types.TypeName#generic()
+ */
+ @Option.Singular
+ @Option.Redundant
+ List upperBounds();
+
/**
* Indicates whether this type is a {@code java.util.List}.
*
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java
index e8a9d38809b..7c87d4eaced 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java
@@ -16,6 +16,7 @@
package io.helidon.common.types;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -23,9 +24,11 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.helidon.builder.api.Prototype;
+import io.helidon.common.GenericType;
final class TypeNameSupport {
private static final TypeName PRIMITIVE_BOOLEAN = TypeName.create(boolean.class);
@@ -141,59 +144,57 @@ static String fqName(TypeName instance) {
@Prototype.PrototypeMethod
@Prototype.Annotated("java.lang.Override") // defined on blueprint
static String resolvedName(TypeName instance) {
- String name = calcName(instance, ".");
- boolean isObject = Object.class.getName().equals(name) || "?".equals(name);
- StringBuilder nameBuilder = (isObject)
- ? new StringBuilder(instance.wildcard() ? "?" : name)
- : new StringBuilder(instance.wildcard() ? "? extends " + name : name);
-
- if (!instance.typeArguments().isEmpty()) {
- nameBuilder.append("<");
- int i = 0;
- for (TypeName param : instance.typeArguments()) {
- if (i > 0) {
- nameBuilder.append(", ");
- }
- nameBuilder.append(param.resolvedName());
- i++;
- }
- nameBuilder.append(">");
+ if (instance.generic() || instance.wildcard()) {
+ return resolveGenericName(instance);
}
-
- if (instance.array()) {
- nameBuilder.append("[]");
- }
-
- return nameBuilder.toString();
+ return resolveClassName(instance);
}
/**
* Update builder from the provided type.
*
* @param builder builder to update
- * @param type type to get information (package name, class name, primitive, array)
+ * @param type type to get information (package name, class name, primitive, array)
*/
@Prototype.BuilderMethod
static void type(TypeName.BuilderBase, ?> builder, Type type) {
Objects.requireNonNull(type);
if (type instanceof Class> classType) {
- Class> componentType = classType.isArray() ? classType.getComponentType() : classType;
- builder.packageName(componentType.getPackageName());
- builder.className(componentType.getSimpleName());
- builder.primitive(componentType.isPrimitive());
- builder.array(classType.isArray());
-
- Class> enclosingClass = classType.getEnclosingClass();
- LinkedList enclosingTypes = new LinkedList<>();
- while (enclosingClass != null) {
- enclosingTypes.addFirst(enclosingClass.getSimpleName());
- enclosingClass = enclosingClass.getEnclosingClass();
+ updateFromClass(builder, classType);
+ return;
+ }
+ Type reflectGenericType = type;
+
+ if (type instanceof GenericType> gt) {
+ if (gt.isClass()) {
+ // simple case - just a class
+ updateFromClass(builder, gt.rawType());
+ return;
+ } else {
+ // complex case - has generic type arguments
+ reflectGenericType = gt.type();
}
- builder.enclosingNames(enclosingTypes);
- } else {
- // todo
- throw new IllegalArgumentException("Currently we only support class as a parameter, but got: " + type);
}
+
+ // translate the generic type into type name
+ if (reflectGenericType instanceof ParameterizedType pt) {
+ Type raw = pt.getRawType();
+ if (raw instanceof Class> theClass) {
+ updateFromClass(builder, theClass);
+ } else {
+ throw new IllegalArgumentException("Raw type of a ParameterizedType is not a class: " + raw.getClass().getName()
+ + ", for " + pt.getTypeName());
+ }
+
+ Type[] actualTypeArguments = pt.getActualTypeArguments();
+ for (Type actualTypeArgument : actualTypeArguments) {
+ builder.addTypeArgument(TypeName.create(actualTypeArgument));
+ }
+ return;
+ }
+
+ throw new IllegalArgumentException("We can only create a type from a class, GenericType, or a ParameterizedType,"
+ + " but got: " + reflectGenericType.getClass().getName());
}
/**
@@ -309,6 +310,71 @@ static TypeName createFromGenericDeclaration(String genericAliasTypeName) {
.build();
}
+ private static String resolveGenericName(TypeName instance) {
+ // ?, ? super Something; ? extends Something
+ String prefix = instance.wildcard() ? "?" : instance.className();
+ if (instance.upperBounds().isEmpty() && instance.lowerBounds().isEmpty()) {
+ return prefix;
+ }
+ if (instance.lowerBounds().isEmpty()) {
+ return prefix + " extends " + instance.upperBounds()
+ .stream()
+ .map(it -> {
+ if (it.generic()) {
+ return it.wildcard() ? "?" : it.className();
+ }
+ return it.resolvedName();
+ })
+ .collect(Collectors.joining(" & "));
+ }
+ TypeName lowerBound = instance.lowerBounds().getFirst();
+ if (lowerBound.generic()) {
+ return prefix + " super " + (lowerBound.wildcard() ? "?" : lowerBound.className());
+ }
+ return prefix + " super " + lowerBound.resolvedName();
+
+ }
+
+ private static String resolveClassName(TypeName instance) {
+ String name = calcName(instance, ".");
+ StringBuilder nameBuilder = new StringBuilder(name);
+
+ if (!instance.typeArguments().isEmpty()) {
+ nameBuilder.append("<");
+ int i = 0;
+ for (TypeName param : instance.typeArguments()) {
+ if (i > 0) {
+ nameBuilder.append(", ");
+ }
+ nameBuilder.append(param.resolvedName());
+ i++;
+ }
+ nameBuilder.append(">");
+ }
+
+ if (instance.array()) {
+ nameBuilder.append("[]");
+ }
+
+ return nameBuilder.toString();
+ }
+
+ private static void updateFromClass(TypeName.BuilderBase, ?> builder, Class> classType) {
+ Class> componentType = classType.isArray() ? classType.getComponentType() : classType;
+ builder.packageName(componentType.getPackageName());
+ builder.className(componentType.getSimpleName());
+ builder.primitive(componentType.isPrimitive());
+ builder.array(classType.isArray());
+
+ Class> enclosingClass = classType.getEnclosingClass();
+ LinkedList enclosingTypes = new LinkedList<>();
+ while (enclosingClass != null) {
+ enclosingTypes.addFirst(enclosingClass.getSimpleName());
+ enclosingClass = enclosingClass.getEnclosingClass();
+ }
+ builder.enclosingNames(enclosingTypes);
+ }
+
private static String calcName(TypeName instance, String typeSeparator) {
String className;
if (instance.enclosingNames().isEmpty()) {
@@ -320,4 +386,38 @@ private static String calcName(TypeName instance, String typeSeparator) {
return (instance.primitive() || instance.packageName().isEmpty())
? className : instance.packageName() + "." + className;
}
+
+ static class Decorator implements Prototype.BuilderDecorator> {
+ @Override
+ public void decorate(TypeName.BuilderBase, ?> target) {
+ fixWildcards(target);
+ }
+
+ private void fixWildcards(TypeName.BuilderBase, ?> target) {
+ // handle wildcards correct
+ if (target.wildcard()) {
+ if (target.upperBounds().size() == 1 && target.lowerBounds().isEmpty()) {
+ // backward compatible for (? extends X)
+ TypeName upperBound = target.upperBounds().getFirst();
+ target.className(upperBound.className());
+ target.packageName(upperBound.packageName());
+ target.enclosingNames(upperBound.enclosingNames());
+ }
+ // wildcard set, if package + class name as well, set them as upper bounds
+ if (target.className().isPresent()
+ && !target.className().get().equals("?")
+ && target.upperBounds().isEmpty()
+ && target.lowerBounds().isEmpty()) {
+ TypeName upperBound = TypeName.builder()
+ .from(target)
+ .wildcard(false)
+ .build();
+ if (!upperBound.equals(TypeNames.OBJECT)) {
+ target.addUpperBound(upperBound);
+ }
+ }
+ target.generic(true);
+ }
+ }
+ }
}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java
index f539e5849af..caf18dbf866 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java
@@ -171,6 +171,10 @@ public final class TypeNames {
* Type name of the type name.
*/
public static final TypeName TYPE_NAME = TypeName.create(TypeName.class);
+ /**
+ * Type name of the resolved type name.
+ */
+ public static final TypeName RESOLVED_TYPE_NAME = TypeName.create(ResolvedType.class);
/**
* Type name of typed element info.
*/
diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptProcessor.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptProcessor.java
index af10b1337d5..7716e72b89e 100644
--- a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptProcessor.java
+++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptProcessor.java
@@ -22,6 +22,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
@@ -100,8 +101,7 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
// 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;
+ return doProcess(annotations, roundEnv);
} catch (CodegenException e) {
Object originatingElement = e.originatingElement()
.orElse(null);
@@ -120,12 +120,12 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
}
}
- private void doProcess(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ private boolean doProcess(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
ctx.logger().log(TRACE, "Process annotations: " + annotations + ", processing over: " + roundEnv.processingOver());
if (roundEnv.processingOver()) {
codegen.processingOver();
- return;
+ return annotations.isEmpty();
}
Set usedAnnotations = usedAnnotations(annotations);
@@ -133,34 +133,39 @@ private void doProcess(Set extends TypeElement> annotations, RoundEnvironment
if (usedAnnotations.isEmpty()) {
// no annotations, no types, still call the codegen, maybe it has something to do
codegen.process(List.of());
- return;
+ return annotations.isEmpty();
}
List allTypes = discoverTypes(usedAnnotations, roundEnv);
codegen.process(allTypes);
+
+ return usedAnnotations.stream()
+ .map(UsedAnnotation::annotationElement)
+ .collect(Collectors.toSet())
+ .equals(annotations);
}
private Set usedAnnotations(Set extends TypeElement> annotations) {
- var exactTypes = codegen.supportedAnnotations()
- .stream()
- .map(TypeName::fqName)
- .collect(Collectors.toSet());
- var prefixes = codegen.supportedAnnotationPackagePrefixes();
+ var typePredicate = typePredicate(codegen.supportedAnnotations(), codegen.supportedAnnotationPackagePrefixes());
+ var metaPredicate = typePredicate(codegen.supportedMetaAnnotations(), Set.of());
Set result = new HashSet<>();
for (TypeElement annotation : annotations) {
TypeName typeName = TypeName.create(annotation.getQualifiedName().toString());
+ Set supportedAnnotations = new HashSet<>();
+ // first check direct support (through exact type or prefix)
+ if (typePredicate.test(typeName)) {
+ supportedAnnotations.add(typeName);
+ }
/*
find meta annotations that are supported:
- annotation that annotates the current annotation
*/
- Set supportedAnnotations = new HashSet<>();
- if (supportedAnnotation(exactTypes, prefixes, typeName)) {
- supportedAnnotations.add(typeName);
- }
- addSupportedAnnotations(exactTypes, prefixes, supportedAnnotations, typeName);
+ addSupportedAnnotations(metaPredicate, supportedAnnotations, typeName);
+
+ // and add all the annotations
if (!supportedAnnotations.isEmpty()) {
result.add(new UsedAnnotation(typeName, annotation, supportedAnnotations));
}
@@ -169,21 +174,23 @@ private Set usedAnnotations(Set extends TypeElement> annotatio
return result;
}
- private boolean supportedAnnotation(Set exactTypes, Set prefixes, TypeName annotationType) {
- if (exactTypes.contains(annotationType.fqName())) {
- return true;
- }
- String packagePrefix = annotationType.packageName() + ".";
- for (String prefix : prefixes) {
- if (packagePrefix.startsWith(prefix)) {
+ private Predicate typePredicate(Set typeNames, Set prefixes) {
+ return typeName -> {
+ if (typeNames.contains(typeName)) {
return true;
}
- }
- return false;
+
+ String packagePrefix = typeName.packageName() + ".";
+ for (String prefix : prefixes) {
+ if (packagePrefix.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ };
}
- private void addSupportedAnnotations(Set exactTypes,
- Set prefixes,
+ private void addSupportedAnnotations(Predicate typeNamePredicate,
Set supportedAnnotations,
TypeName annotationType) {
Optional foundInfo = AptTypeInfoFactory.create(ctx, annotationType);
@@ -192,9 +199,9 @@ private void addSupportedAnnotations(Set exactTypes,
List annotations = annotationInfo.annotations();
for (Annotation annotation : annotations) {
TypeName typeName = annotation.typeName();
- if (supportedAnnotation(exactTypes, prefixes, typeName)) {
+ if (typeNamePredicate.test(typeName)) {
if (supportedAnnotations.add(typeName)) {
- addSupportedAnnotations(exactTypes, prefixes, supportedAnnotations, typeName);
+ addSupportedAnnotations(typeNamePredicate, supportedAnnotations, typeName);
}
}
}
diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeFactory.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeFactory.java
index 19248d1600f..83ddf357e66 100644
--- a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeFactory.java
+++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeFactory.java
@@ -19,8 +19,12 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -33,10 +37,14 @@
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypeNames;
import static io.helidon.common.types.TypeName.createFromGenericDeclaration;
@@ -70,6 +78,10 @@ public static Optional createTypeName(DeclaredType type) {
* none or error)
*/
public static Optional createTypeName(TypeMirror typeMirror) {
+ return createTypeName(new HashSet<>(), typeMirror);
+ }
+
+ private static Optional createTypeName(Set inProgress, TypeMirror typeMirror) {
TypeKind kind = typeMirror.getKind();
if (kind.isPrimitive()) {
Class> type = switch (kind) {
@@ -92,9 +104,35 @@ public static Optional createTypeName(TypeMirror typeMirror) {
return Optional.of(TypeName.create(void.class));
}
case TYPEVAR -> {
- return Optional.of(createFromGenericDeclaration(typeMirror.toString()));
+ if (!inProgress.add(typeMirror)) {
+ return Optional.empty(); // prevent infinite loop
+ }
+
+ try {
+ var builder = TypeName.builder(createFromGenericDeclaration(typeMirror.toString()));
+
+ var typeVar = ((TypeVariable) typeMirror);
+ handleBounds(inProgress, typeVar.getUpperBound(), builder::addUpperBound);
+ handleBounds(inProgress, typeVar.getLowerBound(), builder::addLowerBound);
+
+ return Optional.of(builder.build());
+ } finally {
+ inProgress.remove(typeMirror);
+ }
+ }
+ case WILDCARD -> {
+ WildcardType vt = ((WildcardType) typeMirror);
+ var builder = TypeName.builder()
+ .generic(true)
+ .wildcard(true)
+ .className("?");
+
+ handleBounds(inProgress, vt.getExtendsBound(), builder::addUpperBound);
+ handleBounds(inProgress, vt.getSuperBound(), builder::addLowerBound);
+
+ return Optional.of(builder.build());
}
- case WILDCARD, ERROR -> {
+ case ERROR -> {
return Optional.of(TypeName.create(typeMirror.toString()));
}
// this is most likely a type that is code generated as part of this round, best effort
@@ -107,7 +145,7 @@ public static Optional createTypeName(TypeMirror typeMirror) {
}
if (typeMirror instanceof ArrayType arrayType) {
- return Optional.of(TypeName.builder(createTypeName(arrayType.getComponentType()).orElseThrow())
+ return Optional.of(TypeName.builder(createTypeName(inProgress, arrayType.getComponentType()).orElseThrow())
.array(true)
.build());
}
@@ -115,21 +153,47 @@ public static Optional createTypeName(TypeMirror typeMirror) {
if (typeMirror instanceof DeclaredType declaredType) {
List typeParams = declaredType.getTypeArguments()
.stream()
- .map(AptTypeFactory::createTypeName)
+ .map(it -> createTypeName(inProgress, it))
.flatMap(Optional::stream)
.collect(Collectors.toList());
- TypeName result = createTypeName(declaredType.asElement()).orElse(null);
+ TypeName result = createTypeName(inProgress, declaredType.asElement()).orElse(null);
if (typeParams.isEmpty() || result == null) {
return Optional.ofNullable(result);
}
+ if (!inProgress.add(typeMirror)) {
+ return Optional.empty(); // prevent infinite loop
+ }
return Optional.of(TypeName.builder(result).typeArguments(typeParams).build());
}
throw new IllegalStateException("Unknown type mirror: " + typeMirror);
}
+ private static void handleBounds(Set processed, TypeMirror boundMirror, Consumer boundHandler) {
+ if (boundMirror == null) {
+ return;
+ }
+ if (boundMirror.getKind() != TypeKind.NULL) {
+ if (boundMirror.getKind() == TypeKind.INTERSECTION) {
+ IntersectionType it = (IntersectionType) boundMirror;
+ it.getBounds()
+ .stream()
+ .filter(Predicate.not(processed::equals))
+ .map(typeMirror -> createTypeName(processed, typeMirror))
+ .flatMap(Optional::stream)
+ .filter(Predicate.not(TypeNames.OBJECT::equals))
+ .forEach(boundHandler);
+
+ } else {
+ createTypeName(processed, boundMirror)
+ .filter(Predicate.not(TypeNames.OBJECT::equals))
+ .ifPresent(boundHandler);
+ }
+ }
+ }
+
/**
* Create type from type mirror. The element is needed to correctly map
* type arguments to type parameters.
@@ -139,7 +203,7 @@ public static Optional createTypeName(TypeMirror typeMirror) {
* @return type for the provided values
*/
public static Optional createTypeName(TypeElement element, TypeMirror mirror) {
- Optional result = AptTypeFactory.createTypeName(mirror);
+ Optional result = createTypeName(new HashSet<>(), mirror);
if (result.isEmpty()) {
return result;
}
@@ -167,12 +231,16 @@ public static Optional createTypeName(TypeElement element, TypeMirror
* @return the associated type name instance
*/
public static Optional createTypeName(Element type) {
+ return createTypeName(new HashSet<>(), type);
+ }
+
+ private static Optional createTypeName(Set processed, Element type) {
if (type instanceof VariableElement) {
- return createTypeName(type.asType());
+ return createTypeName(processed, type.asType());
}
- if (type instanceof ExecutableElement) {
- return createTypeName(((ExecutableElement) type).getReturnType());
+ if (type instanceof ExecutableElement ee) {
+ return createTypeName(processed, ee.getReturnType());
}
List classNames = new ArrayList<>();
diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java
index 70146921928..93c08f0fde5 100644
--- a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java
+++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java
@@ -430,6 +430,7 @@ private static Optional createUncached(AptContext ctx,
// this is probably forward referencing a generated type, ignore
return Optional.empty();
}
+ TypeName declaredTypeName = declaredTypeName(ctx, genericTypeName);
List annotations = createAnnotations(ctx,
foundType,
elementUtils);
@@ -451,10 +452,13 @@ private static Optional createUncached(AptContext ctx,
annotationsOnTypeOrElements,
it));
+
Set modifiers = toModifierNames(typeElement.getModifiers());
TypeInfo.Builder builder = TypeInfo.builder()
.originatingElement(typeElement)
.typeName(typeName)
+ .rawType(genericTypeName)
+ .declaredType(declaredTypeName)
.kind(kind(typeElement.getKind()))
.annotations(annotations)
.inheritedAnnotations(inheritedAnnotations)
@@ -547,6 +551,12 @@ private static Optional createUncached(AptContext ctx,
}
}
+ private static TypeName declaredTypeName(AptContext ctx, TypeName typeName) {
+ TypeElement typeElement = ctx.aptEnv().getElementUtils().getTypeElement(typeName.fqName());
+ // we know this type exists, we do not have to check for null
+ return AptTypeFactory.createTypeName(typeElement.asType()).orElseThrow();
+ }
+
private static void collectEnclosedElements(Predicate elementPredicate,
List elementsWeCareAbout,
List otherElements,
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotatedComponent.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotatedComponent.java
index e13b6783f07..86cde78ce0e 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotatedComponent.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotatedComponent.java
@@ -34,8 +34,13 @@ void addImports(ImportOrganizer.Builder imports) {
annotations.forEach(annotation -> annotation.addImports(imports));
}
- List annotations() {
- return annotations;
+ /**
+ * List of annotations on this component.
+ *
+ * @return annotations
+ */
+ public List annotations() {
+ return List.copyOf(annotations);
}
abstract static class Builder, T extends AnnotatedComponent> extends CommonComponent.Builder {
@@ -67,11 +72,7 @@ public B addDescriptionLine(String line) {
* @return updated builder instance
*/
public B addAnnotation(io.helidon.common.types.Annotation annotation) {
- return addAnnotation(newAnnot -> {
- newAnnot.type(annotation.typeName());
- annotation.values()
- .forEach(newAnnot::addParameter);
- });
+ return addAnnotation(Annotation.create(annotation));
}
/**
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Annotation.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Annotation.java
index 6fb06591d3c..b84bbf95365 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Annotation.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Annotation.java
@@ -31,10 +31,12 @@
public final class Annotation extends CommonComponent {
private final List parameters;
+ private final io.helidon.common.types.Annotation commonAnnotation;
private Annotation(Builder builder) {
super(builder);
this.parameters = List.copyOf(builder.parameters.values());
+ this.commonAnnotation = builder.commonAnntation;
}
/**
@@ -88,6 +90,35 @@ public static Annotation parse(String annotationDefinition) {
return builder.build();
}
+ /**
+ * Create a class model annotation from common types annotation.
+ *
+ * @param annotation annotation to process
+ * @return a new class model annotation
+ */
+ public static Annotation create(io.helidon.common.types.Annotation annotation) {
+ return builder().from(annotation).build();
+ }
+
+ /**
+ * Convert class model annotation to Helidon Common Types annotation.
+ *
+ * @return common types annotation
+ */
+ public io.helidon.common.types.Annotation toTypesAnnotation() {
+ if (this.commonAnnotation != null) {
+ return commonAnnotation;
+ }
+ var builder = io.helidon.common.types.Annotation.builder()
+ .typeName(type().genericTypeName());
+
+ for (AnnotationParameter parameter : parameters) {
+ builder.putValue(parameter.name(), parameter.value());
+ }
+
+ return builder.build();
+ }
+
@Override
void writeComponent(ModelWriter writer, Set declaredTokens, ImportOrganizer imports, ClassType classType)
throws IOException {
@@ -128,6 +159,7 @@ void addImports(ImportOrganizer.Builder imports) {
public static final class Builder extends CommonComponent.Builder {
private final Map parameters = new LinkedHashMap<>();
+ private io.helidon.common.types.Annotation commonAnntation;
private Builder() {
}
@@ -210,6 +242,13 @@ public Builder addParameter(AnnotationParameter parameter) {
return this;
}
+ Builder from(io.helidon.common.types.Annotation annotation) {
+ this.commonAnntation = annotation;
+ type(annotation.typeName());
+ annotation.values()
+ .forEach(this::addParameter);
+ return this;
+ }
}
}
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java
index cd6bdfb00b5..1b07dd3633d 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java
@@ -72,6 +72,10 @@ void writeValue(ModelWriter writer, ImportOrganizer imports) throws IOException
writer.write(resolveValueToString(imports, type(), objectValue));
}
+ Object value() {
+ return objectValue;
+ }
+
private static Set resolveImports(Object value) {
Set imports = new HashSet<>();
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassBase.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassBase.java
index 611f3826c0f..0784de2055f 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassBase.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassBase.java
@@ -19,10 +19,12 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -81,36 +83,100 @@ public abstract class ClassBase extends AnnotatedComponent {
this.superType = builder.superType;
}
- private static int methodCompare(Method method1, Method method2) {
- if (method1.accessModifier() == method2.accessModifier()) {
- return 0;
- } else {
- return method1.accessModifier().compareTo(method2.accessModifier());
- }
+ /**
+ * All declared fields.
+ *
+ * @return fields
+ */
+ public List fields() {
+ return List.copyOf(fields);
}
- private static int fieldComparator(Field field1, Field field2) {
- //This is here for ordering purposes.
- if (field1.accessModifier() == field2.accessModifier()) {
- if (field1.isFinal() == field2.isFinal()) {
- if (field1.type().simpleTypeName().equals(field2.type().simpleTypeName())) {
- if (field1.type().resolvedTypeName().equals(field2.type().resolvedTypeName())) {
- return field1.name().compareTo(field2.name());
- }
- return field1.type().resolvedTypeName().compareTo(field2.type().resolvedTypeName());
- } else if (field1.type().simpleTypeName().equalsIgnoreCase(field2.type().simpleTypeName())) {
- //To ensure that types with the types with the same name,
- //but with the different capital letters, will not be mixed
- return field1.type().simpleTypeName().compareTo(field2.type().simpleTypeName());
- }
- //ignoring case sensitivity to ensure primitive types are properly sorted
- return field1.type().simpleTypeName().compareToIgnoreCase(field2.type().simpleTypeName());
- }
- //final fields should be before non-final
- return Boolean.compare(field2.isFinal(), field1.isFinal());
- } else {
- return field1.accessModifier().compareTo(field2.accessModifier());
- }
+ /**
+ * All declared methods.
+ *
+ * @return methods
+ */
+ public List methods() {
+ return List.copyOf(methods);
+ }
+
+ /**
+ * All declared inner classes.
+ *
+ * @return inner classes
+ */
+ public List innerClasses() {
+ return List.copyOf(innerClasses);
+ }
+
+ /**
+ * All declared constructors.
+ *
+ * @return constructors
+ */
+ public List constructors() {
+ return List.copyOf(constructors);
+ }
+
+ /**
+ * Kind of this type.
+ *
+ * @return kind
+ */
+ public ElementKind kind() {
+ return switch (classType) {
+ case CLASS -> ElementKind.CLASS;
+ case INTERFACE -> ElementKind.INTERFACE;
+ };
+ }
+
+ /**
+ * Type name of the super class (if this is a class and it extends another class).
+ *
+ * @return super type
+ */
+ public Optional superTypeName() {
+ return Optional.ofNullable(superType)
+ .map(Type::genericTypeName);
+ }
+
+ /**
+ * Implemented interfaces.
+ *
+ * @return interfaces this type implements (or extends, if this is an interface)
+ */
+ public List interfaceTypeNames() {
+ return interfaces.stream()
+ .map(Type::genericTypeName)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ /**
+ * Is this a final class.
+ *
+ * @return whether this class is final
+ */
+ public boolean isFinal() {
+ return isFinal;
+ }
+
+ /**
+ * Is this an abstract class.
+ *
+ * @return whether this class is abstract
+ */
+ public boolean isAbstract() {
+ return isAbstract;
+ }
+
+ /**
+ * Is this a static class.
+ *
+ * @return whether this class is static
+ */
+ public boolean isStatic() {
+ return isStatic;
}
@Override
@@ -179,6 +245,67 @@ void writeComponent(ModelWriter writer, Set declaredTokens, ImportOrgani
writer.write("}");
}
+ @Override
+ void addImports(ImportOrganizer.Builder imports) {
+ super.addImports(imports);
+ fields.forEach(field -> field.addImports(imports));
+ staticFields.forEach(field -> field.addImports(imports));
+ methods.forEach(method -> method.addImports(imports));
+ staticMethods.forEach(method -> method.addImports(imports));
+ interfaces.forEach(imp -> imp.addImports(imports));
+ constructors.forEach(constructor -> constructor.addImports(imports));
+ genericParameters.forEach(param -> param.addImports(imports));
+ innerClasses.forEach(innerClass -> {
+ imports.from(innerClass.imports());
+ innerClass.addImports(imports);
+ });
+ if (superType != null) {
+ superType.addImports(imports);
+ }
+ }
+
+ ClassType classType() {
+ return classType;
+ }
+
+ Map innerClassesMap() {
+ Map result = new HashMap<>();
+ innerClasses.forEach(innerClass -> result.put(innerClass.name(), innerClass));
+ return result;
+ }
+
+ private static int methodCompare(Method method1, Method method2) {
+ if (method1.accessModifier() == method2.accessModifier()) {
+ return 0;
+ } else {
+ return method1.accessModifier().compareTo(method2.accessModifier());
+ }
+ }
+
+ private static int fieldComparator(Field field1, Field field2) {
+ //This is here for ordering purposes.
+ if (field1.accessModifier() == field2.accessModifier()) {
+ if (field1.isFinal() == field2.isFinal()) {
+ if (field1.type().simpleTypeName().equals(field2.type().simpleTypeName())) {
+ if (field1.type().resolvedTypeName().equals(field2.type().resolvedTypeName())) {
+ return field1.name().compareTo(field2.name());
+ }
+ return field1.type().resolvedTypeName().compareTo(field2.type().resolvedTypeName());
+ } else if (field1.type().simpleTypeName().equalsIgnoreCase(field2.type().simpleTypeName())) {
+ //To ensure that types with the types with the same name,
+ //but with the different capital letters, will not be mixed
+ return field1.type().simpleTypeName().compareTo(field2.type().simpleTypeName());
+ }
+ //ignoring case sensitivity to ensure primitive types are properly sorted
+ return field1.type().simpleTypeName().compareToIgnoreCase(field2.type().simpleTypeName());
+ }
+ //final fields should be before non-final
+ return Boolean.compare(field2.isFinal(), field1.isFinal());
+ } else {
+ return field1.accessModifier().compareTo(field2.accessModifier());
+ }
+ }
+
private void writeGenericParameters(ModelWriter writer, Set declaredTokens, ImportOrganizer imports)
throws IOException {
writer.write("<");
@@ -261,29 +388,6 @@ private void writeInnerClasses(ModelWriter writer, Set declaredTokens, I
writer.decreasePaddingLevel();
}
- @Override
- void addImports(ImportOrganizer.Builder imports) {
- super.addImports(imports);
- fields.forEach(field -> field.addImports(imports));
- staticFields.forEach(field -> field.addImports(imports));
- methods.forEach(method -> method.addImports(imports));
- staticMethods.forEach(method -> method.addImports(imports));
- interfaces.forEach(imp -> imp.addImports(imports));
- constructors.forEach(constructor -> constructor.addImports(imports));
- genericParameters.forEach(param -> param.addImports(imports));
- innerClasses.forEach(innerClass -> {
- imports.from(innerClass.imports());
- innerClass.addImports(imports);
- });
- if (superType != null) {
- superType.addImports(imports);
- }
- }
-
- ClassType classType() {
- return classType;
- }
-
/**
* Fluent API builder for {@link ClassBase}.
*
@@ -687,5 +791,9 @@ B isStatic(boolean isStatic) {
ImportOrganizer.Builder importOrganizer() {
return importOrganizer;
}
+
+ Map innerClasses() {
+ return innerClasses;
+ }
}
}
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassModel.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassModel.java
index 4b935235ab7..bb8b61a358b 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassModel.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ClassModel.java
@@ -17,6 +17,9 @@
import java.io.IOException;
import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import io.helidon.common.types.AccessModifier;
@@ -209,6 +212,49 @@ public Builder type(TypeName type) {
return this;
}
- }
+ /**
+ * Find if the provided type name is handled as part of this generated class.
+ *
+ * @param typeName type name to look for
+ * @return class base that matches the provided type name
+ */
+ public Optional find(TypeName typeName) {
+ if (!typeName.packageName().equals(packageName)) {
+ return Optional.empty();
+ }
+ if (typeName.classNameWithEnclosingNames().equals(name())) {
+ return Optional.of(build());
+ }
+
+ List enclosingNames = typeName.enclosingNames();
+ if (enclosingNames.isEmpty()) {
+ // did not hit above, will not hit below
+ return Optional.empty();
+ }
+ String topLevel = enclosingNames.getFirst();
+ if (!topLevel.equals(name())) {
+ // not an inner class of this class
+ return Optional.empty();
+ }
+ // look for inner classes, ignoring this class
+ Map innerClasses = super.innerClasses();
+
+ InnerClass inProgress = null;
+ for (int i = 1; i < enclosingNames.size(); i++) {
+ String enclosingName = enclosingNames.get(i);
+ InnerClass found = innerClasses.get(enclosingName);
+ if (found == null) {
+ return Optional.empty();
+ }
+ inProgress = found;
+ innerClasses = inProgress.innerClassesMap();
+ }
+ if (inProgress == null) {
+ return Optional.ofNullable(innerClasses.get(typeName.className()));
+ }
+ // we found an inner class that matches the full hierarchy
+ return Optional.ofNullable(inProgress.innerClassesMap().get(typeName.className()));
+ }
+ }
}
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/CommonComponent.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/CommonComponent.java
index 1dc0c9f07c0..d0467c753d4 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/CommonComponent.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/CommonComponent.java
@@ -33,7 +33,12 @@ abstract class CommonComponent extends DescribableComponent {
this.javadoc = builder.javadocBuilder.build(builder);
}
- String name() {
+ /**
+ * Name of this component.
+ *
+ * @return component name
+ */
+ public String name() {
return name;
}
@@ -41,7 +46,12 @@ Javadoc javadoc() {
return javadoc;
}
- AccessModifier accessModifier() {
+ /**
+ * Access modifier of this component.
+ *
+ * @return access modifier
+ */
+ public AccessModifier accessModifier() {
return accessModifier;
}
@@ -216,6 +226,11 @@ B accessModifier(AccessModifier accessModifier) {
return identity();
}
+ /**
+ * Name of this component.
+ *
+ * @return component name
+ */
String name() {
return name;
}
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ConcreteType.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ConcreteType.java
index bded3b90bc0..64b6e4fcc4e 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ConcreteType.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ConcreteType.java
@@ -153,6 +153,11 @@ public int hashCode() {
return Objects.hash(isArray(), typeName.resolvedName());
}
+ @Override
+ TypeName typeName() {
+ return typeName;
+ }
+
static final class Builder extends ModelComponent.Builder {
private final List typeParams = new ArrayList<>();
private TypeName typeName;
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentBuilder.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentBuilder.java
index 4237f75f8ce..109c787e529 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentBuilder.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentBuilder.java
@@ -19,6 +19,7 @@
import java.util.List;
import io.helidon.common.types.Annotation;
+import io.helidon.common.types.ResolvedType;
import io.helidon.common.types.TypeName;
import io.helidon.common.types.TypedElementInfo;
@@ -110,6 +111,25 @@ default T addContentCreate(TypeName typeName) {
return addContent("");
}
+ /**
+ * Add content that creates a new {@link io.helidon.common.types.ResolvedType} in the generated code that is the same as the
+ * type name provided.
+ *
+ * To create a type name without type arguments (such as when used with {@code .class}), use
+ * {@link io.helidon.common.types.TypeName#genericTypeName()}.
+ *
+ * The generated content will be similar to: {@code TypeName.create("some.type.Name")}
+ *
+ * @param type type name to code generate
+ * @return updated builder instance
+ */
+ default T addContentCreate(ResolvedType type) {
+ return addContent(ResolvedType.class)
+ .addContent(".create(\"")
+ .addContent(type.resolvedName())
+ .addContent("\")");
+ }
+
/**
* Add content that creates a new {@link io.helidon.common.types.Annotation} in the generated code that is the same as the
* annotation provided.
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/DescribableComponent.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/DescribableComponent.java
index 956a8f6efcb..d9976eb00e6 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/DescribableComponent.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/DescribableComponent.java
@@ -36,10 +36,25 @@ Type type() {
return type;
}
- List description() {
+ /**
+ * Description (javadoc) of this component.
+ *
+ * @return description lines
+ */
+ public List description() {
return description;
}
+ /**
+ * Type name of this component.
+ *
+ * @return type name
+ */
+ public TypeName typeName() {
+ return type().typeName();
+ }
+
+
@Override
void addImports(ImportOrganizer.Builder imports) {
if (includeImport() && type != null) {
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Executable.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Executable.java
index 3e1866d86a8..5f3bda7881b 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Executable.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Executable.java
@@ -24,6 +24,7 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import io.helidon.common.types.AccessModifier;
import io.helidon.common.types.TypeName;
@@ -54,10 +55,10 @@ void addImports(ImportOrganizer.Builder imports) {
void writeThrows(ModelWriter writer, Set declaredTokens, ImportOrganizer imports, ClassType classType)
throws IOException {
- if (!exceptions().isEmpty()) {
+ if (!exceptionTypes().isEmpty()) {
writer.write(" throws ");
boolean first = true;
- for (Type exception : exceptions()) {
+ for (Type exception : exceptionTypes()) {
if (first) {
first = false;
} else {
@@ -76,11 +77,27 @@ void writeBody(ModelWriter writer, ImportOrganizer imports) throws IOException {
writer.write("\n");
}
- List parameters() {
- return parameters;
+ /**
+ * List of method parameters.
+ *
+ * @return parameters
+ */
+ public List parameters() {
+ return List.copyOf(parameters);
+ }
+
+ /**
+ * List of thrown exceptions.
+ *
+ * @return exceptions
+ */
+ public List exceptions() {
+ return exceptions.stream()
+ .map(Type::genericTypeName)
+ .collect(Collectors.toUnmodifiableList());
}
- List exceptions() {
+ List exceptionTypes() {
return exceptions;
}
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Field.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Field.java
index 3f1b3f2ae26..e80365f381d 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Field.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Field.java
@@ -97,10 +97,6 @@ void addImports(ImportOrganizer.Builder imports) {
defaultValue.addImports(imports);
}
- boolean isStatic() {
- return isStatic;
- }
-
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -130,10 +126,33 @@ public String toString() {
return accessModifier().modifierName() + " " + type().fqTypeName() + " " + name();
}
- boolean isFinal() {
+ /**
+ * Is this field final.
+ *
+ * @return whether this is a final field
+ */
+ public boolean isFinal() {
return isFinal;
}
+ /**
+ * Is this field static.
+ *
+ * @return whether this is a static field
+ */
+ public boolean isStatic() {
+ return isStatic;
+ }
+
+ /**
+ * Is this field volatile.
+ *
+ * @return whether this is a volatile field
+ */
+ public boolean isVolatile() {
+ return isVolatile;
+ }
+
/**
* Fluent API builder for {@link Field}.
*/
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Method.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Method.java
index f02d54fdfb1..7f5723a3657 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Method.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Method.java
@@ -191,10 +191,43 @@ void addImports(ImportOrganizer.Builder imports) {
type().addImports(imports);
}
- boolean isStatic() {
+ /**
+ * Is this a static method.
+ *
+ * @return whether this method is static
+ */
+ public boolean isStatic() {
return isStatic;
}
+ /**
+ * Is this a final method.
+ *
+ * @return whether this method is final
+ */
+ public boolean isFinal() {
+ return isFinal;
+ }
+
+ /**
+ * Is this an abstract method.
+ *
+ * @return whether this method is abstract
+ */
+ public boolean isAbstract() {
+ return isAbstract;
+ }
+
+ /**
+ * Is this a default method (of an interface).
+ *
+ * @return whether this method is default
+ */
+
+ public boolean isDefault() {
+ return isDefault;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Parameter.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Parameter.java
index bfde677d9d3..e6bc9bea10e 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Parameter.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Parameter.java
@@ -46,20 +46,6 @@ public static Builder builder() {
return new Builder();
}
- @Override
- void writeComponent(ModelWriter writer, Set declaredTokens, ImportOrganizer imports, ClassType classType)
- throws IOException {
- for (Annotation annotation : annotations()) {
- annotation.writeComponent(writer, declaredTokens, imports, classType);
- writer.write(" ");
- }
- type().writeComponent(writer, declaredTokens, imports, classType);
- if (vararg) {
- writer.write("...");
- }
- writer.write(" " + name());
- }
-
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -83,17 +69,36 @@ public String toString() {
return "Parameter{type=" + type().fqTypeName() + ", simpleType=" + type().simpleTypeName() + ", name=" + name() + "}";
}
- List description() {
+ /**
+ * Description (javadoc lines) of this parameter.
+ *
+ * @return parameter description
+ */
+ public List description() {
return description;
}
+ @Override
+ void writeComponent(ModelWriter writer, Set declaredTokens, ImportOrganizer imports, ClassType classType)
+ throws IOException {
+ for (Annotation annotation : annotations()) {
+ annotation.writeComponent(writer, declaredTokens, imports, classType);
+ writer.write(" ");
+ }
+ type().writeComponent(writer, declaredTokens, imports, classType);
+ if (vararg) {
+ writer.write("...");
+ }
+ writer.write(" " + name());
+ }
+
/**
* Fluent API builder for {@link Parameter}.
*/
public static final class Builder extends AnnotatedComponent.Builder {
- private boolean vararg = false;
private final List description = new ArrayList<>();
+ private boolean vararg = false;
private Builder() {
}
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Type.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Type.java
index 45e24c4100f..3ab5280599e 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Type.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/Type.java
@@ -15,8 +15,8 @@
*/
package io.helidon.codegen.classmodel;
+import java.util.List;
import java.util.Optional;
-import java.util.stream.Collectors;
import io.helidon.common.types.TypeName;
@@ -37,15 +37,22 @@ static Type fromTypeName(TypeName typeName) {
.type(typeName)
.build();
} else if (typeName.wildcard()) {
- boolean isObject = typeName.name().equals("?") || Object.class.getName().equals(typeName.name());
- if (isObject) {
- return TypeArgument.create("?");
- } else {
+ List upperBounds = typeName.upperBounds();
+ if (upperBounds.isEmpty()) {
+ if (typeName.lowerBounds().isEmpty()) {
+ return TypeArgument.create("?");
+ }
return TypeArgument.builder()
.token("?")
- .bound(extractBoundTypeName(typeName.genericTypeName()))
+ .bound(typeName.lowerBounds().getFirst())
+ .lowerBound(true)
.build();
}
+
+ return TypeArgument.builder()
+ .token("?")
+ .bound(upperBounds.getFirst())
+ .build();
}
return ConcreteType.builder()
.type(typeName)
@@ -58,24 +65,10 @@ static Type fromTypeName(TypeName typeName) {
return typeBuilder.build();
}
- private static String extractBoundTypeName(TypeName instance) {
- String name = calcName(instance);
- StringBuilder nameBuilder = new StringBuilder(name);
-
- if (!instance.typeArguments().isEmpty()) {
- nameBuilder.append('<')
- .append(instance.typeArguments()
- .stream()
- .map(TypeName::resolvedName)
- .collect(Collectors.joining(", ")))
- .append('>');
- }
+ abstract TypeName typeName();
- if (instance.array()) {
- nameBuilder.append("[]");
- }
-
- return nameBuilder.toString();
+ private static String extractBoundTypeName(TypeName instance) {
+ return instance.resolvedName();
}
private static String calcName(TypeName instance) {
diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/TypeArgument.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/TypeArgument.java
index e44e09c6c06..f18cbbfffef 100644
--- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/TypeArgument.java
+++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/TypeArgument.java
@@ -16,10 +16,12 @@
package io.helidon.codegen.classmodel;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import io.helidon.common.types.TypeName;
@@ -29,14 +31,16 @@
public final class TypeArgument extends Type implements TypeName {
private final TypeName token;
- private final Type bound;
+ private final List bounds;
private final List description;
+ private final boolean isLowerBound;
private TypeArgument(Builder builder) {
super(builder);
this.token = builder.tokenBuilder.build();
- this.bound = builder.bound;
+ this.bounds = List.copyOf(builder.bounds);
this.description = builder.description;
+ this.isLowerBound = builder.isLowerBound;
}
/**
@@ -65,25 +69,45 @@ public TypeName boxed() {
@Override
public TypeName genericTypeName() {
- if (bound == null) {
- return null;
+ if (bounds.isEmpty()) {
+ return this;
}
- return bound.genericTypeName();
+ return TypeName.builder()
+ .from(this)
+ .typeArguments(List.of())
+ .typeParameters(List.of())
+ .build();
}
@Override
void writeComponent(ModelWriter writer, Set declaredTokens, ImportOrganizer imports, ClassType classType)
throws IOException {
writer.write(token.className());
- if (bound != null) {
+ if (bounds.isEmpty()) {
+ return;
+ }
+
+ if (isLowerBound) {
+ writer.write(" super ");
+ } else {
writer.write(" extends ");
- bound.writeComponent(writer, declaredTokens, imports, classType);
+ }
+
+ if (bounds.size() == 1) {
+ bounds.getFirst().writeComponent(writer, declaredTokens, imports, classType);
+ return;
+ }
+ for (int i = 0; i < bounds.size(); i++) {
+ if (i != 0) {
+ writer.write(" & ");
+ }
+ bounds.get(i).writeComponent(writer, declaredTokens, imports, classType);
}
}
@Override
void addImports(ImportOrganizer.Builder imports) {
- if (bound != null) {
+ for (Type bound : bounds) {
bound.addImports(imports);
}
}
@@ -176,12 +200,25 @@ public List typeParameters() {
return List.of();
}
+ @Override
+ public List lowerBounds() {
+ // not yet supported
+ return List.of();
+ }
+
+ @Override
+ public List upperBounds() {
+ return bounds.stream()
+ .map(Type::typeName)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
@Override
public String toString() {
- if (bound == null) {
+ if (bounds.isEmpty()) {
return "Token: " + token.className();
}
- return "Token: " + token.className() + " Bound: " + bound;
+ return "Token: " + token.className() + " Bound: " + bounds;
}
@Override
@@ -194,12 +231,12 @@ public boolean equals(Object o) {
}
TypeArgument typeArgument1 = (TypeArgument) o;
return Objects.equals(token, typeArgument1.token)
- && Objects.equals(bound, typeArgument1.bound);
+ && Objects.equals(bounds, typeArgument1.bounds);
}
@Override
public int hashCode() {
- return Objects.hash(token, bound);
+ return Objects.hash(token, bounds);
}
@Override
@@ -207,6 +244,11 @@ public int compareTo(TypeName o) {
return token.compareTo(o);
}
+ @Override
+ TypeName typeName() {
+ return this;
+ }
+
/**
* Fluent API builder for {@link TypeArgument}.
*/
@@ -214,7 +256,9 @@ public static final class Builder extends Type.Builder {
private final TypeName.Builder tokenBuilder = TypeName.builder()
.generic(true);
- private Type bound;
+ private final List bounds = new ArrayList<>();
+
+ private boolean isLowerBound;
private List description = List.of();
private Builder() {
@@ -252,6 +296,18 @@ public Builder bound(Class> bound) {
return bound(TypeName.create(bound));
}
+ /**
+ * Bound is by default an upper bounds (presented as {@code extends} in code).
+ * By specifying that we use a {@code lowerBound}, the keyword will be {@code super}.
+ *
+ * @param lowerBound whether the specified bound is a lower bound (defaults to upper bound); ignore if no bound
+ * @return updated builder instance
+ */
+ public Builder lowerBound(boolean lowerBound) {
+ this.isLowerBound = lowerBound;
+ return this;
+ }
+
/**
* Type this argument is bound to.
*
@@ -259,7 +315,18 @@ public Builder bound(Class> bound) {
* @return updated builder instance
*/
public Builder bound(TypeName bound) {
- this.bound = Type.fromTypeName(bound);
+ this.bounds.add(Type.fromTypeName(bound));
+ return this;
+ }
+
+ /**
+ * Type this argument is bound to (may have more than one for intersection types).
+ *
+ * @param bound argument bound
+ * @return updated builder instance
+ */
+ public Builder addBound(TypeName bound) {
+ this.bounds.add(Type.fromTypeName(bound));
return this;
}
diff --git a/codegen/codegen/src/main/java/io/helidon/codegen/ClassModelFactory.java b/codegen/codegen/src/main/java/io/helidon/codegen/ClassModelFactory.java
new file mode 100644
index 00000000000..1135a3df4eb
--- /dev/null
+++ b/codegen/codegen/src/main/java/io/helidon/codegen/ClassModelFactory.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 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;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import io.helidon.codegen.classmodel.Annotation;
+import io.helidon.codegen.classmodel.ClassBase;
+import io.helidon.codegen.classmodel.Constructor;
+import io.helidon.codegen.classmodel.Executable;
+import io.helidon.codegen.classmodel.Field;
+import io.helidon.codegen.classmodel.Method;
+import io.helidon.codegen.classmodel.Parameter;
+import io.helidon.common.types.ElementKind;
+import io.helidon.common.types.Modifier;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementInfo;
+
+/**
+ * Transforms class model to a {@link io.helidon.common.types.TypeInfo}.
+ */
+final class ClassModelFactory {
+ private ClassModelFactory() {
+ }
+
+ static TypeInfo create(RoundContext ctx,
+ TypeName requestedTypeName,
+ ClassBase requestedType) {
+
+ var builder = TypeInfo.builder()
+ .typeName(requestedTypeName)
+ .kind(requestedType.kind())
+ .accessModifier(requestedType.accessModifier())
+ .description(String.join("\n", requestedType.description()));
+
+ for (Annotation annotation : requestedType.annotations()) {
+ builder.addAnnotation(annotation.toTypesAnnotation());
+ }
+
+ requestedType.superTypeName()
+ .flatMap(ctx::typeInfo)
+ .ifPresent(builder::superTypeInfo);
+
+ List typeNames = requestedType.interfaceTypeNames();
+ for (TypeName typeName : typeNames) {
+ ctx.typeInfo(typeName).ifPresent(builder::addInterfaceTypeInfo);
+ }
+ for (Field field : requestedType.fields()) {
+ addField(builder, field);
+ }
+ for (Constructor constructor : requestedType.constructors()) {
+ addConstructor(requestedTypeName, builder, constructor);
+ }
+ for (Method method : requestedType.methods()) {
+ addMethod(builder, method);
+ }
+
+ for (ClassBase innerClass : requestedType.innerClasses()) {
+ addInnerClass(requestedTypeName, builder, innerClass);
+ }
+
+ return builder.build();
+ }
+
+ private static void addInnerClass(TypeName requestedTypeName, TypeInfo.Builder builder, ClassBase innerClass) {
+
+ builder.addElementInfo(innerInfo -> innerInfo
+ .typeName(innerClassTypeName(requestedTypeName, innerClass.name()))
+ .kind(ElementKind.CLASS)
+ .elementName(innerClass.name())
+ .accessModifier(innerClass.accessModifier())
+ .update(it -> {
+ if (innerClass.isStatic()) {
+ it.addElementModifier(Modifier.STATIC);
+ }
+ if (innerClass.isAbstract()) {
+ it.addElementModifier(Modifier.ABSTRACT);
+ }
+ if (innerClass.isFinal()) {
+ it.addElementModifier(Modifier.FINAL);
+ }
+ })
+ .description(String.join("\n", innerClass.description()))
+ .update(it -> addAnnotations(it, innerClass.annotations()))
+ );
+ }
+
+ private static TypeName innerClassTypeName(TypeName requestedTypeName, String name) {
+ return TypeName.builder(requestedTypeName)
+ .addEnclosingName(requestedTypeName.className())
+ .className(name)
+ .build();
+ }
+
+ private static void addMethod(TypeInfo.Builder builder, Method method) {
+ builder.addElementInfo(methodInfo -> methodInfo
+ .kind(ElementKind.METHOD)
+ .elementName(method.name())
+ .accessModifier(method.accessModifier())
+ .update(it -> {
+ if (method.isStatic()) {
+ it.addElementModifier(Modifier.STATIC);
+ }
+ if (method.isFinal()) {
+ it.addElementModifier(Modifier.FINAL);
+ }
+ if (method.isAbstract()) {
+ it.addElementModifier(Modifier.ABSTRACT);
+ }
+ if (method.isDefault()) {
+ it.addElementModifier(Modifier.DEFAULT);
+ }
+ })
+ .description(String.join("\n", method.description()))
+ .update(it -> addAnnotations(it, method.annotations()))
+ .update(it -> processExecutable(it, method))
+ .typeName(method.typeName())
+ );
+ }
+
+ private static void processExecutable(TypedElementInfo.Builder builder, Executable executable) {
+ for (Parameter parameter : executable.parameters()) {
+ builder.addParameterArgument(arg -> arg
+ .kind(ElementKind.PARAMETER)
+ .elementName(parameter.name())
+ .typeName(parameter.typeName())
+ .description(String.join("\n", parameter.description()))
+ .update(it -> addAnnotations(it, parameter.annotations()))
+ );
+ }
+ builder.addThrowsChecked(executable.exceptions()
+ .stream()
+ .collect(Collectors.toUnmodifiableSet()));
+ }
+
+ private static void addConstructor(TypeName typeName, TypeInfo.Builder builder, Constructor constructor) {
+ builder.addElementInfo(ctrInfo -> ctrInfo
+ .typeName(typeName)
+ .kind(ElementKind.CONSTRUCTOR)
+ .accessModifier(constructor.accessModifier())
+ .description(String.join("\n", constructor.description()))
+ .update(it -> addAnnotations(it, constructor.annotations()))
+ .update(it -> processExecutable(it, constructor))
+ );
+ }
+
+ private static void addField(TypeInfo.Builder builder, Field field) {
+ builder.addElementInfo(fieldInfo -> fieldInfo
+ .typeName(field.typeName())
+ .kind(ElementKind.FIELD)
+ .accessModifier(field.accessModifier())
+ .elementName(field.name())
+ .description(String.join("\n", field.description()))
+ .update(it -> addAnnotations(it, field.annotations()))
+ .update(it -> {
+ if (field.isStatic()) {
+ it.addElementModifier(Modifier.STATIC);
+ }
+ if (field.isFinal()) {
+ it.addElementModifier(Modifier.FINAL);
+ }
+ if (field.isVolatile()) {
+ it.addElementModifier(Modifier.VOLATILE);
+ }
+ })
+ );
+ }
+
+ private static void addAnnotations(TypedElementInfo.Builder element, List annotations) {
+ for (Annotation annotation : annotations) {
+ element.addAnnotation(annotation.toTypesAnnotation());
+ }
+ }
+}
diff --git a/codegen/codegen/src/main/java/io/helidon/codegen/Codegen.java b/codegen/codegen/src/main/java/io/helidon/codegen/Codegen.java
index 9641304b1ef..bad63f59bf3 100644
--- a/codegen/codegen/src/main/java/io/helidon/codegen/Codegen.java
+++ b/codegen/codegen/src/main/java/io/helidon/codegen/Codegen.java
@@ -20,9 +20,9 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Predicate;
@@ -32,6 +32,7 @@
import io.helidon.codegen.spi.CodegenExtension;
import io.helidon.codegen.spi.CodegenExtensionProvider;
import io.helidon.common.HelidonServiceLoader;
+import io.helidon.common.types.Annotation;
import io.helidon.common.types.TypeInfo;
import io.helidon.common.types.TypeName;
@@ -58,52 +59,48 @@ public class Codegen {
SUPPORTED_APT_OPTIONS = Set.copyOf(supportedOptions);
}
- private final Map> typeToExtensions = new HashMap<>();
- private final Map> extensionPredicates = new IdentityHashMap<>();
private final CodegenContext ctx;
- private final List extensions;
+ private final List extensions;
private final Set supportedAnnotations;
+ private final Set supportedMetaAnnotations;
private final Set supportedPackagePrefixes;
private Codegen(CodegenContext ctx, TypeName generator) {
this.ctx = ctx;
+ Set supportedAnnotations = new HashSet<>(ctx.mapperSupportedAnnotations());
+ Set supportedMetaAnnotations = new HashSet<>();
+ Set supportedPackagePrefixes = new HashSet<>();
+
this.extensions = EXTENSIONS.stream()
.map(it -> {
CodegenExtension extension = it.create(this.ctx, generator);
- for (TypeName typeName : it.supportedAnnotations()) {
- typeToExtensions.computeIfAbsent(typeName, key -> new ArrayList<>())
- .add(extension);
- }
- Collection packages = it.supportedAnnotationPackages();
- if (!packages.isEmpty()) {
- extensionPredicates.put(extension, discoveryPredicate(packages));
- }
+ Set extensionAnnotations = it.supportedAnnotations();
+ Set extensionPackages = it.supportedAnnotationPackages();
+ Set extensionMetaAnnotations = it.supportedMetaAnnotations();
- return extension;
- })
- .toList();
+ supportedAnnotations.addAll(extensionAnnotations);
+ supportedMetaAnnotations.addAll(extensionMetaAnnotations);
+ supportedPackagePrefixes.addAll(extensionPackages);
- // handle supported annotations and package prefixes
- Set packagePrefixes = new HashSet<>();
- Set annotations = new HashSet<>(ctx.mapperSupportedAnnotations());
+ Predicate annotationPredicate = discoveryPredicate(extensionAnnotations,
+ extensionPackages);
- for (CodegenExtensionProvider extension : EXTENSIONS) {
- annotations.addAll(extension.supportedAnnotations());
+ return new ExtensionInfo(extension,
+ annotationPredicate,
+ extensionMetaAnnotations);
+ })
+ .toList();
- ctx.mapperSupportedAnnotationPackages()
- .stream()
- .map(Codegen::toPackagePrefix)
- .forEach(packagePrefixes::add);
- }
ctx.mapperSupportedAnnotationPackages()
.stream()
.map(Codegen::toPackagePrefix)
- .forEach(packagePrefixes::add);
+ .forEach(supportedPackagePrefixes::add);
- this.supportedAnnotations = Set.copyOf(annotations);
- this.supportedPackagePrefixes = Set.copyOf(packagePrefixes);
+ this.supportedAnnotations = Set.copyOf(supportedAnnotations);
+ this.supportedPackagePrefixes = Set.copyOf(supportedPackagePrefixes);
+ this.supportedMetaAnnotations = Set.copyOf(supportedMetaAnnotations);
}
/**
@@ -144,12 +141,11 @@ public void process(List allTypes) {
// type info list will contain all mapped annotations, so this is the state we can do annotation processing on
List annotatedTypes = annotatedTypes(allTypes);
- for (CodegenExtension extension : extensions) {
+ for (var extension : extensions) {
// and now for each extension, we discover types that contain annotations supported by that extension
- // and create a new round context for each extension
-
- RoundContextImpl roundCtx = createRoundContext(annotatedTypes, extension);
- extension.process(roundCtx);
+ // and create a new round context
+ RoundContextImpl roundCtx = createRoundContext(annotatedTypes, extension, toWrite);
+ extension.extension().process(roundCtx);
toWrite.addAll(roundCtx.newTypes());
}
@@ -163,9 +159,9 @@ public void processingOver() {
List toWrite = new ArrayList<>();
// do processing over in each extension
- for (CodegenExtension extension : extensions) {
- RoundContextImpl roundCtx = createRoundContext(List.of(), extension);
- extension.processingOver(roundCtx);
+ for (var extension : extensions) {
+ RoundContextImpl roundCtx = createRoundContext(List.of(), extension, toWrite);
+ extension.extension().processingOver(roundCtx);
toWrite.addAll(roundCtx.newTypes());
}
@@ -191,11 +187,25 @@ public Set supportedAnnotationPackagePrefixes() {
return supportedPackagePrefixes;
}
- private static Predicate discoveryPredicate(Collection packages) {
- List prefixes = packages.stream()
+ /**
+ * A set of annotation types that may annotate annotation types.
+ *
+ * @return set of meta annotations for annotations to be processed
+ */
+ public Set supportedMetaAnnotations() {
+ return supportedMetaAnnotations;
+ }
+
+ private static Predicate discoveryPredicate(Set extensionAnnotations,
+ Collection extensionPackages) {
+ List prefixes = extensionPackages.stream()
.map(it -> it.endsWith(".*") ? it.substring(0, it.length() - 2) : it)
.toList();
+
return typeName -> {
+ if (extensionAnnotations.contains(typeName)) {
+ return true;
+ }
String packageName = typeName.packageName();
for (String prefix : prefixes) {
if (packageName.startsWith(prefix)) {
@@ -236,45 +246,65 @@ private void writeNewTypes(List toWrite) {
}
}
- private RoundContextImpl createRoundContext(List annotatedTypes, CodegenExtension extension) {
- Set extAnnots = new HashSet<>();
- Map> extAnnotToType = new HashMap<>();
- Map extTypes = new HashMap<>();
+ private RoundContextImpl createRoundContext(List annotatedTypes,
+ ExtensionInfo extension,
+ List newTypes) {
+ Set availableAnnotations = new HashSet<>();
+ Map> annotationToTypes = new HashMap<>();
+ Map processedTypes = new HashMap<>();
+ Map> metaAnnotationToAnnotations = new HashMap<>();
+ // now go through all available annotated types and make sure we only include the ones required by this extension
for (TypeInfoAndAnnotations annotatedType : annotatedTypes) {
- for (TypeName typeName : annotatedType.annotations()) {
- boolean added = false;
- List validExts = this.typeToExtensions.get(typeName);
- if (validExts != null) {
- for (CodegenExtension validExt : validExts) {
- if (validExt == extension) {
- extAnnots.add(typeName);
- extAnnotToType.computeIfAbsent(typeName, key -> new ArrayList<>())
- .add(annotatedType.typeInfo());
- extTypes.put(annotatedType.typeInfo().typeName(), annotatedType.typeInfo);
- added = true;
- }
- }
- }
- if (!added) {
- Predicate predicate = this.extensionPredicates.get(extension);
- if (predicate != null && predicate.test(typeName)) {
- extAnnots.add(typeName);
- extAnnotToType.computeIfAbsent(typeName, key -> new ArrayList<>())
- .add(annotatedType.typeInfo());
- extTypes.put(annotatedType.typeInfo().typeName(), annotatedType.typeInfo);
- }
+ for (TypeName annotationType : annotatedType.annotations()) {
+ boolean metaAnnotated = metaAnnotations(extension, metaAnnotationToAnnotations, annotationType);
+ if (metaAnnotated || extension.supportedAnnotationsPredicate().test(annotationType)) {
+ availableAnnotations.add(annotationType);
+ processedTypes.put(annotatedType.typeInfo().typeName(), annotatedType.typeInfo());
+ annotationToTypes.computeIfAbsent(annotationType, k -> new ArrayList<>())
+ .add(annotatedType.typeInfo());
+ // annotation is meta-annotated with a supported meta-annotation,
+ // or we support the annotation type, or it is prefixed by the package prefix
}
}
}
return new RoundContextImpl(
ctx,
- Set.copyOf(extAnnots),
- Map.copyOf(extAnnotToType),
- List.copyOf(extTypes.values()));
+ newTypes,
+ Set.copyOf(availableAnnotations),
+ Map.copyOf(annotationToTypes),
+ Map.copyOf(metaAnnotationToAnnotations),
+ List.copyOf(processedTypes.values()));
+ }
+
+ private boolean metaAnnotations(ExtensionInfo extension,
+ Map> metaAnnotationToAnnotations,
+ TypeName annotationType) {
+ Optional annotationInfo = ctx.typeInfo(annotationType);
+ if (annotationInfo.isEmpty()) {
+ return false;
+ }
+ TypeInfo annotationTypeInfo = annotationInfo.get();
+
+ boolean metaAnnotated = false;
+ for (TypeName metaAnnotation : extension.supportedMetaAnnotations()) {
+ for (Annotation anAnnotation : annotationTypeInfo.allAnnotations()) {
+ if (anAnnotation.typeName().equals(metaAnnotation)) {
+ metaAnnotated = true;
+ metaAnnotationToAnnotations.computeIfAbsent(metaAnnotation, k -> new HashSet<>())
+ .add(annotationType);
+ }
+ }
+ }
+ return metaAnnotated;
}
private record TypeInfoAndAnnotations(TypeInfo typeInfo, Set annotations) {
}
+
+ private record ExtensionInfo(CodegenExtension extension,
+ Predicate supportedAnnotationsPredicate,
+ Set supportedMetaAnnotations) {
+ }
}
diff --git a/codegen/codegen/src/main/java/io/helidon/codegen/CodegenContext.java b/codegen/codegen/src/main/java/io/helidon/codegen/CodegenContext.java
index 5a4b2c80579..0133d82af47 100644
--- a/codegen/codegen/src/main/java/io/helidon/codegen/CodegenContext.java
+++ b/codegen/codegen/src/main/java/io/helidon/codegen/CodegenContext.java
@@ -66,7 +66,8 @@ default Optional moduleName() {
CodegenLogger logger();
/**
- * Current code generation scope. Usually guessed from the environment, can be overridden using {@link CodegenOptions#CODEGEN_SCOPE}
+ * Current code generation scope. Usually guessed from the environment, can be overridden using
+ * {@link CodegenOptions#CODEGEN_SCOPE}
*
* @return scope
*/
@@ -80,10 +81,12 @@ default Optional moduleName() {
CodegenOptions options();
/**
- * Discover information about the provided type.
+ * Discover information about the provided type. This method only checks existing classes in the
+ * system, and ignored classes created as part of the current processing round.
*
* @param typeName type name to discover
* @return discovered type information, or empty if the type cannot be discovered
+ * @see io.helidon.codegen.RoundContext#typeInfo(io.helidon.common.types.TypeName)
*/
Optional typeInfo(TypeName typeName);
@@ -144,4 +147,13 @@ default Optional moduleName() {
* @return set of supported options
*/
Set