Skip to content

Commit 4781036

Browse files
committed
Maven plugin to generate bindings and Main class.
Update to tests to validate this all works together.
1 parent b03a59a commit 4781036

File tree

40 files changed

+3363
-38
lines changed

40 files changed

+3363
-38
lines changed

all/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,10 @@
11401140
<groupId>io.helidon.service.inject</groupId>
11411141
<artifactId>helidon-service-inject</artifactId>
11421142
</dependency>
1143+
<dependency>
1144+
<groupId>io.helidon.service.inject</groupId>
1145+
<artifactId>helidon-service-inject-maven-plugin</artifactId>
1146+
</dependency>
11431147
<dependency>
11441148
<groupId>io.helidon.metadata</groupId>
11451149
<artifactId>helidon-metadata-hson</artifactId>

bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,11 @@
15061506
<artifactId>helidon-service-inject</artifactId>
15071507
<version>${helidon.version}</version>
15081508
</dependency>
1509+
<dependency>
1510+
<groupId>io.helidon.service.inject</groupId>
1511+
<artifactId>helidon-service-inject-maven-plugin</artifactId>
1512+
<version>${helidon.version}</version>
1513+
</dependency>
15091514

15101515
<!-- Metadata -->
15111516
<dependency>

service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCode.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@
2525
* New service descriptor metadata with its class code.
2626
*/
2727
public interface DescriptorClassCode {
28+
/**
29+
* Create a new instance.
30+
*
31+
* @param classCode class code that contains necessary information for the generated class.
32+
* @param registryType type of registry that generates the descriptor (core, inject)
33+
* @param weight weight of the service this descriptor describes
34+
* @param contracts contracts of the service (i.e. {@code MyContract})
35+
* @param factoryContracts factory contracts of this service (i.e. {@code Supplier<MyContract>})
36+
* @return a new class code of service descriptor
37+
*/
38+
static DescriptorClassCode create(ClassCode classCode,
39+
String registryType,
40+
double weight,
41+
Set<ResolvedType> contracts,
42+
Set<ResolvedType> factoryContracts) {
43+
return new DescriptorClassCodeImpl(classCode,
44+
registryType,
45+
weight,
46+
contracts,
47+
factoryContracts);
48+
}
49+
2850
/**
2951
* New source code information.
3052
*

service/codegen/src/main/java/io/helidon/service/codegen/GenerateServiceDescriptor.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
/**
4848
* Generates a service descriptor.
4949
*/
50-
class GenerateServiceDescriptor {
50+
public class GenerateServiceDescriptor {
5151
static final TypeName SET_OF_RESOLVED_TYPES = TypeName.builder(TypeNames.SET)
5252
.addTypeArgument(TypeNames.RESOLVED_TYPE_NAME)
5353
.build();
@@ -89,11 +89,11 @@ private GenerateServiceDescriptor(TypeName generator,
8989
* @param service service to create a descriptor for
9090
* @return class model builder of the service descriptor
9191
*/
92-
static ClassModel.Builder generate(TypeName generator,
93-
RegistryCodegenContext ctx,
94-
RegistryRoundContext roundContext,
95-
Collection<TypeInfo> allServices,
96-
TypeInfo service) {
92+
public static ClassModel.Builder generate(TypeName generator,
93+
RegistryCodegenContext ctx,
94+
RegistryRoundContext roundContext,
95+
Collection<TypeInfo> allServices,
96+
TypeInfo service) {
9797
return new GenerateServiceDescriptor(generator,
9898
ctx,
9999
roundContext,

service/inject/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Helidon Inject includes:
1313
- [Aspect Oriented Programming (interceptors)](#interceptors)
1414
- [Events](events)
1515
- [Programmatic Lookup](#programmatic-lookup)
16+
- [Startup](#startup)
1617
- [Other](#other)
1718
- [Glossary](#glossary)
1819

@@ -361,6 +362,29 @@ Lookup parameter options:
361362
- `TypeName` - the same, but using Helidon abstraction of type names (may have type arguments)
362363
- `Lookup` - a full search criteria for a registry lookup
363364

365+
# Startup
366+
367+
The following options are available to start a service registry (and the application):
368+
369+
1. Use API to create an `io.helidon.service.inject.InjectRegistryManager`
370+
2. Use the Helidon startup class `io.helidon.Main`, which will use the injection main class through service loader
371+
3. Use a generated main class, by default named `ApplicationMain` in the main package of the application (supports customization)
372+
373+
## Generated Main Class
374+
375+
To generate a main class, the Helidon Service Inject Maven plugin must be configured.
376+
This is expected to be configured only for an application (i.e. not for library modules) - this is the reason we do not generate it automatically.
377+
378+
The generated main class will contain full, reflection less configuration of the service registry. It registers all services directly through API, and disables service discovery from classpath.
379+
380+
The Main class can also be customized; to do this:
381+
1. Create a custom class (let's call it `CustomMain` as an example)
382+
2. The class must extends the injection main class (`public abstract class CustomMain extends InjectionMain`)
383+
3. The class must be annotated with `@Injection.Main`, so it is discovered by annotation processor
384+
4. Implement any desired methods; the generated class will only implement `serviceDescriptors(InjectConfig.Builder configBuilder)` (always), and `discoverServices()` (if created from the Maven plugin)
385+
386+
For details on how to configure your build, see [Maven Plugin](../maven-plugin/README.md).
387+
364388
# Other
365389

366390
## API types quick reference

service/inject/api/src/main/java/io/helidon/service/inject/api/LookupSupport.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ static void addContract(Lookup.BuilderBase<?, ?> builder, Class<?> contract) {
110110
builder.addContract(ResolvedType.create(contract));
111111
}
112112

113+
/**
114+
* The managed services advertised types (i.e., typically its interfaces).
115+
*
116+
* @param builder builder instance
117+
* @param contract contract the service implements
118+
* @see Lookup#contracts()
119+
*/
120+
@Prototype.BuilderMethod
121+
static void addContract(Lookup.BuilderBase<?, ?> builder, TypeName contract) {
122+
builder.addContract(ResolvedType.create(contract));
123+
}
124+
113125
/**
114126
* The managed service implementation type.
115127
*

service/inject/codegen/src/main/java/io/helidon/service/inject/codegen/ApplicationMainGenerator.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,35 @@
1717
package io.helidon.service.inject.codegen;
1818

1919
import java.util.List;
20+
import java.util.Optional;
2021
import java.util.Set;
22+
import java.util.stream.Collectors;
2123

24+
import io.helidon.codegen.CodegenException;
2225
import io.helidon.codegen.CodegenUtil;
26+
import io.helidon.codegen.ElementInfoPredicates;
2327
import io.helidon.codegen.classmodel.ClassModel;
2428
import io.helidon.codegen.classmodel.Method;
2529
import io.helidon.common.types.AccessModifier;
2630
import io.helidon.common.types.Annotations;
2731
import io.helidon.common.types.ElementSignature;
32+
import io.helidon.common.types.TypeInfo;
2833
import io.helidon.common.types.TypeName;
2934
import io.helidon.common.types.TypeNames;
35+
import io.helidon.common.types.TypedElementInfo;
3036

3137
import static io.helidon.service.inject.codegen.InjectCodegenTypes.DOUBLE_ARRAY;
3238
import static io.helidon.service.inject.codegen.InjectCodegenTypes.INJECT_CONFIG;
3339
import static io.helidon.service.inject.codegen.InjectCodegenTypes.INJECT_CONFIG_BUILDER;
40+
import static io.helidon.service.inject.codegen.InjectCodegenTypes.INJECT_MAIN;
3441
import static io.helidon.service.inject.codegen.InjectCodegenTypes.INJECT_REGISTRY;
3542
import static io.helidon.service.inject.codegen.InjectCodegenTypes.STRING_ARRAY;
43+
import static java.util.function.Predicate.not;
3644

3745
/**
3846
* Utility for {@value #CLASS_NAME} class generation.
3947
*/
40-
final class ApplicationMainGenerator {
48+
public final class ApplicationMainGenerator {
4149
/**
4250
* Default class name of the generated main class.
4351
*/
@@ -87,6 +95,7 @@ private ApplicationMainGenerator() {
8795
* @param runLevelHandler handler of the run level method
8896
* @return class model builder
8997
*/
98+
@SuppressWarnings("checkstyle:ParameterNumber") // all parameters are mandatory
9099
public static ClassModel.Builder generate(TypeName generator,
91100
Set<ElementSignature> declaredSignatures,
92101
TypeName superType,
@@ -167,6 +176,67 @@ public static ClassModel.Builder generate(TypeName generator,
167176
return classModel;
168177
}
169178

179+
/**
180+
* Provides all relevant signatures that may override methods from {@code InjectionMain}.
181+
*
182+
* @param customMain type to analyze
183+
* @return set of method signatures that are non-private, non-static
184+
*/
185+
public static Set<ElementSignature> declaredSignatures(TypeInfo customMain) {
186+
return customMain.elementInfo()
187+
.stream()
188+
.filter(ElementInfoPredicates::isMethod)
189+
.filter(not(ElementInfoPredicates::isStatic))
190+
.filter(not(ElementInfoPredicates::isPrivate))
191+
.map(TypedElementInfo::signature)
192+
.collect(Collectors.toUnmodifiableSet());
193+
}
194+
195+
/**
196+
* Validate a type, to make sure it is a valid custom main class.
197+
*
198+
* @param customMain type to validate
199+
*/
200+
public static void validate(TypeInfo customMain) {
201+
Optional<TypeInfo> superType = customMain.superTypeInfo();
202+
if (superType.isEmpty()) {
203+
throw new CodegenException("Custom main class must directly extend " + INJECT_MAIN.fqName() + ", but "
204+
+ customMain.typeName().fqName() + " does not extend any class",
205+
customMain.originatingElementValue());
206+
}
207+
if (!superType.get().typeName().equals(INJECT_MAIN)) {
208+
throw new CodegenException("Custom main class must directly extend " + INJECT_MAIN.fqName() + ", but "
209+
+ customMain.typeName().fqName() + " extends " + superType.get().typeName(),
210+
customMain.originatingElementValue());
211+
}
212+
if (customMain.accessModifier() == AccessModifier.PRIVATE) {
213+
throw new CodegenException("Custom main class must be accessible (non-private) class, but "
214+
+ customMain.typeName().fqName() + " is private.",
215+
customMain.originatingElementValue());
216+
}
217+
if (customMain.elementInfo()
218+
.stream()
219+
.filter(ElementInfoPredicates::isMethod)
220+
.filter(ElementInfoPredicates::isStatic)
221+
.filter(not(ElementInfoPredicates::isPrivate))
222+
.filter(ElementInfoPredicates.elementName("main"))
223+
.anyMatch(ElementInfoPredicates.hasParams(TypeName.create(String[].class)))) {
224+
throw new CodegenException("Custom main class must not declare a static main(String[]) method, as it is code "
225+
+ "generated into the ApplicationMain class, but "
226+
+ customMain.typeName().fqName() + " declares it.",
227+
customMain.originatingElementValue());
228+
}
229+
if (customMain.elementInfo()
230+
.stream()
231+
.filter(ElementInfoPredicates::isConstructor)
232+
.filter(not(ElementInfoPredicates::isPrivate))
233+
.noneMatch(ElementInfoPredicates.hasParams())) {
234+
throw new CodegenException("Custom main class must have an accessible no-argument constructor, but "
235+
+ customMain.typeName().fqName() + " does not.",
236+
customMain.originatingElementValue());
237+
}
238+
}
239+
170240
private static void mainMethodBody(TypeName type, Method.Builder method) {
171241
method.addContent("new ")
172242
.addContent(type)

service/inject/codegen/src/main/java/io/helidon/service/inject/codegen/EventEmitterObserverProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ private void addMergeQualifiers(ClassModel.Builder classModel) {
184184
.addContentLine("}")
185185
.addContent("var qualifierSet = new ")
186186
.addContent(HashSet.class)
187-
.addContentLine("(QUALIFIERS);")
187+
.addContentLine("<>(QUALIFIERS);")
188188
.addContent("qualifierSet.addAll(")
189189
.addContent(Set.class)
190190
.addContentLine(".of(qualifiers));")

service/inject/codegen/src/main/java/io/helidon/service/inject/codegen/InjectCodegenTypes.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,26 @@ public class InjectCodegenTypes {
140140
*/
141141
public static final TypeName INJECT_REGISTRY =
142142
TypeName.create("io.helidon.service.inject.api.InjectRegistry");
143+
/**
144+
* {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.inject.InjectRegistryManager}.
145+
*/
146+
public static final TypeName INJECT_REGISTRY_MANAGER =
147+
TypeName.create("io.helidon.service.inject.InjectRegistryManager");
143148
/**
144149
* {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.inject.InjectionMain}.
145150
*/
146151
public static final TypeName INJECT_MAIN =
147152
TypeName.create("io.helidon.service.inject.InjectionMain");
153+
/**
154+
* {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.inject.Binding}.
155+
*/
156+
public static final TypeName INJECT_BINDING =
157+
TypeName.create("io.helidon.service.inject.Binding");
158+
/**
159+
* {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.inject.InjectionPlanBinder}.
160+
*/
161+
public static final TypeName INJECT_PLAN_BINDER =
162+
TypeName.create("io.helidon.service.inject.InjectionPlanBinder");
148163

149164
/**
150165
* {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.inject.api.InvocationException}.

service/inject/codegen/src/main/java/io/helidon/service/inject/codegen/InjectOptions.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,14 @@ public final class InjectOptions {
5353
* Name of the generated Main class for Injection. Defaults to
5454
* {@value ApplicationMainGenerator#CLASS_NAME}.
5555
* The same property must be provided to the maven plugin, to correctly update the generated class.
56+
* To configure package name, use {@link io.helidon.codegen.CodegenOptions#CODEGEN_PACKAGE} option.
5657
*/
5758
public static final Option<String> APPLICATION_MAIN_CLASS_NAME =
5859
Option.create("helidon.inject.application.main.class.name",
5960
"Name of the generated Main class for Helidon Injection.",
6061
ApplicationMainGenerator.CLASS_NAME,
6162
Function.identity(),
6263
GenericType.STRING);
63-
/**
64-
* Package name of the generated Main class for Injection.
65-
* This is only needed if there is no custom main class AND the package name cannot be determined from processed classes,
66-
* OR it was determined wrongly.
67-
*/
68-
public static final Option<String> APPLICATION_MAIN_PACKAGE_NAME =
69-
Option.create("helidon.inject.application.main.package.name",
70-
"Package name of the generated Main class for Helidon Injection.",
71-
ApplicationMainGenerator.CLASS_NAME,
72-
Function.identity(),
73-
GenericType.STRING);
7464

7565
/**
7666
* Whether to generate main class for Helidon Injection.

service/inject/codegen/src/main/java/io/helidon/service/inject/codegen/InjectionExtension.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class InjectionExtension implements RegistryCodegenExtension {
137137
.stream()
138138
.map(it -> it.create(codegenContext))
139139
.toList();
140-
this.packageName = InjectOptions.APPLICATION_MAIN_PACKAGE_NAME.findValue(options)
140+
this.packageName = CodegenOptions.CODEGEN_PACKAGE.findValue(options)
141141
.orElse(null);
142142
this.mainClassGenerated = !options.enabled(InjectOptions.APPLICATION_MAIN_GENERATE);
143143
}
@@ -430,7 +430,7 @@ private void generateScopeDescriptor(RegistryRoundContext roundContext, TypeInfo
430430
private void generateMain() {
431431
if (packageName == null) {
432432
throw new CodegenException("Cannot determine package name for the generated main class. "
433-
+ "Please use option " + InjectOptions.APPLICATION_MAIN_PACKAGE_NAME.name()
433+
+ "Please use option " + CodegenOptions.CODEGEN_PACKAGE.name()
434434
+ " to specify it");
435435
}
436436
// generate main class if it doe not exist
@@ -467,33 +467,21 @@ private void generateMain(RegistryRoundContext roundCtx, Collection<TypeInfo> cu
467467
customMain.originatingElementValue());
468468
}
469469

470-
// TODO validate that the custom main class extends InjectionMain
471-
// validate it does not declare `main` method
472-
// validate it has accessible no-arg constructor
473-
// validate it is accessible (at least package local static class)
474-
// add generation to processing over if not generated here (and make sure a conflicting name does not exist)
475-
//
476-
477470
// we always generate the main class, even when there is no Maven plugin
478471
mainClassGenerated = true;
479472
String className = InjectOptions.APPLICATION_MAIN_CLASS_NAME.value(ctx.options());
480473
TypeName generatedType = TypeName.builder()
481474
.packageName(customMain.typeName().packageName())
482475
.className(className)
483476
.build();
484-
var declaredSignatures = customMain.elementInfo()
485-
.stream()
486-
.filter(ElementInfoPredicates::isMethod)
487-
.filter(not(ElementInfoPredicates::isStatic))
488-
.filter(not(ElementInfoPredicates::isPrivate))
489-
.map(TypedElementInfo::signature)
490-
.collect(Collectors.toUnmodifiableSet());
477+
ApplicationMainGenerator.validate(customMain);
478+
var declaredSignatures = ApplicationMainGenerator.declaredSignatures(customMain);
491479

492480
ClassModel.Builder applicationMain = ApplicationMainGenerator.generate(GENERATOR,
493481
declaredSignatures,
494482
customMain.typeName(),
495483
generatedType,
496-
false,
484+
true,
497485
false,
498486
(a, b, c) -> {
499487
},

0 commit comments

Comments
 (0)