Skip to content

Commit

Permalink
Introduce Pico ConfigBean Builder Extensions (#5482)
Browse files Browse the repository at this point in the history
  • Loading branch information
trentjeff authored Dec 2, 2022
1 parent 7972752 commit 4c90786
Show file tree
Hide file tree
Showing 175 changed files with 10,209 additions and 625 deletions.
41 changes: 27 additions & 14 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1391,40 +1391,53 @@
<version>${helidon.version}</version>
</dependency>

<!-- Pico -->
<!-- Builder -->
<dependency>
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico</artifactId>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico-types</artifactId>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-processor-spi</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-processor-tools</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-processor</artifactId>
<version>${helidon.version}</version>
</dependency>

<!-- Pico Builder -->
<!-- Pico Core -->
<dependency>
<groupId>io.helidon.pico.builder</groupId>
<artifactId>helidon-pico-builder</artifactId>
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.pico.builder</groupId>
<artifactId>helidon-pico-builder-processor-spi</artifactId>
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico-types</artifactId>
<version>${helidon.version}</version>
</dependency>

<!-- Pico Config Builder -->
<dependency>
<groupId>io.helidon.pico.builder</groupId>
<artifactId>helidon-pico-builder-processor-tools</artifactId>
<groupId>io.helidon.pico.builder.config</groupId>
<artifactId>helidon-pico-builder-config</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.pico.builder</groupId>
<artifactId>helidon-pico-builder-processor</artifactId>
<groupId>io.helidon.pico.builder.config</groupId>
<artifactId>helidon-pico-builder-config-processor</artifactId>
<version>${helidon.version}</version>
</dependency>


</dependencies>
</dependencyManagement>
</project>
20 changes: 10 additions & 10 deletions pico/builder/README.md → builder/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# pico-builder
# builder

The <b>Helidon Pico Builder</b> provides compile-time code generation for fluent builders. It was inspired by [Lombok]([https://projectlombok.org/), but the implementation here in Helidon is different in a few ways:
The <b>Helidon Builder</b> provides compile-time code generation for fluent builders. It was inspired by [Lombok]([https://projectlombok.org/), but the implementation here in Helidon is different in a few ways:
<ol>
<li>The <i>Builder</i> annotation targets interface or annotation types only. Your interface effectively contains the attributes of your getter as well as serving as the contract for your getter methods.</li>
<li>Generated classes implement your target interface (or annotation) and provide a fluent builder that will always have an implementation of <i>toString(), hashCode(), and equals().</i> implemented</li>
<li>Generated classes always behave like a <i>SuperBuilder</i> from Lombok. Basically this means that builders can form
a hierarchy on the types they target (e.g., Level2 derives from Level1 derives from Level0, etc.).</li>
<li>Lombok uses AOP while the Pico Builder generates source code. You can use the <i>Builder</i> annotation (as well as other annotations in the package and <i>ConfiguredOption</i>) to control the naming and other features of what and how the implementation classes are generated and behave.</li>
<li>Pico Builders are extensible - you can provide your own implementation of the <b>Builder Processor SPI</b> to customize the generated classes for your situation.</li>
<li>Lombok uses AOP while the Helidon Builder generates source code. You can use the <i>Builder</i> annotation (as well as other annotations in the package and <i>ConfiguredOption</i>) to control the naming and other features of what and how the implementation classes are generated and behave.</li>
<li>Builders are extensible - you can provide your own implementation of the <b>Builder Processor SPI</b> to customize the generated classes for your situation.</li>
</ol>

Supported annotation types (see [builder](./builder/src/main/java/io/helidon/pico/builder) for further details):
Supported annotation types (see [builder](./builder/src/main/java/io/helidon/builder) for further details):
* Builder - similar to Lombok's SuperBuilder.
* Singular - similar to Lombok's Singular.

Any and all types are supported by the Builder, with special handling for List, Map, Set, and Optional types. The target interface,
however, should only contain getter like methods (i.e., has a non-void return and takes no arguments). All static and default methods
are ignored on the target being processed.

The Helidon Pico Builder is completely independent of other parts of Pico. It can therefore be used in a standalone manner. The
The Helidon Builder is independent of other parts of Helidon. It can therefore be used in a standalone manner. The
generated implementation class will not require any special module to support those classes - just the types from your interface
and standard JRE types are used. This is made possible when your <i>Builder</i> has the <i>requireBuilderLibrary=false</i>. See the javadoc for details.

Expand All @@ -32,15 +32,15 @@ public interface MyConfigBean {
}
```
2. Annotate your interface definition with <i>Builder</i>, and optionally use <i>ConfiguredOption</i>, <i>Singular</i>, etc. Remember to review the annotation attributes javadoc for any customizations.
3. Compile (using the <i>pico-builder-processor</i> in your annotation classpath).
3. Compile (using the <i>builder-processor</i> in your annotation classpath).

The result of this will create (under ./target/generated-sources/annotations):
* MyConfigBeanImpl (in the same package as MyConfigBean) that will support multi-inheritance builders named MyConfigBeanImpl.Builder.
* Support for toString(), hashCode(), and equals() are always included.
* Support for toBuilder().
* Support for streams (see javadoc for [Builder](./builder/src/main/java/io/helidon/pico/builder/Builder.java)).
* Support for attribute visitors (see [test-builder](./tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/package-info.java)).
* Support for attribute validation (see ConfiguredOption#required() and [builder](./tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/package-info.java)).
* Support for streams (see javadoc for [Builder](./builder/src/main/java/io/helidon/builder/Builder.java)).
* Support for attribute visitors (see [test-builder](./tests/builder/src/main/java/io/helidon/builder/test/testsubjects/package-info.java)).
* Support for attribute validation (see ConfiguredOption#required() and [builder](./tests/builder/src/main/java/io/helidon/builder/test/testsubjects/package-info.java)).

The implementation of the processor also allows for a provider-based extensibility mechanism.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pico-builder
# builder

This module can either be used compile-time only or at runtime as well depending upon your usage.

Expand Down
8 changes: 4 additions & 4 deletions pico/builder/builder/pom.xml → builder/builder/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>
<groupId>io.helidon.pico.builder</groupId>
<artifactId>helidon-pico-builder-project</artifactId>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-project</artifactId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>helidon-pico-builder</artifactId>
<name>Helidon Pico Builder</name>
<artifactId>helidon-builder</artifactId>
<name>Helidon Builder</name>

<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package io.helidon.pico.builder;
package io.helidon.builder;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@
* limitations under the License.
*/

package io.helidon.pico.builder;
package io.helidon.builder;

import java.util.Map;
import java.util.function.Supplier;

/**
* A functional interface that can be used to visit all attributes of this type.
* <p>
* This type is used when {@link io.helidon.pico.builder.Builder#requireLibraryDependencies()} is used.
* This type is used when {@link Builder#requireLibraryDependencies()} is used.
*
* @param <T> type of the user defined context this attribute visitor supports
*/
@FunctionalInterface
public interface AttributeVisitor {
public interface AttributeVisitor<T> {

/**
* Visits the attribute named 'attrName'.
Expand All @@ -40,7 +42,7 @@ public interface AttributeVisitor {
void visit(String attrName,
Supplier<Object> valueSupplier,
Map<String, Object> meta,
Object userDefinedCtx,
T userDefinedCtx,
Class<?> type,
Class<?>... typeArgument);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package io.helidon.pico.builder;
package io.helidon.builder;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand Down Expand Up @@ -62,6 +62,11 @@
*/
String DEFAULT_SUFFIX = "";

/**
* The default value for {@link #allowNulls()}.
*/
boolean DEFAULT_ALLOW_NULLS = false;

/**
* The default list type used for the generated class implementation for any references to {@link java.util.List} is found
* on the methods of the {@link Builder}-annotation interface.
Expand Down Expand Up @@ -167,6 +172,14 @@
*/
boolean requireBeanStyle() default false;

/**
* Should the bean and the builder allow for the possibility of nullable non-{@link java.util.Optional} values to be present.
* Default is {@code false}.
*
* @return true to allow for the possibility of nullable non-Optional values to be present
*/
boolean allowNulls() default DEFAULT_ALLOW_NULLS;

/**
* The list implementation type to apply, defaulting to {@link #DEFAULT_LIST_TYPE}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package io.helidon.pico.builder;
package io.helidon.builder;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,49 +14,66 @@
* limitations under the License.
*/

package io.helidon.pico.builder.spi;
package io.helidon.builder;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import io.helidon.pico.builder.AttributeVisitor;

/**
* An implementation of {@link AttributeVisitor} that will validate each attribute to enforce not-null in accordance with
* {@link io.helidon.config.metadata.ConfiguredOption#required()}.
* <p>
* Note that the source type having the {@link io.helidon.pico.builder.Builder} must be annotated with
* Note that the source type having the {@link io.helidon.builder.Builder} must be annotated with
* {@code ConfiguredOption(required=true)} for this to be enforced.
* Also note that this implementation will be used only when
* {@link io.helidon.pico.builder.Builder#requireLibraryDependencies()} is enabled. If not enabled then an implementation
* {@link io.helidon.builder.Builder#requireLibraryDependencies()} is enabled. If not enabled then an implementation
* similar to this type will be inlined directly into the code-generated builder type.
*
* @deprecated This class is subject to change at any time - Helidon users should not use this directly. It will be referenced in
* code generated sources that Helidon generates.
*/
@Deprecated
public class RequiredAttributeVisitor implements AttributeVisitor {
public class RequiredAttributeVisitor implements AttributeVisitor<Object> {
private final List<String> errors = new ArrayList<>();
private final boolean allowNullsByDefault;

/**
* Default constructor.
*/
// note: this needs to remain public since it will be new'ed from code-generated builder processing ...
// important note: this needs to remain public since it will be new'ed from code-generated builder processing ...
public RequiredAttributeVisitor() {
this(Builder.DEFAULT_ALLOW_NULLS);
}

/**
* Constructor.
*
* @param allowNullsByDefault true if nulls should be allowed
*/
// important note: this needs to remain public since it will be new'ed from code-generated builder processing ...
public RequiredAttributeVisitor(boolean allowNullsByDefault) {
this.allowNullsByDefault = allowNullsByDefault;
}

@Override
// important note: this needs to remain public since it will be new'ed from code-generated builder processing ...
public void visit(String attrName,
Supplier<Object> valueSupplier,
Map<String, Object> meta,
Object userDefinedCtx,
Class<?> type,
Class<?>... typeArgument) {
boolean required = Boolean.parseBoolean((String) meta.get("required"));
if (!required) {
String requiredStr = (String) meta.get("required");
boolean requiredPresent = Objects.nonNull(requiredStr);
boolean required = Boolean.parseBoolean(requiredStr);
if (!required && requiredPresent) {
return;
}

if (allowNullsByDefault && !requiredPresent) {
return;
}

Expand All @@ -73,6 +90,7 @@ public void visit(String attrName,
*
* @throws java.lang.IllegalStateException when any attributes are in violation with the validation policy
*/
// important note: this needs to remain public since it will be new'ed from code-generated builder processing ...
public void validate() {
if (!errors.isEmpty()) {
throw new IllegalStateException(String.join(", ", errors));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package io.helidon.pico.builder;
package io.helidon.builder;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

/**
* The Builder API consists of a few annotations that can be used to create fluent builders for the types that use
* {@link io.helidon.pico.builder.Builder}, or otherwise one of its kind. The meta annotation
* {@link io.helidon.pico.builder.BuilderTrigger} is used to annotate the annotations that trigger custom-style builders.
* {@link io.helidon.builder.Builder}, or otherwise one of its kind. The meta annotation
* {@link io.helidon.builder.BuilderTrigger} is used to annotate the annotations that trigger custom-style builders.
* <p>
* The {@link io.helidon.pico.builder.Builder} annotation typically is applied to the an interface type, but it can also
* The {@link io.helidon.builder.Builder} annotation typically is applied to the an interface type, but it can also
* be used directly on annotation types as well. When applied, and if the annotation processor is applied for the builder-type
* annotation then an implementation class is generated that supports the fluent-builder pattern for that type.
* <p>
Expand All @@ -29,4 +29,4 @@
* <li>Any static or default method will be ignored during APT processing.</li>
* </ul>
*/
package io.helidon.pico.builder;
package io.helidon.builder;
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
*/

/**
* The Pico Builder API / SPI module.
* The Builder API module.
*/
module io.helidon.pico.builder {
exports io.helidon.pico.builder;
exports io.helidon.pico.builder.spi;
module io.helidon.builder {
exports io.helidon.builder;
}
Loading

0 comments on commit 4c90786

Please sign in to comment.