Skip to content

Commit

Permalink
4.x: Introduction of Helidon Service Inject. (#9249)
Browse files Browse the repository at this point in the history
* Introduction of Helidon Service Inject.
* Use existing service descriptor when service registered by contract if possible.
* Support for meta-annotations in codegen.
* TypeInfo can be generated from newly generated classes as well in round context
* Codegen annotation processor now correctly returns if it handled all annotations.
   Added feature processor and metadata codegen to Helidon Service Registry and Inject.
* Service codegen refactoring:
   - introduce inject codegen module
   - analysis/codegen separation

Signed-off-by: Tomas Langer <[email protected]>
Co-authored-by: Romain Grecourt <[email protected]>
  • Loading branch information
tomas-langer and romain-grecourt authored Oct 31, 2024
1 parent 7d773ea commit b38a304
Show file tree
Hide file tree
Showing 400 changed files with 31,034 additions and 1,618 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-20.04, macos-14 ]
module: [ mp-1, mp-2, mp-3, se-1 ]
module: [ mp-1, mp-2, mp-3, se-1, inject ]
include:
- { os: ubuntu-20.04, platform: linux }
- { os: macos-14, platform: macos }
Expand Down
12 changes: 12 additions & 0 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,18 @@
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-codegen</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-codegen</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-api</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.metadata</groupId>
<artifactId>helidon-metadata-hson</artifactId>
Expand Down
15 changes: 15 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,21 @@
<artifactId>helidon-service-codegen</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-codegen</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-api</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject</artifactId>
<version>${helidon.version}</version>
</dependency>

