Skip to content

Commit

Permalink
Failing on private field injection, private constructor injection, mu…
Browse files Browse the repository at this point in the history
…ltiple constructor injection

A few bugfixes for problems discovered by tests
  • Loading branch information
tomas-langer committed Oct 1, 2024
1 parent ce5e6ec commit 47ae6f4
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,23 @@ private static void toEnumValue(ContentBuilder<?> contentBuilder, Enum<?> enumVa
.addContent(enumValue.name());
}

private static void addInterfaceAnnotations(List<Annotation> elementAnnotations,
List<TypedElements.DeclaredElement> declaredElements) {

for (TypedElements.DeclaredElement declaredElement : declaredElements) {
declaredElement.element()
.annotations()
.forEach(it -> addInterfaceAnnotation(elementAnnotations, it));
}
}

private static void addInterfaceAnnotation(List<Annotation> elementAnnotations, Annotation annotation) {
// only add if not already there
if (!elementAnnotations.contains(annotation)) {
elementAnnotations.add(annotation);
}
}

private void generateMain(TypeInfo customMain) {
// main class is ONLY generated if required by the user (through Injection.Main annotation)
// generate the `ApplicationMain` that just starts the registry (with auto-discovery)
Expand Down Expand Up @@ -667,30 +684,75 @@ private SuperType superType(TypeInfo typeInfo, Collection<TypeInfo> services) {

// find constructor with @Inject, if none, find the first constructor (assume @Inject)
private TypedElementInfo injectConstructor(TypeInfo typeInfo) {
// first @Inject
return typeInfo.elementInfo()
var constructors = typeInfo.elementInfo()
.stream()
.filter(it -> it.kind() == ElementKind.CONSTRUCTOR)
.filter(it -> it.hasAnnotation(ServiceCodegenTypes.INJECTION_INJECT))
.findFirst()
// or first non-private constructor
.or(() -> typeInfo.elementInfo()
.stream()
.filter(not(ElementInfoPredicates::isPrivate))
.filter(it -> it.kind() == ElementKind.CONSTRUCTOR)
.findFirst())
// or default constructor
.orElse(TypedElements.DEFAULT_CONSTRUCTOR.element());
.collect(Collectors.toUnmodifiableList());
if (constructors.size() > 1) {
throw new CodegenException("There can only be one constructor annotated with "
+ ServiceCodegenTypes.INJECTION_INJECT.fqName() + ", but there were "
+ constructors.size(),
typeInfo.originatingElementValue());
}
if (!constructors.isEmpty()) {
// @Injection.Inject
TypedElementInfo first = constructors.getFirst();
if (ElementInfoPredicates.isPrivate(first)) {
throw new CodegenException("Constructor annotated with " + ServiceCodegenTypes.INJECTION_INJECT.fqName()
+ " must not be private.");
}
return first;
}

// or first non-private constructor
var allConstructors = typeInfo.elementInfo()
.stream()
.filter(it -> it.kind() == ElementKind.CONSTRUCTOR)
.collect(Collectors.toUnmodifiableList());

if (allConstructors.isEmpty()) {
// there is no constructor declared, we can use default
return TypedElements.DEFAULT_CONSTRUCTOR.element();
}
var nonPrivateConstructors = allConstructors.stream()
.filter(not(ElementInfoPredicates::isPrivate))
.collect(Collectors.toUnmodifiableList());
if (nonPrivateConstructors.isEmpty()) {
throw new CodegenException("There is no non-private constructor defined for " + typeInfo.typeName().fqName(),
typeInfo.originatingElementValue());
}
if (nonPrivateConstructors.size() > 1) {
throw new CodegenException("There are more non-private constructors defined for " + typeInfo.typeName().fqName(),
typeInfo.originatingElementValue());
}
return nonPrivateConstructors.getFirst();
}

private List<TypedElementInfo> fieldInjectElements(TypeInfo typeInfo) {
return typeInfo.elementInfo()
var injectFields = typeInfo.elementInfo()
.stream()
.filter(not(ElementInfoPredicates::isPrivate))
.filter(not(ElementInfoPredicates::isStatic))
.filter(ElementInfoPredicates::isField)
.filter(ElementInfoPredicates.hasAnnotation(ServiceCodegenTypes.INJECTION_INJECT))
.toList();
var firstFound = injectFields.stream()
.filter(ElementInfoPredicates::isPrivate)
.findFirst();
if (firstFound.isPresent()) {
throw new CodegenException("Discovered " + ServiceCodegenTypes.INJECTION_INJECT.fqName()
+ " annotation on private field(s). We cannot support private field injection.",
firstFound.get().originatingElementValue());
}
firstFound = injectFields.stream()
.filter(ElementInfoPredicates::isStatic)
.findFirst();
if (firstFound.isPresent()) {
throw new CodegenException("Discovered " + ServiceCodegenTypes.INJECTION_INJECT.fqName()
+ " annotation on static field(s).",
firstFound.get().originatingElementValue());
}
return injectFields;
}

private List<MethodDefinition> methodParams(Collection<TypeInfo> services,
Expand Down Expand Up @@ -2192,23 +2254,6 @@ private TypeName descriptorInstanceType(TypeName serviceType, TypeName descripto
.build();
}

private static void addInterfaceAnnotations(List<Annotation> elementAnnotations,
List<TypedElements.DeclaredElement> declaredElements) {

for (TypedElements.DeclaredElement declaredElement : declaredElements) {
declaredElement.element()
.annotations()
.forEach(it -> addInterfaceAnnotation(elementAnnotations, it));
}
}

private static void addInterfaceAnnotation(List<Annotation> elementAnnotations, Annotation annotation) {
// only add if not already there
if (!elementAnnotations.contains(annotation)) {
elementAnnotations.add(annotation);
}
}

private record GenericTypeDeclaration(String constantName,
TypeName typeName) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public Set<TypeName> supportedAnnotations() {
return Set.of(ServiceCodegenTypes.INJECTION_INJECT,
ServiceCodegenTypes.INJECTION_MAIN,
ServiceCodegenTypes.INJECTION_DESCRIBE,
ServiceCodegenTypes.INTERCEPTION_DELEGATE);
ServiceCodegenTypes.INTERCEPTION_DELEGATE,
ServiceCodegenTypes.INJECTION_CREATE_FOR);
}

@Override
Expand Down

This file was deleted.

3 changes: 1 addition & 2 deletions service/codegen/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,5 @@
io.helidon.service.codegen.ConfigBeanCodegenProvider;

provides io.helidon.codegen.spi.AnnotationMapperProvider
with io.helidon.service.codegen.MapNamedByClassProvider,
io.helidon.service.codegen.MapCreateFor;
with io.helidon.service.codegen.MapNamedByClassProvider;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.helidon.service.inject.api.ActivationResult;
import io.helidon.service.inject.api.Activator;
import io.helidon.service.inject.api.Injection;
import io.helidon.service.inject.api.ScopeNotActiveException;
import io.helidon.service.inject.api.ScopedRegistry;
import io.helidon.service.registry.ServiceInfo;
import io.helidon.service.registry.ServiceRegistryException;
Expand Down Expand Up @@ -167,7 +168,7 @@ private static Comparator<? super Activator<?>> shutdownComparator() {

private void checkActive() {
if (!active) {
throw new ServiceRegistryException("Injection scope " + scope.fqName() + "[" + id + "] is not active.");
throw new ScopeNotActiveException("Injection scope " + scope.fqName() + "[" + id + "] is not active.", scope);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
class IService implements IContract {
private boolean running;

// note: initially left w/o a ctor here!
// note: intentionally left w/o a ctor here!

/**
* For Testing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import io.helidon.service.registry.Service;

@CustomScope
class CustomScopedProducer implements RequestScopedContract {
class CustomScopedProducer implements CustomScopedContract {
private static final AtomicInteger COUNTER = new AtomicInteger();

private final int id = COUNTER.incrementAndGet();
Expand Down

0 comments on commit 47ae6f4

Please sign in to comment.