Skip to content

Commit 2fdbbbd

Browse files
authored
Builder extensibility (#10838)
* Support for builder extensibility * Jackson builder extension * Support for detached blueprint * Working on the test for builder codegen * Documentation update
1 parent 089d3cc commit 2fdbbbd

File tree

223 files changed

+20883
-6415
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

223 files changed

+20883
-6415
lines changed

builder/README.md

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ There are a few rules we required and enforce:
6262
2. Blueprint interface MUST be package private
6363
3. Blueprint interface must have a name that ends with `Blueprint`; the name before `Blueprint` will be the name of the prototype
6464
4. In case we use the blueprint -> prototype -> runtime type use case (see below):
65-
1. The blueprint must extend `Prototype.Factory<RuntimeType>` where `RuntimeType` is the type of the runtime object
66-
2. The runtime type must be annotated with `@RuntimeType.PrototypedBy(PrototypeBlueprint.class)`
67-
3. The runtime type must implement `RuntimeType.Api<Prototype>`
68-
4. The runtime type must have a `public static Prototype.Builder builder()` method implemented by user
69-
5. The runtime type must have a `public static RuntimeType create(Prototype)` method implemented by user
70-
6. The runtime type must have a `public static RuntimeType create(Consumer<Prototype.Builder>)` method implemented by user
65+
1. The blueprint must extend `Prototype.Factory<RuntimeType>` where `RuntimeType` is the type of the runtime object
66+
2. ~~The runtime type must be annotated with `@RuntimeType.PrototypedBy(PrototypeBlueprint.class)`~~
67+
3. The runtime type must implement `RuntimeType.Api<Prototype>`
68+
4. The runtime type must have a `public static Prototype.Builder builder()` method implemented by user
69+
5. The runtime type must have a `public static RuntimeType create(Prototype)` method implemented by user
70+
6. The runtime type must have a `public static RuntimeType create(Consumer<Prototype.Builder>)` method implemented by user
7171

7272
## Use Cases
7373

@@ -225,20 +225,23 @@ The API has to sections:
225225

226226
Annotations:
227227

228-
| Annotation | Required | Description |
229-
|-----------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
230-
| `Prototype.Blueprint` | `true` | Annotation on the blueprint interface is required to trigger annotation processing |
231-
| `Prototype.Implement` | `false` | Add additional implemented types to the generated prototype |
232-
| `Prototype.Annotated` | `false` | Allows adding an annotation (or annotations) to the generated class or methods |
233-
| `Prototype.FactoryMethod` | `false` | Use in generated code to mark static factory methods, also can be used on blueprint factory methods to be used during code generation, and on custom methods to mark static methods to be added to prototype |
234-
| `Prototype.Singular` | `false` | Used for lists, sets, and maps to add methods `add*`/`put*` in addition to the full collection setters |
235-
| `Prototype.SameGeneric` | `false` | Use for maps, where we want a setter method to use the same generic type for key and for value (such as `Class<T> key, T valuel`) |
236-
| `Prototype.Redundant` | `false` | A redundant option will not be part of generated `toString`, `hashCode`, and `equals` methods (allows finer grained control) |
237-
| `Prototype.Confidential` | `false` | A confidential option will not have value visible when `toString` is called, only if it is `null` or it has a value (`****`) |
238-
| `Prototype.CustomMethods` | `false` | reference a class that will contain declarations (all static) of custom methods to be added to the generated code, can add prototype, builder, and factory methods |
239-
| `Prototype.BuilderMethod` | `false` | Annotation to be placed on factory methods that are to be added to builder, first parameter is the `BuilderBase<?, ?>` of the prototype |
240-
| `Prototype.PrototypeMethod` | `false` | Annotation to be placed on factory methods that are to be added to prototype, first parameter is the prototype instance |
241-
| `RuntimeType.PrototypedBy` | `true` | Annotation on runtime type that is created from a `Prototype`, to map it to the prototype it can be created from, used to trigger annotation processor for validation |
228+
| Annotation | Required | Description |
229+
|--------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
230+
| `Prototype.Blueprint` | `true` | Annotation on the blueprint interface is required to trigger annotation processing |
231+
| `Prototype.Implement` | `false` | Add additional implemented types to the generated prototype |
232+
| `Prototype.Annotated` | `false` | Allows adding an annotation (or annotations) to the generated class or methods |
233+
| ~~`Prototype.FactoryMethod`~~ | `false` | Deprecated and marked for removal |
234+
| `Prototype.PrototypeFactoryMethod` | `false` | Annotates a method in a `CustomMethods` type to be added as a static method to the prototype |
235+
| `Prototype.ConfigFactoryMethod` | `false` | Annotates a method in a `CustomMethods` type that creates an option from `Config` on a configured type |
236+
| `Prototype.RuntimeTypeFactoryMethod` | `false` | Annotates a method in a `CustomMethods` type that creates an option runtime type from its prototype (the parameter must be another prototype |
237+
| `Prototype.Singular` | `false` | Used for lists, sets, and maps to add methods `add*`/`put*` in addition to the full collection setters |
238+
| `Prototype.SameGeneric` | `false` | Use for maps, where we want a setter method to use the same generic type for key and for value (such as `Class<T> key, T valuel`) |
239+
| `Prototype.Redundant` | `false` | A redundant option will not be part of generated `toString`, `hashCode`, and `equals` methods (allows finer grained control) |
240+
| `Prototype.Confidential` | `false` | A confidential option will not have value visible when `toString` is called, only if it is `null` or it has a value (`****`) |
241+
| `Prototype.CustomMethods` | `false` | reference a class that will contain declarations (all static) of custom methods to be added to the generated code, can add prototype, builder, and factory methods |
242+
| `Prototype.BuilderMethod` | `false` | Annotation to be placed on factory methods that are to be added to builder, first parameter is the `BuilderBase<?, ?>` of the prototype |
243+
| `Prototype.PrototypeMethod` | `false` | Annotation to be placed on factory methods that are to be added to prototype, first parameter is the prototype instance |
244+
| ~~`RuntimeType.PrototypedBy`~~ | `true` | Deprecated and marked for removal |
242245

243246
Interfaces:
244247

builder/api/src/main/java/io/helidon/builder/api/Prototype.java

Lines changed: 141 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.annotation.ElementType;
2020
import java.lang.annotation.Inherited;
21+
import java.lang.annotation.Repeatable;
2122
import java.lang.annotation.Retention;
2223
import java.lang.annotation.RetentionPolicy;
2324
import java.lang.annotation.Target;
@@ -143,6 +144,8 @@ public interface Factory<T> {
143144
* Whether to use bean style setters and getters, or not (default is not).
144145
* If set to {@code true}, only methods starting with {@code get} would be used,
145146
* and all setters will start with {@code set}, except for add methods.
147+
* <p>
148+
* NOTE: bean style must be consistent in a hierarchy, otherwise we cannot discover properties correctly.
146149
*
147150
* @return whether to use bean style accessors, defaults to false
148151
*/
@@ -157,6 +160,13 @@ public interface Factory<T> {
157160
* @return decorator type
158161
*/
159162
Class<? extends BuilderDecorator> decorator() default BuilderDecorator.class;
163+
164+
/**
165+
* If set to {@code true} the generated type will not extend the blueprint interface.
166+
*
167+
* @return whether to detach the generated type from the blueprint interface
168+
*/
169+
boolean detach() default false;
160170
}
161171

162172
/**
@@ -248,36 +258,35 @@ public interface OptionDecorator<B, T> {
248258
/**
249259
* Decorate a list of values, when a setter that replaces values is called.
250260
*
251-
* @param builder the target builder being decorated
252-
* @param optionValues option values set by the caller of the setter method
261+
* @param builder the target builder being decorated
262+
* @param optionValues option values set by the caller of the setter method
253263
*/
254264
default void decorateSetList(B builder, List<T> optionValues) {
255265
}
256266

257267
/**
258268
* Decorate a list of values, when a setter that adds values is called.
259269
*
260-
* @param builder the target builder being decorated
261-
* @param optionValues option values set by the caller of the setter method
270+
* @param builder the target builder being decorated
271+
* @param optionValues option values set by the caller of the setter method
262272
*/
263273
default void decorateAddList(B builder, List<T> optionValues) {
264274
}
265275

266276
/**
267277
* Decorate a set of values, when a setter that replaces values is called.
268278
*
269-
* @param builder the target builder being decorated
270-
* @param optionValues option values set by the caller of the setter method
279+
* @param builder the target builder being decorated
280+
* @param optionValues option values set by the caller of the setter method
271281
*/
272282
default void decorateSetSet(B builder, Set<T> optionValues) {
273283
}
274284

275-
276285
/**
277286
* Decorate a set of values, when a setter that adds values is called.
278287
*
279-
* @param builder the target builder being decorated
280-
* @param optionValues option values set by the caller of the setter method
288+
* @param builder the target builder being decorated
289+
* @param optionValues option values set by the caller of the setter method
281290
*/
282291
default void decorateAddSet(B builder, Set<T> optionValues) {
283292
}
@@ -301,9 +310,7 @@ default void decorateAddSet(B builder, Set<T> optionValues) {
301310
}
302311

303312
/**
304-
* This is an annotation used by Helidon code generator that marks a static method
305-
* as a factory method.
306-
* <p>
313+
* Old behavior, kept for backward compatibility:
307314
* This annotation must be defined on any static method that should be used as a factory for runtime types from
308315
* prototypes, and from configuration on {@link Prototype.Blueprint}.
309316
* <p>
@@ -317,15 +324,103 @@ default void decorateAddSet(B builder, Set<T> optionValues) {
317324
* prototype from configuration</li>
318325
* <li>{@code static Prototype create()} - a method that creates a new instance if there are no required fields</li>
319326
* </ul>
320-
* This annotation is also used for triggering an additional round of annotation processing by the generated types, to
321-
* finalize
322-
* validation.
327+
*
328+
* @deprecated use {@link io.helidon.builder.api.Prototype.PrototypeFactoryMethod},
329+
* {@link io.helidon.builder.api.Prototype.ConfigFactoryMethod},
330+
* or {@link io.helidon.builder.api.Prototype.RuntimeTypeFactoryMethod} for other types this annotation
331+
* could have been used for in the past
323332
*/
324333
@Target({ElementType.METHOD, ElementType.TYPE})
325334
@Retention(RetentionPolicy.CLASS)
335+
@Deprecated(forRemoval = true, since = "4.4.0")
326336
public @interface FactoryMethod {
327337
}
328338

339+
/**
340+
* Factory method that is added to the generated prototype.
341+
* <p>
342+
* This annotation MUST be on a static method that is part of a type referenced from blueprint through
343+
* {@link io.helidon.builder.api.Prototype.CustomMethods}.
344+
*/
345+
@Target(ElementType.METHOD)
346+
@Retention(RetentionPolicy.CLASS)
347+
public @interface PrototypeFactoryMethod {
348+
}
349+
350+
/**
351+
* Factory method that creates an option from a config instance.
352+
* The config (the only parameter) must be of type {@code io.helidon.config.Config}.
353+
* <p>
354+
* The return type of the method must match the type of the option.
355+
* <p>
356+
* This annotation MUST be on a static method that is part of a type referenced from blueprint through
357+
* {@link io.helidon.builder.api.Prototype.CustomMethods}.
358+
*/
359+
@Target(ElementType.METHOD)
360+
@Retention(RetentionPolicy.CLASS)
361+
public @interface ConfigFactoryMethod {
362+
/**
363+
* An explicit option name can be defined to only use this factory method for the specified option.
364+
* If none is specified, the factory method will be used for all options of the matching type.
365+
*
366+
* @return option name
367+
*/
368+
String value() default "";
369+
}
370+
371+
/**
372+
* Factory method that creates an option from a prototype instance.
373+
* The prototype (the only parameter) must be a type that has {@code builder} method that returns a builder,
374+
* that can be built using {@code build} method (method names are customizable).
375+
* <p>
376+
* The return type of the method must match the type of the option.
377+
* <p>
378+
* This annotation MUST be on a static method that is part of a type referenced from blueprint through
379+
* {@link io.helidon.builder.api.Prototype.CustomMethods}.
380+
*/
381+
@Target(ElementType.METHOD)
382+
@Retention(RetentionPolicy.CLASS)
383+
public @interface RuntimeTypeFactoryMethod {
384+
/**
385+
* An explicit option name can be defined to only use this factory method for the specified option.
386+
* If none is specified, the factory method will be used for all options of the matching type.
387+
*
388+
* @return option name
389+
*/
390+
String value() default "";
391+
392+
/**
393+
* Name of the builder method on the prototype.
394+
* If set to {@code <init>}, the builder will be created through its constructor, in such a case
395+
* {@link #builderType()} must be provided as well.
396+
*
397+
* @return builder method name, defaults to {@code builder} if not set
398+
*/
399+
String builderMethodName() default "builder";
400+
401+
/**
402+
* Name of the build method on the builder to create an instance of the desired type.
403+
*
404+
* @return build method name, defaults to {@code build} if not set
405+
*/
406+
String buildMethodName() default "build";
407+
408+
/**
409+
* Type of the builder to use when creating the prototype instance.
410+
*
411+
* @return type of the builder, defaults to the type returned by the {@link #builderMethodName()}
412+
*/
413+
Class<?> builderType() default RuntimeTypeFactoryMethod.class;
414+
415+
/**
416+
* Type of the class declaring a {@link #builderMethodName()} used to get an instance of the builder to send
417+
* to the generated setter with consumer.
418+
*
419+
* @return type declaring the builder method
420+
*/
421+
Class<?> builderMethodType() default RuntimeTypeFactoryMethod.class;
422+
}
423+
329424
/**
330425
* Additional methods from this type will be added to the prototype and its builder.
331426
* All methods must be declared as static.
@@ -446,5 +541,36 @@ default void decorateAddSet(B builder, Set<T> optionValues) {
446541
String[] value() default {};
447542
}
448543

544+
/**
545+
* Extension definition for a blueprint.
546+
* Extensions allow modification of the generated code that is outside the scope of the default builder codegen.
547+
* This can be used, for example, to generate {@code JSON} binding annotations.
548+
*/
549+
@Target(ElementType.TYPE)
550+
@Retention(RetentionPolicy.CLASS)
551+
@Repeatable(Extensions.class)
552+
public @interface Extension {
553+
/**
554+
* Type the extension supports, see documentation of appropriate extension.
555+
*
556+
* @return type the extension supports
557+
*/
558+
Class<?> value();
559+
}
560+
561+
/**
562+
* Container of {@link io.helidon.builder.api.Prototype.Extension} annotations, to allow more than one extension to be
563+
* used.
564+
*/
565+
@Target(ElementType.TYPE)
566+
@Retention(RetentionPolicy.CLASS)
567+
public @interface Extensions {
568+
/**
569+
* Extensions to use.
570+
*
571+
* @return extensions to use
572+
*/
573+
Extension[] value();
574+
}
449575
}
450576

builder/api/src/main/java/io/helidon/builder/api/RuntimeType.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,16 +42,15 @@ public interface Api<T extends Prototype.Api> {
4242
T prototype();
4343
}
4444

45-
4645
/**
47-
* Mark this runtime type as prototyped by a specific prototype.
48-
* The prototype is generated through {@link io.helidon.builder.api.Prototype.Blueprint} definition.
49-
* <p>
50-
* This is complementing {@link RuntimeType} interface.
51-
* We need to have both the annotation and implement the interface, to correctly validate the object tree.
46+
* This annotation is no longer used.
47+
*
48+
* @deprecated this is an unnecessary duplication of what is required on the blueprint, use only blueprint
49+
* {@link io.helidon.builder.api.Prototype.Factory} instead.
5250
*/
5351
@Target(ElementType.TYPE)
5452
@Retention(RetentionPolicy.CLASS)
53+
@Deprecated(forRemoval = true, since = "4.4.0")
5554
public @interface PrototypedBy {
5655
/**
5756
* Type of the prototype.

0 commit comments

Comments
 (0)