<!-- Metadata -->
<dependency>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,17 @@ static FactoryMethods create(CodegenContext ctx,
private static Optional<FactoryMethod> builder(CodegenContext ctx,
TypeHandler typeHandler,
Set<TypeName> builderCandidates) {
if (typeHandler.actualType().equals(OBJECT)) {
if (typeHandler.actualType().equals(OBJECT)
|| typeHandler.actualType().primitive()
|| typeHandler.actualType().generic()) {
return Optional.empty();
}

builderCandidates.add(typeHandler.actualType());
FactoryMethod found = null;
FactoryMethod secondary = null;
for (TypeName builderCandidate : builderCandidates) {
if (typeHandler.actualType().primitive()) {
// primitive methods do not have builders
continue;
}

TypeInfo typeInfo = ctx.typeInfo(builderCandidate.genericTypeName()).orElse(null);
if (typeInfo == null) {
if (secondary == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ static void generate(ClassModel.Builder classModel,
TypeName prototype,
TypeName runtimeType,
List<TypeArgument> typeArguments,
List<TypeName> typeArgumentNames,
TypeContext typeContext) {
Optional<TypeName> superType = typeContext.typeInfo()
.superPrototype();
Expand All @@ -73,7 +74,7 @@ static void generate(ClassModel.Builder classModel,
.description("type of the builder extending this abstract builder")
.bound(TypeName.builder()
.from(TypeName.create(prototype.fqName() + ".BuilderBase"))
.addTypeArguments(typeArguments)
.addTypeArguments(typeArgumentNames)
.addTypeArgument(TypeName.createFromGenericDeclaration("BUILDER"))
.addTypeArgument(TypeName.createFromGenericDeclaration("PROTOTYPE"))
.build()))
Expand Down Expand Up @@ -107,7 +108,7 @@ static void generate(ClassModel.Builder classModel,

// method "from(prototype)"
fromInstanceMethod(builder, typeContext, prototype);
fromBuilderMethod(builder, typeContext, typeArguments);
fromBuilderMethod(builder, typeContext, typeArgumentNames);

// method preBuildPrototype() - handles providers, decorator
preBuildPrototypeMethod(builder, typeContext);
Expand All @@ -126,7 +127,7 @@ static void generate(ClassModel.Builder classModel,
true);

// before the builder class is finished, we also generate a protected implementation
generatePrototypeImpl(builder, typeContext, typeArguments);
generatePrototypeImpl(builder, typeContext, typeArguments, typeArgumentNames);
});
}

Expand Down Expand Up @@ -431,7 +432,8 @@ private static void fromInstanceMethod(InnerClass.Builder builder, TypeContext t

private static void fromBuilderMethod(InnerClass.Builder classBuilder,
TypeContext typeContext,
List<TypeArgument> arguments) {
List<TypeName> arguments) {

TypeName prototype = typeContext.typeInfo().prototype();
TypeName parameterType = TypeName.builder()
.from(TypeName.create(prototype.fqName() + ".BuilderBase"))
Expand Down Expand Up @@ -823,7 +825,8 @@ private static void requiredValidation(Method.Builder validateBuilder, TypeConte

private static void generatePrototypeImpl(InnerClass.Builder classBuilder,
TypeContext typeContext,
List<TypeArgument> typeArguments) {
List<TypeArgument> typeArguments,
List<TypeName> typeArgumentNames) {
Optional<TypeName> superPrototype = typeContext.typeInfo()
.superPrototype();
TypeName prototype = typeContext.typeInfo().prototype();
Expand Down Expand Up @@ -864,7 +867,7 @@ private static void generatePrototypeImpl(InnerClass.Builder classBuilder,
.addParameter(param -> param.name("builder")
.type(TypeName.builder()
.from(TypeName.create(ifaceName + ".BuilderBase"))
.addTypeArguments(typeArguments)
.addTypeArguments(typeArgumentNames)
.addTypeArgument(TypeArgument.create("?"))
.addTypeArgument(TypeArgument.create("?"))
.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,21 @@ static void generate(ClassModel.Builder classBuilder,
TypeName prototype,
TypeName runtimeType,
List<TypeArgument> typeArguments,
List<TypeName> typeArgumentNames,
boolean isFactory,
TypeContext typeContext) {
classBuilder.addInnerClass(builder -> {
TypeName builderType = TypeName.builder()
.from(TypeName.create(prototype.fqName() + ".Builder"))
.addTypeArguments(typeArguments)
.addTypeArguments(typeArgumentNames)
.build();
typeArguments.forEach(builder::addGenericArgument);
builder.name("Builder")
.accessModifier(AccessModifier.PACKAGE_PRIVATE)
.description("Fluent API builder for {@link " + runtimeType.className() + "}.")
.superType(TypeName.builder()
.from(TypeName.create(prototype.fqName() + ".BuilderBase"))
.addTypeArguments(typeArguments)
.addTypeArguments(typeArgumentNames)
.addTypeArgument(builderType)
.addTypeArgument(prototype)
.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

import io.helidon.codegen.CodegenException;
import io.helidon.codegen.CodegenValidator;
import io.helidon.codegen.classmodel.ContentBuilder;
import io.helidon.codegen.classmodel.Field;
Expand All @@ -38,8 +40,38 @@
import io.helidon.common.types.TypedElementInfo;

import static io.helidon.builder.codegen.Types.OPTION_DEFAULT;
import static io.helidon.common.types.TypeNames.BOXED_BOOLEAN;
import static io.helidon.common.types.TypeNames.BOXED_BYTE;
import static io.helidon.common.types.TypeNames.BOXED_CHAR;
import static io.helidon.common.types.TypeNames.BOXED_DOUBLE;
import static io.helidon.common.types.TypeNames.BOXED_FLOAT;
import static io.helidon.common.types.TypeNames.BOXED_INT;
import static io.helidon.common.types.TypeNames.BOXED_LONG;
import static io.helidon.common.types.TypeNames.BOXED_SHORT;
import static io.helidon.common.types.TypeNames.BOXED_VOID;
import static io.helidon.common.types.TypeNames.PRIMITIVE_BOOLEAN;
import static io.helidon.common.types.TypeNames.PRIMITIVE_BYTE;
import static io.helidon.common.types.TypeNames.PRIMITIVE_CHAR;
import static io.helidon.common.types.TypeNames.PRIMITIVE_DOUBLE;
import static io.helidon.common.types.TypeNames.PRIMITIVE_FLOAT;
import static io.helidon.common.types.TypeNames.PRIMITIVE_INT;
import static io.helidon.common.types.TypeNames.PRIMITIVE_LONG;
import static io.helidon.common.types.TypeNames.PRIMITIVE_SHORT;
import static io.helidon.common.types.TypeNames.PRIMITIVE_VOID;

class TypeHandler {
private static final Map<TypeName, TypeName> BOXED_TO_PRIMITIVE = Map.of(
BOXED_BOOLEAN, PRIMITIVE_BOOLEAN,
BOXED_BYTE, PRIMITIVE_BYTE,
BOXED_SHORT, PRIMITIVE_SHORT,
BOXED_INT, PRIMITIVE_INT,
BOXED_LONG, PRIMITIVE_LONG,
BOXED_CHAR, PRIMITIVE_CHAR,
BOXED_FLOAT, PRIMITIVE_FLOAT,
BOXED_DOUBLE, PRIMITIVE_DOUBLE,
BOXED_VOID, PRIMITIVE_VOID
);

private final TypeName enclosingType;
private final TypedElementInfo annotatedMethod;
private final String name;
Expand Down Expand Up @@ -74,14 +106,18 @@ static TypeHandler create(TypeName blueprintType,
if (TypeNames.SUPPLIER.equals(returnType)) {
return new TypeHandlerSupplier(blueprintType, annotatedMethod, name, getterName, setterName, returnType);
}

if (TypeNames.SET.equals(returnType)) {
checkTypeArgsSizeAndTypes(annotatedMethod, returnType, TypeNames.SET, 1);
return new TypeHandlerSet(blueprintType, annotatedMethod, name, getterName, setterName, returnType);
}

if (TypeNames.LIST.equals(returnType)) {
checkTypeArgsSizeAndTypes(annotatedMethod, returnType, TypeNames.LIST, 1);
return new TypeHandlerList(blueprintType, annotatedMethod, name, getterName, setterName, returnType);
}
if (TypeNames.MAP.equals(returnType)) {
checkTypeArgsSizeAndTypes(annotatedMethod, returnType, TypeNames.MAP, 2);
return new TypeHandlerMap(blueprintType, annotatedMethod, name, getterName, setterName, returnType, sameGeneric);
}

Expand All @@ -96,6 +132,12 @@ static TypeName toWildcard(TypeName typeName) {
if (typeName.wildcard()) {
return typeName;
}
if (typeName.generic()) {
return TypeName.builder()
.className(typeName.className())
.wildcard(true)
.build();
}
return TypeName.builder(typeName).wildcard(true).build();
}

Expand Down Expand Up @@ -524,6 +566,30 @@ protected void declaredSetter(InnerClass.Builder classBuilder,
classBuilder.addMethod(builder);
}

protected TypeName toPrimitive(TypeName typeName) {
return Optional.ofNullable(BOXED_TO_PRIMITIVE.get(typeName))
.orElse(typeName);
}

private static void checkTypeArgsSizeAndTypes(TypedElementInfo annotatedMethod,
TypeName returnType,
TypeName collectionType,
int expectedTypeArgs) {
List<TypeName> typeNames = returnType.typeArguments();
if (typeNames.size() != expectedTypeArgs) {
throw new CodegenException("Property of type " + collectionType.fqName() + " must have " + expectedTypeArgs
+ " type arguments defined",
annotatedMethod.originatingElementValue());
}
for (TypeName typeName : typeNames) {
if (typeName.wildcard()) {
throw new CodegenException("Property of type " + returnType.resolvedName() + " is not supported for builder,"
+ " as wildcards cannot be handled correctly in setters",
annotatedMethod.originatingElementValue());
}
}
}

private <T> T singleDefault(List<T> defaultValues) {
if (defaultValues.isEmpty()) {
throw new IllegalArgumentException("Default values configured for " + name() + " are empty, one value is expected.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,13 @@ String generateMapListFromConfig(FactoryMethods factoryMethods) {

@Override
TypeName argumentTypeName() {
TypeName type = actualType();
if (TypeNames.STRING.equals(type) || toPrimitive(type).primitive() || type.array()) {
return declaredType();
}

return TypeName.builder(collectionType)
.addTypeArgument(toWildcard(actualType()))
.addTypeArgument(toWildcard(type))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,18 @@ void generateFromConfig(Method.Builder method,

@Override
TypeName argumentTypeName() {
TypeName firstType = declaredType().typeArguments().get(0);
if (!(TypeNames.STRING.equals(firstType) || toPrimitive(firstType).primitive() || firstType.array())) {
firstType = toWildcard(firstType);
}
TypeName secondType = declaredType().typeArguments().get(1);
if (!(TypeNames.STRING.equals(secondType) || toPrimitive(secondType).primitive() || secondType.array())) {
secondType = toWildcard(secondType);
}

return TypeName.builder(MAP)
.addTypeArgument(toWildcard(declaredType().typeArguments().get(0)))
.addTypeArgument(toWildcard(declaredType().typeArguments().get(1)))
.addTypeArgument(firstType)
.addTypeArgument(secondType)
.build();
}

Expand Down
Loading

0 comments on commit b38a304

Please sign in to comment.