diff --git a/bom/pom.xml b/bom/pom.xml
index 49e4931b70a..751eda9e6e5 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -681,6 +681,11 @@
helidon-common-task${helidon.version}
+
+ io.helidon.common
+ helidon-common-types
+ ${helidon.version}
+ io.helidon.common.testinghelidon-common-testing-junit5
@@ -1405,36 +1410,65 @@
helidon-builder-processor${helidon.version}
+
+ io.helidon.builder
+ helidon-builder-config
+ ${helidon.version}
+
+
+ io.helidon.builder
+ helidon-builder-config-processor
+ ${helidon.version}
+ io.helidon.pico
- helidon-pico
+ helidon-pico-api${helidon.version}io.helidon.pico
- helidon-pico-types
+ helidon-pico-tools${helidon.version}io.helidon.pico
- helidon-pico-tools
+ helidon-pico-processor${helidon.version}
-
-
- io.helidon.pico.builder.config
- helidon-pico-builder-config
+ io.helidon.pico
+ helidon-pico-maven-plugin
+ ${helidon.version}
+
+
+ io.helidon.pico
+ helidon-pico-testing${helidon.version}
- io.helidon.pico.builder.config
- helidon-pico-builder-config-processor
+ io.helidon.pico
+ helidon-pico-services${helidon.version}
+
+
+ io.helidon.pico.configdriven
+ helidon-pico-configdriven-api
+ ${helidon.version}
+
+
+ io.helidon.pico.configdriven
+ helidon-pico-configdriven-services
+ ${helidon.version}
+
+
+ io.helidon.pico.configdriven
+ helidon-pico-configdriven-processor
+ ${helidon.version}
+
diff --git a/builder/README.md b/builder/README.md
index 62884107602..3a72dcd91d7 100644
--- a/builder/README.md
+++ b/builder/README.md
@@ -3,7 +3,7 @@
The Helidon Builder 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 Builder 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.
-
Generated classes implement your target interface (or annotation) and provide a fluent builder that will always have an implementation of toString(), hashCode(), and equals(). implemented
+
Generated classes implement your target interface (or annotation or abstract class) and provide a fluent builder that will always have an implementation of toString(), hashCode(), and equals(). implemented
Generated classes always behave like a SuperBuilder 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.).
Lombok uses AOP while the Helidon Builder generates source code. You can use the Builder annotation (as well as other annotations in the package and ConfiguredOption) to control the naming and other features of what and how the implementation classes are generated and behave.
@@ -38,7 +38,35 @@ public interface MyConfigBean {
}
```
2. Annotate your interface definition with Builder, and optionally use ConfiguredOption, Singular, etc. Remember to review the annotation attributes javadoc for any customizations.
-3. Compile (using the builder-processor in your annotation classpath).
+3. Builder (using the builder-processor in your annotation classpath).
+```xml
+ ...
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+
+ io.helidon.builder
+ helidon-builder-processor
+ ${helidon.version}
+
+
+
+ io.helidon.builder
+ helidon-builder-processor
+ ${helidon.version}
+
+
+
+
+
+
+ ...
+```
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.
@@ -49,16 +77,28 @@ The result of this will create (under ./target/generated-sources/annotations):
* Support for attribute validation (see ConfiguredOption#required() and [test-builder](./tests/builder/src/main/java/io/helidon/builder/test/testsubjects/package-info.java)).
* Support for builder interception (i.e., including decoration or mutation). (see [test-builder](./tests/builder/src/main/java/io/helidon/builder/test/testsubjects/package-info.java)).
+Also note that the generated code from Helidon Builder processors may add other dependencies that you will need to add (typically in provided scope).
+```xml
+
+ jakarta.annotation
+ jakarta.annotation-api
+ provided
+ true
+
+```
+
## Modules
* [builder](./builder) - provides the compile-time annotations, as well as optional runtime supporting types.
* [processor-spi](./processor-spi) - defines the Builder Processor SPI runtime definitions used by builder tooling. This module is only needed at compile time.
* [processor-tools](./processor-tools) - provides the concrete creators & code generators. This module is only needed at compile time.
* [processor](./processor) - the annotation processor which delegates to the processor-tools module for the main processing logic. This module is only needed at compile time.
-* [tests/builder](./tests/builder) - tests that can also serve as examples for usage.
+* [builder-config](./builder-config) - extension to the builder to additionally support [Helidon (Common) Config](../common/config) and [@ConfigBean](./builder-config/src/main/java/io/helidon/builder/config/ConfigBean.java).
+* [builder-config-processor](./builder-config-processor) - defines the ConfigBean builder.
+* [tests](./tests) - tests that can also serve as examples for usage.
-## Customizations
+## Customizations and Extensibility
To implement your own custom Builder:
-* See [pico/builder-config](../pico/builder-config) for an example.
+* See [builder-config](../builder-config) serving as an example.
## Usage
See [tests/builder](./tests/builder) for usage examples.
diff --git a/builder/builder-config-processor/README.md b/builder/builder-config-processor/README.md
new file mode 100644
index 00000000000..e3cecbdd9ef
--- /dev/null
+++ b/builder/builder-config-processor/README.md
@@ -0,0 +1,4 @@
+# builder-config-processor
+
+This module adds support for ConfigBean annotation.
+This module should typically only be used during compile time, in the APT compiler path only.
diff --git a/pico/builder-config/processor/pom.xml b/builder/builder-config-processor/pom.xml
similarity index 76%
rename from pico/builder-config/processor/pom.xml
rename to builder/builder-config-processor/pom.xml
index 194b7c1549c..a2d166456e2 100644
--- a/pico/builder-config/processor/pom.xml
+++ b/builder/builder-config-processor/pom.xml
@@ -19,36 +19,33 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- io.helidon.pico.builder.config
- helidon-pico-builder-config-project
+ io.helidon.builder
+ helidon-builder-project4.0.0-SNAPSHOT../pom.xml4.0.0
- helidon-pico-builder-config-processor
-
- Helidon Pico Builder ConfigBean Processor
+ helidon-builder-config-processor
+ Helidon Builder ConfigBean Processor
- io.helidon.pico.builder.config
- helidon-pico-builder-config
+ io.helidon.builder
+ helidon-builder-configio.helidon.builderhelidon-builder-processor
- io.helidon.pico
- helidon-pico-types
-
-
- io.helidon.pico
- helidon-pico
+ io.helidon.common
+ helidon-common-types
+
+
io.helidon.commonhelidon-common-config
diff --git a/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/ConfigBeanBuilderCreator.java b/builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/ConfigBeanBuilderCreator.java
similarity index 60%
rename from pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/ConfigBeanBuilderCreator.java
rename to builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/ConfigBeanBuilderCreator.java
index 235ffd69d60..3e6042adfc8 100644
--- a/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/ConfigBeanBuilderCreator.java
+++ b/builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/ConfigBeanBuilderCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.processor;
+package io.helidon.builder.config.processor;
import java.lang.annotation.Annotation;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -26,42 +25,54 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
import java.util.function.Supplier;
-
-import io.helidon.builder.processor.spi.TypeInfo;
+import java.util.stream.Collectors;
+
+import io.helidon.builder.config.ConfigBean;
+import io.helidon.builder.config.spi.ConfigBeanBuilderValidator;
+import io.helidon.builder.config.spi.ConfigBeanInfo;
+import io.helidon.builder.config.spi.ConfigResolver;
+import io.helidon.builder.config.spi.DefaultConfigResolverRequest;
+import io.helidon.builder.config.spi.GeneratedConfigBean;
+import io.helidon.builder.config.spi.GeneratedConfigBeanBase;
+import io.helidon.builder.config.spi.GeneratedConfigBeanBuilder;
+import io.helidon.builder.config.spi.GeneratedConfigBeanBuilderBase;
+import io.helidon.builder.config.spi.MetaConfigBeanInfo;
+import io.helidon.builder.config.spi.ResolutionContext;
import io.helidon.builder.processor.tools.BodyContext;
-import io.helidon.builder.processor.tools.BuilderTypeTools;
import io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider;
import io.helidon.common.Weight;
import io.helidon.common.Weighted;
import io.helidon.common.config.Config;
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.DefaultAnnotationAndValue;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
import io.helidon.config.metadata.ConfiguredOption;
-import io.helidon.pico.Contract;
-import io.helidon.pico.ExternalContracts;
-import io.helidon.pico.builder.config.ConfigBean;
-import io.helidon.pico.builder.config.spi.ConfigBeanBase;
-import io.helidon.pico.builder.config.spi.ConfigBeanBuilderBase;
-import io.helidon.pico.builder.config.spi.ConfigBeanBuilderValidator;
-import io.helidon.pico.builder.config.spi.ConfigBeanInfo;
-import io.helidon.pico.builder.config.spi.ConfigResolver;
-import io.helidon.pico.builder.config.spi.DefaultConfigResolverRequest;
-import io.helidon.pico.builder.config.spi.MetaConfigBeanInfo;
-import io.helidon.pico.builder.config.spi.ResolutionContext;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.DefaultAnnotationAndValue;
-import io.helidon.pico.types.DefaultTypeName;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
+
+import static io.helidon.builder.config.spi.ConfigBeanInfo.LevelType;
+import static io.helidon.builder.config.spi.ConfigBeanInfo.TAG_AT_LEAST_ONE;
+import static io.helidon.builder.config.spi.ConfigBeanInfo.TAG_DRIVES_ACTIVATION;
+import static io.helidon.builder.config.spi.ConfigBeanInfo.TAG_KEY;
+import static io.helidon.builder.config.spi.ConfigBeanInfo.TAG_LEVEL_TYPE;
+import static io.helidon.builder.config.spi.ConfigBeanInfo.TAG_REPEATABLE;
+import static io.helidon.builder.config.spi.ConfigBeanInfo.TAG_WANT_DEFAULT_CONFIG_BEAN;
/**
- * A specialization of {@link io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider} that supports the additional add-ons to the builder generated classes that
- * binds to the config sub-system.
+ * A specialization of {@link io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider} that supports the additional
+ * add-ons to the builder generated classes that binds to the config sub-system.
*
- * @see io.helidon.pico.builder.config.spi.ConfigBean
- * @see io.helidon.pico.builder.config.spi.ConfigBeanBuilder
+ * @see GeneratedConfigBean
+ * @see GeneratedConfigBeanBuilder
*/
@Weight(Weighted.DEFAULT_WEIGHT)
public class ConfigBeanBuilderCreator extends DefaultBuilderCreatorProvider {
+ static final String PICO_CONTRACT_TYPENAME = "io.helidon.pico.Contract";
+ static final String PICO_EXTERNAL_CONTRACT_TYPENAME = "io.helidon.pico.ExternalContracts";
+ static final String PICO_CONFIGUREDBY_TYPENAME = "io.helidon.pico.configdriven.ConfiguredBy";
/**
* Default constructor.
@@ -73,39 +84,81 @@ public ConfigBeanBuilderCreator() {
@Override
public Set> supportedAnnotationTypes() {
- return Collections.singleton(ConfigBean.class);
+ return Set.of(ConfigBean.class);
}
@Override
protected void preValidate(TypeName implTypeName,
TypeInfo typeInfo,
- AnnotationAndValue builderAnnotation) {
- assertNoAnnotation(Contract.class.getName(), typeInfo);
- assertNoAnnotation(ExternalContracts.class.getName(), typeInfo);
- // note to self: add this when ConfiguredBy is introduced --jtrent
-// assertNoAnnotation(ConfiguredBy.class.getName(), typeInfo);
+ AnnotationAndValue configBeanAnno) {
+ assertNoAnnotation(PICO_CONTRACT_TYPENAME, typeInfo);
+ assertNoAnnotation(PICO_EXTERNAL_CONTRACT_TYPENAME, typeInfo);
+ assertNoAnnotation(PICO_CONFIGUREDBY_TYPENAME, typeInfo);
assertNoAnnotation(jakarta.inject.Singleton.class.getName(), typeInfo);
assertNoAnnotation("javax.inject.Singleton", typeInfo);
- if (!typeInfo.typeKind().equals("INTERFACE")) {
- throw new IllegalStateException("@" + builderAnnotation.typeName().className()
+ if (!typeInfo.typeKind().equals(TypeInfo.KIND_INTERFACE)) {
+ throw new IllegalStateException("@" + configBeanAnno.typeName().className()
+ " is only supported on interface types: " + typeInfo.typeName());
}
+
+ boolean drivesActivation = Boolean.parseBoolean(configBeanAnno.value(TAG_DRIVES_ACTIVATION).orElseThrow());
+ LevelType levelType = LevelType.valueOf(configBeanAnno.value(TAG_LEVEL_TYPE).orElseThrow());
+ if (drivesActivation && levelType != LevelType.ROOT) {
+ throw new IllegalStateException("only levelType {" + LevelType.ROOT + "} config beans can drive activation for: "
+ + typeInfo.typeName());
+ }
+
+ boolean wantDefaultConfigBean = Boolean.parseBoolean(configBeanAnno.value(TAG_WANT_DEFAULT_CONFIG_BEAN).orElseThrow());
+ if (wantDefaultConfigBean && levelType != LevelType.ROOT) {
+ throw new IllegalStateException("only levelType {" + LevelType.ROOT + "} config beans can have a default bean for: "
+ + typeInfo.typeName());
+ }
+
+ assertNoGenericMaps(typeInfo);
+
+ super.preValidate(implTypeName, typeInfo, configBeanAnno);
+ }
+
+ /**
+ * Generic/simple map types are not supported on config beans, only <String, <Known ConfigBean types>>.
+ */
+ private void assertNoGenericMaps(TypeInfo typeInfo) {
+ List list = typeInfo.elementInfo().stream()
+ .filter(it -> it.typeName().isMap())
+ .filter(it -> {
+ TypeName typeName = it.typeName();
+ List componentArgs = typeName.typeArguments();
+ boolean bad = (componentArgs.size() != 2);
+ if (!bad) {
+ bad = !componentArgs.get(0).name().equals(String.class.getName());
+ // right now we will accept any component type - ConfigBean Type or other (just not generic)
+// bad |= !typeInfo.referencedTypeNamesToAnnotations().containsKey(componentArgs.get(1));
+ bad |= componentArgs.get(1).generic();
+ }
+ return bad;
+ })
+ .collect(Collectors.toList());
+
+ if (!list.isEmpty()) {
+ throw new IllegalStateException(list + ": only methods returning Map> are supported "
+ + "for: " + typeInfo.typeName());
+ }
}
@Override
- protected String generatedStickerFor(BodyContext ctx) {
- return BuilderTypeTools.generatedStickerFor(getClass().getName(), Versions.CURRENT_PICO_CONFIG_BUILDER_VERSION);
+ protected String generatedVersionFor(BodyContext ctx) {
+ return Versions.CURRENT_BUILDER_CONFIG_VERSION;
}
@Override
protected Optional baseExtendsTypeName(BodyContext ctx) {
- return Optional.of(DefaultTypeName.create(ConfigBeanBase.class));
+ return Optional.of(DefaultTypeName.create(GeneratedConfigBeanBase.class));
}
@Override
protected Optional baseExtendsBuilderTypeName(BodyContext ctx) {
- return Optional.of(DefaultTypeName.create(ConfigBeanBuilderBase.class));
+ return Optional.of(DefaultTypeName.create(GeneratedConfigBeanBuilderBase.class));
}
@Override
@@ -117,7 +170,9 @@ protected String instanceIdRef(BodyContext ctx) {
protected void appendExtraImports(StringBuilder builder,
BodyContext ctx) {
builder.append("\nimport ").append(AtomicInteger.class.getName()).append(";\n");
+
builder.append("import ").append(Optional.class.getName()).append(";\n");
+ builder.append("import ").append(Function.class.getName()).append(";\n\n");
builder.append("import ").append(Supplier.class.getName()).append(";\n\n");
super.appendExtraImports(builder, ctx);
@@ -178,7 +233,8 @@ protected void appendExtraCtorCode(StringBuilder builder,
BodyContext ctx,
String builderTag) {
if (!ctx.hasParent()) {
- builder.append("\t\tsuper(b, String.valueOf(__INSTANCE_ID.getAndIncrement()));\n");
+ builder.append("\t\tsuper(b, b.__config().isPresent() ? String.valueOf(__INSTANCE_ID.getAndIncrement()) : "
+ + "\"-1\");\n");
}
super.appendExtraCtorCode(builder, ctx, builderTag);
@@ -214,7 +270,7 @@ protected void appendExtraBuilderMethods(StringBuilder builder,
builder.append("\t\tpublic void acceptConfig"
+ "(Config cfg, ConfigResolver resolver, ConfigBeanBuilderValidator> validator) {\n");
builder.append("\t\t\t").append(ResolutionContext.class.getName())
- .append(" ctx = createResolutionContext(__configBeanType(), cfg, resolver, validator);\n");
+ .append(" ctx = createResolutionContext(__configBeanType(), cfg, resolver, validator, __mappers());\n");
builder.append("\t\t\t__config(ctx.config());\n");
builder.append("\t\t\t__acceptAndResolve(ctx);\n");
builder.append("\t\t\tsuper.finishedResolution(ctx);\n");
@@ -248,10 +304,9 @@ protected void appendExtraBuilderMethods(StringBuilder builder,
TypeName mapKeyType = null;
TypeName mapKeyComponentType = null;
boolean isMap = typeName.equals(Map.class.getName());
- boolean isCollection = (
- typeName.equals(Collection.class.getName())
- || typeName.equals(Set.class.getName())
- || typeName.equals(List.class.getName()));
+ boolean isCollection = (typeName.equals(Collection.class.getName())
+ || typeName.equals(Set.class.getName())
+ || typeName.equals(List.class.getName()));
if (isCollection) {
ofClause = "ofCollection";
type = type.typeArguments().get(0);
@@ -284,45 +339,84 @@ protected void appendExtraBuilderMethods(StringBuilder builder,
}
if (isMap) {
builder.append(".keyType(").append(Objects.requireNonNull(mapKeyType)).append(".class)");
- if (Objects.nonNull(mapKeyComponentType)) {
+ if (mapKeyComponentType != null) {
builder.append(".keyComponentType(").append(mapKeyComponentType.name()).append(".class)");
}
}
- builder.append(".build())\n\t\t\t\t\t.ifPresent((val) -> this.").append(method.elementName()).append("((");
+ builder.append(".build())\n\t\t\t\t\t.ifPresent(val -> this.").append(method.elementName()).append("((");
builder.append(outerTypeName).append(") val));\n");
i++;
}
+ builder.append("\t\t}\n\n");
+
+ builder.append("\t\t@Override\n");
+ builder.append("\t\tpublic Class> __configBeanType() {\n"
+ + "\t\t\treturn ")
+ .append(ctx.typeInfo().typeName().name()).append(".class;\n\t\t}\n\n");
+ builder.append("\t\t@Override\n");
+ builder.append("\t\tpublic Map, Function> __mappers() {\n"
+ + "\t\t\tMap, Function> result = ");
+ if (ctx.hasParent()) {
+ builder.append("super.__mappers();\n");
+ } else {
+ builder.append("new java.util.LinkedHashMap<>();\n");
+ }
+ appendAvailableReferencedBuilders(builder, "\t\t\tresult.", ctx.typeInfo());
+ builder.append("\t\t\treturn result;\n");
builder.append("\t\t}\n\n");
}
- builder.append("\t\t@Override\n");
- builder.append("\t\tpublic Class> __configBeanType() {\n"
- + "\t\t\treturn ")
- .append(ctx.typeInfo().typeName().name()).append(".class;\n\t\t}\n\n");
-
super.appendExtraBuilderMethods(builder, ctx);
}
+ private void appendAvailableReferencedBuilders(StringBuilder builder,
+ String prefix,
+ TypeInfo typeInfo) {
+ typeInfo.referencedTypeNamesToAnnotations().forEach((k, v) -> {
+ AnnotationAndValue builderAnnotation = DefaultAnnotationAndValue
+ .findFirst(io.helidon.builder.Builder.class.getName(), v).orElse(null);
+ if (builderAnnotation == null) {
+ builderAnnotation = DefaultAnnotationAndValue
+ .findFirst(ConfigBean.class.getName(), v).orElse(null);
+ }
+
+ if (builderAnnotation != null) {
+ TypeName referencedBuilderTypeName = toBuilderImplTypeName(k, builderAnnotation);
+ builder.append(prefix).append("put(").append(k.name()).append(".class, ");
+ builder.append(referencedBuilderTypeName).append("::toBuilder);\n");
+ }
+ });
+ }
+
@Override
protected boolean overridesVisitAttributes(BodyContext ctx) {
return true;
}
+ @Override
+ protected String toConfigKey(String name,
+ boolean isAttribute) {
+ return (isAttribute) ? ConfigBeanInfo.toConfigAttributeName(name) : ConfigBeanInfo.toConfigBeanName(name);
+ }
+
private void appendConfigBeanInfoAttributes(StringBuilder builder,
TypeInfo typeInfo,
AnnotationAndValue configBeanAnno) {
- String configKey = configBeanAnno.value("key").orElse(null);
- configKey = normalizeConfiguredOptionKey(configKey, typeInfo.typeName().className(), null);
-
- builder.append("\t\t\t\t\t\t.key(\"")
- .append(Objects.requireNonNull(configKey)).append("\")\n");
- builder.append("\t\t\t\t\t\t.repeatable(")
- .append(configBeanAnno.value("repeatable").orElseThrow()).append(")\n");
- builder.append("\t\t\t\t\t\t.drivesActivation(")
- .append(configBeanAnno.value("drivesActivation").orElseThrow()).append(")\n");
- builder.append("\t\t\t\t\t\t.atLeastOne(")
- .append(configBeanAnno.value("atLeastOne").orElseThrow()).append(")\n");
+ String configKey = configBeanAnno.value(TAG_KEY).orElse(null);
+ configKey = Objects.requireNonNull(normalizeConfiguredOptionKey(configKey, typeInfo.typeName().className(), false));
+ builder.append("\t\t\t\t\t\t.value(\"")
+ .append(configKey).append("\")\n");
+ builder.append("\t\t\t\t\t\t.").append(TAG_REPEATABLE).append("(")
+ .append(configBeanAnno.value(TAG_REPEATABLE).orElseThrow()).append(")\n");
+ builder.append("\t\t\t\t\t\t.").append(TAG_DRIVES_ACTIVATION).append("(")
+ .append(configBeanAnno.value(TAG_DRIVES_ACTIVATION).orElseThrow()).append(")\n");
+ builder.append("\t\t\t\t\t\t.").append(TAG_AT_LEAST_ONE).append("(")
+ .append(configBeanAnno.value(TAG_AT_LEAST_ONE).orElseThrow()).append(")\n");
+ builder.append("\t\t\t\t\t\t.").append(TAG_WANT_DEFAULT_CONFIG_BEAN).append("(")
+ .append(configBeanAnno.value(TAG_WANT_DEFAULT_CONFIG_BEAN).orElseThrow()).append(")\n");
+ builder.append("\t\t\t\t\t\t.").append(TAG_LEVEL_TYPE).append("(").append(LevelType.class.getCanonicalName()).append(".")
+ .append(configBeanAnno.value(TAG_LEVEL_TYPE).orElseThrow()).append(")\n");
}
private void javaDocMetaAttributesGetter(StringBuilder builder) {
@@ -363,13 +457,13 @@ private String toConfigKey(String attrName,
if (configuredOptions.isPresent()) {
configKey = configuredOptions.get().value("key").orElse(null);
}
- if (Objects.isNull(configKey) || configKey.isBlank()) {
- configKey = ConfigBeanInfo.toConfigKey(attrName);
+ if (configKey == null || configKey.isBlank()) {
+ configKey = ConfigBeanInfo.toConfigAttributeName(attrName);
}
return configKey;
}
- private void assertNoAnnotation(String annoTypeName,
+ private static void assertNoAnnotation(String annoTypeName,
TypeInfo typeInfo) {
Optional extends AnnotationAndValue> anno = DefaultAnnotationAndValue
.findFirst(annoTypeName, typeInfo.annotations());
diff --git a/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/Versions.java b/builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/Versions.java
similarity index 68%
rename from pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/Versions.java
rename to builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/Versions.java
index 8dc1b56f74c..1d10c150438 100644
--- a/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/Versions.java
+++ b/builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/Versions.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.processor;
+package io.helidon.builder.config.processor;
/**
- * Keeps track of the Pico Config Builder Interop Versions.
+ * Keeps track of the Helidon {@link io.helidon.builder.config.processor.ConfigBeanBuilderCreator} Builder Interop Versions.
*
- * Since Pico Config Builder performs code-generation, each previously generated artifact version may need to be discoverable in
+ * Since ConfigBean Builder performs code-generation, each previously generated artifact version may need to be discoverable in
* order to determine interoperability with previous release versions. This class will only track version changes for anything
* that might affect interoperability - it will not be rev'ed for general code enhancements and fixes.
*
@@ -30,12 +30,12 @@ public class Versions {
/**
* Version 1 - the initial release of Builder.
*/
- public static final String PICO_CONFIG_BUILDER_VERSION_1 = "1";
+ public static final String BUILDER_CONFIG_VERSION_1 = "1";
/**
- * The current release is {@link #PICO_CONFIG_BUILDER_VERSION_1}.
+ * The current release is {@link #BUILDER_CONFIG_VERSION_1}.
*/
- public static final String CURRENT_PICO_CONFIG_BUILDER_VERSION = PICO_CONFIG_BUILDER_VERSION_1;
+ public static final String CURRENT_BUILDER_CONFIG_VERSION = BUILDER_CONFIG_VERSION_1;
private Versions() {
}
diff --git a/builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/package-info.java b/builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/package-info.java
new file mode 100644
index 00000000000..28da5f089d4
--- /dev/null
+++ b/builder/builder-config-processor/src/main/java/io/helidon/builder/config/processor/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Helidon ConfigBean Builder Processor Extensions.
+ */
+package io.helidon.builder.config.processor;
diff --git a/pico/builder-config/processor/src/main/java/module-info.java b/builder/builder-config-processor/src/main/java/module-info.java
similarity index 61%
rename from pico/builder-config/processor/src/main/java/module-info.java
rename to builder/builder-config-processor/src/main/java/module-info.java
index 32bc81b9d04..ba982e394a8 100644
--- a/pico/builder-config/processor/src/main/java/module-info.java
+++ b/builder/builder-config-processor/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,22 +15,23 @@
*/
/**
- * Helidon Pico ConfigBean Builder Processor (Tools) module.
+ * Helidon ConfigBean Builder Processor module.
*/
-module io.helidon.pico.builder.config.processor {
+module io.helidon.builder.config.processor {
+ requires java.compiler;
requires jakarta.inject;
+ requires io.helidon.builder;
+ requires io.helidon.builder.config;
+ requires io.helidon.builder.processor.tools;
+ requires io.helidon.common.types;
requires io.helidon.common;
requires io.helidon.common.config;
requires io.helidon.config.metadata;
- requires io.helidon.pico.builder.config;
- requires io.helidon.builder.processor;
- requires io.helidon.builder.processor.spi;
- requires io.helidon.builder.processor.tools;
- requires io.helidon.pico.types;
- requires io.helidon.pico;
+ requires transitive io.helidon.builder.processor;
+ requires transitive io.helidon.builder.processor.spi;
- exports io.helidon.pico.builder.config.processor;
+ exports io.helidon.builder.config.processor;
provides io.helidon.builder.processor.spi.BuilderCreatorProvider
- with io.helidon.pico.builder.config.processor.ConfigBeanBuilderCreator;
+ with io.helidon.builder.config.processor.ConfigBeanBuilderCreator;
}
diff --git a/builder/builder-config-processor/src/test/java/io/helidon/builder/config/processor/ConfigBeanBuilderCreatorTest.java b/builder/builder-config-processor/src/test/java/io/helidon/builder/config/processor/ConfigBeanBuilderCreatorTest.java
new file mode 100644
index 00000000000..09561dea8db
--- /dev/null
+++ b/builder/builder-config-processor/src/test/java/io/helidon/builder/config/processor/ConfigBeanBuilderCreatorTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.processor;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.helidon.builder.Builder;
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+import io.helidon.builder.config.spi.ConfigBeanInfo;
+import io.helidon.common.types.DefaultAnnotationAndValue;
+import io.helidon.common.types.DefaultTypeInfo;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.DefaultTypedElementName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ConfigBeanBuilderCreatorTest {
+ final ConfigBeanBuilderCreator creator = new ConfigBeanBuilderCreator();
+
+ @Test
+ void supportedAnnotationTypes() {
+ assertThat(creator.supportedAnnotationTypes().toString(), creator.supportedAnnotationTypes().size(),
+ is(1));
+ assertThat(creator.supportedAnnotationTypes().iterator().next(),
+ equalTo(ConfigBean.class));
+ }
+
+ @Test
+ void preValidateConfigBeansMustBeInterfaces() {
+ TypeName implTypeName = DefaultTypeName.create(getClass());
+ TypeInfo typeInfo = DefaultTypeInfo.builder()
+ .typeKind(TypeInfo.KIND_CLASS)
+ .typeName(DefaultTypeName.create(getClass()))
+ .build();
+ DefaultAnnotationAndValue configBeanAnno = DefaultAnnotationAndValue.builder()
+ .typeName(DefaultTypeName.create(ConfigBean.class))
+ .values(Map.of(
+ ConfigBeanInfo.TAG_LEVEL_TYPE, ConfigBean.LevelType.ROOT.name(),
+ ConfigBeanInfo.TAG_DRIVES_ACTIVATION, "true"))
+ .build();
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> creator.preValidate(implTypeName, typeInfo, configBeanAnno));
+ assertThat(e.getMessage(),
+ equalTo("@ConfigBean is only supported on interface types: " + getClass().getName()));
+ }
+
+ @Test
+ void preValidateConfigBeansMustBeRootToDriveActivation() {
+ TypeName implTypeName = DefaultTypeName.create(getClass());
+ TypeInfo typeInfo = DefaultTypeInfo.builder()
+ .typeKind(TypeInfo.KIND_INTERFACE)
+ .typeName(DefaultTypeName.create(getClass()))
+ .build();
+ DefaultAnnotationAndValue configBeanAnno = DefaultAnnotationAndValue.builder()
+ .typeName(DefaultTypeName.create(ConfigBean.class))
+ .values(Map.of(
+ ConfigBeanInfo.TAG_LEVEL_TYPE, ConfigBean.LevelType.NESTED.name(),
+ ConfigBeanInfo.TAG_DRIVES_ACTIVATION, "true"))
+ .build();
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> creator.preValidate(implTypeName, typeInfo, configBeanAnno));
+ assertThat(e.getMessage(),
+ equalTo("only levelType {ROOT} config beans can drive activation for: " + getClass().getName()));
+ }
+
+ @Test
+ void preValidateConfigBeansMustBeRootToHaveDefaults() {
+ TypeName implTypeName = DefaultTypeName.create(getClass());
+ TypeInfo typeInfo = DefaultTypeInfo.builder()
+ .typeKind(TypeInfo.KIND_INTERFACE)
+ .typeName(DefaultTypeName.create(getClass()))
+ .build();
+ DefaultAnnotationAndValue configBeanAnno = DefaultAnnotationAndValue.builder()
+ .typeName(DefaultTypeName.create(ConfigBean.class))
+ .values(Map.of(
+ ConfigBeanInfo.TAG_LEVEL_TYPE, ConfigBean.LevelType.NESTED.name(),
+ ConfigBeanInfo.TAG_DRIVES_ACTIVATION, "false",
+ ConfigBeanInfo.TAG_WANT_DEFAULT_CONFIG_BEAN, "true"))
+ .build();
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> creator.preValidate(implTypeName, typeInfo, configBeanAnno));
+ assertThat(e.getMessage(),
+ equalTo("only levelType {ROOT} config beans can have a default bean for: " + getClass().getName()));
+ }
+
+ @Test
+ void preValidateConfigBeansMustNotHaveDuplicateSingularNames() {
+ TypedElementName method1 = DefaultTypedElementName.builder()
+ .elementName("socket")
+ .typeName(String.class)
+ .build();
+ TypedElementName method2 = DefaultTypedElementName.builder()
+ .elementName("socketSet")
+ .typeName(String.class)
+ .addAnnotation(DefaultAnnotationAndValue.create(Singular.class, "socket"))
+ .build();
+ TypeName implTypeName = DefaultTypeName.create(getClass());
+ TypeInfo typeInfo = DefaultTypeInfo.builder()
+ .typeKind(TypeInfo.KIND_INTERFACE)
+ .typeName(DefaultTypeName.create(getClass()))
+ .elementInfo(Set.of(method1, method2))
+ .build();
+ DefaultAnnotationAndValue configBeanAnno = DefaultAnnotationAndValue.builder()
+ .typeName(DefaultTypeName.create(ConfigBean.class))
+ .values(Map.of(
+ ConfigBeanInfo.TAG_LEVEL_TYPE, ConfigBean.LevelType.NESTED.name(),
+ ConfigBeanInfo.TAG_DRIVES_ACTIVATION, "false",
+ ConfigBeanInfo.TAG_WANT_DEFAULT_CONFIG_BEAN, "false"))
+ .build();
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> creator.preValidate(implTypeName, typeInfo, configBeanAnno));
+ assertThat(e.getMessage(),
+ startsWith("duplicate methods are using the same names [socket] for: "));
+ }
+
+ @Test
+ void preValidateConfigBeansMustHaveMapTypesWithNestedConfigBeans() {
+ TypedElementName method1 = DefaultTypedElementName.builder()
+ .elementName("socket")
+ .typeName(DefaultTypeName.builder()
+ .type(Map.class)
+ .typeArguments(List.of(
+ DefaultTypeName.create(String.class),
+ DefaultTypeName.create(String.class)))
+ .build())
+ .build();
+ TypeName implTypeName = DefaultTypeName.create(getClass());
+ TypeInfo typeInfo = DefaultTypeInfo.builder()
+ .typeKind(TypeInfo.KIND_INTERFACE)
+ .typeName(DefaultTypeName.create(getClass()))
+ .elementInfo(Set.of(method1))
+ .build();
+ DefaultAnnotationAndValue configBeanAnno = DefaultAnnotationAndValue.builder()
+ .typeName(DefaultTypeName.create(ConfigBean.class))
+ .values(Map.of(
+ ConfigBeanInfo.TAG_LEVEL_TYPE, ConfigBean.LevelType.NESTED.name(),
+ ConfigBeanInfo.TAG_DRIVES_ACTIVATION, "false",
+ ConfigBeanInfo.TAG_WANT_DEFAULT_CONFIG_BEAN, "false"))
+ .build();
+ // Map is supported
+// IllegalStateException e = assertThrows(IllegalStateException.class,
+// () -> creator.preValidate(implTypeName, typeInfo, configBeanAnno));
+// assertThat(e.getMessage(), startsWith(
+// "[java.util.Map socket]: only methods returning Map> are supported for: "));
+ creator.preValidate(implTypeName, typeInfo, configBeanAnno);
+
+ // now we will validate the exceptions when ConfigBeans are attempted to be embedded
+ TypedElementName method2 = DefaultTypedElementName.builder()
+ .elementName("unsupported1")
+ .typeName(DefaultTypeName.builder()
+ .type(Map.class)
+ // here we register a known config bean value (see below)
+ .typeArguments(List.of(
+ DefaultTypeName.create(String.class),
+ DefaultTypeName.create(getClass())))
+ .build())
+ .build();
+ TypedElementName method3 = DefaultTypedElementName.builder()
+ .elementName("unsupported2")
+ .typeName(DefaultTypeName.builder()
+ .type(Map.class)
+ // here we will just leave it generic, and this should fail
+// .typeArguments(List.of(
+// DefaultTypeName.create(String.class),
+// DefaultTypeName.create(getClass())))
+ .build())
+ .build();
+ TypeInfo typeInfo2 = DefaultTypeInfo.builder()
+ .typeKind(TypeInfo.KIND_INTERFACE)
+ .typeName(DefaultTypeName.create(getClass()))
+ .elementInfo(List.of(method2, method3))
+ .referencedTypeNamesToAnnotations(Map.of(DefaultTypeName.create(getClass()),
+ List.of(DefaultAnnotationAndValue.create(Builder.class))))
+ .build();
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> creator.preValidate(implTypeName, typeInfo2, configBeanAnno));
+ assertThat(e.getMessage(), startsWith(
+ "[java.util.Map unsupported2]: only methods returning Map> are supported for: "));
+ }
+
+}
diff --git a/builder/builder-config/README.md b/builder/builder-config/README.md
new file mode 100644
index 00000000000..747181d4235
--- /dev/null
+++ b/builder/builder-config/README.md
@@ -0,0 +1,43 @@
+# builder-config
+
+This module can be used at compile time or at runtime.
+
+The primary usage for this module involves the [ConfigBean](./src/main/java/io/helidon/builder/config/ConfigBean.java) annotation.
+A {@code ConfigBean} is another {@link io.helidon.builder.BuilderTrigger} that extends the {@link io.helidon.builder.Builder} concept to support the integration to Helidon's configuration sub-system. It basically provides everything that [io.helidon.builder.Builder](../builder) provides. However, unlike the base Builder generated classes that can handle any object type, the types used within your target ConfigBean-annotated interface must have all of its attribute getter method types resolvable by Helidon's [Config](../../common/config) sub-system.
+
+One should write a ConfigBean-annotated interface in such a way as to group the collection of configurable elements that logically belong together to then be delivered (and perhaps drive an activation of) one or more java service types that are said to be "[ConfiguredBy](../../pico/configdriven)" the given ConfigBean instance.
+
+The ConfigBean is therefore a logical grouping for the "pure configuration set of attributes (and sub-ConfigBean attributes) that typically originate from an external media store (e.g., property files, config maps, etc.), and are integrated via Helidon's [Config](../../common/config) subsystem at runtime.
+
+The [builder-config-processor](../builder-config-processor) module is required to be on the APT classpath in order to code-generate the implementation classes for the {@code ConfigBean}. This can replace the normal use of the [builder-processor](../processor) that supports just the Builder annotation. Using the builder-config-processor will support both Builder and ConfigBean annotation types as part of its processing.
+
+## Example
+```java
+@ConfigBean
+public interface MyConfigBean {
+ String getName();
+ int getPort();
+}
+```
+When [Helidon Pico](../../pico) services are incorporated into the application lifecycle at runtime, the configuration sub-system is scanned at startup and ConfigBean instances are created and fed into the ConfigBeanRegistry. This mapping occurs based upon the [io.helidon.config.metadata.ConfiguredOption#key()](../../config/metadata/src/main/java/io/helidon/config/metadata/ConfiguredOption.java) on each of the ConfigBean's attributes. If no such ConfiguredOption annotation is found then the type name is used as the key (e.g., MyConfigBean would map to "my-config-bean").
+
+Here is a modified example that shows the use of ConfiguredOption having default values applied.
+
+```java
+@ConfigBean("server")
+public interface ServerConfig {
+ @ConfiguredOption("0.0.0.0")
+ String host();
+
+ @ConfiguredOption("0")
+ int port();
+}
+```
+
+ConfigBean generated sources have a few extra methods on them. The most notable of these methods is the toBuilder(Config cfg) static method as demonstrated below.
+```java
+Config cfg = ...
+ServerConfig config = DefaultServerConfig.toBuilder(cfg).build();
+```
+
+The above can be used to programmatically create ConfigBean instances directly. However, when using [Helidon Pico](../../pico), and using various annotation attributes (see [the javadoc](./src/main/java/io/helidon/builder/config/ConfigBean.java) for details) on ConfigBean, the runtime behavior can be simplified further to automatically create these bean instances, and further drive startup activation of the services that are declared to be "configured by" these config bean instances. This means that simply having configuration present from your config subsystem will drive bean and service activations accordingly.
diff --git a/pico/builder-config/builder-config/pom.xml b/builder/builder-config/pom.xml
similarity index 82%
rename from pico/builder-config/builder-config/pom.xml
rename to builder/builder-config/pom.xml
index 78056393c23..084b5d23aa3 100644
--- a/pico/builder-config/builder-config/pom.xml
+++ b/builder/builder-config/pom.xml
@@ -21,16 +21,15 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- io.helidon.pico.builder.config
- helidon-pico-builder-config-project
+ io.helidon.builder
+ helidon-builder-project4.0.0-SNAPSHOT../pom.xml4.0.0
- helidon-pico-builder-config
-
- Helidon Pico Config Builder API / SPI
+ helidon-builder-config
+ Helidon Builder ConfigBean Builder
@@ -42,6 +41,13 @@
helidon-builder-processorprovided
+
+ jakarta.annotation
+ jakarta.annotation-api
+ provided
+ true
+
+
io.helidon.commonhelidon-common-config
@@ -49,12 +55,8 @@
jakarta.injectjakarta.inject-api
- provided
-
-
- io.helidon.config
- helidon-config-metadata
- provided
+ provided
+ trueio.helidon.common.testing
diff --git a/builder/builder-config/src/main/java/io/helidon/builder/config/ConfigBean.java b/builder/builder-config/src/main/java/io/helidon/builder/config/ConfigBean.java
new file mode 100644
index 00000000000..f2be3f7e891
--- /dev/null
+++ b/builder/builder-config/src/main/java/io/helidon/builder/config/ConfigBean.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import io.helidon.builder.BuilderTrigger;
+
+/**
+ * A {@code ConfigBean} is another {@link io.helidon.builder.BuilderTrigger} that extends the
+ * {@link io.helidon.builder.Builder} concept to support the integration to Helidon's configuration sub-system. It basically
+ * provides everything that {@link io.helidon.builder.Builder} provides. However, unlike the base
+ * {@link io.helidon.builder.Builder} generated classes that can handle any object type, the types used within your target
+ * {@code ConfigBean}-annotated interface must have all of its attribute getter method types resolvable by Helidon's configuration
+ * sub-system.
+ *
+ * The @code ConfigBean} is therefore a logical grouping for the "pure configuration" set of attributes (and
+ * sub-ConfigBean attributes) that typically originate from an external media store (e.g., property files, config maps,
+ * etc.), and are integrated via Helidon's {@link io.helidon.common.config.Config} subsystem at runtime.
+ *
+ * One should write a {@code ConfigBean}-annotated interface in such a way as to group the collection of configurable elements
+ * that logically belong together to then be delivered (and perhaps drive an activation of) one or more java service types that
+ * are said to be {@code ConfiguredBy} the given {@link ConfigBean} instance.
+ *
+ * The {@code builder-config-processor} module is required to be on the APT classpath to code-generate the implementation
+ * classes for the {@code ConfigBean}.
+ *
+ * When {@code Pico} services are incorporated into the application lifecycle at runtime, the configuration
+ * sub-system is scanned at startup and {@code ConfigBean} instances are created and fed into the {@code ConfigBeanRegistry}.
+ * This mapping occurs based upon the {@link io.helidon.config.metadata.ConfiguredOption#key()} on each of
+ * the {@code ConfigBean}'s attributes. If no such {@code ConfiguredOption} is found then the type name is used as the key
+ * (e.g., MyConfigBean would map to "my-config-bean").
+ *
+ * Also see {@code ConfiguredBy} in Pico's config-driven module.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.TYPE)
+@BuilderTrigger
+public @interface ConfigBean {
+
+ /**
+ * The overridden key to use. If not set this will default to use the expanded name from the config subsystem,
+ * (e.g. MyConfig -> "my-config").
+ *
+ * @return the overriding key to use
+ */
+ String value() default "";
+
+ /**
+ * Determines whether an instance of this config bean in the bean registry will result in the backing service
+ * {@code ConfiguredBy} this bean to be activated.
+ *
+ * As of the current release, only {@link LevelType#ROOT} level config beans can drive activation.
+ *
+ * The default value is {@code false}.
+ *
+ * @return true if this config bean should drive {@code ConfiguredBy} service activation
+ */
+ boolean drivesActivation() default false;
+
+ /**
+ * An instance of this bean will be created if there are no instances discovered by the configuration provider(s) post
+ * startup, and will use all default values annotated using {@code ConfiguredOptions} from the bean interface methods.
+ *
+ * The default value is {@code false}.
+ *
+ * @return the default config bean instance using defaults
+ */
+ boolean atLeastOne() default false;
+
+ /**
+ * Determines whether there can be more than one bean instance of this type.
+ *
+ * If false then only 0..1 behavior will be permissible for active beans in the config registry. If true then {@code > 1}
+ * instances will be permitted.
+ *
+ * Note: this attribute is dynamic in nature, and therefore cannot be validated at compile time. All violations found to this
+ * policy will be observed during PicoServices activation.
+ *
+ * The default value is {@code true}.
+ *
+ * @return true if repeatable
+ */
+ boolean repeatable() default true;
+
+ /**
+ * An instance of this bean will be created if there are no instances discovered by the configuration provider(s) post
+ * startup, and will use all default values annotated on the bean interface.
+ *
+ * As of the current release, only {@link LevelType#ROOT} level config beans can be defaulted.
+ *
+ * The default value is {@code false}.
+ *
+ * @return use the default config instance
+ */
+ boolean wantDefaultConfigBean() default false;
+
+ /**
+ * The {@link LevelType} of this config bean.
+ *
+ * The default level type is {@link LevelType#ROOT}.
+ *
+ * @return the level type of this config bean
+ */
+ LevelType levelType() default LevelType.ROOT;
+
+
+ /**
+ * Represents the level in the config tree to search for config bean instances. Currently, only
+ * {@link ConfigBean.LevelType#ROOT} is supported.
+ */
+ enum LevelType {
+ /**
+ * The config bean {@link #value()} must be at the root of the {@link io.helidon.common.config.ConfigValue} tree in order
+ * to trigger config bean instance creation.
+ *
+ * As of the current release, only {@code ROOT} level config beans can {@link #drivesActivation()}.
+ */
+ ROOT,
+
+ /**
+ * The config bean {@link #value()} must be at a depth > 0 of the config tree.
+ * As of the current release, {@code NESTED} config beans are unable to {@link #drivesActivation()}.
+ */
+ NESTED,
+
+ }
+
+}
diff --git a/pico/builder-config/builder-config/src/main/java/io/helidon/pico/builder/config/package-info.java b/builder/builder-config/src/main/java/io/helidon/builder/config/package-info.java
similarity index 81%
rename from pico/builder-config/builder-config/src/main/java/io/helidon/pico/builder/config/package-info.java
rename to builder/builder-config/src/main/java/io/helidon/builder/config/package-info.java
index fa8c61aee55..caf6563aa77 100644
--- a/pico/builder-config/builder-config/src/main/java/io/helidon/pico/builder/config/package-info.java
+++ b/builder/builder-config/src/main/java/io/helidon/builder/config/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,6 @@
*/
/**
- * Helidon Pico ConfigBean Builder API.
+ * Helidon Builder ConfigBean Support.
*/
-package io.helidon.pico.builder.config;
+package io.helidon.builder.config;
diff --git a/pico/builder-config/builder-config/src/main/java/io/helidon/pico/builder/config/spi/ConfigBeanBuilderValidator.java b/builder/builder-config/src/main/java/io/helidon/builder/config/spi/ConfigBeanBuilderValidator.java
similarity index 84%
rename from pico/builder-config/builder-config/src/main/java/io/helidon/pico/builder/config/spi/ConfigBeanBuilderValidator.java
rename to builder/builder-config/src/main/java/io/helidon/builder/config/spi/ConfigBeanBuilderValidator.java
index e8b64253edc..50b90f8b95f 100644
--- a/pico/builder-config/builder-config/src/main/java/io/helidon/pico/builder/config/spi/ConfigBeanBuilderValidator.java
+++ b/builder/builder-config/src/main/java/io/helidon/builder/config/spi/ConfigBeanBuilderValidator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.spi;
+package io.helidon.builder.config.spi;
import java.util.List;
import java.util.Map;
@@ -24,7 +24,7 @@
import io.helidon.builder.AttributeVisitor;
/**
- * Validates a {@link io.helidon.pico.builder.config.ConfigBean} generated builder type instance bean the builder build() is
+ * Validates a {@link io.helidon.builder.config.ConfigBean} generated builder type instance bean the builder build() is
* called and the result is consumed.
*
* @param the config bean builder type
@@ -42,10 +42,9 @@ public interface ConfigBeanBuilderValidator {
ValidationRound createValidationRound(CBB builder,
Class configBeanBuilderType);
-
/**
* The validation issue severity level.
- * @see ConfigBeanBuilderValidator.ValidationIssue#getSeverity()
+ * @see ConfigBeanBuilderValidator.ValidationIssue#severity()
*/
enum Severity {
@@ -73,7 +72,7 @@ interface ValidationRound extends AttributeVisitor
-
- io.helidon.pico
- helidon-pico-types
+ io.helidon.common
+ helidon-common-typesio.helidon.builder
diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/BuilderCreatorProvider.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/BuilderCreatorProvider.java
index e7e3c8f4f67..ea619f9b7a5 100644
--- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/BuilderCreatorProvider.java
+++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/BuilderCreatorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,8 @@
import java.util.List;
import java.util.Set;
-import io.helidon.pico.types.AnnotationAndValue;
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.TypeInfo;
/**
* Java {@link java.util.ServiceLoader} provider interface used to discover builder creators.
@@ -53,6 +54,7 @@ public interface BuilderCreatorProvider {
* @return the list of TypeAndBody sources to code-generate (tooling will handle the actual code generation aspects), or empty
* list to signal that the target type is not handled
*/
- List create(TypeInfo typeInfo, AnnotationAndValue builderAnnotation);
+ List create(TypeInfo typeInfo,
+ AnnotationAndValue builderAnnotation);
}
diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeAndBody.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeAndBody.java
index da6b48a3335..546b30dfc88 100644
--- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeAndBody.java
+++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeAndBody.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package io.helidon.builder.processor.spi;
-import io.helidon.pico.types.TypeName;
+import io.helidon.common.types.TypeName;
/**
* The default implementation of {@link TypeAndBody}.
diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeAndBody.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeAndBody.java
index 699cf86e1b5..0de7f8f3087 100644
--- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeAndBody.java
+++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeAndBody.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package io.helidon.builder.processor.spi;
-import io.helidon.pico.types.TypeName;
+import io.helidon.common.types.TypeName;
/**
* Represents the generated source as a model object.
diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfo.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfo.java
deleted file mode 100644
index 361cdf81166..00000000000
--- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfo.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.helidon.builder.processor.spi;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
-
-/**
- * Represents the model object for an interface or an abstract type (i.e., one that was annotated with
- * {@link io.helidon.builder.Builder}).
- */
-public interface TypeInfo {
-
- /**
- * The type name.
- *
- * @return the type name
- */
- TypeName typeName();
-
- /**
- * The type element kind.
- *
- * @return the type element kind (e.g., "INTERFACE", "ANNOTATION_TYPE", etc.)
- */
- String typeKind();
-
- /**
- * The annotations on the type.
- *
- * @return the annotations on the type
- */
- List annotations();
-
- /**
- * The elements that make up the type that are relevant for processing.
- *
- * @return the elements that make up the type that are relevant for processing
- */
- List elementInfo();
-
- /**
- * The elements that make up this type that are considered "other", or being skipped because they are irrelevant to processing.
- *
- * @return the elements that still make up the type, but are otherwise deemed irrelevant for processing
- */
- List otherElementInfo();
-
- /**
- * The parent/super class for this type info.
- *
- * @return the super type
- */
- Optional superTypeInfo();
-
- /**
- * Element modifiers.
- *
- * @return element modifiers
- */
- Set modifierNames();
-
-}
diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java
index a5fbcad60be..499327c87b2 100644
--- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java
+++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java
@@ -21,12 +21,13 @@
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.TypeElement;
-import io.helidon.pico.types.TypeName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
/**
* Java {@link java.util.ServiceLoader} provider interface used to discover type info creators.
*
- * Used to create a {@link TypeInfo} from the provided arguments.
+ * Used to create a {@link io.helidon.common.types.TypeInfo} from the provided arguments.
*/
public interface TypeInfoCreatorProvider {
diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/package-info.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/package-info.java
index c8d75d6a9e9..aa7ffcc5dfa 100644
--- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/package-info.java
+++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
/**
- * The Pico Builder Processor SPI module provides these definitions:
+ * The Builder Processor SPI module provides these definitions:
*
*
{@link io.helidon.builder.processor.spi.BuilderCreatorProvider} - responsible for code generating the
* implementation w/ a fluent builder.
diff --git a/builder/processor-spi/src/main/java/module-info.java b/builder/processor-spi/src/main/java/module-info.java
index 71e34822b14..8f118230e95 100644
--- a/builder/processor-spi/src/main/java/module-info.java
+++ b/builder/processor-spi/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
module io.helidon.builder.processor.spi {
requires java.compiler;
requires io.helidon.builder;
- requires io.helidon.pico.types;
+ requires io.helidon.common.types;
requires io.helidon.common;
exports io.helidon.builder.processor.spi;
diff --git a/builder/processor-tools/README.md b/builder/processor-tools/README.md
index f1241a09ca7..3f227ba6390 100644
--- a/builder/processor-tools/README.md
+++ b/builder/processor-tools/README.md
@@ -1,3 +1,3 @@
# builder-tools
-This module should typically only be used during compile time.
+This module should typically only be used during compile time, in the APT compiler path only.
diff --git a/builder/processor-tools/pom.xml b/builder/processor-tools/pom.xml
index 2023f2ca7b3..b46ab613995 100644
--- a/builder/processor-tools/pom.xml
+++ b/builder/processor-tools/pom.xml
@@ -41,8 +41,8 @@
helidon-builder-processor-spi
- io.helidon.pico
- helidon-pico-types
+ io.helidon.common
+ helidon-common-typesio.helidon.common
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BeanUtils.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BeanUtils.java
index 93b4786757b..ba6f566493b 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BeanUtils.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BeanUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +16,43 @@
package io.helidon.builder.processor.tools;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import io.helidon.common.LazyValue;
+import io.helidon.common.types.TypeName;
+
+import static io.helidon.common.types.TypeInfo.KIND_CLASS;
+import static io.helidon.common.types.TypeInfo.KIND_ENUM;
+import static io.helidon.common.types.TypeInfo.KIND_INTERFACE;
+import static io.helidon.common.types.TypeInfo.KIND_PACKAGE;
+import static io.helidon.common.types.TypeInfo.KIND_RECORD;
+import static io.helidon.common.types.TypeInfo.MODIFIER_ABSTRACT;
+import static io.helidon.common.types.TypeInfo.MODIFIER_FINAL;
+import static io.helidon.common.types.TypeInfo.MODIFIER_PRIVATE;
+import static io.helidon.common.types.TypeInfo.MODIFIER_PROTECTED;
+import static io.helidon.common.types.TypeInfo.MODIFIER_PUBLIC;
+import static io.helidon.common.types.TypeInfo.MODIFIER_STATIC;
+
/**
* Provides functions to aid with bean naming and parsing.
*/
public class BeanUtils {
+ private static final LazyValue> RESERVED = LazyValue.create(
+ Set.of(KIND_CLASS,
+ KIND_INTERFACE,
+ KIND_PACKAGE,
+ KIND_ENUM,
+ MODIFIER_STATIC,
+ MODIFIER_FINAL,
+ MODIFIER_PUBLIC,
+ MODIFIER_PROTECTED,
+ MODIFIER_PRIVATE,
+ KIND_RECORD,
+ MODIFIER_ABSTRACT
+ ));
private BeanUtils() {
}
@@ -110,10 +138,17 @@ public static boolean validateAndParseMethodName(String methodName,
* @return true if it appears to be a reserved word
*/
public static boolean isReservedWord(String word) {
- word = word.toLowerCase();
- return word.equals("class") || word.equals("interface") || word.equals("package") || word.equals("static")
- || word.equals("final") || word.equals("public") || word.equals("protected") || word.equals("private")
- || word.equals("abstract");
+ return RESERVED.get().contains(word.toUpperCase());
+ }
+
+ /**
+ * Returns true if the given type is known to be a built-in java type (e.g., package name starts with "java").
+ *
+ * @param type the fully qualified type name
+ * @return true if the given type is definitely known to be built-in Java type
+ */
+ public static boolean isBuiltInJavaType(TypeName type) {
+ return type.primitive() || type.name().startsWith("java.");
}
private static boolean validMethod(String name,
@@ -129,14 +164,14 @@ private static boolean validMethod(String name,
c = Character.toLowerCase(c);
String altName = "" + c + attrName.substring(1);
- attributeNameRef.set(Optional.of(Collections.singletonList(isReservedWord(altName) ? name : altName)));
+ attributeNameRef.set(Optional.of(List.of(isReservedWord(altName) ? name : altName)));
return true;
}
private static boolean validBooleanIsMethod(String name,
- AtomicReference>> attributeNameRef,
- boolean throwIfInvalid) {
+ AtomicReference>> attributeNameRef,
+ boolean throwIfInvalid) {
assert (name.trim().equals(name));
char c = name.charAt(2);
@@ -151,7 +186,9 @@ private static boolean validBooleanIsMethod(String name,
return true;
}
- private static boolean validMethodCase(String name, char c, boolean throwIfInvalid) {
+ private static boolean validMethodCase(String name,
+ char c,
+ boolean throwIfInvalid) {
if (!Character.isAlphabetic(c)) {
return invalidMethod(name,
throwIfInvalid,
@@ -167,7 +204,9 @@ private static boolean validMethodCase(String name, char c, boolean throwIfInval
return true;
}
- private static boolean invalidMethod(String methodName, boolean throwIfInvalid, String message) {
+ private static boolean invalidMethod(String methodName,
+ boolean throwIfInvalid,
+ String message) {
if (throwIfInvalid) {
throw new RuntimeException(message + ": " + methodName);
}
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java
index 62f7e023822..633ed71cabc 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java
@@ -17,7 +17,6 @@
package io.helidon.builder.processor.tools;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -26,13 +25,16 @@
import java.util.concurrent.atomic.AtomicReference;
import io.helidon.builder.Builder;
-import io.helidon.builder.processor.spi.TypeInfo;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.DefaultAnnotationAndValue;
-import io.helidon.pico.types.DefaultTypeName;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
-
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.DefaultAnnotationAndValue;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
+
+import static io.helidon.builder.processor.tools.BeanUtils.isBooleanType;
+import static io.helidon.builder.processor.tools.BeanUtils.isReservedWord;
+import static io.helidon.builder.processor.tools.BeanUtils.validateAndParseMethodName;
import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.BUILDER_ANNO_TYPE_NAME;
import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.DEFAULT_INCLUDE_META_ATTRIBUTES;
import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.DEFAULT_LIST_TYPE;
@@ -55,8 +57,6 @@ public class BodyContext {
private final Map map = new LinkedHashMap<>();
private final List allTypeInfos = new ArrayList<>();
private final List allAttributeNames = new ArrayList<>();
- private final AtomicReference parentTypeName = new AtomicReference<>();
- private final AtomicReference parentAnnotationType = new AtomicReference<>();
private final boolean hasStreamSupportOnImpl;
private final boolean hasStreamSupportOnBuilder;
private final boolean includeMetaAttributes;
@@ -77,6 +77,8 @@ public class BodyContext {
private final String publicOrPackagePrivateDecl;
private final TypeName interceptorTypeName;
private final String interceptorCreateMethod;
+ private final TypeName parentTypeName;
+ private final TypeName parentAnnotationTypeName;
/**
* Constructor.
@@ -104,19 +106,14 @@ public class BodyContext {
this.listType = toListImplType(builderTriggerAnnotation, typeInfo);
this.mapType = toMapImplType(builderTriggerAnnotation, typeInfo);
this.setType = toSetImplType(builderTriggerAnnotation, typeInfo);
- try {
- gatherAllAttributeNames(this, typeInfo);
- } catch (Exception e) {
- throw new IllegalStateException("Failed while processing: " + typeInfo.typeName(), e);
- }
+ this.parentTypeName = toParentTypeName(builderTriggerAnnotation, typeInfo);
+ this.parentAnnotationTypeName = toParentAnnotationTypeName(typeInfo);
+ gatherAllAttributeNames(typeInfo);
assert (allTypeInfos.size() == allAttributeNames.size());
- this.hasParent = (parentTypeName.get() != null && hasBuilder(typeInfo.superTypeInfo(), builderTriggerAnnotation));
+ this.hasParent = (parentTypeName != null && hasBuilder(typeInfo.superTypeInfo().orElse(null), builderTriggerAnnotation));
this.hasAnyBuilderClashingMethodNames = determineIfHasAnyClashingMethodNames();
- this.isExtendingAnAbstractClass = typeInfo.typeKind().equals("CLASS");
- this.ctorBuilderAcceptTypeName = (hasParent)
- ? typeInfo.typeName()
- : (Objects.nonNull(parentAnnotationType.get()) && typeInfo.elementInfo().isEmpty()
- ? typeInfo.superTypeInfo().orElseThrow().typeName() : typeInfo.typeName());
+ this.isExtendingAnAbstractClass = typeInfo.typeKind().equals(TypeInfo.KIND_CLASS);
+ this.ctorBuilderAcceptTypeName = toCtorBuilderAcceptTypeName(typeInfo, hasParent, parentAnnotationTypeName);
this.genericBuilderClassDecl = "Builder";
this.genericBuilderAliasDecl = ("B".equals(typeInfo.typeName().className())) ? "BU" : "B";
this.genericBuilderAcceptAliasDecl = ("T".equals(typeInfo.typeName().className())) ? "TY" : "T";
@@ -127,9 +124,10 @@ public class BodyContext {
searchForBuilderAnnotation("interceptorCreateMethod", builderTriggerAnnotation, typeInfo);
this.interceptorCreateMethod = (interceptorCreateMethod == null || interceptorCreateMethod.isEmpty())
? null : interceptorCreateMethod;
- this.publicOrPackagePrivateDecl = (typeInfo.typeKind().equals("INTERFACE")
+ this.publicOrPackagePrivateDecl = (typeInfo.typeKind().equals(TypeInfo.KIND_INTERFACE)
|| typeInfo.modifierNames().isEmpty()
- || typeInfo.modifierNames().contains("PUBLIC")) ? "public " : "";
+ || typeInfo.modifierNames().contains(TypeInfo.MODIFIER_PUBLIC))
+ ? "public " : "";
}
@Override
@@ -206,17 +204,17 @@ public List allAttributeNames() {
*
* @return the parent type name
*/
- public AtomicReference parentTypeName() {
- return parentTypeName;
+ public Optional parentTypeName() {
+ return Optional.ofNullable(parentTypeName);
}
/**
- * Returns the parent annotation type.
+ * Returns the parent annotation type name.
*
* @return the parent annotation type
*/
- protected AtomicReference parentAnnotationType() {
- return parentAnnotationType;
+ protected Optional parentAnnotationTypeName() {
+ return Optional.ofNullable(parentAnnotationTypeName);
}
/**
@@ -440,8 +438,8 @@ public boolean hasOtherMethod(String name,
protected static String toBeanAttributeName(TypedElementName method,
boolean isBeanStyleRequired) {
AtomicReference>> refAttrNames = new AtomicReference<>();
- BeanUtils.validateAndParseMethodName(method.elementName(), method.typeName().name(), isBeanStyleRequired, refAttrNames);
- List attrNames = (refAttrNames.get().isEmpty()) ? Collections.emptyList() : refAttrNames.get().get();
+ validateAndParseMethodName(method.elementName(), method.typeName().name(), isBeanStyleRequired, refAttrNames);
+ List attrNames = (refAttrNames.get().isEmpty()) ? List.of() : refAttrNames.get().get();
if (!isBeanStyleRequired) {
return (!attrNames.isEmpty()) ? attrNames.get(0) : method.elementName();
}
@@ -503,15 +501,6 @@ private static boolean toIncludeGeneratedAnnotation(AnnotationAndValue builderTr
return (val == null) ? Builder.DEFAULT_INCLUDE_GENERATED_ANNOTATION : Boolean.parseBoolean(val);
}
- /**
- * In support of {@link io.helidon.builder.Builder#defineDefaultMethods()}.
- */
- private static boolean toDefineDefaultMethods(AnnotationAndValue builderTriggerAnnotation,
- TypeInfo typeInfo) {
- String val = searchForBuilderAnnotation("defineDefaultMethods", builderTriggerAnnotation, typeInfo);
- return (val == null) ? Builder.DEFAULT_DEFINE_DEFAULT_METHODS : Boolean.parseBoolean(val);
- }
-
/**
* In support of {@link io.helidon.builder.Builder#listImplType()}.
*/
@@ -550,7 +539,7 @@ private static String searchForBuilderAnnotation(String key,
if (!builderTriggerAnnotation.typeName().equals(BUILDER_ANNO_TYPE_NAME)) {
AnnotationAndValue builderAnnotation = DefaultAnnotationAndValue
.findFirst(BUILDER_ANNO_TYPE_NAME.name(), typeInfo.annotations()).orElse(null);
- if (Objects.nonNull(builderAnnotation)) {
+ if (builderAnnotation != null) {
val = builderAnnotation.value(key).orElse(null);
}
}
@@ -562,70 +551,60 @@ private static String searchForBuilderAnnotation(String key,
return val;
}
- private static void gatherAllAttributeNames(BodyContext ctx,
- TypeInfo typeInfo) {
+ private void gatherAllAttributeNames(TypeInfo typeInfo) {
TypeInfo superTypeInfo = typeInfo.superTypeInfo().orElse(null);
- if (Objects.nonNull(superTypeInfo)) {
+ if (superTypeInfo != null) {
Optional extends AnnotationAndValue> superBuilderAnnotation = DefaultAnnotationAndValue
- .findFirst(ctx.builderTriggerAnnotation.typeName().name(), superTypeInfo.annotations());
+ .findFirst(builderTriggerAnnotation.typeName().name(), superTypeInfo.annotations());
if (superBuilderAnnotation.isEmpty()) {
- gatherAllAttributeNames(ctx, superTypeInfo);
+ gatherAllAttributeNames(superTypeInfo);
} else {
- populateMap(ctx.map, superTypeInfo, ctx.beanStyleRequired);
- }
-
- if (Objects.isNull(ctx.parentTypeName.get())
- && superTypeInfo.typeKind().equals(DefaultBuilderCreatorProvider.INTERFACE)) {
- ctx.parentTypeName.set(superTypeInfo.typeName());
- } else if (Objects.isNull(ctx.parentAnnotationType.get())
- && superTypeInfo.typeKind().equals("ANNOTATION_TYPE")) {
- ctx.parentAnnotationType.set(superTypeInfo.typeName());
+ populateMap(map, superTypeInfo, beanStyleRequired);
}
}
for (TypedElementName method : typeInfo.elementInfo()) {
- String beanAttributeName = toBeanAttributeName(method, ctx.beanStyleRequired);
- TypedElementName existing = ctx.map.get(beanAttributeName);
- if (Objects.nonNull(existing)
- && BeanUtils.isBooleanType(method.typeName().name())
+ String beanAttributeName = toBeanAttributeName(method, beanStyleRequired);
+ TypedElementName existing = map.get(beanAttributeName);
+ if (existing != null
+ && isBooleanType(method.typeName().name())
&& method.elementName().startsWith("is")) {
AtomicReference>> alternateNames = new AtomicReference<>();
- BeanUtils.validateAndParseMethodName(method.elementName(),
+ validateAndParseMethodName(method.elementName(),
method.typeName().name(), true, alternateNames);
- assert (Objects.nonNull(alternateNames.get()));
- final String currentAttrName = beanAttributeName;
- Optional alternateName = alternateNames.get().orElse(Collections.emptyList()).stream()
+ String currentAttrName = beanAttributeName;
+ Optional alternateName = alternateNames.get().orElse(List.of()).stream()
.filter(it -> !it.equals(currentAttrName))
.findFirst();
- if (alternateName.isPresent() && !ctx.map.containsKey(alternateName.get())
- && !BeanUtils.isReservedWord(alternateName.get())) {
+ if (alternateName.isPresent() && !map.containsKey(alternateName.get())
+ && !isReservedWord(alternateName.get())) {
beanAttributeName = alternateName.get();
- existing = ctx.map.get(beanAttributeName);
+ existing = map.get(beanAttributeName);
}
}
- if (Objects.nonNull(existing)) {
+ if (existing != null) {
if (!existing.typeName().equals(method.typeName())) {
throw new IllegalStateException(method + " cannot redefine types from super for " + beanAttributeName);
}
// allow the subclass to override the defaults, etc.
- Objects.requireNonNull(ctx.map.put(beanAttributeName, method));
- int pos = ctx.allAttributeNames.indexOf(beanAttributeName);
+ Objects.requireNonNull(map.put(beanAttributeName, method));
+ int pos = allAttributeNames.indexOf(beanAttributeName);
if (pos >= 0) {
- ctx.allTypeInfos.set(pos, method);
+ allTypeInfos.set(pos, method);
}
continue;
}
- Object prev = ctx.map.put(beanAttributeName, method);
- assert (Objects.isNull(prev));
+ Object prev = map.put(beanAttributeName, method);
+ assert (prev == null);
- ctx.allTypeInfos.add(method);
- if (ctx.allAttributeNames.contains(beanAttributeName)) {
+ allTypeInfos.add(method);
+ if (allAttributeNames.contains(beanAttributeName)) {
throw new IllegalStateException("duplicate attribute name: " + beanAttributeName + " processing " + typeInfo);
}
- ctx.allAttributeNames.add(beanAttributeName);
+ allAttributeNames.add(beanAttributeName);
}
}
@@ -639,7 +618,7 @@ private static void populateMap(Map map,
for (TypedElementName method : typeInfo.elementInfo()) {
String beanAttributeName = toBeanAttributeName(method, isBeanStyleRequired);
TypedElementName existing = map.get(beanAttributeName);
- if (Objects.nonNull(existing)) {
+ if (existing != null) {
if (!existing.typeName().equals(method.typeName())) {
throw new IllegalStateException(method + " cannot redefine types from super for " + beanAttributeName);
}
@@ -648,7 +627,7 @@ private static void populateMap(Map map,
Objects.requireNonNull(map.put(beanAttributeName, method));
} else {
Object prev = map.put(beanAttributeName, method);
- assert (Objects.isNull(prev));
+ assert (prev == null);
}
}
}
@@ -663,16 +642,55 @@ private boolean isBuilderClashingMethodName(String beanAttributeName) {
|| beanAttributeName.equals("toStringInner");
}
- private boolean hasBuilder(Optional typeInfo, AnnotationAndValue builderTriggerAnnotation) {
- if (typeInfo.isEmpty()) {
+ private boolean hasBuilder(TypeInfo typeInfo,
+ AnnotationAndValue builderTriggerAnnotation) {
+ if (typeInfo == null) {
return false;
}
TypeName builderAnnoTypeName = builderTriggerAnnotation.typeName();
- boolean hasBuilder = typeInfo.get().annotations().stream()
+ boolean hasBuilder = typeInfo.annotations().stream()
.map(AnnotationAndValue::typeName)
.anyMatch(it -> it.equals(builderAnnoTypeName));
- return hasBuilder || hasBuilder(typeInfo.get().superTypeInfo(), builderTriggerAnnotation);
+ return hasBuilder || hasBuilder(typeInfo.superTypeInfo().orElse(null), builderTriggerAnnotation);
+ }
+
+ private static TypeName toCtorBuilderAcceptTypeName(TypeInfo typeInfo,
+ boolean hasParent,
+ TypeName parentAnnotationTypeName) {
+ if (hasParent) {
+ return typeInfo.typeName();
+ }
+
+ return (parentAnnotationTypeName != null && typeInfo.elementInfo().isEmpty()
+ ? typeInfo.superTypeInfo().orElseThrow().typeName() : typeInfo.typeName());
+ }
+
+ private static TypeName toParentTypeName(AnnotationAndValue builderTriggerAnnotation,
+ TypeInfo typeInfo) {
+ TypeInfo superTypeInfo = typeInfo.superTypeInfo().orElse(null);
+ if (superTypeInfo != null) {
+ Optional extends AnnotationAndValue> superBuilderAnnotation = DefaultAnnotationAndValue
+ .findFirst(builderTriggerAnnotation.typeName().name(), superTypeInfo.annotations());
+ if (superBuilderAnnotation.isEmpty()) {
+ return toParentTypeName(builderTriggerAnnotation, superTypeInfo);
+ }
+
+ if (superTypeInfo.typeKind().equals(TypeInfo.KIND_INTERFACE)) {
+ return superTypeInfo.typeName();
+ }
+ }
+
+ return null;
+ }
+
+ private static TypeName toParentAnnotationTypeName(TypeInfo typeInfo) {
+ TypeInfo superTypeInfo = typeInfo.superTypeInfo().orElse(null);
+ if (superTypeInfo != null && superTypeInfo.typeKind().equals(TypeInfo.KIND_ANNOTATION_TYPE)) {
+ return superTypeInfo.typeName();
+ }
+
+ return null;
}
}
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java
index b9edbf070fc..3c3da5f6cff 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java
@@ -44,17 +44,19 @@
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
-import io.helidon.builder.processor.spi.DefaultTypeInfo;
-import io.helidon.builder.processor.spi.TypeInfo;
import io.helidon.builder.processor.spi.TypeInfoCreatorProvider;
import io.helidon.common.Weight;
import io.helidon.common.Weighted;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.DefaultAnnotationAndValue;
-import io.helidon.pico.types.DefaultTypeName;
-import io.helidon.pico.types.DefaultTypedElementName;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.DefaultAnnotationAndValue;
+import io.helidon.common.types.DefaultTypeInfo;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.DefaultTypedElementName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
+
+import static io.helidon.builder.processor.tools.BeanUtils.isBuiltInJavaType;
/**
* The default implementation for {@link io.helidon.builder.processor.spi.TypeInfoCreatorProvider}. This also contains an abundance of
@@ -62,36 +64,27 @@
*/
@Weight(Weighted.DEFAULT_WEIGHT - 1)
public class BuilderTypeTools implements TypeInfoCreatorProvider {
- private static final boolean ACCEPT_ABSTRACT_CLASS_TARGETS = true;
-
/**
* Default constructor. Service loaded.
*
- * @deprecated
+ * @deprecated needed for service loader
*/
- // note: this needs to remain public since it will be resolved via service loader ...
@Deprecated
public BuilderTypeTools() {
}
@Override
- public Optional createTypeInfo(
- TypeName annotationTypeName,
- TypeName typeName,
- TypeElement element,
- ProcessingEnvironment processingEnv,
- boolean wantDefaultMethods) {
+ @SuppressWarnings("unchecked")
+ public Optional createTypeInfo(TypeName annotationTypeName,
+ TypeName typeName,
+ TypeElement element,
+ ProcessingEnvironment processingEnv,
+ boolean wantDefaultMethods) {
Objects.requireNonNull(annotationTypeName);
if (typeName.name().equals(Annotation.class.getName())) {
return Optional.empty();
}
- if (!isAcceptableBuilderTarget(element)) {
- String msg = annotationTypeName + " is not intended to be targeted to this type: " + element;
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg);
- throw new IllegalStateException(msg);
- }
-
List problems = element.getEnclosedElements().stream()
.filter(it -> it.getKind() == ElementKind.METHOD)
.map(ExecutableElement.class::cast)
@@ -113,37 +106,53 @@ public Optional createTypeInfo(
return Optional.of(DefaultTypeInfo.builder()
.typeName(typeName)
.typeKind(String.valueOf(element.getKind()))
- .annotations(BuilderTypeTools
- .createAnnotationAndValueListFromElement(element,
- processingEnv.getElementUtils()))
+ .annotations(
+ createAnnotationAndValueListFromElement(element, processingEnv.getElementUtils()))
.elementInfo(elementInfo)
.otherElementInfo(otherElementInfo)
+ .referencedTypeNamesToAnnotations(
+ toReferencedTypeNamesAndAnnotations(
+ processingEnv, typeName, elementInfo, otherElementInfo))
.modifierNames(modifierNames)
.update(it -> toTypeInfo(annotationTypeName, element, processingEnv, wantDefaultMethods)
.ifPresent(it::superTypeInfo))
.build());
}
- /**
- * Determines if the target element with the {@link io.helidon.builder.Builder} annotation is an acceptable element type.
- * If it is not acceptable then the caller is expected to throw an exception or log an error, etc.
- *
- * @param element the element
- * @return true if the element is acceptable
- */
- protected boolean isAcceptableBuilderTarget(
- Element element) {
- final ElementKind kind = element.getKind();
- final Set modifiers = element.getModifiers();
- boolean isAcceptable = (kind == ElementKind.INTERFACE
- || kind == ElementKind.ANNOTATION_TYPE
- || (ACCEPT_ABSTRACT_CLASS_TARGETS
- && (kind == ElementKind.CLASS && modifiers.contains(Modifier.ABSTRACT))));
- return isAcceptable;
+ @SuppressWarnings("unchecked")
+ private Map> toReferencedTypeNamesAndAnnotations(ProcessingEnvironment processingEnv,
+ TypeName typeName,
+ Collection... refs) {
+ Map> result = new LinkedHashMap<>();
+ for (Collection ref : refs) {
+ for (TypedElementName typedElementName : ref) {
+ collectReferencedTypeNames(result, processingEnv, typeName, List.of(typedElementName.typeName()));
+ collectReferencedTypeNames(result, processingEnv, typeName, typedElementName.typeName().typeArguments());
+ }
+ }
+ return result;
+ }
+
+ private void collectReferencedTypeNames(Map> result,
+ ProcessingEnvironment processingEnv,
+ TypeName typeName,
+ Collection referencedColl) {
+ for (TypeName referenced : referencedColl) {
+ if (isBuiltInJavaType(referenced) || typeName.equals(referenced)) {
+ continue;
+ }
+
+ // first time processing, we only need to do this on pass #1
+ result.computeIfAbsent(referenced, (k) -> {
+ TypeElement typeElement = processingEnv.getElementUtils().getTypeElement(k.name());
+ return (typeElement == null)
+ ? null : createAnnotationAndValueListFromElement(typeElement, processingEnv.getElementUtils());
+ });
+ }
}
/**
- * Translation the arguments to a collection of {@link io.helidon.pico.types.TypedElementName}'s.
+ * Translation the arguments to a collection of {@link io.helidon.common.types.TypedElementName}'s.
*
* @param element the typed element (i.e., class)
* @param processingEnv the processing env
@@ -151,11 +160,10 @@ protected boolean isAcceptableBuilderTarget(
* @param wantDefaultMethods true to process {@code default} methods
* @return the collection of typed elements
*/
- protected Collection toElementInfo(
- TypeElement element,
- ProcessingEnvironment processingEnv,
- boolean wantWhatWeCanAccept,
- boolean wantDefaultMethods) {
+ protected Collection toElementInfo(TypeElement element,
+ ProcessingEnvironment processingEnv,
+ boolean wantWhatWeCanAccept,
+ boolean wantDefaultMethods) {
return element.getEnclosedElements().stream()
.filter(it -> it.getKind() == ElementKind.METHOD)
.map(ExecutableElement.class::cast)
@@ -172,9 +180,8 @@ protected Collection toElementInfo(
* @param defineDefaultMethods true if we should also process default methods
* @return true if not able to accept
*/
- protected boolean canAccept(
- ExecutableElement ee,
- boolean defineDefaultMethods) {
+ protected boolean canAccept(ExecutableElement ee,
+ boolean defineDefaultMethods) {
Set mods = ee.getModifiers();
if (mods.contains(Modifier.ABSTRACT)) {
return true;
@@ -189,11 +196,10 @@ protected boolean canAccept(
return false;
}
- private Optional toTypeInfo(
- TypeName annotationTypeName,
- TypeElement element,
- ProcessingEnvironment processingEnv,
- boolean wantDefaultMethods) {
+ private Optional toTypeInfo(TypeName annotationTypeName,
+ TypeElement element,
+ ProcessingEnvironment processingEnv,
+ boolean wantDefaultMethods) {
List extends TypeMirror> ifaces = element.getInterfaces();
if (ifaces.size() > 1) {
processingEnv.getMessager()
@@ -220,8 +226,7 @@ private Optional toTypeInfo(
* @param typeMirror the type mirror
* @return the type element
*/
- public static Optional toTypeElement(
- TypeMirror typeMirror) {
+ public static Optional toTypeElement(TypeMirror typeMirror) {
if (TypeKind.DECLARED == typeMirror.getKind()) {
TypeElement te = (TypeElement) ((DeclaredType) typeMirror).asElement();
return (te.toString().equals(Object.class.getName())) ? Optional.empty() : Optional.of(te);
@@ -235,8 +240,7 @@ public static Optional toTypeElement(
* @param type the element type
* @return the associated type name instance
*/
- public static Optional createTypeNameFromDeclaredType(
- DeclaredType type) {
+ public static Optional createTypeNameFromDeclaredType(DeclaredType type) {
return createTypeNameFromElement(type.asElement());
}
@@ -246,8 +250,7 @@ public static Optional createTypeNameFromDeclaredType(
* @param type the element type
* @return the associated type name instance
*/
- public static Optional createTypeNameFromElement(
- Element type) {
+ public static Optional createTypeNameFromElement(Element type) {
if (type instanceof VariableElement) {
return createTypeNameFromMirror(type.asType());
}
@@ -258,7 +261,7 @@ public static Optional createTypeNameFromElement(
List classNames = new ArrayList<>();
classNames.add(type.getSimpleName().toString());
- while (Objects.nonNull(type.getEnclosingElement())
+ while (type.getEnclosingElement() != null
&& ElementKind.PACKAGE != type.getEnclosingElement().getKind()) {
classNames.add(type.getEnclosingElement().getSimpleName().toString());
type = type.getEnclosingElement();
@@ -277,8 +280,7 @@ public static Optional createTypeNameFromElement(
* @param typeMirror the type mirror
* @return the type name associated with the type mirror, or empty for generic type variables
*/
- public static Optional createTypeNameFromMirror(
- TypeMirror typeMirror) {
+ public static Optional createTypeNameFromMirror(TypeMirror typeMirror) {
TypeKind kind = typeMirror.getKind();
if (kind.isPrimitive()) {
Class> type;
@@ -355,9 +357,8 @@ public static Optional createTypeNameFromMirror(
* @param ams the collection to search through
* @return the annotation mirror, or empty if not found
*/
- public static Optional extends AnnotationMirror> findAnnotationMirror(
- String annotationType,
- Collection extends AnnotationMirror> ams) {
+ public static Optional extends AnnotationMirror> findAnnotationMirror(String annotationType,
+ Collection extends AnnotationMirror> ams) {
return ams.stream()
.filter(it -> annotationType.equals(it.getAnnotationType().toString()))
.findFirst();
@@ -370,9 +371,8 @@ public static Optional extends AnnotationMirror> findAnnotationMirror(
* @param elements the elements
* @return the new instance or empty if the annotation mirror passed is invalid
*/
- public static Optional createAnnotationAndValueFromMirror(
- AnnotationMirror am,
- Elements elements) {
+ public static Optional createAnnotationAndValueFromMirror(AnnotationMirror am,
+ Elements elements) {
Optional val = createTypeNameFromMirror(am.getAnnotationType());
return val.map(it -> DefaultAnnotationAndValue.create(it, extractValues(am, elements)));
@@ -385,9 +385,8 @@ public static Optional createAnnotationAndValueFromMirror(
* @param elements the elements
* @return the list of annotations extracted from the element
*/
- public static List createAnnotationAndValueListFromElement(
- Element e,
- Elements elements) {
+ public static List createAnnotationAndValueListFromElement(Element e,
+ Elements elements) {
return e.getAnnotationMirrors().stream().map(it -> createAnnotationAndValueFromMirror(it, elements))
.filter(Optional::isPresent)
.map(Optional::orElseThrow)
@@ -398,12 +397,11 @@ public static List createAnnotationAndValueListFromElement(
* Extracts values from the annotation mirror value.
*
* @param am the annotation mirror
- * @param elements the optional elements
+ * @param elements the elements
* @return the extracted values
*/
- public static Map extractValues(
- AnnotationMirror am,
- Elements elements) {
+ public static Map extractValues(AnnotationMirror am,
+ Elements elements) {
return extractValues(elements.getElementValuesWithDefaults(am));
}
@@ -413,13 +411,12 @@ public static Map extractValues(
* @param values the element values
* @return the extracted values
*/
- public static Map extractValues(
- Map extends ExecutableElement, ? extends AnnotationValue> values) {
+ public static Map extractValues(Map extends ExecutableElement, ? extends AnnotationValue> values) {
Map result = new LinkedHashMap<>();
values.forEach((el, val) -> {
String name = el.getSimpleName().toString();
String value = val.accept(new ToStringAnnotationValueVisitor(), null);
- if (Objects.nonNull(value)) {
+ if (value != null) {
result.put(name, value);
}
});
@@ -427,16 +424,27 @@ public static Map extractValues(
}
/**
- * Creates an instance of a {@link io.helidon.pico.types.TypedElementName} given its type and variable element from
+ * Extracts the singular {@code value()} value. Return value will always be non-null.
+ *
+ * @param am the annotation mirror
+ * @param elements the elements
+ * @return the extracted values
+ */
+ public static String extractValue(AnnotationMirror am,
+ Elements elements) {
+ return Objects.requireNonNull(extractValues(elements.getElementValuesWithDefaults(am)).get("value"));
+ }
+
+ /**
+ * Creates an instance of a {@link io.helidon.common.types.TypedElementName} given its type and variable element from
* annotation processing.
*
* @param v the element (from annotation processing)
* @param elements the elements
* @return the created instance
*/
- public static TypedElementName createTypedElementNameFromElement(
- Element v,
- Elements elements) {
+ public static TypedElementName createTypedElementNameFromElement(Element v,
+ Elements elements) {
TypeName type = createTypeNameFromElement(v).orElse(null);
List componentTypeNames = null;
String defaultValue = null;
@@ -470,26 +478,27 @@ public static TypedElementName createTypedElementNameFromElement(
}
componentTypeNames = (componentTypeNames == null) ? List.of() : componentTypeNames;
- return DefaultTypedElementName.builder()
+ DefaultTypedElementName.Builder builder = DefaultTypedElementName.builder()
.typeName(type)
.componentTypeNames(componentTypeNames)
.elementName(v.getSimpleName().toString())
.elementKind(v.getKind().name())
- .defaultValue(defaultValue)
.annotations(createAnnotationAndValueListFromElement(v, elements))
.elementTypeAnnotations(elementTypeAnnotations)
- .modifierNames(modifierNames)
- .build();
+ .modifierNames(modifierNames);
+
+ Optional.ofNullable(defaultValue).ifPresent(builder::defaultValue);
+
+ return builder.build();
}
/**
* Helper method to determine if the value is present (i.e., non-null and non-blank).
*
* @param val the value to check
- * @return true if the value provided is non-null and non-blank.
+ * @return true if the value provided is non-null and non-blank
*/
- static boolean hasNonBlankValue(
- String val) {
+ static boolean hasNonBlankValue(String val) {
return (val != null) && !val.isBlank();
}
@@ -500,10 +509,21 @@ static boolean hasNonBlankValue(
* @param versionId the generator version identifier
* @return the generated sticker
*/
- public static String generatedStickerFor(
- String generatorClassTypeName,
- String versionId) {
+ public static String generatedStickerFor(String generatorClassTypeName,
+ String versionId) {
return "value = \"" + Objects.requireNonNull(generatorClassTypeName)
+ "\", comments = \"version=" + versionId + "\"";
}
+
+ /**
+ * Produces the generated copy right header on code generated artifacts.
+ *
+ * @param generatorClassTypeName the generator class type name
+ * @return the generated comments
+ */
+ public static String copyrightHeaderFor(String generatorClassTypeName) {
+ return "// This is a generated file (powered by Helidon). "
+ + "Do not edit or extend from this artifact as it is subject to change at any time!";
+ }
+
}
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java
index 1c619b2cc12..341fba3df00 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java
@@ -20,8 +20,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedList;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -40,25 +39,26 @@
import io.helidon.builder.processor.spi.BuilderCreatorProvider;
import io.helidon.builder.processor.spi.DefaultTypeAndBody;
import io.helidon.builder.processor.spi.TypeAndBody;
-import io.helidon.builder.processor.spi.TypeInfo;
import io.helidon.common.Weight;
import io.helidon.common.Weighted;
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.DefaultAnnotationAndValue;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
import io.helidon.config.metadata.ConfiguredOption;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.DefaultAnnotationAndValue;
-import io.helidon.pico.types.DefaultTypeName;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
import static io.helidon.builder.processor.tools.BodyContext.TAG_META_PROPS;
import static io.helidon.builder.processor.tools.BodyContext.toBeanAttributeName;
+import static io.helidon.builder.processor.tools.BuilderTypeTools.copyrightHeaderFor;
+import static io.helidon.builder.processor.tools.BuilderTypeTools.hasNonBlankValue;
/**
* Default implementation for {@link io.helidon.builder.processor.spi.BuilderCreatorProvider}.
*/
@Weight(Weighted.DEFAULT_WEIGHT - 1) // allow all other creators to take precedence over us...
public class DefaultBuilderCreatorProvider implements BuilderCreatorProvider {
- static final String INTERFACE = "INTERFACE";
static final boolean DEFAULT_INCLUDE_META_ATTRIBUTES = true;
static final boolean DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES = true;
static final String DEFAULT_IMPL_PREFIX = Builder.DEFAULT_IMPL_PREFIX;
@@ -81,7 +81,7 @@ public DefaultBuilderCreatorProvider() {
@Override
public Set> supportedAnnotationTypes() {
- return Collections.singleton(Builder.class);
+ return Set.of(Builder.class);
}
@Override
@@ -89,10 +89,10 @@ public List create(TypeInfo typeInfo,
AnnotationAndValue builderAnnotation) {
try {
TypeName abstractImplTypeName = toAbstractImplTypeName(typeInfo.typeName(), builderAnnotation);
- TypeName implTypeName = toImplTypeName(typeInfo.typeName(), builderAnnotation);
+ TypeName implTypeName = toBuilderImplTypeName(typeInfo.typeName(), builderAnnotation);
preValidate(implTypeName, typeInfo, builderAnnotation);
- LinkedList builds = new LinkedList<>();
+ List builds = new ArrayList<>();
builds.add(DefaultTypeAndBody.builder()
.typeName(abstractImplTypeName)
.body(toBody(createBodyContext(false, abstractImplTypeName, typeInfo, builderAnnotation)))
@@ -118,6 +118,25 @@ public List create(TypeInfo typeInfo,
protected void preValidate(TypeName implTypeName,
TypeInfo typeInfo,
AnnotationAndValue builderAnnotation) {
+ assertNoDuplicateSingularNames(typeInfo);
+ }
+
+ private void assertNoDuplicateSingularNames(TypeInfo typeInfo) {
+ Set names = new LinkedHashSet<>();
+ Set duplicateNames = new LinkedHashSet<>();
+
+ typeInfo.elementInfo().stream()
+ .map(DefaultBuilderCreatorProvider::nameOf)
+ .forEach(name -> {
+ if (!names.add(name)) {
+ duplicateNames.add(name);
+ }
+ });
+
+ if (!duplicateNames.isEmpty()) {
+ throw new IllegalStateException("duplicate methods are using the same names " + duplicateNames + " for: "
+ + typeInfo.typeName());
+ }
}
/**
@@ -146,14 +165,14 @@ protected TypeName toAbstractImplTypeName(TypeName typeName,
}
/**
- * Constructs the default implementation type name for what is code generated.
+ * Returns the default implementation Builder's class name for what is code generated.
*
* @param typeName the target interface that the builder applies to
* @param builderAnnotation the builder annotation triggering the build
* @return the type name of the implementation
*/
- protected TypeName toImplTypeName(TypeName typeName,
- AnnotationAndValue builderAnnotation) {
+ public static TypeName toBuilderImplTypeName(TypeName typeName,
+ AnnotationAndValue builderAnnotation) {
String toPackageName = toPackageName(typeName.packageName(), builderAnnotation);
String prefix = toImplTypePrefix(builderAnnotation);
String suffix = toImplTypeSuffix(builderAnnotation);
@@ -173,7 +192,11 @@ protected BodyContext createBodyContext(boolean doingConcreteType,
TypeName typeName,
TypeInfo typeInfo,
AnnotationAndValue builderAnnotation) {
- return new BodyContext(doingConcreteType, typeName, typeInfo, builderAnnotation);
+ try {
+ return new BodyContext(doingConcreteType, typeName, typeInfo, builderAnnotation);
+ } catch (Throwable t) {
+ throw new IllegalStateException("Failed while processing: " + typeName, t);
+ }
}
/**
@@ -335,6 +358,7 @@ protected void appendFields(StringBuilder builder,
*/
protected void appendHeader(StringBuilder builder,
BodyContext ctx) {
+ builder.append(generatedCopyrightHeaderFor(ctx)).append("\n");
builder.append("package ").append(ctx.implTypeName().packageName()).append(";\n\n");
builder.append("import java.util.Collections;\n");
builder.append("import java.util.List;\n");
@@ -377,12 +401,12 @@ protected void appendHeader(StringBuilder builder,
builder.append(toAbstractImplTypeName(ctx.typeInfo().typeName(), ctx.builderTriggerAnnotation()));
} else {
if (ctx.hasParent()) {
- builder.append(toAbstractImplTypeName(ctx.parentTypeName().get(), ctx.builderTriggerAnnotation()));
+ builder.append(toAbstractImplTypeName(ctx.parentTypeName().orElseThrow(), ctx.builderTriggerAnnotation()));
} else if (baseExtendsTypeName.isPresent()) {
builder.append(baseExtendsTypeName.get().fqName());
}
- LinkedList impls = new LinkedList<>();
+ List impls = new ArrayList<>();
if (!ctx.isExtendingAnAbstractClass()) {
impls.add(ctx.typeInfo().typeName().fqName());
}
@@ -400,14 +424,34 @@ protected void appendHeader(StringBuilder builder,
builder.append(" {\n");
}
+ /**
+ * Returns the copyright level header comment.
+ *
+ * @param ctx the context
+ * @return the copyright level header
+ */
+ protected String generatedCopyrightHeaderFor(BodyContext ctx) {
+ return copyrightHeaderFor(getClass().getName());
+ }
+
/**
* Returns the {@code Generated} sticker to be added.
*
* @param ctx the context
- * @return the generated sticker.
+ * @return the generated sticker
*/
protected String generatedStickerFor(BodyContext ctx) {
- return BuilderTypeTools.generatedStickerFor(getClass().getName(), Versions.CURRENT_BUILDER_VERSION);
+ return BuilderTypeTools.generatedStickerFor(getClass().getName(), generatedVersionFor(ctx));
+ }
+
+ /**
+ * Returns the {@code Generated} version identifier.
+ *
+ * @param ctx the context
+ * @return the generated version identifier
+ */
+ protected String generatedVersionFor(BodyContext ctx) {
+ return Versions.CURRENT_BUILDER_VERSION;
}
/**
@@ -712,6 +756,9 @@ protected void appendMetaProps(StringBuilder builder,
BodyContext ctx,
String tag,
AtomicBoolean needsCustomMapOf) {
+ builder.append("\t\t").append(tag);
+ builder.append(".put(\"__generated\", Map.of(\"version\", \"")
+ .append(io.helidon.builder.processor.tools.Versions.BUILDER_VERSION_1).append("\"));\n");
ctx.map().forEach((attrName, method) ->
builder.append("\t\t")
.append(tag)
@@ -725,15 +772,27 @@ protected void appendMetaProps(StringBuilder builder,
/**
* Normalize the configured option key.
*
- * @param key the key attribute
- * @param attrName the attribute name
- * @param method the method
- * @return the key to write on the generated output.
+ * @param key the key attribute
+ * @param name the name
+ * @param isAttribute if the name represents an attribute value (otherwise is a config bean name)
+ * @return the key to write on the generated output
*/
protected String normalizeConfiguredOptionKey(String key,
- String attrName,
- TypedElementName method) {
- return BuilderTypeTools.hasNonBlankValue(key) ? key : "";
+ String name,
+ boolean isAttribute) {
+ return hasNonBlankValue(key) ? key : toConfigKey(name, isAttribute);
+ }
+
+ /**
+ * Applicable if this builder is intended for config beans.
+ *
+ * @param name the name
+ * @param isAttribute if the name represents an attribute value (otherwise is a config bean name)
+ * @return the config key
+ */
+ protected String toConfigKey(String name,
+ boolean isAttribute) {
+ return "";
}
/**
@@ -776,6 +835,19 @@ protected static String maybeSingularFormOf(String beanAttributeName) {
return beanAttributeName;
}
+ /**
+ * Attempts to use the singular name of the element, defaulting to the element name if no singular annotation exists.
+ *
+ * @param elem the element
+ * @return the (singular) name of the element
+ */
+ protected static String nameOf(TypedElementName elem) {
+ return DefaultAnnotationAndValue.findFirst(Singular.class.getName(), elem.annotations())
+ .flatMap(AnnotationAndValue::value)
+ .filter(BuilderTypeTools::hasNonBlankValue)
+ .orElseGet(elem::elementName);
+ }
+
/**
* Append the setters for the given bean attribute name.
*
@@ -898,7 +970,7 @@ protected void appendAnnotations(StringBuilder builder,
for (AnnotationAndValue methodAnno : annotations) {
if (methodAnno.typeName().name().equals(Annotated.class.getName())) {
String val = methodAnno.value().orElse("");
- if (!BuilderTypeTools.hasNonBlankValue(val)) {
+ if (!hasNonBlankValue(val)) {
continue;
}
if (!val.startsWith("@")) {
@@ -989,7 +1061,7 @@ protected static Optional toValue(Class extends Annotation> annoType,
boolean wantTypeElementDefaults,
boolean avoidBlanks) {
if (wantTypeElementDefaults && method.defaultValue().isPresent()) {
- if (!avoidBlanks || BuilderTypeTools.hasNonBlankValue(method.defaultValue().orElse(null))) {
+ if (!avoidBlanks || hasNonBlankValue(method.defaultValue().orElse(null))) {
return method.defaultValue();
}
}
@@ -1001,7 +1073,7 @@ protected static Optional toValue(Class extends Annotation> annoType,
if (!avoidBlanks) {
return val;
}
- return BuilderTypeTools.hasNonBlankValue(val.orElse(null)) ? val : Optional.empty();
+ return hasNonBlankValue(val.orElse(null)) ? val : Optional.empty();
}
}
@@ -1055,8 +1127,8 @@ private static char[] reverseBeanName(String beanName) {
/**
* In support of {@link io.helidon.builder.Builder#packageName()}.
*/
- private String toPackageName(String packageName,
- AnnotationAndValue builderAnnotation) {
+ private static String toPackageName(String packageName,
+ AnnotationAndValue builderAnnotation) {
String packageNameFromAnno = builderAnnotation.value("packageName").orElse(null);
if (packageNameFromAnno == null || packageNameFromAnno.isBlank()) {
return packageName;
@@ -1077,14 +1149,14 @@ private String toAbstractImplTypePrefix(AnnotationAndValue builderAnnotation) {
/**
* In support of {@link io.helidon.builder.Builder#implPrefix()}.
*/
- private String toImplTypePrefix(AnnotationAndValue builderAnnotation) {
+ private static String toImplTypePrefix(AnnotationAndValue builderAnnotation) {
return builderAnnotation.value("implPrefix").orElse(DEFAULT_IMPL_PREFIX);
}
/**
* In support of {@link io.helidon.builder.Builder#implSuffix()}.
*/
- private String toImplTypeSuffix(AnnotationAndValue builderAnnotation) {
+ private static String toImplTypeSuffix(AnnotationAndValue builderAnnotation) {
return builderAnnotation.value("implSuffix").orElse(DEFAULT_SUFFIX);
}
@@ -1340,7 +1412,7 @@ private void appendBuilderHeader(StringBuilder builder,
builder.append(ctx.ctorBuilderAcceptTypeName()).append(">");
if (ctx.hasParent()) {
builder.append(" extends ")
- .append(toAbstractImplTypeName(ctx.parentTypeName().get(), ctx.builderTriggerAnnotation()))
+ .append(toAbstractImplTypeName(ctx.parentTypeName().orElseThrow(), ctx.builderTriggerAnnotation()))
.append(".").append(ctx.genericBuilderClassDecl());
builder.append("<").append(ctx.genericBuilderAliasDecl())
.append(", ").append(ctx.genericBuilderAcceptAliasDecl());
@@ -1356,7 +1428,7 @@ private void appendBuilderHeader(StringBuilder builder,
}
}
- LinkedList impls = new LinkedList<>();
+ List impls = new ArrayList<>();
if (!ctx.isExtendingAnAbstractClass() && !ctx.hasAnyBuilderClashingMethodNames()) {
impls.add(ctx.typeInfo().typeName().name());
}
@@ -1417,7 +1489,7 @@ private void appendInterfaceBasedGetters(StringBuilder builder,
i++;
}
- if (Objects.nonNull(ctx.parentAnnotationType().get())) {
+ if (ctx.parentAnnotationTypeName().isPresent()) {
builder.append(prefix)
.append("\t@Override\n");
builder.append(prefix)
@@ -1643,7 +1715,7 @@ private void appendOverridesOfDefaultValues(StringBuilder builder,
// candidate for override...
String thisDefault = toConfiguredOptionValue(method, true, true).orElse(null);
String superDefault = superValue(ctx.typeInfo().superTypeInfo(), beanAttributeName, ctx.beanStyleRequired());
- if (BuilderTypeTools.hasNonBlankValue(thisDefault) && !Objects.equals(thisDefault, superDefault)) {
+ if (hasNonBlankValue(thisDefault) && !Objects.equals(thisDefault, superDefault)) {
appendDefaultOverride(builder, beanAttributeName, method, thisDefault);
}
}
@@ -1662,7 +1734,7 @@ private String superValue(Optional optSuperTypeInfo,
.findFirst();
if (method.isPresent()) {
Optional defaultValue = toConfiguredOptionValue(method.get(), true, true);
- if (defaultValue.isPresent() && BuilderTypeTools.hasNonBlankValue(defaultValue.get())) {
+ if (defaultValue.isPresent() && hasNonBlankValue(defaultValue.get())) {
return defaultValue.orElse(null);
}
} else {
@@ -1696,25 +1768,25 @@ private void appendCustomMapOf(StringBuilder builder) {
private String mapOf(String attrName,
TypedElementName method,
AtomicBoolean needsCustomMapOf) {
- final Optional extends AnnotationAndValue> configuredOptions = DefaultAnnotationAndValue
+ Optional extends AnnotationAndValue> configuredOptions = DefaultAnnotationAndValue
.findFirst(ConfiguredOption.class.getName(), method.annotations());
TypeName typeName = method.typeName();
- String typeDecl = "\"type\", " + typeName.name() + ".class";
+ String typeDecl = "\"__type\", " + typeName.name() + ".class";
if (!typeName.typeArguments().isEmpty()) {
int pos = typeName.typeArguments().size() - 1;
- typeDecl += ", \"componentType\", " + normalize(typeName.typeArguments().get(pos).name()) + ".class";
+ typeDecl += ", \"__componentType\", " + normalize(typeName.typeArguments().get(pos).name()) + ".class";
}
String key = (configuredOptions.isEmpty())
? null : configuredOptions.get().value("key").orElse(null);
- key = normalizeConfiguredOptionKey(key, attrName, method);
- if (BuilderTypeTools.hasNonBlankValue(key)) {
- typeDecl += ", " + quotedTupleOf("key", key);
+ key = normalizeConfiguredOptionKey(key, attrName, true);
+ if (hasNonBlankValue(key)) {
+ typeDecl += ", " + quotedTupleOf(method.typeName(), "key", key);
}
String defaultValue = method.defaultValue().orElse(null);
- if (configuredOptions.isEmpty() && !BuilderTypeTools.hasNonBlankValue(defaultValue)) {
+ if (configuredOptions.isEmpty() && !hasNonBlankValue(defaultValue)) {
return "Map.of(" + typeDecl + ")";
}
@@ -1723,18 +1795,22 @@ private String mapOf(String attrName,
result.append("__mapOf(").append(typeDecl);
if (configuredOptions.isEmpty()) {
+ result.append(", ");
if (defaultValue.startsWith("{")) {
defaultValue = "new String[] " + defaultValue;
+ result.append(quotedValueOf("value"));
+ result.append(", ");
+ result.append(defaultValue);
+ } else {
+ result.append(quotedTupleOf(typeName, "value", defaultValue));
}
- result.append(", ");
- result.append(quotedValueOf("value")).append(", ").append(defaultValue);
} else {
configuredOptions.get().values().entrySet().stream()
- .filter(e -> BuilderTypeTools.hasNonBlankValue(e.getValue()))
+ .filter(e -> hasNonBlankValue(e.getValue()))
.filter(e -> !e.getKey().equals("key"))
- .forEach((e) -> {
+ .forEach(e -> {
result.append(", ");
- result.append(quotedTupleOf(e.getKey(), e.getValue()));
+ result.append(quotedTupleOf(typeName, e.getKey(), e.getValue()));
});
}
result.append(")");
@@ -1746,13 +1822,15 @@ private String normalize(String name) {
return name.equals("?") ? "Object" : name;
}
- private String quotedTupleOf(String key,
+ private String quotedTupleOf(TypeName valType,
+ String key,
String val) {
- assert (Objects.nonNull(key));
- assert (BuilderTypeTools.hasNonBlankValue(val)) : key;
- if (key.equals("value") && ConfiguredOption.UNCONFIGURED.equals(val)) {
- val = ConfiguredOption.class.getName() + ".UNCONFIGURED";
- } else {
+ assert (key != null);
+ assert (hasNonBlankValue(val)) : key;
+ boolean isEnumLikeType = isEnumLikeType(valType, key, val);
+ if (isEnumLikeType) {
+ val = valType + "." + val;
+ } else if (!key.equals("value") || !val.startsWith(ConfiguredOption.class.getName())) {
val = quotedValueOf(val);
}
return quotedValueOf(key) + ", " + val;
@@ -1766,6 +1844,25 @@ private String quotedValueOf(String val) {
return "\"" + val + "\"";
}
+ private boolean isEnumLikeType(TypeName valType,
+ String key,
+ String val) {
+ if (!hasNonBlankValue(val) || valType.primitive()) {
+ return false;
+ }
+
+ int dotPos = key.indexOf(".");
+ if (dotPos < 0) {
+ return false;
+ }
+
+ if (valType.isOptional() && !valType.typeArguments().isEmpty()) {
+ return isEnumLikeType(valType.typeArguments().get(0), key, val);
+ }
+
+ return !BeanUtils.isBuiltInJavaType(valType);
+ }
+
private String maybeRequireNonNull(BodyContext ctx,
String tag) {
return ctx.allowNulls() ? tag : "Objects.requireNonNull(" + tag + ")";
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java
index da8401018d3..a1ad7a40b49 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java
@@ -16,8 +16,8 @@
package io.helidon.builder.processor.tools;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
final class GenerateJavadoc {
private GenerateJavadoc() {
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java
index 89def95e341..fae124e05f1 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java
@@ -20,8 +20,8 @@
import java.util.Objects;
import java.util.Optional;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
final class GenerateMethod {
static final String SINGULAR_PREFIX = "add";
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateVisitorSupport.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateVisitorSupport.java
index b7427e75375..caa2d615ee9 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateVisitorSupport.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateVisitorSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -88,7 +88,7 @@ static void appendExtraInnerClasses(StringBuilder builder,
+ "\t\t\t\t\t\t Class> type,\n"
+ "\t\t\t\t\t\t Class>... typeArgument) {\n"
+ "\t\t\tString requiredStr = (String) meta.get(\"required\");\n"
- + "\t\t\tboolean requiredPresent = Objects.nonNull(requiredStr);\n"
+ + "\t\t\tboolean requiredPresent = (requiredStr != null);\n"
+ "\t\t\tboolean required = Boolean.parseBoolean(requiredStr);\n"
+ "\t\t\tif (!required && requiredPresent) {\n"
+ "\t\t\t\treturn;\n"
@@ -99,7 +99,7 @@ static void appendExtraInnerClasses(StringBuilder builder,
+ "\t\t\t}\n"
+ "\t\t\t\n"
+ "\t\t\tObject val = valueSupplier.get();\n"
- + "\t\t\tif (Objects.nonNull(val)) {\n"
+ + "\t\t\tif (val != null) {\n"
+ "\t\t\t\treturn;\n"
+ "\t\t\t}\n"
+ "\t\t\t\n"
@@ -109,10 +109,12 @@ static void appendExtraInnerClasses(StringBuilder builder,
+ "\n"
+ "\t\tvoid validate() {\n"
+ "\t\t\tif (!errors.isEmpty()) {\n"
- + "\t\t\t\tthrow new java.lang.IllegalStateException(String.join(\", \", errors));\n"
+ + "\t\t\t\tthrow new java.lang.IllegalStateException(\"problems building configbean '\" + "
+ + ctx.typeInfo().typeName() + ".class.getName() + \"': \" + String.join(\", \", errors));\n"
+ "\t\t\t}\n"
+ "\t\t}\n"
+ "\t}\n");
}
}
+
}
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/ToStringAnnotationValueVisitor.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/ToStringAnnotationValueVisitor.java
index 336a5882707..2d8976742b1 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/ToStringAnnotationValueVisitor.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/ToStringAnnotationValueVisitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
@@ -109,7 +108,7 @@ public String visitShort(short s, Object o) {
@Override
public String visitString(String s, Object o) {
- if (mapEmptyStringToNull && Objects.nonNull(s) && s.isBlank()) {
+ if (mapEmptyStringToNull && s != null && s.isBlank()) {
return null;
}
@@ -154,7 +153,7 @@ public String visitArray(List extends AnnotationValue> vals, Object o) {
String result = String.join(", ", values);
if (mapBlankArrayToNull && result.isBlank()) {
result = null;
- } else if (Objects.nonNull(result) && mapToSourceDeclaration) {
+ } else if (mapToSourceDeclaration) {
result = "{";
for (AnnotationValue val : vals) {
String stringVal = val.accept(this, null);
diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/package-info.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/package-info.java
index 67c2fd93fea..8e565bdcab9 100644
--- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/package-info.java
+++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
/**
- * The Helidon Pico Builder Processor Tools package. These are generally only needed by other Helidon modules like the Builder
+ * The Helidon Builder Processor Tools package. These are generally only needed by other Helidon modules like the Builder
* annotation processor, etc.
*/
package io.helidon.builder.processor.tools;
diff --git a/builder/processor-tools/src/main/java/module-info.java b/builder/processor-tools/src/main/java/module-info.java
index 39041e73c44..99c5b5ed2b2 100644
--- a/builder/processor-tools/src/main/java/module-info.java
+++ b/builder/processor-tools/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
*/
module io.helidon.builder.processor.tools {
requires java.compiler;
- requires io.helidon.pico.types;
+ requires io.helidon.common.types;
requires io.helidon.builder;
requires io.helidon.builder.processor.spi;
requires io.helidon.common;
diff --git a/builder/processor-tools/src/test/java/io/helidon/builder/processor/tools/BeanUtilsTest.java b/builder/processor-tools/src/test/java/io/helidon/builder/processor/tools/BeanUtilsTest.java
index 66128c96dfa..411d5c6bc3e 100644
--- a/builder/processor-tools/src/test/java/io/helidon/builder/processor/tools/BeanUtilsTest.java
+++ b/builder/processor-tools/src/test/java/io/helidon/builder/processor/tools/BeanUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,40 +23,41 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
-import io.helidon.common.testing.junit5.OptionalMatcher;
-
-import org.hamcrest.Matchers;
-import org.hamcrest.core.Is;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static io.helidon.builder.processor.tools.BeanUtils.isBooleanType;
+import static io.helidon.builder.processor.tools.BeanUtils.isReservedWord;
import static io.helidon.builder.processor.tools.BeanUtils.isValidMethodType;
import static io.helidon.builder.processor.tools.BeanUtils.validateAndParseMethodName;
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty;
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.core.Is.is;
class BeanUtilsTest {
@Test
void testIsBooleanType() {
- assertThat(isBooleanType(boolean.class), Is.is(true));
- assertThat(isBooleanType(Boolean.class), Is.is(true));
- assertThat(isBooleanType(String.class), Is.is(false));
- assertThat(isBooleanType(""), Is.is(false));
+ assertThat(isBooleanType(boolean.class), is(true));
+ assertThat(isBooleanType(Boolean.class), is(true));
+ assertThat(isBooleanType(String.class), is(false));
+ assertThat(isBooleanType(""), is(false));
}
@Test
void testIsValidMethodType() {
- assertThat(isValidMethodType(boolean.class.getName()), Is.is(true));
- assertThat(isValidMethodType(String.class.getName()), Is.is(true));
- assertThat(isValidMethodType(Collection.class.getName()), Is.is(true));
- assertThat(isValidMethodType(Map.class.getName()), Is.is(true));
- assertThat(isValidMethodType(Set.class.getName()), Is.is(true));
- assertThat(isValidMethodType(List.class.getName()), Is.is(true));
- assertThat(isValidMethodType(Object.class.getName()), Is.is(true));
- assertThat(isValidMethodType(""), Is.is(false));
- assertThat(isValidMethodType(void.class.getName()), Is.is(false));
- assertThat(isValidMethodType(Void.class.getName()), Is.is(false));
+ assertThat(isValidMethodType(boolean.class.getName()), is(true));
+ assertThat(isValidMethodType(String.class.getName()), is(true));
+ assertThat(isValidMethodType(Collection.class.getName()), is(true));
+ assertThat(isValidMethodType(Map.class.getName()), is(true));
+ assertThat(isValidMethodType(Set.class.getName()), is(true));
+ assertThat(isValidMethodType(List.class.getName()), is(true));
+ assertThat(isValidMethodType(Object.class.getName()), is(true));
+ assertThat(isValidMethodType(""), is(false));
+ assertThat(isValidMethodType(void.class.getName()), is(false));
+ assertThat(isValidMethodType(Void.class.getName()), is(false));
}
@Test
@@ -65,84 +66,134 @@ void testValidateAndParseMethodName() {
RuntimeException e = Assertions.assertThrows(RuntimeException.class,
() -> validateAndParseMethodName("x", "", true, attrName));
- assertThat(e.getMessage(), Is.is("invalid return type: x"));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(e.getMessage(), is("invalid return type: x"));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("isAlpha", Boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alpha", "isAlpha")));
+ assertThat(validateAndParseMethodName("isAlpha", Boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alpha", "isAlpha")));
- assertThat(validateAndParseMethodName("isAlpha", boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alpha", "isAlpha")));
+ assertThat(validateAndParseMethodName("isAlpha", boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alpha", "isAlpha")));
- assertThat(validateAndParseMethodName("getAlpha", boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alpha")));
+ assertThat(validateAndParseMethodName("getAlpha", boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alpha")));
- assertThat(validateAndParseMethodName("getAlpha", Boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alpha")));
+ assertThat(validateAndParseMethodName("getAlpha", Boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alpha")));
- assertThat(validateAndParseMethodName("getAlpha", String.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alpha")));
+ assertThat(validateAndParseMethodName("getAlpha", String.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alpha")));
- assertThat(validateAndParseMethodName("getAlpha", Object.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alpha")));
+ assertThat(validateAndParseMethodName("getAlpha", Object.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alpha")));
- assertThat(validateAndParseMethodName("isAlphaNumeric", boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alphaNumeric", "isAlphaNumeric")));
+ assertThat(validateAndParseMethodName("isAlphaNumeric", boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alphaNumeric", "isAlphaNumeric")));
- assertThat(validateAndParseMethodName("getAlphaNumeric", boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("alphaNumeric")));
+ assertThat(validateAndParseMethodName("getAlphaNumeric", boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("alphaNumeric")));
- assertThat(validateAndParseMethodName("isX", boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("x", "isX")));
+ assertThat(validateAndParseMethodName("isX", boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("x", "isX")));
- assertThat(validateAndParseMethodName("getX", boolean.class.getName(), false, attrName), Is.is(true));
- assertThat(attrName.get(), OptionalMatcher.optionalValue(Matchers.contains("x")));
+ assertThat(validateAndParseMethodName("getX", boolean.class.getName(), false, attrName), is(true));
+ assertThat(attrName.get(), optionalValue(contains("x")));
// negative cases ...
- assertThat(validateAndParseMethodName("isX", String.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("isX", String.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
+
+ assertThat(validateAndParseMethodName("is_AlphaNumeric", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("is_AlphaNumeric", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("is", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("is", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("is", Boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("is", Boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("get", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("get", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("get", Boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("get", Boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("get1AlphaNumeric", Boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("get1AlphaNumeric", Boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("getalphaNumeric", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("getalphaNumeric", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("isalphaNumeric", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("isalphaNumeric", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("is9alphaNumeric", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("is9alphaNumeric", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("isAlphaNumeric", void.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("isAlphaNumeric", void.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("getAlphaNumeric", Void.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("getAlphaNumeric", Void.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("x", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("x", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("IsX", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
- assertThat(validateAndParseMethodName("IsX", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ assertThat(validateAndParseMethodName("GetX", boolean.class.getName(), false, attrName), is(false));
+ assertThat(attrName.get(), optionalEmpty());
+ }
- assertThat(validateAndParseMethodName("GetX", boolean.class.getName(), false, attrName), Is.is(false));
- assertThat(attrName.get(), OptionalMatcher.optionalEmpty());
+ @Test
+ void testIsReservedWord() {
+ assertThat(isReservedWord("whatever"),
+ is(false));
+ assertThat(isReservedWord("class"),
+ is(true));
+ assertThat(isReservedWord("Class"),
+ is(true));
+ assertThat(isReservedWord("interface"),
+ is(true));
+ assertThat(isReservedWord("INTERFACE"),
+ is(true));
+ assertThat(isReservedWord("package"),
+ is(true));
+ assertThat(isReservedWord("PACKAGE"),
+ is(true));
+ assertThat(isReservedWord("enum"),
+ is(true));
+ assertThat(isReservedWord("ENUM"),
+ is(true));
+ assertThat(isReservedWord("static"),
+ is(true));
+ assertThat(isReservedWord("STATIC"),
+ is(true));
+ assertThat(isReservedWord("final"),
+ is(true));
+ assertThat(isReservedWord("FINAL"),
+ is(true));
+ assertThat(isReservedWord("public"),
+ is(true));
+ assertThat(isReservedWord("PUBLIC"),
+ is(true));
+ assertThat(isReservedWord("protected"),
+ is(true));
+ assertThat(isReservedWord("PROTECTED"),
+ is(true));
+ assertThat(isReservedWord("private"),
+ is(true));
+ assertThat(isReservedWord("PRIVATE"),
+ is(true));
+ assertThat(isReservedWord("record"),
+ is(true));
+ assertThat(isReservedWord("RECORD"),
+ is(true));
+ assertThat(isReservedWord("abstract"),
+ is(true));
+ assertThat(isReservedWord("ABSTRACT"),
+ is(true));
}
}
diff --git a/builder/processor/README.md b/builder/processor/README.md
new file mode 100644
index 00000000000..7657bc35d13
--- /dev/null
+++ b/builder/processor/README.md
@@ -0,0 +1,4 @@
+# builder-processor
+
+This module adds support for Builder an other builder trigger-type annotations.
+This module should typically only be used during compile time, in the APT compiler path only.
diff --git a/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java b/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java
index 70136ca46b5..9c989e5a703 100644
--- a/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java
+++ b/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java
@@ -41,14 +41,14 @@
import io.helidon.builder.processor.spi.BuilderCreatorProvider;
import io.helidon.builder.processor.spi.TypeAndBody;
-import io.helidon.builder.processor.spi.TypeInfo;
import io.helidon.builder.processor.spi.TypeInfoCreatorProvider;
import io.helidon.builder.processor.tools.BuilderTypeTools;
import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.Weights;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.DefaultTypeName;
-import io.helidon.pico.types.TypeName;
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
/**
* The processor for handling any annotation having a {@link io.helidon.builder.BuilderTrigger}.
diff --git a/builder/processor/src/main/java/module-info.java b/builder/processor/src/main/java/module-info.java
index 530a9243547..375d1962f41 100644
--- a/builder/processor/src/main/java/module-info.java
+++ b/builder/processor/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
requires io.helidon.builder;
requires io.helidon.builder.processor.spi;
requires io.helidon.builder.processor.tools;
- requires io.helidon.pico.types;
+ requires io.helidon.common.types;
exports io.helidon.builder.processor;
diff --git a/builder/tests/builder/pom.xml b/builder/tests/builder/pom.xml
index 19a5ca45736..0ab22029a52 100644
--- a/builder/tests/builder/pom.xml
+++ b/builder/tests/builder/pom.xml
@@ -28,7 +28,7 @@
4.0.0
- helidon-builder-test-builder
+ helidon-builder-tests-test-builderHelidon Builder Tests
@@ -56,6 +56,7 @@
jakarta.annotationjakarta.annotation-apiprovided
+ truecom.fasterxml.jackson.core
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ComplexCase.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ComplexCase.java
index 867a7f1e683..3900422a774 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ComplexCase.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ComplexCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@
import io.helidon.builder.Singular;
/**
- * Used for demonstrating and testing the Pico Builder.
+ * Used for demonstrating and testing the Builder.
* In this case we are enforcing bean style is used, and overriding the generated class to have a suffix of Impl on the class name.
*/
@Builder(requireLibraryDependencies = false, requireBeanStyle = true, implPrefix = "", implSuffix = "Impl")
@@ -61,7 +61,7 @@ public interface ComplexCase extends MyConfigBean {
Class> getClassType();
/**
- * The Pico Builder will ignore {@code default} and {@code static} functions.
+ * The Builder will ignore {@code default} and {@code static} functions.
*
* @return ignored, here for testing purposes only
*/
@@ -70,7 +70,7 @@ default String getCompositeName() {
}
/**
- * The Pico Builder will ignore {@code default} and {@code static} functions.
+ * The Builder will ignore {@code default} and {@code static} functions.
*
* @return ignored, here for testing purposes only
*/
@@ -79,7 +79,7 @@ default boolean hasBeenEnabled() {
}
/**
- * The Pico Builder will ignore {@code default} and {@code static} functions.
+ * The Builder will ignore {@code default} and {@code static} functions.
*
* @return ignored, here for testing purposes only
*/
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/CustomNamed.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/CustomNamed.java
index c9a5285c494..df750ea1044 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/CustomNamed.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/CustomNamed.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
import io.helidon.builder.Singular;
/**
- * Used for demonstrating and testing the Pico Builder.
+ * Used for demonstrating and testing the Builder.
*
* In this case we are overriding the Map, Set, and List types, usages of annotations placed on the generated class, as well as
* changing the package name targeted for the generated class.
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java
index 24005215d30..d6c31b1ea40 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java
@@ -25,7 +25,7 @@
import io.helidon.config.metadata.ConfiguredOption;
/**
- * Used for demonstrating and testing the Pico Builder.
+ * Used for demonstrating and testing the Builder.
*/
@Builder(includeMetaAttributes = false)
public interface EdgeCases {
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/GeneralInterceptor.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/GeneralInterceptor.java
index 847869c0844..0b25935649d 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/GeneralInterceptor.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/GeneralInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package io.helidon.builder.test.testsubjects;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import io.helidon.common.Builder;
@@ -27,7 +27,7 @@
@Deprecated
@SuppressWarnings("unchecked")
public class GeneralInterceptor {
- private static final List INTERCEPT_CALLS = new LinkedList<>();
+ private static final List INTERCEPT_CALLS = new ArrayList<>();
/**
* Generic interceptor.
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyConfigBean.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyConfigBean.java
index 08b4d572fc2..3bd1df18063 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyConfigBean.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyConfigBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
import io.helidon.config.metadata.ConfiguredValue;
/**
- * Used for demonstrating and testing the Pico Builder.
+ * Used for demonstrating and testing the Builder.
*
* @see MyDerivedConfigBean
*/
@@ -29,7 +29,7 @@
public interface MyConfigBean {
/**
- * Used for demonstrating and testing the Pico Builder. Here we can see that a {@code required=true} is placed on the configured
+ * Used for demonstrating and testing the Builder. Here we can see that a {@code required=true} is placed on the configured
* option.
*
* @return ignored, here for testing purposes only
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyDerivedConfigBean.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyDerivedConfigBean.java
index d4572021604..b0eee1ad2b2 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyDerivedConfigBean.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MyDerivedConfigBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
import io.helidon.builder.Builder;
/**
- * Used for demonstrating and testing the Pico Builder.
+ * Used for demonstrating and testing the Builder.
*
* @see MyConfigBean
*/
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ParentInterfaceNotABuilder.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ParentInterfaceNotABuilder.java
index 995eae4bf97..b3edf8efa5c 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ParentInterfaceNotABuilder.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/ParentInterfaceNotABuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,13 +26,13 @@
public interface ParentInterfaceNotABuilder extends ParentOfParentInterfaceIsABuilder {
/**
- * The Pico Builder will ignore {@code default} and {@code static} functions.
+ * The Builder will ignore {@code default} and {@code static} functions.
*/
default void ignoreMe() {
}
/**
- * The Pico Builder will ignore {@code default} and {@code static} functions.
+ * The Builder will ignore {@code default} and {@code static} functions.
*
* @return ignored, here for testing purposes only
*/
@@ -41,7 +41,7 @@ default Optional maybeOverrideMe() {
}
/**
- * The Pico Builder will ignore {@code default} and {@code static} functions.
+ * The Builder will ignore {@code default} and {@code static} functions.
*
* @return ignored, here for testing purposes only
*/
diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Pickle.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Pickle.java
index 6e6faa2673a..34ee2014bd8 100644
--- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Pickle.java
+++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Pickle.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ enum Size {
/**
* The type of pickle is marked as required; which means that we cannot be build unless the type is defined.
*
- * @return the type of pickle.
+ * @return the type of pickle
*/
@ConfiguredOption(required = true)
Type type();
diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/ComplexCaseTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/ComplexCaseTest.java
index 448d3c71340..f4f3f895a89 100644
--- a/builder/tests/builder/src/test/java/io/helidon/builder/test/ComplexCaseTest.java
+++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/ComplexCaseTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import io.helidon.builder.test.testsubjects.ComplexCaseImpl;
import io.helidon.builder.test.testsubjects.MyConfigBean;
@@ -39,7 +40,7 @@ void testIt() {
ComplexCaseImpl val = ComplexCaseImpl.builder()
.name("name")
.mapOfKeyToConfigBeans(mapWithNull)
- .setOfLists(Collections.singleton(Collections.singletonList(null)))
+ .setOfLists(Set.of(Collections.singletonList(null)))
.classType(Object.class)
.build();
assertThat(val.toString(),
diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/LevelTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/LevelTest.java
index cdb1fa061f5..6ce725cdf36 100644
--- a/builder/tests/builder/src/test/java/io/helidon/builder/test/LevelTest.java
+++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/LevelTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package io.helidon.builder.test;
import java.util.Collections;
+import java.util.Set;
import java.util.function.Supplier;
import io.helidon.builder.test.testsubjects.Level0;
@@ -44,7 +45,7 @@ void manualGeneric() {
.level1booleanAttribute(false)
.level1IntegerAttribute(null)
.level2Level0Info(null)
- .level2Level0Info(Collections.singleton(Level0ManualImpl.builder().build()))
+ .level2Level0Info(Set.of(Level0ManualImpl.builder().build()))
.addLevel0(Level0ManualImpl.builder().build())
.addStringToLevel1("key", Level1ManualImpl.builder().build())
.build();
@@ -79,7 +80,7 @@ void manualNonGeneric() {
.level1booleanAttribute(false)
.level1IntegerAttribute(null)
.level2Level0Info(null)
- .level2Level0Info(Collections.singleton(Level0ManualImpl.builder().build()))
+ .level2Level0Info(Set.of(Level0ManualImpl.builder().build()))
.addLevel0(Level0ManualImpl.builder().build())
.addStringToLevel1("key", Level1ManualImpl.builder().build())
.build();
@@ -116,7 +117,7 @@ void codeGen() {
Level2 val2 = Level2Impl.builder()
.level0StringAttribute("a")
.level1booleanAttribute(false)
- .level2Level0Info(Collections.singleton(Level0Impl.builder().build()))
+ .level2Level0Info(Set.of(Level0Impl.builder().build()))
.addLevel0(Level0Impl.builder().build())
.addStringToLevel1("key", Level1Impl.builder().build())
.build();
diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/MyDerivedConfigBeanTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/MyDerivedConfigBeanTest.java
index 4f310faeb0d..055f1224378 100644
--- a/builder/tests/builder/src/test/java/io/helidon/builder/test/MyDerivedConfigBeanTest.java
+++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/MyDerivedConfigBeanTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,10 +34,11 @@ class MyDerivedConfigBeanTest {
@Test
void testIt() {
assertThat(sort(MyDerivedConfigBeanImpl.__metaAttributes()).toString(),
- equalTo("{enabled={type=boolean}, name={deprecated=false, experimental=false, kind=VALUE, "
+ equalTo("{__generated={version=1}, enabled={__type=boolean}, name={__type=class java.lang.String, "
+ + "deprecated=false, experimental=false, kind=VALUE, "
+ "mergeWithParent=false, provider=false, required=true, type=io.helidon.config.metadata"
+ ".ConfiguredOption, value=io.helidon.config.metadata.ConfiguredOption.UNCONFIGURED}, "
- + "port={type=int}}"));
+ + "port={__type=int}}"));
MyDerivedConfigBean cfg = MyDerivedConfigBeanImpl.builder().name("test").build();
assertThat(cfg.toString(),
diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level0ManualImpl.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level0ManualImpl.java
index 4835de2b9af..80535ab1c77 100644
--- a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level0ManualImpl.java
+++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level0ManualImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -135,7 +135,7 @@ public void accept(T val) {
* @return ignored, here for testing only
*/
private void acceptThis(T val) {
- if (Objects.isNull(val)) {
+ if (val == null) {
return;
}
diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level1ManualImpl.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level1ManualImpl.java
index 4d354aac3c0..fbe1a762354 100644
--- a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level1ManualImpl.java
+++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level1ManualImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -168,7 +168,7 @@ public void accept(T val) {
* Used for testing purposes only.
*/
private void acceptThis(T val) {
- if (Objects.isNull(val)) {
+ if (val == null) {
return;
}
diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level2ManualImpl.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level2ManualImpl.java
index da8673d9c12..63a9eba7ef3 100644
--- a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level2ManualImpl.java
+++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/Level2ManualImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,10 @@
package io.helidon.builder.test.testsubjects;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -36,12 +36,12 @@ public class Level2ManualImpl extends Level1ManualImpl implements Level2 {
protected Level2ManualImpl(Builder builder) {
super(builder);
- this.level2Level0Info = Objects.isNull(builder.level2Level0Info)
- ? Collections.emptyList() : Collections.unmodifiableList(new LinkedList<>(builder.level2Level0Info));
- this.level2ListOfLevel0s = Objects.isNull(builder.level2ListOfLevel0s)
- ? Collections.emptyList() : Collections.unmodifiableList(new LinkedList<>(builder.level2ListOfLevel0s));
- this.level2MapOfStringToLevel1s = Objects.isNull(builder.level2MapOfStringToLevel1s)
- ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap<>(builder.level2MapOfStringToLevel1s));
+ this.level2Level0Info = (builder.level2Level0Info == null)
+ ? List.of() : Collections.unmodifiableList(new ArrayList<>(builder.level2Level0Info));
+ this.level2ListOfLevel0s = (builder.level2ListOfLevel0s == null)
+ ? List.of() : Collections.unmodifiableList(new ArrayList<>(builder.level2ListOfLevel0s));
+ this.level2MapOfStringToLevel1s = (builder.level2MapOfStringToLevel1s == null)
+ ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(builder.level2MapOfStringToLevel1s));
}
@Override
@@ -172,21 +172,21 @@ public void accept(T val) {
}
private void acceptThis(T val) {
- if (Objects.isNull(val)) {
+ if (val == null) {
return;
}
{
Collection v = val.getLevel2Level0Info();
- this.level2Level0Info = Objects.isNull(v) ? null : new LinkedList<>(v);
+ this.level2Level0Info = (v == null) ? null : new ArrayList<>(v);
}
{
Collection v = val.getLevel2ListOfLevel0s();
- this.level2ListOfLevel0s = Objects.isNull(v) ? null : new LinkedList<>(v);
+ this.level2ListOfLevel0s = (v == null) ? null : new ArrayList<>(v);
}
{
Map v = val.getLevel2MapOfStringToLevel1s();
- this.level2MapOfStringToLevel1s = Objects.isNull(v) ? null : new LinkedHashMap<>(v);
+ this.level2MapOfStringToLevel1s = (v == null) ? null : new LinkedHashMap<>(v);
}
}
@@ -196,30 +196,7 @@ private void acceptThis(T val) {
* @return ignored, here for testing only
*/
public B level2Level0Info(Collection val) {
- this.level2Level0Info = Objects.isNull(val) ? null : new LinkedList<>(val);
- return identity();
- }
-
- /**
- * Used for testing purposes only.
- *
- * @return ignored, here for testing only
- */
- public B addlevel2Level0Info(Level0 val) {
- if (Objects.isNull(level2Level0Info)) {
- level2Level0Info = new LinkedList<>();
- }
- level2Level0Info.add(val);
- return identity();
- }
-
- /**
- * Used for testing purposes only.
- *
- * @return ignored, here for testing only
- */
- public B level2ListOfLevel0s(Collection val) {
- this.level2ListOfLevel0s = Objects.isNull(val) ? null : new LinkedList<>(val);
+ this.level2Level0Info = (val == null) ? null : new ArrayList<>(val);
return identity();
}
@@ -229,30 +206,20 @@ public B level2ListOfLevel0s(Collection val) {
* @return ignored, here for testing only
*/
public B addLevel0(Level0 val) {
- if (Objects.isNull(level2ListOfLevel0s)) {
- level2ListOfLevel0s = new LinkedList<>();
+ if (level2ListOfLevel0s == null) {
+ level2ListOfLevel0s = new ArrayList<>();
}
level2ListOfLevel0s.add(val);
return identity();
}
- /**
- * Used for testing purposes only.
- *
- * @return ignored, here for testing only
- */
- public B level2MapOfStringToLevel1s(Map val) {
- this.level2MapOfStringToLevel1s = Objects.isNull(val) ? null : new LinkedHashMap<>(val);
- return identity();
- }
-
/**
* Used for testing purposes only.
*
* @return ignored, here for testing only
*/
public B addStringToLevel1(String key, Level1 val) {
- if (Objects.isNull(level2MapOfStringToLevel1s)) {
+ if (level2MapOfStringToLevel1s == null) {
level2MapOfStringToLevel1s = new LinkedHashMap<>();
}
level2MapOfStringToLevel1s.put(key, val);
diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/MyConfigBeanManualImpl.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/MyConfigBeanManualImpl.java
index 354b37672d0..c8379f6187f 100644
--- a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/MyConfigBeanManualImpl.java
+++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/MyConfigBeanManualImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@ public static Builder extends Builder> builder() {
public static Builder extends Builder> toBuilder(MyConfigBean val) {
Builder b = new Builder();
- if (Objects.nonNull(val)) {
+ if (val != null) {
b.name(val.getName());
b.port(val.getPort());
b.enabled(val.isEnabled());
diff --git a/pico/builder-config/tests/configbean/pom.xml b/builder/tests/configbean/pom.xml
similarity index 79%
rename from pico/builder-config/tests/configbean/pom.xml
rename to builder/tests/configbean/pom.xml
index 2864f8e3254..dabc0f9db49 100644
--- a/pico/builder-config/tests/configbean/pom.xml
+++ b/builder/tests/configbean/pom.xml
@@ -1,6 +1,5 @@
- io.helidon.pico.builder.config.tests
- helidon-pico-builder-config-tests-project
+ io.helidon.builder.tests
+ helidon-builder-tests-project4.0.0-SNAPSHOT../pom.xml4.0.0
- helidon-pico-builder-config-tests-test-config
- Helidon Pico ConfigBean Tests
+ helidon-builder-tests-test-configbean
+ Helidon Builder ConfigBean Tests
+ Tests only the config bean basics (i.e., only common config, and no config-driven services)true
@@ -43,13 +42,21 @@
- io.helidon.pico.builder.config
- helidon-pico-builder-config
+ io.helidon.builder
+ helidon-builder-config
+
+
+ io.helidon.config
+ helidon-config-metadata
+ providedio.helidon.confighelidon-config
- test
+
+
+ io.helidon.config
+ helidon-config-yamljakarta.inject
@@ -61,11 +68,6 @@
jakarta.annotation-apiprovided
-
- io.helidon.pico.builder.config
- helidon-pico-builder-config-processor
- provided
- io.helidon.common.testinghelidon-common-testing-junit5
@@ -97,8 +99,8 @@
true
- io.helidon.pico.builder.config
- helidon-pico-builder-config-processor
+ io.helidon.builder
+ helidon-builder-config-processor${helidon.version}
diff --git a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/ClientConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestClientConfig.java
similarity index 84%
rename from pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/ClientConfig.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestClientConfig.java
index 6a260de4a14..19734ae8313 100644
--- a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/ClientConfig.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestClientConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.testsubjects;
+package io.helidon.builder.config.testsubjects;
import java.util.Map;
+import io.helidon.builder.config.ConfigBean;
import io.helidon.config.metadata.ConfiguredOption;
-import io.helidon.pico.builder.config.ConfigBean;
/**
* For testing purpose.
*/
@ConfigBean(drivesActivation = false)
-public interface ClientConfig extends CommonConfig {
+public interface TestClientConfig extends TestCommonConfig {
/**
* For testing purpose.
diff --git a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/CommonConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestCommonConfig.java
similarity index 85%
rename from pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/CommonConfig.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestCommonConfig.java
index 364f7c228f9..e8588169887 100644
--- a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/CommonConfig.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestCommonConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.testsubjects;
+package io.helidon.builder.config.testsubjects;
import java.util.List;
import io.helidon.builder.Builder;
import io.helidon.config.metadata.ConfiguredOption;
-import io.helidon.pico.builder.config.ConfigBean;
+import io.helidon.builder.config.ConfigBean;
/**
* For testing purpose.
*/
@ConfigBean
@Builder(allowNulls = true)
-public interface CommonConfig {
+public interface TestCommonConfig {
/**
* For testing purpose.
@@ -56,6 +56,6 @@ public interface CommonConfig {
*
* @return for testing purposes
*/
- char[] pwd();
+ char[] pswd();
}
diff --git a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/ServerConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestServerConfig.java
similarity index 82%
rename from pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/ServerConfig.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestServerConfig.java
index f5a569a08ee..7b6b9d3125d 100644
--- a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/ServerConfig.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/TestServerConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.testsubjects;
+package io.helidon.builder.config.testsubjects;
import java.util.Optional;
import io.helidon.config.metadata.ConfiguredOption;
-import io.helidon.pico.builder.config.ConfigBean;
+import io.helidon.builder.config.ConfigBean;
/**
* For testing purpose.
*/
@ConfigBean(atLeastOne = true)
-public interface ServerConfig extends CommonConfig {
+public interface TestServerConfig extends TestCommonConfig {
/**
* For testing purpose.
diff --git a/pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeClientAuth.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeClientAuth.java
similarity index 91%
rename from pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeClientAuth.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeClientAuth.java
index 65fb259f034..00e14b8e1ce 100644
--- a/pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeClientAuth.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeClientAuth.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.fakes;
+package io.helidon.builder.config.testsubjects.fakes;
/**
* Indicates whether the server requires authentication of tbe client by the certificate.
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeComponentTracingConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeComponentTracingConfig.java
new file mode 100644
index 00000000000..44286929d94
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeComponentTracingConfig.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.util.Map;
+
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+
+/**
+ * aka ComponentTracing.
+ *
+ * A component is a single "layer" of the application that can trace.
+ * Component examples:
+ *
+ *
web-server: webServer adds the root tracing span + two additional spans (content-read and content-write)
+ *
security: security adds the overall request security span, a span for authentication ("security:atn"), a span for
+ * authorization "security:atz", and a span for response processing ("security:response")
+ *
jax-rs: JAX-RS integration adds spans for overall resource invocation
+ *
+ */
+@ConfigBean
+public interface FakeComponentTracingConfig extends FakeTraceableConfig {
+
+ @Singular("span") // Builder::addSpan(String span, FakeSpanLogTracingConfigBean val), Impl::getSpan(String span), etc.
+ Map spanLogMap();
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeKeyConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeKeyConfig.java
new file mode 100644
index 00000000000..679e02fe9d6
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeKeyConfig.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Optional;
+
+import io.helidon.builder.Builder;
+
+/**
+ * aka KeyConfig.
+ *
+ */
+@Builder
+public interface FakeKeyConfig {
+
+ /**
+ * The public key of this config if configured.
+ *
+ * @return the public key of this config or empty if not configured
+ */
+ Optional publicKey();
+
+ /**
+ * The private key of this config if configured.
+ *
+ * @return the private key of this config or empty if not configured
+ */
+ Optional privateKey();
+
+ /**
+ * The public X.509 Certificate if configured.
+ *
+ * @return the public certificate of this config or empty if not configured
+ */
+ Optional publicCert();
+
+ /**
+ * The X.509 Certificate Chain.
+ *
+ * @return the certificate chain or empty list if not configured
+ */
+ List certChain();
+
+ /**
+ * The X.509 Certificates.
+ *
+ * @return the certificates configured or empty list if none configured
+ */
+ List certs();
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeKeystoreConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeKeystoreConfig.java
new file mode 100644
index 00000000000..4020998ace0
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeKeystoreConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.util.List;
+
+import io.helidon.config.metadata.ConfiguredOption;
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+
+/**
+ * aka KeyConfig.Keystore.Builder
+ *
+ * This is a ConfigBean since it marries up to the backing config.
+ */
+@ConfigBean
+public interface FakeKeystoreConfig {
+
+ String DEFAULT_KEYSTORE_TYPE = "PKCS12";
+
+ @ConfiguredOption(key = "trust-store")
+ boolean trustStore();
+
+ @ConfiguredOption(key = "type", value = DEFAULT_KEYSTORE_TYPE)
+ String keystoreType();
+
+ @ConfiguredOption(key = "passphrase")
+ char[] keystorePassphrase();
+
+ @ConfiguredOption(key = "key.alias", value = "1")
+ String keyAlias();
+
+ @ConfiguredOption(key = "key.passphrase")
+ char[] keyPassphrase();
+
+ @ConfiguredOption(key = "cert.alias")
+ @Singular("certAlias")
+ List certAliases();
+
+ @ConfiguredOption(key = "cert-chain.alias")
+ String certChainAlias();
+
+}
diff --git a/pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeNettyClientAuth.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeNettyClientAuth.java
similarity index 86%
rename from pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeNettyClientAuth.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeNettyClientAuth.java
index 26809c9d12a..b1fdbad9ee1 100644
--- a/pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeNettyClientAuth.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeNettyClientAuth.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.fakes;
+package io.helidon.builder.config.testsubjects.fakes;
public enum FakeNettyClientAuth {
NONE,
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakePathTracingConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakePathTracingConfig.java
new file mode 100644
index 00000000000..13eb926b51d
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakePathTracingConfig.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.util.List;
+
+import io.helidon.config.metadata.ConfiguredOption;
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+
+/**
+ * aka PathTracing.
+ *
+ * Traced system configuration for web server for a specific path.
+ */
+@ConfigBean
+public interface FakePathTracingConfig {
+
+ /**
+ * Path this configuration should configure.
+ *
+ * @return path on the web server
+ */
+ String path();
+
+ /**
+ * Method(s) this configuration should be valid for. This can be used to restrict the configuration
+ * only to specific HTTP methods (such as {@code GET} or {@code POST}).
+ *
+ * @return list of methods, if empty, this configuration is valid for any method
+ */
+ @Singular("method") // Builder::addMethod(String method);
+ List methods();
+
+ @ConfiguredOption(required = true)
+ FakeTracingConfig tracedConfig();
+
+}
diff --git a/pico/types/src/main/java/module-info.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeRoutingConfig.java
similarity index 75%
rename from pico/types/src/main/java/module-info.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeRoutingConfig.java
index 5b28632adf3..2cdf61d8dda 100644
--- a/pico/types/src/main/java/module-info.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeRoutingConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+package io.helidon.builder.config.testsubjects.fakes;
+
/**
- * Pico minimal (spi) types module.
+ * aka Routing.
*/
-module io.helidon.pico.types {
- requires io.helidon.common;
+public interface FakeRoutingConfig extends FakeServerLifecycle {
- exports io.helidon.pico.types;
}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeServerConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeServerConfig.java
new file mode 100644
index 00000000000..1565ecfec46
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeServerConfig.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+import io.helidon.config.metadata.ConfiguredOption;
+
+/**
+ * aka ServerConfiguration.
+ */
+@ConfigBean(drivesActivation = true)
+public interface FakeServerConfig extends FakeSocketConfig {
+
+ /**
+ * Returns the count of threads in the pool used to process HTTP requests.
+ *
+ * Default value is {@link Runtime#availableProcessors()}.
+ *
+ * @return a workers count
+ */
+ int workersCount();
+
+ /**
+ * Returns a server port to listen on with the default server socket. If port is
+ * {@code 0} then any available ephemeral port will be used.
+ *
+ * @return the server port of the default server socket
+ */
+ @Override
+ int port();
+
+ /**
+ * Returns a maximum length of the queue of incoming connections on the default server
+ * socket.
+ *
+ * Default value is {@link FakeSocketConfig#DEFAULT_BACKLOG_SIZE}.
+ *
+ * @return a maximum length of the queue of incoming connections
+ */
+ @Override
+ int backlog();
+
+ /**
+ * Returns a default server socket timeout in milliseconds or {@code 0} for an infinite timeout.
+ *
+ * @return a default server socket timeout in milliseconds or {@code 0}
+ */
+ @Override
+ int timeoutMillis();
+
+ /**
+ * Returns proposed value of the TCP receive window that is advertised to the remote peer on the
+ * default server socket.
+ *
+ * If {@code 0} then use implementation default.
+ *
+ * @return a buffer size in bytes of the default server socket or {@code 0}
+ */
+ @Override
+ int receiveBufferSize();
+
+ /**
+ * A socket configuration of an additional named server socket.
+ *
+ * An additional named server socket may have a dedicated {@link FakeRoutingConfig} configured
+ *
+ * @param name the name of the additional server socket
+ * @return an additional named server socket configuration or {@code empty} if there is no such
+ * named server socket configured
+ */
+ default Optional namedSocket(String name) {
+ return Optional.ofNullable(sockets().get(name));
+ }
+
+ //
+ // the socketList, socketSet, and sockets are sharing the same config key. This is atypical but here to ensure that the
+ // underlying builder machinery can handle these variants. We need to ensure that the attribute names do not clash, however,
+ // which is why we've used @Singular to disambiguate the attribute names where necessary.
+ //
+
+ /**
+ * A list of all the configured sockets. This maps to the same underlying config as {@link #sockets()}.
+ *
+ * @return a list of all the configured server sockets, never null
+ */
+ @Singular("sock") // note that singular names cannot clash
+ @ConfiguredOption(key = "sockets")
+ List socketList();
+
+ /**
+ * A set of all the configured sockets. This maps to the same underlying config as {@link #sockets()}.
+ *
+ * @return a set of all the configured server sockets, never null
+ */
+ @ConfiguredOption(key = "sockets")
+ Set socketSet();
+
+ /**
+ * A map of all the configured server sockets; that is the default server socket.
+ *
+ * @return a map of all the configured server sockets, never null
+ */
+ @Singular("socket") // note that singular names cannot clash
+ @ConfiguredOption(key = "sockets")
+ Map sockets();
+
+ /**
+ * The maximum amount of time that the server will wait to shut
+ * down regardless of the value of any additionally requested
+ * quiet period.
+ *
+ *
The default implementation of this method returns {@link
+ * java.time.Duration#ofSeconds(long) Duration.ofSeconds(10L)}.
+ *
+ * @return the {@link java.time.Duration} to use
+ */
+ @ConfiguredOption(key = "whatever")
+ default Duration maxShutdownTimeout() {
+ return Duration.ofSeconds(10L);
+ }
+
+ /**
+ * The quiet period during which the webserver will wait for new
+ * incoming connections after it has been told to shut down.
+ *
+ *
The webserver will wait no longer than the duration returned
+ * by the {@link #maxShutdownTimeout()} method.
+ *
+ *
The default implementation of this method returns {@link
+ * java.time.Duration#ofSeconds(long) Duration.ofSeconds(0L)}, indicating
+ * that there will be no quiet period.
+ *
+ * @return the {@link java.time.Duration} to use
+ */
+ default Duration shutdownQuietPeriod() {
+ return Duration.ofSeconds(0L);
+ }
+
+ /**
+ * Whether to print details of HelidonFeatures.
+ *
+ * @return whether to print details
+ */
+ boolean printFeatureDetails();
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeServerLifecycle.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeServerLifecycle.java
new file mode 100644
index 00000000000..f23aa1d2953
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeServerLifecycle.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+/**
+ * aka ServerLifecycle.
+ * Basic server lifecycle operations.
+ */
+public interface FakeServerLifecycle {
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSocketConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSocketConfig.java
new file mode 100644
index 00000000000..89390654cdd
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSocketConfig.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.util.Optional;
+
+import io.helidon.builder.config.ConfigBean;
+import io.helidon.config.metadata.ConfiguredOption;
+
+/**
+ * aka ServerConfiguration.
+ * The SocketConfiguration configures a port to listen on and its associated server socket parameters.
+ */
+@ConfigBean(levelType = ConfigBean.LevelType.NESTED)
+public interface FakeSocketConfig {
+
+ /**
+ * The default backlog size to configure the server sockets with if no other value
+ * is provided.
+ */
+ int DEFAULT_BACKLOG_SIZE = 1024;
+
+ /**
+ * Name of this socket.
+ * Default to WebServer#DEFAULT_SOCKET_NAME for the main and
+ * default server socket. All other sockets must be named.
+ *
+ * @return name of this socket
+ */
+ @ConfiguredOption("@default")
+ String name();
+
+ /**
+ * Returns a server port to listen on with the server socket. If port is
+ * {@code 0} then any available ephemeral port will be used.
+ *
+ * @return the server port of the server socket
+ */
+ int port();
+
+ @ConfiguredOption(key = "bind-address")
+ String bindAddress();
+
+ /**
+ * Returns a maximum length of the queue of incoming connections on the server
+ * socket.
+ *
+ * Default value is {@link #DEFAULT_BACKLOG_SIZE}.
+ *
+ * @return a maximum length of the queue of incoming connections
+ */
+ @ConfiguredOption("1024")
+ int backlog();
+
+ /**
+ * Returns a server socket timeout in milliseconds or {@code 0} for an infinite timeout.
+ *
+ * @return a server socket timeout in milliseconds or {@code 0}
+ */
+ @ConfiguredOption(key = "timeout-millis")
+ int timeoutMillis();
+
+ /**
+ * Returns proposed value of the TCP receive window that is advertised to the remote peer on the
+ * server socket.
+ *
+ * If {@code 0} then use implementation default.
+ *
+ * @return a buffer size in bytes of the server socket or {@code 0}
+ */
+ int receiveBufferSize();
+
+ /**
+ * Return a {@link FakeWebServerTlsConfig} containing server TLS configuration. When empty {@link java.util.Optional} is returned
+ * no TLS should be configured.
+ *
+ * @return web server tls configuration
+ */
+ Optional tls();
+
+ /**
+ * Whether this socket is enabled (and will be opened on server startup), or disabled
+ * (and ignored on server startup).
+ *
+ * @return {@code true} for enabled socket, {@code false} for socket that should not be opened
+ */
+ @ConfiguredOption("true")
+ boolean enabled();
+
+ /**
+ * Maximal size of all headers combined.
+ *
+ * @return size in bytes
+ */
+ @ConfiguredOption(key = "max-header-size", value = "8192")
+ int maxHeaderSize();
+
+ /**
+ * Maximal length of the initial HTTP line.
+ *
+ * @return length
+ */
+ @ConfiguredOption("4096")
+ int maxInitialLineLength();
+
+ /**
+ * Maximum size allowed for an HTTP payload in a client request. A negative
+ * value indicates that there is no maximum set.
+ *
+ * @return maximum payload size
+ */
+ @ConfiguredOption("-1")
+ long maxPayloadSize();
+
+ /**
+ * Maximum length of the content of an upgrade request.
+ *
+ * @return maximum length of the content of an upgrade request
+ */
+ @ConfiguredOption("65536")
+ int maxUpgradeContentLength();
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSpanLogTracingConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSpanLogTracingConfig.java
new file mode 100644
index 00000000000..6c57ee4be28
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSpanLogTracingConfig.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import io.helidon.builder.config.ConfigBean;
+
+/**
+ * aka SpanLogTracingConfig.
+ * Configuration of a single log event in a traced span.
+ */
+@ConfigBean
+public interface FakeSpanLogTracingConfig extends FakeTraceableConfig {
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSpanTracingConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSpanTracingConfig.java
new file mode 100644
index 00000000000..ed4a44bb166
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeSpanTracingConfig.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+
+/**
+ * aka SpanTracingConfig.
+ *
+ * Configuration of a single traced span.
+ */
+@ConfigBean
+public interface FakeSpanTracingConfig extends FakeTraceableConfig {
+
+ /**
+ * When rename is desired, returns the new name.
+ *
+ * @return new name for this span or empty when rename is not desired
+ */
+ Optional newName();
+
+ @Singular("spanLog") // B addSpanLog(String, FakeSpanLogTracingConfigBean);
+ Map spanLogMap();
+
+}
diff --git a/pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeTraceableConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTraceableConfig.java
similarity index 87%
rename from pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeTraceableConfig.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTraceableConfig.java
index 2a3d4a4e432..24a2a44cc58 100644
--- a/pico/builder-config/tests/configbean/src/test/java/io/helidon/pico/builder/config/fakes/FakeTraceableConfig.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTraceableConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package io.helidon.pico.builder.config.fakes;
+package io.helidon.builder.config.testsubjects.fakes;
import java.util.Optional;
-import io.helidon.pico.builder.config.ConfigBean;
+import io.helidon.builder.config.ConfigBean;
/**
* aka Traceable.
@@ -33,7 +33,7 @@ public interface FakeTraceableConfig {
* {@code false} if it should not,
* {@code empty} when this flag is not explicitly configured
*/
- /*protected*/ Optional isEnabled();
+ Optional isEnabled();
/**
* Name of this traceable unit.
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTracer.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTracer.java
new file mode 100644
index 00000000000..7403f3f4688
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTracer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+/**
+ * Tracer abstraction.
+ * Tracer is the central point that collects tracing spans, and (probably) pushes them to backend.
+ */
+public interface FakeTracer {
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTracingConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTracingConfig.java
new file mode 100644
index 00000000000..fc0646fdcbf
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeTracingConfig.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.util.Map;
+
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+
+/**
+ * aka TracingConfig.
+ *
+ * Tracing configuration that contains traced components (such as WebServer, Security) and their traced spans and span logs.
+ * Spans can be renamed through configuration, components, spans and span logs may be disabled through this configuration.
+ */
+@ConfigBean("tracing")
+public interface FakeTracingConfig extends FakeTraceableConfig {
+
+ @Singular("component") // Builder::addComponent(String component); Impl::getComponent(String component);
+ Map components();
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeWebServerTlsConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeWebServerTlsConfig.java
new file mode 100644
index 00000000000..c2b3db1d398
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/FakeWebServerTlsConfig.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+
+import javax.net.ssl.SSLContext;
+
+import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
+import io.helidon.common.LazyValue;
+import io.helidon.config.metadata.ConfiguredOption;
+
+/**
+ * aka WebServerTls.
+ *
+ * A class wrapping transport layer security (TLS) configuration for
+ * WebServer sockets.
+ */
+@ConfigBean(value = "tls", drivesActivation = false)
+public interface FakeWebServerTlsConfig {
+ String PROTOCOL = "TLS";
+ // secure random cannot be stored in native image, it must be initialized at runtime
+ LazyValue RANDOM = LazyValue.create(SecureRandom::new);
+
+ /**
+ * This constant is a context classifier for the x509 client certificate if it is present. Callers may use this
+ * constant to lookup the client certificate associated with the current request context.
+ */
+ String CLIENT_X509_CERTIFICATE = FakeWebServerTlsConfig.class.getName() + ".client-x509-certificate";
+
+ Set enabledTlsProtocols();
+
+ // TODO: had to make this Optional - we might need something like 'ExternalConfigBean' for this case ?
+ Optional sslContext();
+
+ @Singular("cipher")
+ @ConfiguredOption(key = "cipher")
+// Set cipherSuite();
+ List cipherSuite();
+
+ /**
+ * Whether this TLS config has security enabled (and the socket is going to be
+ * protected by one of the TLS protocols), or no (and the socket is going to be plain).
+ *
+ * @return {@code true} if this configuration represents a TLS configuration, {@code false} for plain configuration
+ */
+ boolean enabled();
+
+}
diff --git a/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/SSLContextConfig.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/SSLContextConfig.java
new file mode 100644
index 00000000000..28192cbad29
--- /dev/null
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/fakes/SSLContextConfig.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.testsubjects.fakes;
+
+import java.util.Random;
+
+import io.helidon.builder.Builder;
+
+/**
+ * aka SSLContextBuilder.
+ * Note that this is just a normal builder, and will not be integrated with Config.
+ * Builder for configuring a new SslContext for creation.
+ */
+@Builder
+public interface SSLContextConfig {
+
+ String PROTOCOL = "TLS";
+ Random RANDOM = new Random();
+
+ FakeKeyConfig privateKeyConfig();
+
+ FakeKeyConfig trustConfig();
+
+ long sessionCacheSize();
+
+ long sessionTimeout();
+
+}
diff --git a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/package-info.java b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/package-info.java
similarity index 85%
rename from pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/package-info.java
rename to builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/package-info.java
index 0c9849e207d..1dfc3ea4b78 100644
--- a/pico/builder-config/tests/configbean/src/main/java/io/helidon/pico/builder/config/testsubjects/package-info.java
+++ b/builder/tests/configbean/src/main/java/io/helidon/builder/config/testsubjects/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,4 +17,4 @@
/**
* ConfigBean test subjects.
*/
-package io.helidon.pico.builder.config.testsubjects;
+package io.helidon.builder.config.testsubjects;
diff --git a/pico/builder-config/tests/configbean/src/main/java/module-info.java b/builder/tests/configbean/src/main/java/module-info.java
similarity index 72%
rename from pico/builder-config/tests/configbean/src/main/java/module-info.java
rename to builder/tests/configbean/src/main/java/module-info.java
index e8fd093bb5f..6fd2df4edb4 100644
--- a/pico/builder-config/tests/configbean/src/main/java/module-info.java
+++ b/builder/tests/configbean/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,17 +15,16 @@
*/
/**
- * Pico ConfigBean Builder test module.
+ * Helidon ConfigBean Builder test Module (i.e., only common config and w/o config-driven services).
*/
-module io.helidon.pico.builder.config.tests.test.config {
+module io.helidon.builder.config.tests.test.config {
requires static jakarta.inject;
requires static jakarta.annotation;
-
requires static io.helidon.config.metadata;
-
requires io.helidon.common;
requires io.helidon.common.config;
- requires io.helidon.pico;
- requires io.helidon.pico.builder.config;
requires io.helidon.builder;
+ requires io.helidon.builder.config;
+
+ exports io.helidon.builder.config.testsubjects;
}
diff --git a/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/AbstractConfigBeanTest.java b/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/AbstractConfigBeanTest.java
new file mode 100644
index 00000000000..3b8cd63d0bd
--- /dev/null
+++ b/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/AbstractConfigBeanTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.test;
+
+import java.util.Map;
+
+import io.helidon.builder.config.testsubjects.fakes.FakeWebServerTlsConfig;
+import io.helidon.config.ConfigSources;
+import io.helidon.config.MapConfigSource;
+
+class AbstractConfigBeanTest {
+ static final String NESTED = "nested";
+ static final String FAKE_SOCKET_CONFIG = "sockets";
+ static final String FAKE_SERVER_CONFIG = "fake-server";
+
+ MapConfigSource.Builder createRootPlusOneSocketTestingConfigSource() {
+ return ConfigSources.create(
+ Map.of(
+ FAKE_SERVER_CONFIG + ".name", "root",
+ FAKE_SERVER_CONFIG + ".port", "8080",
+ FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".1.name", "first",
+ FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".1.port", "8081"
+ ), "config-nested-plus-one-socket");
+ }
+
+ MapConfigSource.Builder createNestedPlusOneSocketAndOneTlsTestingConfigSource() {
+ return ConfigSources.create(
+ Map.of(
+ NESTED + "." + FAKE_SERVER_CONFIG + ".name", "nested",
+ NESTED + "." + FAKE_SERVER_CONFIG + ".port", "8080",
+ NESTED + "." + FAKE_SERVER_CONFIG + ".worker-count", "2",
+ NESTED + "." + FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".1.name", "first",
+ NESTED + "." + FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".1.port", "8081",
+ NESTED + "." + FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".1.tls.enabled", "true",
+ NESTED + "." + FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".1.tls.cipher", "cipher-1",
+ NESTED + "." + FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".1.tls.enabled-tls-protocols",
+ FakeWebServerTlsConfig.PROTOCOL
+ ), "config-nested-plus-one-socket-and-tls");
+ }
+
+}
diff --git a/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/BasicConfigBeanTest.java b/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/BasicConfigBeanTest.java
new file mode 100644
index 00000000000..537e16ba2cf
--- /dev/null
+++ b/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/BasicConfigBeanTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.test;
+
+import java.util.List;
+import java.util.Map;
+
+import io.helidon.builder.config.testsubjects.DefaultTestClientConfig;
+import io.helidon.builder.config.testsubjects.DefaultTestServerConfig;
+import io.helidon.builder.config.testsubjects.TestClientConfig;
+import io.helidon.builder.config.testsubjects.TestServerConfig;
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty;
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue;
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasEntry;
+
+class BasicConfigBeanTest {
+
+ @Test
+ void acceptConfig() {
+ Config cfg = Config.builder(
+ ConfigSources.create(
+ Map.of("name", "server",
+ "port", "8080",
+ "description", "test",
+ "pswd", "pwd1",
+ "cipher-suites.0", "a",
+ "cipher-suites.1", "b",
+ "cipher-suites.2", "c",
+ "headers.0", "header1",
+ "headers.1", "header2"),
+ "my-simple-config-1"))
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .build();
+ TestServerConfig serverConfig = DefaultTestServerConfig.toBuilder(cfg).build();
+ assertThat(serverConfig.description(),
+ optionalValue(equalTo("test")));
+ assertThat(serverConfig.name(),
+ equalTo("server"));
+ assertThat(serverConfig.port(),
+ equalTo(8080));
+ assertThat(new String(serverConfig.pswd()),
+ equalTo("pwd1"));
+ assertThat(serverConfig.toString(),
+ startsWith("TestServerConfig"));
+ assertThat(serverConfig.cipherSuites(),
+ contains("a", "b", "c"));
+ assertThat(serverConfig.toString(),
+ endsWith("(name=server, port=8080, cipherSuites=[a, b, c], pswd=not-null, description=Optional[test])"));
+
+ TestClientConfig clientConfig = DefaultTestClientConfig.toBuilder(cfg).build();
+ assertThat(clientConfig.name(),
+ equalTo("server"));
+ assertThat(clientConfig.port(),
+ equalTo(8080));
+ assertThat(new String(clientConfig.pswd()),
+ equalTo("pwd1"));
+ assertThat(clientConfig.toString(),
+ startsWith("TestClientConfig"));
+ assertThat(clientConfig.cipherSuites(),
+ contains("a", "b", "c"));
+ assertThat(clientConfig.headers(),
+ hasEntry("headers.0", "header1"));
+ assertThat(clientConfig.headers(),
+ hasEntry("headers.1", "header2"));
+ assertThat(clientConfig.toString(),
+ endsWith("(name=server, port=8080, cipherSuites=[a, b, c], pswd=not-null, "
+ + "serverPort=0, headers={headers.1=header2, headers.0=header1})"));
+ }
+
+ @Test
+ void emptyConfig() {
+ Config cfg = Config.create();
+ TestServerConfig serverConfig = DefaultTestServerConfig.toBuilder(cfg).build();
+ assertThat(serverConfig.description(),
+ optionalEmpty());
+ assertThat(serverConfig.name(),
+ equalTo("default"));
+ assertThat(serverConfig.port(),
+ equalTo(0));
+ }
+
+ /**
+ * Callers can conceptually use config beans as just plain old vanilla builders, void of any config usage.
+ */
+ @Test
+ void noConfig() {
+ TestServerConfig serverConfig = DefaultTestServerConfig.builder().build();
+ assertThat(serverConfig.description(), optionalEmpty());
+ assertThat(serverConfig.name(),
+ equalTo("default"));
+ assertThat(serverConfig.port(),
+ equalTo(0));
+ assertThat(serverConfig.cipherSuites(),
+ equalTo(List.of()));
+
+ serverConfig = DefaultTestServerConfig.toBuilder(serverConfig).port(123).build();
+ assertThat(serverConfig.description(),
+ optionalEmpty());
+ assertThat(serverConfig.name(),
+ equalTo("default"));
+ assertThat(serverConfig.port(),
+ equalTo(123));
+ assertThat(serverConfig.cipherSuites(),
+ equalTo(List.of()));
+
+ TestClientConfig clientConfig = DefaultTestClientConfig.builder().build();
+ assertThat(clientConfig.name(),
+ equalTo("default"));
+ assertThat(clientConfig.port(),
+ equalTo(0));
+ assertThat(clientConfig.headers(),
+ equalTo(Map.of()));
+ assertThat(clientConfig.cipherSuites(),
+ equalTo(List.of()));
+
+ clientConfig = DefaultTestClientConfig.toBuilder(clientConfig).port(123).build();
+ assertThat(clientConfig.name(),
+ equalTo("default"));
+ assertThat(clientConfig.port(),
+ equalTo(123));
+ assertThat(clientConfig.headers(),
+ equalTo(Map.of()));
+ assertThat(clientConfig.cipherSuites(),
+ equalTo(List.of()));
+ }
+
+}
diff --git a/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/NestedConfigBeanTest.java b/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/NestedConfigBeanTest.java
new file mode 100644
index 00000000000..4a8499e6f5f
--- /dev/null
+++ b/builder/tests/configbean/src/test/java/io/helidon/builder/config/test/NestedConfigBeanTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.builder.config.test;
+
+import java.util.Objects;
+
+import io.helidon.builder.config.testsubjects.fakes.DefaultFakeServerConfig;
+import io.helidon.builder.config.testsubjects.fakes.DefaultFakeSocketConfig;
+import io.helidon.builder.config.testsubjects.fakes.FakeSocketConfig;
+import io.helidon.builder.config.testsubjects.fakes.FakeWebServerTlsConfig;
+import io.helidon.common.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.config.yaml.YamlConfigParser;
+
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasEntry;
+
+class NestedConfigBeanTest extends AbstractConfigBeanTest {
+
+ @Test
+ void rootServerConfigPlusOneSocket() {
+ Config cfg = io.helidon.config.Config.builder(createRootPlusOneSocketTestingConfigSource())
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .build();
+ DefaultFakeServerConfig serverConfig = DefaultFakeServerConfig
+ .toBuilder(cfg.get(FAKE_SERVER_CONFIG)).build();
+
+ assertThat(serverConfig.name(),
+ equalTo("root"));
+ assertThat(serverConfig.port(),
+ equalTo(8080));
+
+ // validate the map
+ assertThat(serverConfig.sockets(),
+ hasEntry("1",
+ DefaultFakeSocketConfig.builder()
+ .name("first")
+ .port(8081)
+ .build()));
+ assertThat(serverConfig.sockets().get("1").tls(),
+ optionalEmpty());
+ }
+
+ @Test
+ void nestedServerConfigPlusOneSocketAndOneTls() {
+ Config cfg = io.helidon.config.Config.builder(createNestedPlusOneSocketAndOneTlsTestingConfigSource())
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .build();
+ DefaultFakeServerConfig serverConfig = DefaultFakeServerConfig
+ .toBuilder(cfg.get(NESTED + "." + FAKE_SERVER_CONFIG)).build();
+
+ assertThat(serverConfig.name(),
+ equalTo("nested"));
+ assertThat(serverConfig.port(),
+ equalTo(8080));
+
+ // validate the map
+ FakeWebServerTlsConfig tls = serverConfig.sockets().get("1").tls().orElseThrow();
+ assertThat(tls.enabled(),
+ is(true));
+ assertThat(tls.cipherSuite(),
+ containsInAnyOrder("cipher-1"));
+ assertThat(tls.enabledTlsProtocols(),
+ containsInAnyOrder(FakeWebServerTlsConfig.PROTOCOL));
+ }
+
+ @Test
+ void fakeServerConfigFromUnnamedYaml() {
+ Config cfg = io.helidon.config.Config.builder()
+ .sources(ConfigSources.classpath("io/helidon/builder/config/test/FakeServerConfigPlusTwoUnnamedSockets.yaml"))
+ .addParser(YamlConfigParser.create())
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .build();
+ DefaultFakeServerConfig serverConfig = DefaultFakeServerConfig
+ .toBuilder(cfg.get(FAKE_SERVER_CONFIG)).build();
+
+ assertThat(serverConfig.name(),
+ equalTo("@default"));
+
+ // validate the map
+ FakeSocketConfig zero = Objects.requireNonNull(serverConfig.namedSocket("0").orElse(null),
+ serverConfig.sockets().toString());
+ assertThat(zero.bindAddress(),
+ equalTo("127.0.0.1"));
+ assertThat(zero.port(),
+ equalTo(8086));
+ assertThat(zero.tls(),
+ optionalEmpty());
+ FakeSocketConfig one = Objects.requireNonNull(serverConfig.sockets().get("1"),
+ serverConfig.sockets().toString());
+ assertThat(one.bindAddress(),
+ equalTo("localhost"));
+ assertThat(one.port(),
+ equalTo(8087));
+ FakeWebServerTlsConfig tls = one.tls().orElseThrow();
+ assertThat(tls.enabled(),
+ is(true));
+ assertThat(tls.cipherSuite(),
+ containsInAnyOrder("cipher-1"));
+ assertThat(tls.enabledTlsProtocols(),
+ containsInAnyOrder(FakeWebServerTlsConfig.PROTOCOL));
+
+ // validate the list
+ assertThat(serverConfig.socketList(),
+ contains(DefaultFakeSocketConfig.builder()
+ .bindAddress("127.0.0.1")
+ .port(8086)
+ .build(),
+ DefaultFakeSocketConfig.builder()
+ .bindAddress("localhost")
+ .port(8087)
+ .tls(tls)
+ .build()));
+
+ // validate the set
+ assertThat(serverConfig.socketSet(),
+ containsInAnyOrder(DefaultFakeSocketConfig.builder()
+ .bindAddress("127.0.0.1")
+ .port(8086)
+ .build(),
+ DefaultFakeSocketConfig.builder()
+ .bindAddress("localhost")
+ .port(8087)
+ .tls(tls)
+ .build()));
+ }
+
+ @Test
+ void fakeServerConfigFromNamedYaml() {
+ Config cfg = io.helidon.config.Config.builder()
+ .sources(ConfigSources.classpath("io/helidon/builder/config/test/FakeServerConfigPlusTwoNamedSockets.yaml"))
+ .addParser(YamlConfigParser.create())
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .build();
+ DefaultFakeServerConfig serverConfig = DefaultFakeServerConfig
+ .toBuilder(cfg.get(FAKE_SERVER_CONFIG)).build();
+
+ // validate the map
+ assertThat(serverConfig.name(),
+ equalTo("@default"));
+ FakeSocketConfig admin = serverConfig.namedSocket("admin").orElseThrow();
+ assertThat(admin.port(),
+ equalTo(8086));
+ assertThat(admin.name(),
+ equalTo("admin"));
+
+ FakeSocketConfig secure = serverConfig.namedSocket("secure").orElseThrow();
+ assertThat(secure.port(),
+ equalTo(8087));
+ assertThat(secure.name(),
+ equalTo("obscure"));
+ FakeWebServerTlsConfig tls = secure.tls().orElseThrow();
+ assertThat(tls.enabled(),
+ is(true));
+ assertThat(tls.cipherSuite(),
+ containsInAnyOrder("cipher-1"));
+ assertThat(tls.enabledTlsProtocols(),
+ containsInAnyOrder(FakeWebServerTlsConfig.PROTOCOL));
+ }
+
+}
diff --git a/builder/tests/configbean/src/test/resources/io/helidon/builder/config/test/FakeServerConfigPlusTwoNamedSockets.yaml b/builder/tests/configbean/src/test/resources/io/helidon/builder/config/test/FakeServerConfigPlusTwoNamedSockets.yaml
new file mode 100644
index 00000000000..dfced8379bb
--- /dev/null
+++ b/builder/tests/configbean/src/test/resources/io/helidon/builder/config/test/FakeServerConfigPlusTwoNamedSockets.yaml
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+fake-server:
+ sockets:
+ admin:
+ name: "admin"
+ bind-address: "127.0.0.1"
+ port: 8086
+ secure:
+ name: "obscure"
+ bind-address: "localhost"
+ port: 8087
+ tls:
+ enabled: true
+ cipher: "cipher-1"
+ enabled-tls-protocols: "TLS"
diff --git a/builder/tests/configbean/src/test/resources/io/helidon/builder/config/test/FakeServerConfigPlusTwoUnnamedSockets.yaml b/builder/tests/configbean/src/test/resources/io/helidon/builder/config/test/FakeServerConfigPlusTwoUnnamedSockets.yaml
new file mode 100644
index 00000000000..8d3147c7f3f
--- /dev/null
+++ b/builder/tests/configbean/src/test/resources/io/helidon/builder/config/test/FakeServerConfigPlusTwoUnnamedSockets.yaml
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+fake-server:
+ sockets:
+ - bind-address: "127.0.0.1"
+ port: 8086
+ - bind-address: "localhost"
+ port: 8087
+ tls:
+ enabled: true
+ cipher: "cipher-1"
+ enabled-tls-protocols: "TLS"
diff --git a/builder/tests/nodeps/pom.xml b/builder/tests/nodeps/pom.xml
index 168f33db2b7..be347a4c45b 100644
--- a/builder/tests/nodeps/pom.xml
+++ b/builder/tests/nodeps/pom.xml
@@ -1,4 +1,5 @@
+
+
+
+ 4.0.0
+
+ io.helidon.common
+ helidon-common-project
+ 4.0.0-SNAPSHOT
+
+
+ helidon-common-types
+ Helidon Common Types
+
+ Abstraction of language types, that can be used during annotation processing and at runtime instead of reflection.
+
+
+
+
+ 11
+
+
+
+
+ io.helidon.common
+ helidon-common
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+
+
+
+
+
+
+
+
diff --git a/pico/types/src/main/java/io/helidon/pico/types/AnnotationAndValue.java b/common/types/src/main/java/io/helidon/common/types/AnnotationAndValue.java
similarity index 76%
rename from pico/types/src/main/java/io/helidon/pico/types/AnnotationAndValue.java
rename to common/types/src/main/java/io/helidon/common/types/AnnotationAndValue.java
index 37217871324..6b657ee9165 100644
--- a/pico/types/src/main/java/io/helidon/pico/types/AnnotationAndValue.java
+++ b/common/types/src/main/java/io/helidon/common/types/AnnotationAndValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.types;
+package io.helidon.common.types;
import java.util.Map;
import java.util.Optional;
@@ -53,14 +53,4 @@ public interface AnnotationAndValue {
*/
Map values();
- /**
- * Determines whether the {@link #value()} is present and a non-blank String (see {@link String#isBlank()}.
- *
- * @return true if our value is present and non-blank
- */
- default boolean hasNonBlankValue() {
- Optional val = value();
- return val.isPresent() && !val.get().isBlank();
- }
-
}
diff --git a/pico/types/src/main/java/io/helidon/pico/types/DefaultAnnotationAndValue.java b/common/types/src/main/java/io/helidon/common/types/DefaultAnnotationAndValue.java
similarity index 82%
rename from pico/types/src/main/java/io/helidon/pico/types/DefaultAnnotationAndValue.java
rename to common/types/src/main/java/io/helidon/common/types/DefaultAnnotationAndValue.java
index f663230d034..618f4860eec 100644
--- a/pico/types/src/main/java/io/helidon/pico/types/DefaultAnnotationAndValue.java
+++ b/common/types/src/main/java/io/helidon/common/types/DefaultAnnotationAndValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.types;
+package io.helidon.common.types;
import java.lang.annotation.Annotation;
import java.util.Collection;
@@ -28,7 +28,6 @@
*/
public class DefaultAnnotationAndValue implements AnnotationAndValue, Comparable {
private final TypeName typeName;
- private final String value;
private final Map values;
/**
@@ -38,67 +37,13 @@ public class DefaultAnnotationAndValue implements AnnotationAndValue, Comparable
* @see #builder()
*/
protected DefaultAnnotationAndValue(Builder b) {
- this.typeName = b.typeName;
- this.value = b.value;
- this.values = Map.copyOf(b.values);
- }
-
- @Override
- public String toString() {
- String result = getClass().getSimpleName() + "(typeName=" + typeName();
- if (values != null && !values.isEmpty()) {
- result += ", values=" + values();
- } else if (value != null) {
- result += ", value=" + value().orElse(null);
- }
- return result + ")";
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(typeName());
- }
-
- @Override
- public boolean equals(Object another) {
- if (!(another instanceof AnnotationAndValue)) {
- return false;
- }
- if (!Objects.equals(typeName(), ((AnnotationAndValue) another).typeName())) {
- return false;
- }
- if (Objects.nonNull(values) && (another instanceof DefaultAnnotationAndValue)
- && Objects.equals(values(), ((DefaultAnnotationAndValue) another).values())) {
- return true;
- } else if (Objects.nonNull(values) && values.size() > 1) {
- return false;
- }
- String thisValue = value().orElse("");
- String anotherValue = ((AnnotationAndValue) another).value().orElse("");
- return thisValue.equals(anotherValue);
- }
-
- @Override
- public TypeName typeName() {
- return typeName;
- }
-
- @Override
- public Optional value() {
- if (Objects.nonNull(value)) {
- return Optional.of(value);
+ LinkedHashMap map = new LinkedHashMap<>(b.values);
+ if (b.value != null) {
+ String prev = map.put("value", b.value);
+ assert (prev == null || prev.equals(b.value));
}
- return value("value");
- }
-
- @Override
- public Optional value(String name) {
- return Objects.isNull(values) ? Optional.empty() : Optional.ofNullable(values.get(name));
- }
-
- @Override
- public Map values() {
- return values;
+ this.typeName = b.typeName;
+ this.values = Map.copyOf(map);
}
/**
@@ -132,7 +77,8 @@ public static DefaultAnnotationAndValue create(TypeName annoType) {
* @param value the annotation value
* @return the new instance
*/
- public static DefaultAnnotationAndValue create(Class extends Annotation> annoType, String value) {
+ public static DefaultAnnotationAndValue create(Class extends Annotation> annoType,
+ String value) {
return create(DefaultTypeName.create(annoType), value);
}
@@ -143,7 +89,8 @@ public static DefaultAnnotationAndValue create(Class extends Annotation> annoT
* @param values the annotation values
* @return the new instance
*/
- public static DefaultAnnotationAndValue create(Class extends Annotation> annoType, Map values) {
+ public static DefaultAnnotationAndValue create(Class extends Annotation> annoType,
+ Map values) {
return create(DefaultTypeName.create(annoType), values);
}
@@ -154,7 +101,8 @@ public static DefaultAnnotationAndValue create(Class extends Annotation> annoT
* @param value the annotation value
* @return the new instance
*/
- public static DefaultAnnotationAndValue create(TypeName annoTypeName, String value) {
+ public static DefaultAnnotationAndValue create(TypeName annoTypeName,
+ String value) {
return DefaultAnnotationAndValue.builder().typeName(annoTypeName).value(value).build();
}
@@ -165,7 +113,8 @@ public static DefaultAnnotationAndValue create(TypeName annoTypeName, String val
* @param values the annotation values
* @return the new instance
*/
- public static DefaultAnnotationAndValue create(TypeName annoTypeName, Map values) {
+ public static DefaultAnnotationAndValue create(TypeName annoTypeName,
+ Map values) {
return DefaultAnnotationAndValue.builder().typeName(annoTypeName).values(values).build();
}
@@ -177,21 +126,15 @@ public static DefaultAnnotationAndValue create(TypeName annoTypeName, Map findFirst(String annoTypeName,
- Collection extends AnnotationAndValue> coll) {
+ Collection extends AnnotationAndValue> coll) {
assert (!annoTypeName.isBlank());
return coll.stream()
.filter(it -> it.typeName().name().equals(annoTypeName))
.findFirst();
}
- @Override
- public int compareTo(AnnotationAndValue other) {
- return typeName().compareTo(other.typeName());
- }
-
-
/**
- * Creates a builder for {@link io.helidon.pico.types.AnnotationAndValue}.
+ * Creates a builder for {@link AnnotationAndValue}.
*
* @return a fluent builder
*/
@@ -199,9 +142,64 @@ public static Builder builder() {
return new Builder();
}
+ @Override
+ public String toString() {
+ String result = getClass().getSimpleName() + "(typeName=" + typeName();
+ Optional value = value();
+ if (!values.isEmpty() && value.isEmpty()) {
+ result += ", values=" + values();
+ } else if (value.isPresent()) {
+ result += ", value=" + value.orElse(null);
+ }
+ return result + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(typeName(), values);
+ }
+
+ @Override
+ public boolean equals(Object another) {
+ if (!(another instanceof AnnotationAndValue)) {
+ return false;
+ }
+ if (!Objects.equals(typeName(), ((AnnotationAndValue) another).typeName())) {
+ return false;
+ }
+ if (!Objects.equals(values, ((AnnotationAndValue) another).values())) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public TypeName typeName() {
+ return typeName;
+ }
+
+ @Override
+ public Optional value() {
+ return value("value");
+ }
+
+ @Override
+ public Optional value(String name) {
+ return Optional.ofNullable(values.get(name));
+ }
+
+ @Override
+ public Map values() {
+ return values;
+ }
+
+ @Override
+ public int compareTo(AnnotationAndValue other) {
+ return typeName().compareTo(other.typeName());
+ }
/**
- * Fluent API builder for {@link io.helidon.pico.types.DefaultAnnotationAndValue}.
+ * Fluent API builder for {@link DefaultAnnotationAndValue}.
*/
public static class Builder implements io.helidon.common.Builder {
private final Map values = new LinkedHashMap<>();
@@ -268,4 +266,5 @@ public Builder type(Class extends Annotation> annoType) {
return typeName(DefaultTypeName.create(annoType));
}
}
+
}
diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeInfo.java b/common/types/src/main/java/io/helidon/common/types/DefaultTypeInfo.java
similarity index 76%
rename from builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeInfo.java
rename to common/types/src/main/java/io/helidon/common/types/DefaultTypeInfo.java
index eb9a5bb5ebe..97c117d64ad 100644
--- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeInfo.java
+++ b/common/types/src/main/java/io/helidon/common/types/DefaultTypeInfo.java
@@ -14,20 +14,18 @@
* limitations under the License.
*/
-package io.helidon.builder.processor.spi;
+package io.helidon.common.types;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
-
/**
* Default implementation for {@link TypeInfo}.
*/
@@ -37,6 +35,7 @@ public class DefaultTypeInfo implements TypeInfo {
private final List annotations;
private final List elementInfo;
private final List otherElementInfo;
+ private final Map> referencedTypeNamesToAnnotations;
private final TypeInfo superTypeInfo;
private final Set modifierNames;
@@ -54,6 +53,7 @@ protected DefaultTypeInfo(Builder b) {
this.otherElementInfo = List.copyOf(b.otherElementInfo);
this.superTypeInfo = b.superTypeInfo;
this.modifierNames = Set.copyOf(b.modifierNames);
+ this.referencedTypeNamesToAnnotations = Map.copyOf(b.referencedTypeNamesToAnnotations);
}
/**
@@ -90,6 +90,11 @@ public List otherElementInfo() {
return otherElementInfo;
}
+ @Override
+ public Map> referencedTypeNamesToAnnotations() {
+ return referencedTypeNamesToAnnotations;
+ }
+
@Override
public Optional superTypeInfo() {
return Optional.ofNullable(superTypeInfo);
@@ -125,6 +130,7 @@ public static class Builder implements io.helidon.common.Builder annotations = new ArrayList<>();
private final List elementInfo = new ArrayList<>();
private final List otherElementInfo = new ArrayList<>();
+ private final Map> referencedTypeNamesToAnnotations = new LinkedHashMap<>();
private final Set modifierNames = new LinkedHashSet<>();
private TypeName typeName;
private String typeKind;
@@ -155,7 +161,7 @@ public DefaultTypeInfo build() {
*/
public Builder typeName(TypeName val) {
this.typeName = val;
- return this;
+ return identity();
}
/**
@@ -166,7 +172,7 @@ public Builder typeName(TypeName val) {
*/
public Builder typeKind(String val) {
this.typeKind = val;
- return this;
+ return identity();
}
/**
@@ -179,7 +185,7 @@ public Builder annotations(Collection val) {
Objects.requireNonNull(val);
this.annotations.clear();
this.annotations.addAll(val);
- return this;
+ return identity();
}
/**
@@ -191,7 +197,7 @@ public Builder annotations(Collection val) {
public Builder addAnnotation(AnnotationAndValue val) {
Objects.requireNonNull(val);
annotations.add(Objects.requireNonNull(val));
- return this;
+ return identity();
}
/**
@@ -204,7 +210,7 @@ public Builder elementInfo(Collection val) {
Objects.requireNonNull(val);
this.elementInfo.clear();
this.elementInfo.addAll(val);
- return this;
+ return identity();
}
/**
@@ -216,7 +222,7 @@ public Builder elementInfo(Collection val) {
public Builder addElementInfo(TypedElementName val) {
Objects.requireNonNull(val);
elementInfo.add(val);
- return this;
+ return identity();
}
/**
@@ -229,7 +235,7 @@ public Builder otherElementInfo(Collection val) {
Objects.requireNonNull(val);
this.otherElementInfo.clear();
this.otherElementInfo.addAll(val);
- return this;
+ return identity();
}
/**
@@ -241,9 +247,52 @@ public Builder otherElementInfo(Collection val) {
public Builder addOtherElementInfo(TypedElementName val) {
Objects.requireNonNull(val);
otherElementInfo.add(val);
+ return identity();
+ }
+
+ /**
+ * Sets the referencedTypeNamesToAnnotations to val.
+ *
+ * @param val the value
+ * @return this fluent builder
+ */
+ public Builder referencedTypeNamesToAnnotations(Map> val) {
+ Objects.requireNonNull(val);
+ this.referencedTypeNamesToAnnotations.clear();
+ this.referencedTypeNamesToAnnotations.putAll(val);
return this;
}
+ /**
+ * Adds a single referencedTypeNamesToAnnotations val.
+ *
+ * @param key the key
+ * @param val the value
+ * @return this fluent builder
+ */
+ public Builder addReferencedTypeNamesToAnnotations(TypeName key, AnnotationAndValue val) {
+ return addReferencedTypeNamesToAnnotations(key, List.of(val));
+ }
+
+ /**
+ * Adds a collection of referencedTypeNamesToAnnotations values.
+ *
+ * @param key the key
+ * @param vals the values
+ * @return this fluent builder
+ */
+ public Builder addReferencedTypeNamesToAnnotations(TypeName key, Collection vals) {
+ Objects.requireNonNull(vals);
+ referencedTypeNamesToAnnotations.compute(key, (k, v) -> {
+ if (v == null) {
+ v = new ArrayList<>();
+ }
+ v.addAll(vals);
+ return v;
+ });
+ return identity();
+ }
+
/**
* Sets the modifiers to val.
*
@@ -254,7 +303,7 @@ public Builder modifierNames(Collection val) {
Objects.requireNonNull(val);
this.modifierNames.clear();
this.modifierNames.addAll(val);
- return this;
+ return identity();
}
/**
@@ -266,7 +315,7 @@ public Builder modifierNames(Collection val) {
public Builder addModifierName(String val) {
Objects.requireNonNull(val);
modifierNames.add(val);
- return this;
+ return identity();
}
/**
@@ -278,7 +327,7 @@ public Builder addModifierName(String val) {
public Builder superTypeInfo(TypeInfo val) {
Objects.requireNonNull(val);
this.superTypeInfo = val;
- return this;
+ return identity();
}
}
diff --git a/pico/types/src/main/java/io/helidon/pico/types/DefaultTypeName.java b/common/types/src/main/java/io/helidon/common/types/DefaultTypeName.java
similarity index 95%
rename from pico/types/src/main/java/io/helidon/pico/types/DefaultTypeName.java
rename to common/types/src/main/java/io/helidon/common/types/DefaultTypeName.java
index 049d3bbedf5..06d97c4e2d9 100644
--- a/pico/types/src/main/java/io/helidon/pico/types/DefaultTypeName.java
+++ b/common/types/src/main/java/io/helidon/common/types/DefaultTypeName.java
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-package io.helidon.pico.types;
+package io.helidon.common.types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
/**
- * Default implementation for {@link io.helidon.pico.types.TypeName}.
+ * Default implementation for {@link TypeName}.
*/
public class DefaultTypeName implements TypeName {
private final String packageName;
@@ -61,7 +60,7 @@ public String toString() {
@Override
public int hashCode() {
- return Objects.hashCode(name());
+ return Objects.hash(name(), primitive, array);
}
@Override
@@ -77,7 +76,16 @@ public boolean equals(Object o) {
@Override
public int compareTo(TypeName o) {
- return name().compareTo(o.name());
+ int diff = name().compareTo(o.name());
+ if (diff != 0) {
+ // different name
+ return diff;
+ }
+ diff = Boolean.compare(primitive, o.primitive());
+ if (diff != 0) {
+ return diff;
+ }
+ return Boolean.compare(array, o.array());
}
/**
@@ -139,7 +147,7 @@ public static DefaultTypeName createFromTypeName(String typeName) {
// a.b.c.SomeClass
// a.b.c.SomeClass.InnerClass.Builder
String className = typeName;
- List packageElements = new LinkedList<>();
+ List packageElements = new ArrayList<>();
while (true) {
if (Character.isUpperCase(className.charAt(0))) {
@@ -180,7 +188,7 @@ public static TypeName createExtendsTypeName(TypeName typeName) {
* Throws an exception if the provided type name is not fully qualified, having a package and class name representation.
*
* @param name the type name to check
- * @throws java.lang.IllegalStateException if the name is invalid
+ * @throws IllegalStateException if the name is invalid
*/
public static void ensureIsFQN(TypeName name) {
if (!isFQN(name)) {
@@ -294,7 +302,7 @@ protected String calcFQName() {
/**
- * Creates a builder for {@link io.helidon.pico.types.TypeName}.
+ * Creates a builder for {@link TypeName}.
*
* @return a fluent builder
*/
diff --git a/pico/types/src/main/java/io/helidon/pico/types/DefaultTypedElementName.java b/common/types/src/main/java/io/helidon/common/types/DefaultTypedElementName.java
similarity index 92%
rename from pico/types/src/main/java/io/helidon/pico/types/DefaultTypedElementName.java
rename to common/types/src/main/java/io/helidon/common/types/DefaultTypedElementName.java
index 39b5fed7e26..0216db7590f 100644
--- a/pico/types/src/main/java/io/helidon/pico/types/DefaultTypedElementName.java
+++ b/common/types/src/main/java/io/helidon/common/types/DefaultTypedElementName.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.types;
+package io.helidon.common.types;
import java.util.ArrayList;
import java.util.Collection;
@@ -25,7 +25,7 @@
import java.util.Set;
/**
- * Default implementation for {@link io.helidon.pico.types.TypedElementName}.
+ * Default implementation for {@link io.helidon.common.types.TypedElementName}.
*/
public class DefaultTypedElementName implements TypedElementName {
private final TypeName typeName;
@@ -65,7 +65,7 @@ public String elementName() {
}
@Override
- public String elementKind() {
+ public String elementTypeKind() {
return elementKind;
}
@@ -96,7 +96,7 @@ public Set modifierNames() {
@Override
public int hashCode() {
- return System.identityHashCode(typeName());
+ return Objects.hash(typeName(), elementName(), elementTypeKind(), annotations());
}
@Override
@@ -108,7 +108,7 @@ public boolean equals(Object another) {
TypedElementName other = (TypedElementName) another;
return Objects.equals(typeName(), other.typeName())
&& Objects.equals(elementName(), other.elementName())
- && Objects.equals(elementKind(), other.elementKind())
+ && Objects.equals(elementTypeKind(), other.elementTypeKind())
&& Objects.equals(annotations(), other.annotations());
}
@@ -128,7 +128,7 @@ public String toDeclaration() {
/**
- * Creates a builder for {@link io.helidon.pico.types.TypedElementName}.
+ * Creates a builder for {@link io.helidon.common.types.TypedElementName}.
*
* @return a fluent builder
*/
@@ -164,6 +164,7 @@ protected Builder() {
* @return this fluent builder
*/
public Builder typeName(TypeName val) {
+ Objects.requireNonNull(val);
this.typeName = val;
return this;
}
@@ -198,6 +199,7 @@ public Builder componentTypeNames(List val) {
* @return this fluent builder
*/
public Builder elementName(String val) {
+ Objects.requireNonNull(val);
this.elementName = val;
return this;
}
@@ -209,6 +211,7 @@ public Builder elementName(String val) {
* @return this fluent builder
*/
public Builder elementKind(String val) {
+ Objects.requireNonNull(val);
this.elementKind = val;
return this;
}
@@ -220,6 +223,7 @@ public Builder elementKind(String val) {
* @return this fluent builder
*/
public Builder defaultValue(String val) {
+ Objects.requireNonNull(val);
this.defaultValue = val;
return this;
}
diff --git a/common/types/src/main/java/io/helidon/common/types/TypeInfo.java b/common/types/src/main/java/io/helidon/common/types/TypeInfo.java
new file mode 100644
index 00000000000..f0daf685835
--- /dev/null
+++ b/common/types/src/main/java/io/helidon/common/types/TypeInfo.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.common.types;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Represents the model object for a type.
+ */
+public interface TypeInfo {
+ /**
+ * The {@code public} modifier.
+ */
+ String MODIFIER_PUBLIC = "PUBLIC";
+ /**
+ * The {@code protected} modifier.
+ */
+ String MODIFIER_PROTECTED = "PROTECTED";
+ /**
+ * The {@code private} modifier.
+ */
+ String MODIFIER_PRIVATE = "PRIVATE";
+ /**
+ * The {@code abstract} modifier.
+ */
+ String MODIFIER_ABSTRACT = "ABSTRACT";
+ /**
+ * The {@code default} modifier.
+ */
+ String MODIFIER_DEFAULT = "DEFAULT";
+ /**
+ * The {@code static} modifier.
+ */
+ String MODIFIER_STATIC = "STATIC";
+ /**
+ * The {@code sealed} modifier.
+ */
+ String MODIFIER_SEALED = "SEALED";
+ /**
+ * The {@code final} modifier.
+ */
+ String MODIFIER_FINAL = "FINAL";
+
+
+ /**
+ * Field element type kind.
+ * See javax.lang.model.element.ElementKind#FIELD
+ */
+ String KIND_FIELD = "FIELD";
+
+ /**
+ * Method element type kind.
+ * See javax.lang.model.element.ElementKind#METHOD
+ */
+ String KIND_METHOD = "METHOD";
+
+ /**
+ * Constructor element type kind.
+ * See javax.lang.model.element.ElementKind#CONSTRUCTOR
+ */
+ String KIND_CONSTRUCTOR = "CONSTRUCTOR";
+
+ /**
+ * Parameter element type kind.
+ * See javax.lang.model.element.ElementKind#PARAMETER
+ */
+ String KIND_PARAMETER = "PARAMETER";
+
+ /**
+ * Interface element type kind.
+ * See javax.lang.model.element.ElementKind#INTERFACE
+ */
+ String KIND_INTERFACE = "INTERFACE";
+
+ /**
+ * Interface element type kind.
+ * See javax.lang.model.element.ElementKind#CLASS
+ */
+ String KIND_CLASS = "CLASS";
+
+ /**
+ * Enum element type kind.
+ * See javax.lang.model.element.ElementKind#ENUM
+ */
+ String KIND_ENUM = "ENUM";
+
+ /**
+ * Annotation element type kind.
+ * See javax.lang.model.element.ElementKind#ANNOTATION_TYPE
+ */
+ String KIND_ANNOTATION_TYPE = "ANNOTATION_TYPE";
+
+ /**
+ * Package element type kind.
+ * See javax.lang.model.element.ElementKind#PACKAGE
+ */
+ String KIND_PACKAGE = "PACKAGE";
+
+ /**
+ * Record element type kind (since Java 16).
+ * See javax.lang.model.element.ElementKind#RECORD
+ */
+ String KIND_RECORD = "RECORD";
+
+ /**
+ * The type name.
+ *
+ * @return the type name
+ */
+ TypeName typeName();
+
+ /**
+ * The type element kind.
+ *
+ * @return the type element kind (e.g., "{@value #KIND_INTERFACE}", "{@value #KIND_ANNOTATION_TYPE}", etc.)
+ * @see #KIND_CLASS and other constants on this class prefixed with {@code TYPE}
+ */
+ String typeKind();
+
+ /**
+ * The annotations on the type.
+ *
+ * @return the annotations on the type
+ */
+ List annotations();
+
+ /**
+ * The elements that make up the type that are relevant for processing.
+ *
+ * @return the elements that make up the type that are relevant for processing
+ */
+ List elementInfo();
+
+ /**
+ * The elements that make up this type that are considered "other", or being skipped because they are irrelevant to
+ * processing.
+ *
+ * @return the elements that still make up the type, but are otherwise deemed irrelevant for processing
+ */
+ List otherElementInfo();
+
+ /**
+ * Any Map, List, Set, or method that has {@link TypeName#typeArguments()} will be analyzed and any type arguments will have
+ * its annotations added here. Note that this only applies to non-built-in types.
+ *
+ * @return all referenced types
+ */
+ Map> referencedTypeNamesToAnnotations();
+
+ /**
+ * The parent/super class for this type info.
+ *
+ * @return the super type
+ */
+ Optional superTypeInfo();
+
+ /**
+ * Element modifiers.
+ *
+ * @return element modifiers
+ * @see #MODIFIER_PUBLIC and other constants prefixed with {@code MODIFIER}
+ */
+ Set modifierNames();
+
+}
diff --git a/pico/types/src/main/java/io/helidon/pico/types/TypeName.java b/common/types/src/main/java/io/helidon/common/types/TypeName.java
similarity index 94%
rename from pico/types/src/main/java/io/helidon/pico/types/TypeName.java
rename to common/types/src/main/java/io/helidon/common/types/TypeName.java
index c45f002b470..59a748634cf 100644
--- a/pico/types/src/main/java/io/helidon/pico/types/TypeName.java
+++ b/common/types/src/main/java/io/helidon/common/types/TypeName.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.types;
+package io.helidon.common.types;
import java.util.List;
@@ -22,11 +22,11 @@
* TypeName is similar to {@link java.lang.reflect.Type} in its most basic use case. The {@link #name()} returns the package +
* class name tuple for the given type (i.e., the canonical type name).
*
- * This class also provides a number of methods that are typically found in {@link java.lang.Class} that can be used to avoid
+ * This class also provides a number of methods that are typically found in {@link Class} that can be used to avoid
* classloading resolution:
*
*
{@link #packageName()} and {@link #className()} - access to the package and simple class names.
- *
{@link #primitive()} and {@link #array()} - access to flags that is typically found in {@link java.lang.Class}.
+ *
{@link #primitive()} and {@link #array()} - access to flags that is typically found in {@link Class}.
*
* Additionally, this class offers a number of additional methods that are useful for handling generics:
*
@@ -134,7 +134,8 @@ default boolean isOptional() {
String declaredName();
/**
- * The fully qualified type name. This will include the generic portion of the declaration, as well as any array declaration, etc.
+ * The fully qualified type name. This will include the generic portion of the declaration, as well as any array
+ * declaration, etc.
*
* @return the fully qualified name which includes the use of generics/parameterized types, arrays, etc.
*/
diff --git a/pico/types/src/main/java/io/helidon/pico/types/TypedElementName.java b/common/types/src/main/java/io/helidon/common/types/TypedElementName.java
similarity index 85%
rename from pico/types/src/main/java/io/helidon/pico/types/TypedElementName.java
rename to common/types/src/main/java/io/helidon/common/types/TypedElementName.java
index 95df3697c3e..9720dce9c40 100644
--- a/pico/types/src/main/java/io/helidon/pico/types/TypedElementName.java
+++ b/common/types/src/main/java/io/helidon/common/types/TypedElementName.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.types;
+package io.helidon.common.types;
import java.util.List;
import java.util.Optional;
@@ -24,9 +24,9 @@
* Provides a way to describe method, field, or annotation attribute.
*/
public interface TypedElementName {
-
/**
- * The type name for the element (e.g., java.util.List).
+ * The type name for the element (e.g., java.util.List). If the element is a method, then this is the return type of
+ * the method.
*
* @return the type name of the element
*/
@@ -43,8 +43,9 @@ public interface TypedElementName {
* The kind of element (e.g., method, field, etc).
*
* @return the element kind
+ * @see io.helidon.common.types.TypeInfo
*/
- String elementKind();
+ String elementTypeKind();
/**
* The default value assigned to the element, represented as a string.
@@ -79,6 +80,7 @@ public interface TypedElementName {
* Element modifiers.
*
* @return element modifiers
+ * @see io.helidon.common.types.TypeInfo
*/
Set modifierNames();
diff --git a/pico/types/src/main/java/io/helidon/pico/types/package-info.java b/common/types/src/main/java/io/helidon/common/types/package-info.java
similarity index 73%
rename from pico/types/src/main/java/io/helidon/pico/types/package-info.java
rename to common/types/src/main/java/io/helidon/common/types/package-info.java
index 1d313b87557..b67a9300628 100644
--- a/pico/types/src/main/java/io/helidon/pico/types/package-info.java
+++ b/common/types/src/main/java/io/helidon/common/types/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
/**
- * Subset of Pico's SPI types that are useful for runtime. Used in the Pico Config builder, etc., that require a minimal set of
+ * Subset of Builder's SPI types that are useful for runtime. Used in the ConfigBean builder, etc., that require a minimal set of
* types present at runtime.
*/
-package io.helidon.pico.types;
+package io.helidon.common.types;
diff --git a/common/types/src/main/java/module-info.java b/common/types/src/main/java/module-info.java
new file mode 100644
index 00000000000..5ad3598384c
--- /dev/null
+++ b/common/types/src/main/java/module-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Builder (minimal) types support.
+ */
+module io.helidon.common.types {
+ requires transitive io.helidon.common;
+
+ exports io.helidon.common.types;
+}
diff --git a/pico/types/src/test/java/io/helidon/pico/types/test/DefaultAnnotationAndValueTest.java b/common/types/src/test/java/io/helidon/common/types/DefaultAnnotationAndValueTest.java
similarity index 73%
rename from pico/types/src/test/java/io/helidon/pico/types/test/DefaultAnnotationAndValueTest.java
rename to common/types/src/test/java/io/helidon/common/types/DefaultAnnotationAndValueTest.java
index 7f2459d0cd8..8051155e24a 100644
--- a/pico/types/src/test/java/io/helidon/pico/types/test/DefaultAnnotationAndValueTest.java
+++ b/common/types/src/test/java/io/helidon/common/types/DefaultAnnotationAndValueTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package io.helidon.pico.types.test;
+package io.helidon.common.types;
+import java.lang.annotation.Target;
import java.util.Map;
-import io.helidon.pico.types.DefaultAnnotationAndValue;
-
-import jakarta.inject.Named;
import org.junit.jupiter.api.Test;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -34,19 +33,19 @@ void sanity() {
DefaultAnnotationAndValue val1 = DefaultAnnotationAndValue.create(Test.class);
assertThat(val1.typeName().toString(), equalTo(Test.class.getName()));
assertThat(val1.toString(),
- equalTo("DefaultAnnotationAndValue(typeName=" + Test.class.getName() + ")"));
+ containsString("typeName=" + Test.class.getName()));
DefaultAnnotationAndValue val2 = DefaultAnnotationAndValue.create(Test.class);
assertThat(val2, equalTo(val1));
assertThat(val2.compareTo(val1), is(0));
- DefaultAnnotationAndValue val3 = DefaultAnnotationAndValue.create(Named.class, "name");
+ DefaultAnnotationAndValue val3 = DefaultAnnotationAndValue.create(Target.class, "name");
assertThat(val3.toString(),
- equalTo("DefaultAnnotationAndValue(typeName=jakarta.inject.Named, value=name)"));
+ containsString("typeName=java.lang.annotation.Target, value=name"));
DefaultAnnotationAndValue val4 = DefaultAnnotationAndValue.create(Test.class, Map.of("a", "1"));
assertThat(val4.toString(),
- equalTo("DefaultAnnotationAndValue(typeName=" + Test.class.getName() + ", values={a=1})"));
+ containsString("typeName=" + Test.class.getName() + ", values={a=1}"));
}
}
diff --git a/pico/types/src/test/java/io/helidon/pico/types/test/DefaultTypeNameTest.java b/common/types/src/test/java/io/helidon/common/types/DefaultTypeNameTest.java
similarity index 65%
rename from pico/types/src/test/java/io/helidon/pico/types/test/DefaultTypeNameTest.java
rename to common/types/src/test/java/io/helidon/common/types/DefaultTypeNameTest.java
index 9188b1c8ae4..84bb0204afc 100644
--- a/pico/types/src/test/java/io/helidon/pico/types/test/DefaultTypeNameTest.java
+++ b/common/types/src/test/java/io/helidon/common/types/DefaultTypeNameTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,25 +14,29 @@
* limitations under the License.
*/
-package io.helidon.pico.types.test;
+package io.helidon.common.types;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-
-import io.helidon.pico.types.DefaultTypeName;
-import io.helidon.pico.types.TypeName;
+import java.util.Set;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
-import static io.helidon.pico.types.DefaultTypeName.create;
-import static io.helidon.pico.types.DefaultTypeName.createFromTypeName;
+import static io.helidon.common.types.DefaultTypeName.builder;
+import static io.helidon.common.types.DefaultTypeName.create;
+import static io.helidon.common.types.DefaultTypeName.createFromTypeName;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
class DefaultTypeNameTest {
@@ -152,7 +156,7 @@ void primitiveArrayTypes() {
}
@Test
- public void nonPrimitiveUsages() {
+ void nonPrimitiveUsages() {
assertThat(create(Boolean.class).toString(), is("java.lang.Boolean"));
assertThat(create(Long.class).toString(), is("java.lang.Long"));
assertThat(create(Object.class).toString(), is("java.lang.Object"));
@@ -193,7 +197,7 @@ public void nonPrimitiveUsages() {
}
@Test
- public void typeArguments() {
+ void typeArguments() {
DefaultTypeName typeName = DefaultTypeName.create(List.class)
.toBuilder()
.typeArguments(Collections.singletonList(DefaultTypeName.create(String.class)))
@@ -201,29 +205,29 @@ public void typeArguments() {
assertThat(typeName.fqName(),
is("java.util.List"));
assertThat(typeName.toString(),
- is("java.util.List"));
+ equalTo(typeName.fqName()));
assertThat(typeName.name(),
- is("java.util.List"));
+ is("java.util.List"));
typeName = DefaultTypeName.createFromTypeName("? extends pkg.Something");
assertThat(typeName.wildcard(), is(true));
assertThat(typeName.fqName(),
is("? extends pkg.Something"));
assertThat(typeName.toString(),
- is("? extends pkg.Something"));
+ is("? extends pkg.Something"));
assertThat(typeName.name(),
- is("pkg.Something"));
+ is("pkg.Something"));
assertThat(typeName.packageName(),
- is("pkg"));
+ is("pkg"));
assertThat(typeName.className(),
- is("Something"));
+ is("Something"));
typeName = DefaultTypeName.createFromTypeName("?");
assertThat(typeName.wildcard(), is(true));
assertThat(typeName.fqName(),
is("?"));
assertThat(typeName.toString(),
- is("?"));
+ equalTo(typeName.fqName()));
assertThat(typeName.name(),
is(Object.class.getName()));
assertThat(typeName.packageName(),
@@ -236,28 +240,28 @@ public void typeArguments() {
.typeArguments(Collections.singletonList(DefaultTypeName.createFromTypeName("? extends pkg.Something")))
.build();
assertThat(typeName.fqName(),
- is("java.util.List extends pkg.Something>"));
+ is("java.util.List extends pkg.Something>"));
assertThat(typeName.toString(),
- is("java.util.List extends pkg.Something>"));
+ equalTo(typeName.fqName()));
assertThat(typeName.name(),
- is("java.util.List"));
+ is("java.util.List"));
}
@SuppressWarnings("unchecked")
@Test
- public void declaredName() {
- List> list = new LinkedList<>();
+ void declaredName() {
+ List> list = new ArrayList<>();
List>[] arrayOfLists = new List[] {};
assertThat(DefaultTypeName.create(char.class).declaredName(), equalTo("char"));
assertThat(DefaultTypeName.create(char[].class).declaredName(), equalTo("char[]"));
- assertThat(DefaultTypeName.create(list.getClass()).declaredName(), equalTo("java.util.LinkedList"));
+ assertThat(DefaultTypeName.create(list.getClass()).declaredName(), equalTo("java.util.ArrayList"));
assertThat(DefaultTypeName.create(arrayOfLists.getClass()).declaredName(), equalTo("java.util.List[]"));
assertThat(DefaultTypeName.create(List[].class).declaredName(), equalTo("java.util.List[]"));
}
@Test
- public void genericDecl() {
+ void genericDecl() {
DefaultTypeName genericTypeName = DefaultTypeName.createFromGenericDeclaration("CB");
assertThat(genericTypeName.name(), equalTo("CB"));
assertThat(genericTypeName.fqName(), equalTo("CB"));
@@ -283,13 +287,13 @@ public void genericDecl() {
.build();
assertThat(typeName.name(), equalTo("java.util.Map"));
assertThat(typeName.fqName(), equalTo("java.util.Map"));
- assertThat(typeName.toString(), equalTo("java.util.Map"));
+ assertThat(typeName.toString(), equalTo(typeName.fqName()));
// note: in the future was can always add getBoundedTypeName()
genericTypeName = DefaultTypeName.createFromGenericDeclaration("CB extends MyClass");
assertThat(genericTypeName.name(), equalTo("CB extends MyClass"));
assertThat(genericTypeName.fqName(), equalTo("CB extends MyClass"));
- assertThat(genericTypeName.toString(), equalTo("CB extends MyClass"));
+ assertThat(genericTypeName.toString(), equalTo(genericTypeName.fqName()));
assertThat(genericTypeName.generic(), is(true));
assertThat(genericTypeName.wildcard(), is(false));
}
@@ -325,4 +329,119 @@ void extendsTypeName() {
assertThat(extendsName.name(), equalTo("java.util.Map"));
}
+ @Test
+ void testDefaultMethods() {
+ TypeName typeName = DefaultTypeName.create(Optional.class);
+ assertThat("isOptional() for: " + typeName.name(), typeName.isOptional(), is(true));
+ assertThat("isList() for: " + typeName.name(), typeName.isList(), is(false));
+ assertThat("isMap() for: " + typeName.name(), typeName.isMap(), is(false));
+ assertThat("isSet() for: " + typeName.name(), typeName.isSet(), is(false));
+
+ typeName = DefaultTypeName.create(Set.class);
+ assertThat("isOptional() for: " + typeName.name(), typeName.isOptional(), is(false));
+ assertThat("isList() for: " + typeName.name(), typeName.isList(), is(false));
+ assertThat("isMap() for: " + typeName.name(), typeName.isMap(), is(false));
+ assertThat("isSet() for: " + typeName.name(), typeName.isSet(), is(true));
+
+ typeName = DefaultTypeName.create(List.class);
+ assertThat("isOptional() for: " + typeName.name(), typeName.isOptional(), is(false));
+ assertThat("isList() for: " + typeName.name(), typeName.isList(), is(true));
+ assertThat("isMap() for: " + typeName.name(), typeName.isMap(), is(false));
+ assertThat("isSet() for: " + typeName.name(), typeName.isSet(), is(false));
+
+ typeName = DefaultTypeName.create(Map.class);
+ assertThat("isOptional() for: " + typeName.name(), typeName.isOptional(), is(false));
+ assertThat("isList() for: " + typeName.name(), typeName.isList(), is(false));
+ assertThat("isMap() for: " + typeName.name(), typeName.isMap(), is(true));
+ assertThat("isSet() for: " + typeName.name(), typeName.isSet(), is(false));
+
+ typeName = DefaultTypeName.create(String.class);
+ assertThat("isOptional() for: " + typeName.name(), typeName.isOptional(), is(false));
+ assertThat("isList() for: " + typeName.name(), typeName.isList(), is(false));
+ assertThat("isMap() for: " + typeName.name(), typeName.isMap(), is(false));
+ assertThat("isSet() for: " + typeName.name(), typeName.isSet(), is(false));
+ }
+
+ @ParameterizedTest
+ @MethodSource("equalsAndCompareSource")
+ @SuppressWarnings("unchecked")
+ void hashEqualsAndCompare(EqualsData data) {
+ if (data.equal) {
+ assertThat("equals", data.first, equalTo(data.second));
+ assertThat("equals", data.second, equalTo(data.first));
+ assertThat("has", data.first.hashCode(), is(data.second.hashCode()));
+ if (data.canCompare) {
+ assertThat("compare", data.first.compareTo(data.second), is(0));
+ assertThat("compare", data.second.compareTo(data.first), is(0));
+ }
+ } else {
+ assertThat("equals", data.first, not(equalTo(data.second)));
+ assertThat("equals", data.second, not(equalTo(data.first)));
+ assertThat("has", data.first.hashCode(), not(data.second.hashCode()));
+ if (data.canCompare) {
+ int compareOne = data.first.compareTo(data.second);
+ int compareTwo = data.second.compareTo(data.first);
+ assertThat("compare", compareOne, not(0));
+ assertThat("compare", compareTwo, not(0));
+ assertThat("compare", compareOne, not(compareTwo));
+ // also make sure one is negative and one positive
+ assertThat("compare has negative and positive", (compareOne * compareTwo), lessThan(0));
+ }
+ }
+ }
+
+ private static Stream equalsAndCompareSource() {
+ return Stream.of(
+ new EqualsData(create(DefaultTypeNameTest.class), create(DefaultTypeNameTest.class), true),
+ new EqualsData(create(DefaultTypeNameTest.class),
+ builder().type(DefaultTypeNameTest.class).array(true).build(),
+ false),
+ new EqualsData(create(DefaultTypeNameTest.class),
+ builder().type(DefaultTypeNameTest.class).primitive(true).build(),
+ false),
+ new EqualsData(create(DefaultTypeNameTest.class),
+ builder().type(DefaultTypeNameTest.class).primitive(true).array(true).build(),
+ false),
+ new EqualsData(builder().type(DefaultTypeNameTest.class).array(true).build(),
+ builder().type(DefaultTypeNameTest.class).array(true).build(),
+ true),
+ new EqualsData(builder().type(DefaultTypeNameTest.class).array(true).build(),
+ builder().type(DefaultTypeNameTest.class).array(true).primitive(true).build(),
+ false),
+ new EqualsData(builder().type(DefaultTypeNameTest.class).primitive(true).build(),
+ builder().type(DefaultTypeNameTest.class).primitive(true).build(),
+ true),
+ new EqualsData(builder().type(DefaultTypeNameTest.class).primitive(true).build(),
+ builder().type(DefaultTypeNameTest.class).array(true).primitive(true).build(),
+ false),
+ new EqualsData(create(long.class),
+ builder().className("long").primitive(false).build(),
+ false),
+ new EqualsData(create(DefaultTypeNameTest.class), "Some string", false, false)
+ );
+ }
+
+ @SuppressWarnings("rawtypes")
+ private final static class EqualsData {
+ private final Comparable first;
+ private final Comparable second;
+ private final boolean equal;
+ private final boolean canCompare;
+
+ private EqualsData(Comparable> first, Comparable> second, boolean equal) {
+ this(first, second, equal, true);
+ }
+
+ private EqualsData(Comparable> first, Comparable> second, boolean equal, boolean canCompare) {
+ this.first = first;
+ this.second = second;
+ this.equal = equal;
+ this.canCompare = canCompare;
+ }
+
+ @Override
+ public String toString() {
+ return first + ", " + second + ", equals: " + equal;
+ }
+ }
}
diff --git a/pico/types/src/test/java/io/helidon/pico/types/test/DefaultTypedElementNameTest.java b/common/types/src/test/java/io/helidon/common/types/DefaultTypedElementNameTest.java
similarity index 93%
rename from pico/types/src/test/java/io/helidon/pico/types/test/DefaultTypedElementNameTest.java
rename to common/types/src/test/java/io/helidon/common/types/DefaultTypedElementNameTest.java
index 87c18ffd82a..6552662f6f4 100644
--- a/pico/types/src/test/java/io/helidon/pico/types/test/DefaultTypedElementNameTest.java
+++ b/common/types/src/test/java/io/helidon/common/types/DefaultTypedElementNameTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package io.helidon.pico.types.test;
-
-import io.helidon.pico.types.DefaultTypedElementName;
+package io.helidon.common.types;
import org.junit.jupiter.api.Test;
-import static io.helidon.pico.types.DefaultTypeName.create;
+import static io.helidon.common.types.DefaultTypeName.create;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/etc/checkstyle-suppressions.xml b/etc/checkstyle-suppressions.xml
index 1ec36407da1..1f2c11c3f36 100644
--- a/etc/checkstyle-suppressions.xml
+++ b/etc/checkstyle-suppressions.xml
@@ -90,17 +90,21 @@
checks=".*"/>
-
-
-
-
-
+
+
+
+
+
+
diff --git a/etc/copyright-exclude.txt b/etc/copyright-exclude.txt
index c72f400ab06..50bf4f6698c 100644
--- a/etc/copyright-exclude.txt
+++ b/etc/copyright-exclude.txt
@@ -67,3 +67,4 @@ persistence_3_0.xsd
# excluded as this is a test file and we need to validate its content
src/test/resources/static/classpath/index.html
._java_
+._pico_
diff --git a/examples/nima/pico/pom.xml b/examples/nima/pico/pom.xml
new file mode 100644
index 00000000000..50b0007e6a6
--- /dev/null
+++ b/examples/nima/pico/pom.xml
@@ -0,0 +1,158 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.0.0-SNAPSHOT
+ ../../../applications/se/pom.xml
+
+
+ io.helidon.examples.nima
+ helidon-examples-nima-pico
+ Helidon NÃma Pico Example
+
+
+ io.helidon.examples.nima.pico.PicoMain
+
+
+
+
+ io.helidon.nima
+ helidon-nima
+
+
+ io.helidon.nima.webserver
+ helidon-nima-webserver
+
+
+ io.helidon.nima.webclient
+ helidon-nima-webclient
+
+
+ io.helidon.common.features
+ helidon-common-features
+
+
+ io.helidon.pico
+ helidon-pico-api
+
+
+ io.helidon.pico
+ helidon-pico-services
+
+
+ io.helidon.pico.configdriven
+ helidon-pico-configdriven-services
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.nima.testing.junit5
+ helidon-nima-testing-junit5-webserver
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ io.helidon.pico
+ helidon-pico-processor
+ ${helidon.version}
+
+
+ io.helidon.nima.http
+ helidon-nima-http-processor
+ ${helidon.version}
+
+
+
+ -Aio.helidon.pico.autoAddNonContractInterfaces=true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
diff --git a/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/ConfigService.java b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/ConfigService.java
new file mode 100644
index 00000000000..bc9b188b7ce
--- /dev/null
+++ b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/ConfigService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.nima.pico;
+
+import io.helidon.config.Config;
+
+import jakarta.inject.Provider;
+import jakarta.inject.Singleton;
+
+/**
+ * This service will be part of NÃma on Pico module.
+ * It may use pico to get config sources exposed through pico.
+ */
+@Singleton
+public class ConfigService implements Provider {
+ @Override
+ public Config get() {
+ return Config.create();
+ }
+}
diff --git a/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/GreetEndpoint.java b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/GreetEndpoint.java
new file mode 100644
index 00000000000..faefddb7092
--- /dev/null
+++ b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/GreetEndpoint.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.nima.pico;
+
+import io.helidon.common.http.Entity;
+import io.helidon.common.http.GET;
+import io.helidon.common.http.HeaderParam;
+import io.helidon.common.http.Http;
+import io.helidon.common.http.POST;
+import io.helidon.common.http.Path;
+import io.helidon.common.http.PathParam;
+import io.helidon.common.http.QueryParam;
+
+import jakarta.inject.Singleton;
+
+@Singleton
+@Path("/greet")
+class GreetEndpoint {
+ private String greeting = "Hello";
+
+ @GET
+ String greet() {
+ return greeting + " World!";
+ }
+
+ @GET
+ @Path("/{name}")
+ String greetNamed(@PathParam("name") String name,
+ @HeaderParam(Http.Header.HOST_STRING) String hostHeader,
+ @QueryParam("required") String requiredQuery,
+ @QueryParam(value = "optional", defaultValue = "defaultValue") String optionalQuery) {
+ return greeting + " " + name + "! Requested host: " + hostHeader + ", required query: " + requiredQuery
+ + ", optionalQuery: " + optionalQuery;
+ }
+
+ @POST
+ void post(@Entity String message) {
+ greeting = message;
+ }
+}
diff --git a/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/PicoMain.java b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/PicoMain.java
new file mode 100644
index 00000000000..cd85207af34
--- /dev/null
+++ b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/PicoMain.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.nima.pico;
+
+import java.util.Optional;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.pico.Bootstrap;
+import io.helidon.pico.DefaultBootstrap;
+import io.helidon.pico.PicoServices;
+
+/**
+ * As simple as possible with a fixed port.
+ */
+public final class PicoMain {
+ private PicoMain() {
+ }
+
+ /**
+ * Start the example.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ // todo move to a NÃma on Pico module
+ LogConfig.configureRuntime();
+
+ Optional existingBootstrap = PicoServices.globalBootstrap();
+ if (existingBootstrap.isEmpty()) {
+ Config config = Config.builder()
+ .addSource(ConfigSources.classpath("application.yaml"))
+ .disableSystemPropertiesSource()
+ .disableEnvironmentVariablesSource()
+ .build();
+ Bootstrap bootstrap = DefaultBootstrap.builder()
+ .config(config)
+ .build();
+ PicoServices.globalBootstrap(bootstrap);
+ }
+
+ PicoServices picoServices = PicoServices.picoServices().get();
+ // this line is needed!
+ picoServices.services();
+ }
+}
diff --git a/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/PlaintextEndpoint.java b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/PlaintextEndpoint.java
new file mode 100644
index 00000000000..f4d91258e62
--- /dev/null
+++ b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/PlaintextEndpoint.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.nima.pico;
+
+import java.nio.charset.StandardCharsets;
+
+import io.helidon.common.http.GET;
+import io.helidon.common.http.Http;
+import io.helidon.common.http.Path;
+import io.helidon.nima.webserver.http.ServerResponse;
+
+import jakarta.inject.Singleton;
+
+@Singleton
+@Path("/plaintext")
+class PlaintextEndpoint {
+ static final Http.HeaderValue CONTENT_TYPE = Http.Header.createCached(Http.Header.CONTENT_TYPE,
+ "text/plain; charset=UTF-8");
+ static final Http.HeaderValue CONTENT_LENGTH = Http.Header.createCached(Http.Header.CONTENT_LENGTH, "13");
+ static final Http.HeaderValue SERVER = Http.Header.createCached(Http.Header.SERVER, "Nima");
+
+ private static final byte[] RESPONSE_BYTES = "Hello, World!".getBytes(StandardCharsets.UTF_8);
+
+ @GET
+ void plaintext(ServerResponse res) {
+ res.header(CONTENT_LENGTH);
+ res.header(CONTENT_TYPE);
+ res.header(SERVER);
+ res.send(RESPONSE_BYTES);
+ }
+}
diff --git a/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/package-info.java b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/package-info.java
new file mode 100644
index 00000000000..876594cb2ea
--- /dev/null
+++ b/examples/nima/pico/src/main/java/io/helidon/examples/nima/pico/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example showing NÃma on Pico.
+ */
+package io.helidon.examples.nima.pico;
diff --git a/examples/nima/pico/src/main/resources/application.yaml b/examples/nima/pico/src/main/resources/application.yaml
new file mode 100644
index 00000000000..6089c766f78
--- /dev/null
+++ b/examples/nima/pico/src/main/resources/application.yaml
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ host: "localhost"
+ port: 8080
+# sockets: - not yet
+# - name: "https"
+# port: 8081
+# - name: "admin"
+# port: 8082
diff --git a/examples/nima/pico/src/main/resources/logging.properties b/examples/nima/pico/src/main/resources/logging.properties
new file mode 100644
index 00000000000..9d15362275a
--- /dev/null
+++ b/examples/nima/pico/src/main/resources/logging.properties
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %5$s%6$s%n
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+io.helidon.nima.level=INFO
diff --git a/examples/nima/pom.xml b/examples/nima/pom.xml
index 5fbf6209ff5..40053039bb2 100644
--- a/examples/nima/pom.xml
+++ b/examples/nima/pom.xml
@@ -40,6 +40,7 @@
tracingobservestatic-content
+ pico
diff --git a/examples/pico/README.md b/examples/pico/README.md
new file mode 100644
index 00000000000..70204793fb6
--- /dev/null
+++ b/examples/pico/README.md
@@ -0,0 +1,7 @@
+# examples-pico
+
+Helidon Pico was inspired by several DI frameworks (e.g., Hk2, Guice and Dagger, etc.). These examples will serve as a way to learn about the capabilities of Pico by comparing Pico to each of these frameworks. It is recommended to review each example to understand the capabilities of Pico and to see it in action.
+
+* [book](./book/README.md) - compares Pico to Hk2.
+* [car](./car/README.md) - compares Pico to Dagger2.
+* [logger](./logger/README.md) - compares Pico to Guice.
diff --git a/examples/pico/book/README.md b/examples/pico/book/README.md
new file mode 100644
index 00000000000..395441309df
--- /dev/null
+++ b/examples/pico/book/README.md
@@ -0,0 +1,152 @@
+# pico-examples-car
+
+This example compares Pico to Jakarta's Hk2. The example is constructed around a virtual library with bookcases, books, and a color wheel on display. This example is different from the other examples in that it combines Hk2 and Pico into the same application. A best practice is to only have one injection framework being used, so this combination is generally not a very realistic example for how a developer would construct their application. Nevertheless, this will be done here in order to convey how the frameworks are similar in some ways while different in other ways.
+
+Take a momemt to review the code. Note that the generated source code is found under
+"./target/generated-sources" for Pico. Generate META-INF is found under "./target/classes". Similarities and differences are listed below.
+
+# Building and Running
+```
+> mvn clean install
+> ./run.sh
+```
+
+# Notable
+
+1. Both Pico and Hk2 supports jakarta.inject and use annotation processors (see [pom.xml](pom.xml)) to process the injection model in compliance to jsr-330 specifications. But the way they work is very different. Hk2 uses compile-time to determine the set of services in the model, and then at runtime will reflectively analyze those classes for resolving injection point dependencies. Pico, on the other hand, relies 100% on compile-time processing that generates source. In Pico the entire application can be analyzed and verified for completeness (i.e., no missing non-optional dependencies) at compile-time instead of Hk2's approach to perform this at runtime lazily during a service activation - and that validation only happens if the service being looked up is missing its dependencies - which might not happen unless your testing and runtime goes down the path of activating a service that is missing its dependencies. In Pico, when the application is created it is completely bound and verified safeguarding against this possibility. This technique of code generation and binding also leads to better performance of Pico as compared to Hk2, and additionally helps ensure deterministic behavior.
+
+2. The API programming model between Hk2 and Pico is very similar (see the application).
+
+* Declaring contracts. Contracts (usually interface types) are a means to lookup or inject into other classes. In the example, there is a contract called BookHolder. Implementation classes of this include: BookCase, EmptyRedBookCase, GreenBookCase, and Library.
+
+```java
+import io.helidon.pico.api.Contract;
+
+@Contract
+@org.jvnet.hk2.annotations.Contract
+public interface BookHolder {
+ Collection> getBooks();
+}
+```
+
+In Hk2's annotation processing the use of Contract or ExternalContract is required to be present. In Pico this is optional when using -Aio.helidon.pico.autoAddNonContractInterfaces=true (see pom).
+
+* Declaring services. Services (usually concrete class types implementing zero or more contracts) can have zero or more injection points using the standard @Inject annotation. In the below example we see how Library is annotated as a Service for Hk2, while not being necessary for Pico. Pico will naturally resolve any standard Singleton scoped (or ApplicationScoped w/ another -A flag) type, or any service type that contains injection points even without having a Scope annotation.
+
+```
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@ToString
+public class Library implements BookHolder {
+ ...
+}
+```
+
+* Injection points. If we look more closely at the Library class we will see it uses constructor injection. Constructor, setter, and field injection are all supported in both Hk2 and Pico. Note, however, that Pico can only handle public, protected, or package-privates (i.e., no pure private) types and the only for types that are also non-static. This is due to the way Activators are code generated to work with your main service classes.
+
+```
+@io.helidon.pico.Contract
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+public class Library implements BookHolder {
+ private List> books;
+ private List bookHolders;
+ private ColorWheel colorWheel;
+
+ @Inject
+ public Library(List> books, List bookHolders, ColorWheel colorWheel) {
+ this.books = books;
+ this.bookHolders = bookHolders;
+ this.colorWheel = colorWheel;
+ }
+
+ ...
+}
+```
+
+* Pico can handle injection of T, Provider, Optional, and List> while Hk2 can handle T, Provider, Optional, and IterableProvider. Notice how Hk2 does not handle List and yet the annotation processor accepted this form of injection. It is not until runtime that the problem is observed. This type of issue would be found at compile time in Pico.
+
+
+```
+ public static void main(String[] args) {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
+
+ try {
+ ServiceHandle librarySh = locator.getServiceHandle(Library.class);
+ System.out.println("found a library handle: " + librarySh.getActiveDescriptor());
+ Library library = librarySh.getService();
+ System.out.println("found a library: " + library);
+ } catch (Exception e) {
+ // list injection is not supported in Hk2 - must switch to use IterableProvider instead.
+ // see https://javaee.github.io/hk2/apidocs/org/glassfish/hk2/api/IterableProvider.html
+ // and see https://javaee.github.io/hk2/introduction.html
+ System.out.println("error: " + e.getMessage());
+ }
+
+```
+
+* Output from MainHk2 at runtime:
+
+```
+RUN 1: (HK2)
+found a library handle: SystemDescriptor(
+ implementation=io.helidon.examples.examples.book.Library
+ contracts={io.helidon.examples.pico.book.Library,io.helidon.examples.pico.book.BookHolder}
+ scope=jakarta.inject.Singleton
+ qualifiers={}
+ descriptorType=CLASS
+ descriptorVisibility=NORMAL
+ metadata=
+ rank=0
+ loader=null
+ proxiable=null
+ proxyForSameScope=null
+ analysisName=null
+ id=12
+ locatorId=0
+ identityHashCode=99347477
+ reified=true)
+...
+error: A MultiException has 4 exceptions. They are:
+1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available in __HK2_Generated_0 for injection at SystemInjecteeImpl(requiredType=List>,parent=Library,qualifiers={},position=0,optional=false,self=false,unqualified=null,940060004)
+2. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available in __HK2_Generated_0 for injection at SystemInjecteeImpl(requiredType=List,parent=Library,qualifiers={},position=1,optional=false,self=false,unqualified=null,1121172875)
+3. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of io.helidon.examples.pico.book.Library errors were found
+4. java.lang.IllegalStateException: Unable to perform operation: resolve on io.helidon.examples.pico.book.Library
+```
+
+* Output from MainPico at runtime:
+
+```
+RUN 1: (PICO))
+found a library provider: Library$$picoActivator:io.helidon.examples.pico.book.Library:INIT:[io.helidon.examples.pico.book.BookHolder]
+...
+library is open: Library(books=[MobyDickInBlue$$picoActivator@50134894:io.helidon.examples.pico.book.MobyDickInBlue@0 : INIT : [io.helidon.examples.pico.book.Book], ParadiseLostInGreen$$picoActivator@28ba21f3:io.helidon.pico.examples.book.ParadiseLostInGreen@0 : INIT : [io.helidon.pico.examples.book.Book], UlyssesInGreen$$picoActivator@2530c12:io.helidon.pico.examples.book.UlyssesInGreen@0 : INIT : [io.helidon.pico.examples.book.Book]], bookHolders=[BookCase(allBooks=[MobyDickInBlue$$picoActivator@50134894:io.helidon.pico.examples.book.MobyDickInBlue@0 : INIT : [io.helidon.pico.examples.book.Book], ParadiseLostInGreen$$picoActivator@28ba21f3:io.helidon.pico.examples.book.ParadiseLostInGreen@0 : INIT : [io.helidon.pico.examples.book.Book], UlyssesInGreen$$picoActivator@2530c12:io.helidon.pico.examples.book.UlyssesInGreen@0 : INIT : [io.helidon.pico.examples.book.Book]]), EmptyRedBookCase(books=[Optional.empty]), GreenBookCase(greenBooks=[ParadiseLostInGreen$$picoActivator@28ba21f3:io.helidon.pico.examples.book.ParadiseLostInGreen@0 : INIT : [io.helidon.pico.examples.book.Book], UlyssesInGreen$$picoActivator@2530c12:io.helidon.pico.examples.book.UlyssesInGreen@0 : INIT : [io.helidon.pico.examples.book.Book]])], colorWheel=ColorWheel(preferredOptionalRedThing=Optional[EmptyRedBookCase(books=[Optional.empty])], preferredOptionalGreenThing=Optional[GreenColor()], preferredOptionalBlueThing=Optional[BlueColor()], preferredProviderRedThing=EmptyRedBookCase$$picoActivator@619a5dff:io.helidon.pico.examples.book.EmptyRedBookCase@16b4a017 : ACTIVE : [io.helidon.pico.examples.book.BookHolder, io.helidon.pico.examples.book.Color, io.helidon.pico.examples.book.RedColor], preferredProviderGreenThing=GreenColor$$picoActivator@7506e922:io.helidon.pico.examples.book.GreenColor@8807e25 : ACTIVE : [io.helidon.pico.examples.book.Color], preferredProviderBlueThing=BlueColor$$picoActivator@25f38edc:io.helidon.pico.examples.book.BlueColor@2a3046da : ACTIVE : [io.helidon.pico.examples.book.Color]))
+library: Library(books=[MobyDickInBlue$$picoActivator@50134894:io.helidon.pico.examples.book.MobyDickInBlue@0 : INIT : [io.helidon.pico.examples.book.Book], ParadiseLostInGreen$$picoActivator@28ba21f3:io.helidon.pico.examples.book.ParadiseLostInGreen@0 : INIT : [io.helidon.pico.examples.book.Book], UlyssesInGreen$$picoActivator@2530c12:io.helidon.pico.examples.book.UlyssesInGreen@0 : INIT : [io.helidon.pico.examples.book.Book]], bookHolders=[BookCase(allBooks=[MobyDickInBlue$$picoActivator@50134894:io.helidon.pico.examples.book.MobyDickInBlue@0 : INIT : [io.helidon.pico.examples.book.Book], ParadiseLostInGreen$$picoActivator@28ba21f3:io.helidon.pico.examples.book.ParadiseLostInGreen@0 : INIT : [io.helidon.pico.examples.book.Book], UlyssesInGreen$$picoActivator@2530c12:io.helidon.pico.examples.book.UlyssesInGreen@0 : INIT : [io.helidon.pico.examples.book.Book]]), EmptyRedBookCase(books=[Optional.empty]), GreenBookCase(greenBooks=[ParadiseLostInGreen$$picoActivator@28ba21f3:io.helidon.pico.examples.book.ParadiseLostInGreen@0 : INIT : [io.helidon.pico.examples.book.Book], UlyssesInGreen$$picoActivator@2530c12:io.helidon.pico.examples.book.UlyssesInGreen@0 : INIT : [io.helidon.pico.examples.book.Book]])], colorWheel=ColorWheel(preferredOptionalRedThing=Optional[EmptyRedBookCase(books=[Optional.empty])], preferredOptionalGreenThing=Optional[GreenColor()], preferredOptionalBlueThing=Optional[BlueColor()], preferredProviderRedThing=EmptyRedBookCase$$picoActivator@619a5dff:io.helidon.pico.examples.book.EmptyRedBookCase@16b4a017 : ACTIVE : [io.helidon.pico.examples.book.BookHolder, io.helidon.pico.examples.book.Color, io.helidon.pico.examples.book.RedColor], preferredProviderGreenThing=GreenColor$$picoActivator@7506e922:io.helidon.pico.examples.book.GreenColor@8807e25 : ACTIVE : [io.helidon.pico.examples.book.Color], preferredProviderBlueThing=BlueColor$$picoActivator@25f38edc:io.helidon.pico.examples.book.BlueColor@2a3046da : ACTIVE : [io.helidon.pico.examples.book.Color]))
+
+```
+
+* The output for Pico (see "library is open:") reveals how lifecycle is supported via the use of the standard PostConstruct and PreDestroy annotations. Pico will also display services that have not yet been lazily initialized (see "INIT") vs those services that have been (see "ACTIVE"). Hk2 and Pico behave in similar ways - it will only activate a service when the provider (or ServiceHandle in Hk2) is resolved. Until that time no service will be activated. It is a best practice, therefore, to use Provider<> type injection as much as possible as it will avoid chains of activation at runtime (i.e., every non-provided type injection point will be recursively activated).
+
+3. Pico generates a suggested module-info.java based upon analysis of your injection/dependency model (see ./target/pico/classes/module-info.java.pico). Hk2 does not have this feature.
+
+```
+// @Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+module io.helidon.examples.pico.book {
+ exports io.helidon.examples.pico.book;
+ // pico module - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ provides io.helidon.pico.Module with io.helidon.examples.pico.book.Pico$$Module;
+ // pico services - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ requires transitive io.helidon.pico;
+}
+```
+
+4. As previously mentioned, both Hk2 and Pico supports PostConstruct and PreDestroy annotations. Additionally, both frameworks offers a notion of RunLevel where RunLevel(value==0) typically represents a "startup" like service. Check the javadoc for details.
+
+5. Pico can optionally generate the activators (i.e., the DI supporting classes) on an external jar module. See the [logger](../logger) example for details. Hk2 has a similar mechanism called inhabitants generator - see references below.
+
+# References
+* https://javaee.github.io/hk2/introduction.html
+* https://javaee.github.io/hk2/getting-started.html
diff --git a/examples/pico/book/pom.xml b/examples/pico/book/pom.xml
new file mode 100644
index 00000000000..9f39df6a3f5
--- /dev/null
+++ b/examples/pico/book/pom.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+ io.helidon.examples.pico
+ helidon-examples-pico-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ io.helidon.examples.pico.book
+ helidon-examples-pico-book
+ Helidon Pico Examples - Book (Hk2 and Pico)
+
+
+
+ 3.0.3
+ 5.1.0
+
+ io.helidon.examples.pico.book.MainHk2
+ io.helidon.examples.pico.book.MainPico
+
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+
+ org.glassfish.hk2
+ hk2-locator
+ ${version.lib.hk2}
+
+
+
+ io.helidon.pico
+ helidon-pico-services
+
+
+ io.helidon.builder
+ helidon-builder
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+ -Apico.autoAddNonContractInterfaces=false
+
+
+
+
+ org.glassfish.hk2
+ hk2-metadata-generator
+ ${version.lib.hk2}
+
+
+
+ io.helidon.pico
+ helidon-pico-processor
+ ${helidon.version}
+
+
+
+
+
+ io.helidon.pico
+ helidon-pico-maven-plugin
+ ${helidon.version}
+
+
+ compile
+ compile
+
+ application-create
+
+
+
+
+ ALL
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
diff --git a/examples/pico/book/run.sh b/examples/pico/book/run.sh
new file mode 100755
index 00000000000..8d2f870392b
--- /dev/null
+++ b/examples/pico/book/run.sh
@@ -0,0 +1,28 @@
+#!/bin/bash -e
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# mvn clean install
+clear
+echo "RUN 1: (HK2)"
+java -cp target/helidon-examples-pico-book-4.0.0-SNAPSHOT-jar-with-dependencies.jar io.helidon.examples.pico.book.MainHk2
+echo "RUN 2: (HK2)"
+java -cp target/helidon-examples-pico-book-4.0.0-SNAPSHOT-jar-with-dependencies.jar io.helidon.examples.pico.book.MainHk2
+echo "========================"
+echo "RUN 1: (PICO)"
+java -cp target/helidon-examples-pico-book-4.0.0-SNAPSHOT-jar-with-dependencies.jar io.helidon.examples.pico.book.MainPico
+echo "RUN 2: (PICO)"
+java -cp target/helidon-examples-pico-book-4.0.0-SNAPSHOT-jar-with-dependencies.jar io.helidon.examples.pico.book.MainPico
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Blue.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Blue.java
new file mode 100644
index 00000000000..438012ac4dd
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Blue.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jakarta.inject.Qualifier;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({TYPE, METHOD, FIELD, PARAMETER})
+public @interface Blue {
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BlueColor.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BlueColor.java
new file mode 100644
index 00000000000..ca28f727bcb
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BlueColor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import io.helidon.pico.Contract;
+
+@Contract
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@jakarta.inject.Named
+@Blue
+public class BlueColor implements Color {
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Book.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Book.java
new file mode 100644
index 00000000000..aa1ba73a188
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Book.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import io.helidon.pico.Contract;
+
+@Contract
+@org.jvnet.hk2.annotations.Contract
+public interface Book {
+
+ String getName();
+
+ Class extends Color> getColor();
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BookCase.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BookCase.java
new file mode 100644
index 00000000000..f89301c4de6
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BookCase.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.util.Collection;
+import java.util.List;
+
+import io.helidon.pico.Contract;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+
+/**
+ * Demonstrates a field type injection point.
+ */
+@Contract
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+public class BookCase implements BookHolder {
+
+ @Inject
+ List> allBooks;
+
+ @Override
+ public Collection> getBooks() {
+ return allBooks;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BookHolder.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BookHolder.java
new file mode 100644
index 00000000000..48a95485d43
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/BookHolder.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.util.Collection;
+
+import io.helidon.pico.Contract;
+
+@Contract
+@org.jvnet.hk2.annotations.Contract
+public interface BookHolder {
+
+ Collection> getBooks();
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Color.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Color.java
new file mode 100644
index 00000000000..1240b34bfb8
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Color.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import io.helidon.pico.Contract;
+
+@Contract
+@org.jvnet.hk2.annotations.Contract
+public interface Color {
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/ColorWheel.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/ColorWheel.java
new file mode 100644
index 00000000000..057dd8fe81f
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/ColorWheel.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.util.List;
+import java.util.Optional;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+
+import static io.helidon.pico.services.ServiceUtils.toDescription;
+import static io.helidon.pico.services.ServiceUtils.toDescriptions;
+
+/**
+ * Demonstrates setter type injection points w/ qualifiers & optionals.
+ */
+@org.jvnet.hk2.annotations.Service
+public class ColorWheel {
+ Optional> preferredOptionalRedThing;
+ Optional> preferredOptionalGreenThing;
+ Optional> preferredOptionalBlueThing;
+
+ Provider> preferredProviderRedThing;
+ Provider> preferredProviderGreenThing;
+ Provider> preferredProviderBlueThing;
+
+ @Inject
+ void setPreferredOptionalRedThing(@org.jvnet.hk2.annotations.Optional @Red Optional thing) {
+ System.out.println("setting optional color wheel red to " + thing);
+ preferredOptionalRedThing = thing;
+ }
+
+ @Inject
+ void setPreferredOptionalGreenThing(@org.jvnet.hk2.annotations.Optional @Green Optional thing) {
+ System.out.println("setting optional color wheel green to " + thing);
+ preferredOptionalGreenThing = thing;
+ }
+
+ @Inject
+ void setPreferredOptionalBlueThing(@org.jvnet.hk2.annotations.Optional @Blue Optional thing) {
+ System.out.println("setting optional color wheel blue to " + thing);
+ preferredOptionalBlueThing = thing;
+ }
+
+ @Inject
+ void setPreferredProviderRedThing(@org.jvnet.hk2.annotations.Optional @Red Provider thing) {
+ System.out.println("setting provider color wheel red to " + toDescription(thing));
+ preferredProviderRedThing = thing;
+ }
+
+ @Inject
+ void setPreferredProviderGreenThing(@org.jvnet.hk2.annotations.Optional @Green Provider thing) {
+ System.out.println("setting provider wheel green to " + toDescription(thing));
+ preferredProviderGreenThing = thing;
+ }
+
+ @Inject
+ void setPreferredBlueThing(@org.jvnet.hk2.annotations.Optional @Blue Provider thing) {
+ System.out.println("setting provider wheel blue to " + toDescription(thing));
+ preferredProviderBlueThing = thing;
+ }
+
+ @Inject
+ void setListProviderRedThings(@org.jvnet.hk2.annotations.Optional @Red List> things) {
+ System.out.println("setting providerList color wheel red to " + (things == null ? "null" : toDescriptions(things)));
+ }
+
+ @Inject
+ void setListProviderGreenThings(@org.jvnet.hk2.annotations.Optional @Green List> things) {
+ System.out.println("setting providerList wheel green to " + (things == null ? "null" : toDescriptions(things)));
+ }
+
+ @Inject
+ void setListProviderBlueThings(@org.jvnet.hk2.annotations.Optional @Blue List> things) {
+ System.out.println("setting providerList wheel blue to " + (things == null ? "null" : toDescriptions(things)));
+ }
+
+ // not supported by Pico...
+// @Inject
+// void setIterableProviderRedThings(@org.jvnet.hk2.annotations.Optional @Red IterableProvider things) {
+// System.out.println("setting iterableList color wheel red to " + things);
+// }
+//
+// @Inject
+// void setIterableProviderGreenThings(@org.jvnet.hk2.annotations.Optional @Green IterableProvider things) {
+// System.out.println("setting iterableList wheel green to " + things);
+// }
+//
+// @Inject
+// void setIterableProviderBlueThings(@org.jvnet.hk2.annotations.Optional @Blue IterableProvider things) {
+// System.out.println("setting iterableList wheel blue to " + things);
+// }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/EmptyRedBookCase.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/EmptyRedBookCase.java
new file mode 100644
index 00000000000..ae2dad45b9b
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/EmptyRedBookCase.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+
+import io.helidon.pico.Contract;
+
+import jakarta.inject.Inject;
+
+@Contract
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@Red
+public class EmptyRedBookCase extends RedColor implements BookHolder {
+
+ private Collection> books;
+
+ @Override
+ public Collection> getBooks() {
+ return books;
+ }
+
+ @Inject
+ public EmptyRedBookCase(@Red Optional preferrredRedBook) {
+ this.books = Collections.singleton(preferrredRedBook);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(books=" + getBooks() + ")";
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Green.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Green.java
new file mode 100644
index 00000000000..51a1596a5b9
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Green.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jakarta.inject.Qualifier;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({TYPE, METHOD, FIELD, PARAMETER})
+public @interface Green {
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/GreenBookCase.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/GreenBookCase.java
new file mode 100644
index 00000000000..9247f2efa89
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/GreenBookCase.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.util.Collection;
+import java.util.List;
+
+import io.helidon.pico.Contract;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+
+/**
+ * Demonstrates ctor injection with qualifiers and lists of providers.
+ */
+@Contract
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+public class GreenBookCase implements BookHolder {
+
+ private final List> greenBooks;
+
+ @Override
+ public Collection> getBooks() {
+ return greenBooks;
+ }
+
+ @Inject
+ GreenBookCase(@Green List> greenBooks) {
+ this.greenBooks = greenBooks;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(books=" + getBooks() + ")";
+ }
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/GreenColor.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/GreenColor.java
new file mode 100644
index 00000000000..83a3dabda56
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/GreenColor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import io.helidon.pico.Contract;
+
+@Contract
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@jakarta.inject.Named
+@Green
+public class GreenColor implements Color {
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Library.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Library.java
new file mode 100644
index 00000000000..48ff6b58c22
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Library.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.util.Collection;
+import java.util.List;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+
+/**
+ * Demonstrates constructor injection of various types.
+ */
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+public class Library implements BookHolder {
+
+ private List> books;
+ private List bookHolders;
+ private ColorWheel colorWheel;
+
+ @Inject
+ public Library(
+ List> books,
+ List bookHolders,
+ ColorWheel colorWheel) {
+ this.books = books;
+ this.bookHolders = bookHolders;
+ this.colorWheel = colorWheel;
+ }
+
+ @Override
+ public Collection> getBooks() {
+ return books;
+ }
+
+ public Collection getBookHolders() {
+ return bookHolders;
+ }
+
+ public ColorWheel getColorWheel() {
+ return colorWheel;
+ }
+
+ @PostConstruct
+ public void postConstruct() {
+ System.out.println("library is open: " + this);
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ System.out.println("library is closed: " + this);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(books=" + getBooks() + ")";
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MainHk2.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MainHk2.java
new file mode 100644
index 00000000000..d8a8ebb9c83
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MainHk2.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import org.glassfish.hk2.api.ServiceHandle;
+import org.glassfish.hk2.api.ServiceLocator;
+import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
+
+public class MainHk2 {
+
+ public static void main(String[] args) {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
+
+ try {
+ ServiceHandle librarySh = locator.getServiceHandle(Library.class);
+ System.out.println("found a library handle: " + librarySh.getActiveDescriptor());
+ Library library = librarySh.getService();
+ System.out.println("found a library: " + library);
+ } catch (Exception e) {
+ // list injection is not supported in Hk2 - must switch to use IterableProvider instead.
+ // see https://javaee.github.io/hk2/apidocs/org/glassfish/hk2/api/IterableProvider.html
+ // and see https://javaee.github.io/hk2/introduction.html
+ System.out.println("error: " + e.getMessage());
+ }
+
+ ServiceHandle colorWheelSh = locator.getServiceHandle(ColorWheel.class);
+ System.out.println("found a color wheel handle: " + colorWheelSh.getActiveDescriptor());
+ ColorWheel library = colorWheelSh.getService();
+ System.out.println("color wheel: " + library);
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Hk2 Main memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Hk2 Main elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MainPico.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MainPico.java
new file mode 100644
index 00000000000..fa9cd841b2a
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MainPico.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import io.helidon.pico.PicoServices;
+import io.helidon.pico.ServiceProvider;
+import io.helidon.pico.Services;
+
+public class MainPico {
+
+ public static void main(String[] args) {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ Services services = PicoServices.realizedServices();
+
+ ServiceProvider librarySp = services.lookupFirst(Library.class);
+ System.out.println("found a library provider: " + librarySp.description());
+ Library library = librarySp.get();
+ System.out.println("library: " + library);
+
+ ServiceProvider colorWheelSp = services.lookupFirst(ColorWheel.class);
+ System.out.println("found a color wheel provider: " + colorWheelSp.description());
+ ColorWheel colorWheel = colorWheelSp.get();
+ System.out.println("color wheel: " + colorWheel);
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Pico Main memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Pico Main elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MobyDickInBlue.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MobyDickInBlue.java
new file mode 100644
index 00000000000..0633d24c34c
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/MobyDickInBlue.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@jakarta.inject.Named
+@Blue
+public class MobyDickInBlue implements Book {
+
+ @Override
+ public String getName() {
+ return "Moby Dick";
+ }
+
+ @Override
+ public Class extends Color> getColor() {
+ return BlueColor.class;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/ParadiseLostInGreen.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/ParadiseLostInGreen.java
new file mode 100644
index 00000000000..806c8946f4f
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/ParadiseLostInGreen.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@jakarta.inject.Named
+@Green
+public class ParadiseLostInGreen implements Book {
+
+ @Override
+ public String getName() {
+ return "Paradise Lost";
+ }
+
+ @Override
+ public Class extends Color> getColor() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Red.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Red.java
new file mode 100644
index 00000000000..24b6f5ba99c
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/Red.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jakarta.inject.Qualifier;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({TYPE, METHOD, FIELD, PARAMETER})
+public @interface Red {
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/RedColor.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/RedColor.java
new file mode 100644
index 00000000000..d2749e53616
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/RedColor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import io.helidon.pico.Contract;
+
+@Contract
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@jakarta.inject.Named
+@Red
+public class RedColor implements Color {
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/UlyssesInGreen.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/UlyssesInGreen.java
new file mode 100644
index 00000000000..4d484779282
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/UlyssesInGreen.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+@org.jvnet.hk2.annotations.Service
+@jakarta.inject.Singleton
+@jakarta.inject.Named
+@Green
+public class UlyssesInGreen implements Book {
+
+ @Override
+ public String getName() {
+ return "Ulysses";
+ }
+
+ @Override
+ public Class extends Color> getColor() {
+ return GreenColor.class;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/book/src/main/java/io/helidon/examples/pico/book/package-info.java b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/package-info.java
new file mode 100644
index 00000000000..4c2a7178c5a
--- /dev/null
+++ b/examples/pico/book/src/main/java/io/helidon/examples/pico/book/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Pico Book Example.
+ */
+package io.helidon.examples.pico.book;
diff --git a/examples/pico/book/src/main/resources/logging.properties b/examples/pico/book/src/main/resources/logging.properties
new file mode 100644
index 00000000000..b3e81c5cb71
--- /dev/null
+++ b/examples/pico/book/src/main/resources/logging.properties
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL %5$s%6$s%n
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+io.helidon.nima.level=INFO
diff --git a/examples/pico/book/src/test/java/io/helidon/examples/pico/book/MainHk2Test.java b/examples/pico/book/src/test/java/io/helidon/examples/pico/book/MainHk2Test.java
new file mode 100644
index 00000000000..ab356fca811
--- /dev/null
+++ b/examples/pico/book/src/test/java/io/helidon/examples/pico/book/MainHk2Test.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import org.junit.jupiter.api.Test;
+
+public class MainHk2Test {
+
+ @Test
+ public void testMain() {
+ final long start = System.currentTimeMillis();
+
+ MainHk2.main(null);
+
+ final long finish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Hk2 Junit elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/book/src/test/java/io/helidon/examples/pico/book/MainPicoTest.java b/examples/pico/book/src/test/java/io/helidon/examples/pico/book/MainPicoTest.java
new file mode 100644
index 00000000000..668472596ec
--- /dev/null
+++ b/examples/pico/book/src/test/java/io/helidon/examples/pico/book/MainPicoTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.book;
+
+import org.junit.jupiter.api.Test;
+
+public class MainPicoTest {
+
+ @Test
+ public void testMain() {
+ final long start = System.currentTimeMillis();
+
+ MainPico.main(null);
+
+ final long finish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Pico Junit elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/car/README.md b/examples/pico/car/README.md
new file mode 100644
index 00000000000..317efd2d857
--- /dev/null
+++ b/examples/pico/car/README.md
@@ -0,0 +1,220 @@
+# pico-examples-car
+
+This example compares Pico to Google's Dagger 2. It was taken from an example found on the internet (see references below). The example is fairly trivial, but it is sufficient to compare the similarities between the two.
+
+[dagger2](dagger2) contains the Dagger2 application module.
+[pico](pico) contains the Pico application module.
+
+Unlike the [logger](../logger) example, this example replicates the entire set of classes for each application module. This was done for a few reasons. The primary reason is that while Pico offers the ability to generate the DI supporting module on an external jar (as the logger example demonstrates), Dagger does not provide such an option - the DI module for dagger
+only provides for an annotation processor mechanism, thereby requiring the developer to inject the Dagger annotation processor
+into the build of the project to generate the DI module at compilation time. The other reason why this was done was to demonstrate
+a few variations in the way developers can use Pico for more complicated use cases.
+
+Review the code to see the similarities and differences. Note that the generated source code is found under
+"./target/generated-sources" for each sub application module. Summaries of similarities and differences are listed below.
+
+# Building and Running
+```
+> mvn clean install
+> ./run.sh
+```
+
+# Notable
+
+1. Pico supports jakarta.inject as well as javax.inject packaging. Dagger 2 (at the time of this writing - see https://github.com/google/dagger/issues/2058) only supports javax.inject. This example uses javax.inject for the Dagger app, and uses jakarta.inject for the Pico app. Functionally, however, both are the same.
+
+2. The API programming model between Pico and Dagger is fairly different. Pico strives to closely follow the jsr-330 specification and relies heavily on annotation processing to generate *all* of the supporting DI classes, and those classes are hidden as an implementation detail not directly exposed to the (typical) developer. This can be seen by observing Dagger's [VehiclesComponent.java](./dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesComponent.java) and [VehiclesModule.java](./dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesModule.java) classes - notice the imports as well as the code the developer is expected to write.
+
+On the Pico side, you will notice that the only #import of Pico is found on the [Vehicle.java](./pico/src/main/java/io/helidon/examples/pico/car/pico/Vehicle.java) class. The @Contract annotation is used to demarcate the Vehcile iterface that Car implements/advertises. All the other imports are using standard javax/jakarta annotations. Pico actually offers an option to advertise all interfaces as contracts (see the pom.xml snippet below). Turning on this switch will allow the @Contract to be removed as well, thereby using 100% standard javax/jakarta types.
+
+```
+
+
+
+
+```
+
+There are a few other "forced" examples under Pico which demonstrate additional options available. For example, the [BrandProvider.java](./pico/src/main/java/io/helidon/examples/pico/car/pico/BrandProvider.java) class is the standard means to produce an instance of a Brand. The implementation can choose the cardinality of the instances created. At times, it might be convenient to "know" the injection point consumer requesting the Brand instance, in order to change the cardinality or somehow make it dependent in scope. This option is demonstrated in the [BrandProvider.java](./pico/src/main/java/io/helidon/examples/pico/car/pico/EngineProvider.java) class.
+
+3. Both Dagger and Pico are using compile-time to generate the DI supporting classes as mentioned above. The code generated between the two, however is a little different. Let's have a closer look:
+
+The Dagger generated Car_Factory:
+```java
+@ScopeMetadata
+@QualifierMetadata
+@DaggerGenerated
+@Generated(
+ value = "dagger.internal.codegen.ComponentProcessor",
+ comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+ "unchecked",
+ "rawtypes"
+})
+public final class Car_Factory implements Factory {
+ private final Provider engineProvider;
+
+ private final Provider brandProvider;
+
+ public Car_Factory(Provider engineProvider, Provider brandProvider) {
+ this.engineProvider = engineProvider;
+ this.brandProvider = brandProvider;
+ }
+
+ @Override
+ public Car get() {
+ return newInstance(engineProvider.get(), brandProvider.get());
+ }
+
+ public static Car_Factory create(Provider engineProvider, Provider brandProvider) {
+ return new Car_Factory(engineProvider, brandProvider);
+ }
+
+ public static Car newInstance(Engine engine, Brand brand) {
+ return new Car(engine, brand);
+ }
+}
+```
+
+The Pico generated Car$$picoActivator:
+```java
+/**
+ * Activator for {@link io.helidon.examples.pico.car.pico.Car}.
+ */
+// @Singleton
+@SuppressWarnings("unchecked")
+@Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+public class Car$$picoActivator
+ extends io.helidon.pico.services.AbstractServiceProvider {
+ private static final DefaultServiceInfo serviceInfo =
+ DefaultServiceInfo.builder()
+ .serviceTypeName(io.helidon.examples.pico.car.pico.Car.class.getName())
+ .addContractsImplemented(io.helidon.examples.pico.car.pico.Vehicle.class.getName())
+ .activatorTypeName(Car$$picoActivator.class.getName())
+ .addScopeTypeName(jakarta.inject.Singleton.class.getName())
+ .build();
+
+ /**
+ * The global singleton instance for this service provider activator.
+ */
+ public static final Car$$picoActivator INSTANCE = new Car$$picoActivator();
+
+ /**
+ * Default activator constructor.
+ */
+ protected Car$$picoActivator() {
+ serviceInfo(serviceInfo);
+ }
+
+ /**
+ * The service type of the managed service.
+ *
+ * @return the service type of the managed service
+ */
+ public Class> serviceType() {
+ return io.helidon.examples.pico.car.pico.Car.class;
+ }
+
+ @Override
+ public DependenciesInfo dependencies() {
+ DependenciesInfo deps = Dependencies.builder(io.helidon.examples.pico.car.pico.Car.class.getName())
+ .add(CONSTRUCTOR, io.helidon.examples.pico.car.pico.Engine.class, ElementKind.CONSTRUCTOR, 2, Access.PUBLIC).elemOffset(1)
+ .add(CONSTRUCTOR, io.helidon.examples.pico.car.pico.Brand.class, ElementKind.CONSTRUCTOR, 2, Access.PUBLIC).elemOffset(2)
+ .build();
+ return Dependencies.combine(super.dependencies(), deps);
+ }
+
+ @Override
+ protected Car createServiceProvider(Map deps) {
+ io.helidon.examples.pico.car.pico.Engine c1 = (io.helidon.examples.pico.car.pico.Engine) get(deps, "io.helidon.examples.pico.car.pico.|2(1)");
+ io.helidon.examples.pico.car.pico.Brand c2 = (io.helidon.examples.pico.car.pico.Brand) get(deps, "io.helidon.examples.pico.car.pico.|2(2)");
+ return new io.helidon.examples.pico.car.pico.Car(c1, c2);
+ }
+
+}
+```
+
+Here is the main difference:
+
+* Pico attempts to model each service in terms of the contracts/interfaces each service offers, as well as the dependencies (other contracts/interfaces) that it requires. A more elaborate dependency model would additionally include the qualifiers (such as @Named), whether the services are optional, list-based, etc. This model extends down to mention each element (for methods or constructors for example). All of this is generated at compile-time. In this way the Pico Services registry has knowledge of every available service and what it offers and what it requires. These services are left to be lazily activated on-demand.
+
+4. Pico provides the ability (as demonstrated in the [pom.xml](./pico/pom.xml)) to take the injection model, analyze and validate it, and ultimately bind to the final injection model at assembly time. Using this option provides several key benefits including deterministic behavior, speed & performance enhancements, and helps to ensure the completeness & validity of the entire application's dependency graph at compile time. When this option is applied the Pico$$Application is generated. Here is what it looks like for this example:
+
+```java
+@Generated(value = "io.helidon.pico.maven.plugin.ApplicationCreatorMojo", comments = "version=1")
+@Singleton @Named(Pico$$Application.NAME)
+public class Pico$$Application implements Application {
+ static final String NAME = "io.helidon.examples.pico.car.pico";
+
+ @Override
+ public Optional named() {
+ return Optional.of(NAME);
+ }
+
+ @Override
+ public String toString() {
+ return NAME + ":" + getClass().getName();
+ }
+
+ @Override
+ public void configure(ServiceInjectionPlanBinder binder) {
+ /**
+ * In module name "io.helidon.examples.pico.car.pico".
+ * @see {@link io.helidon.examples.pico.car.pico.BrandProvider }
+ */
+ binder.bindTo(io.helidon.examples.pico.car.pico.BrandProvider$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "io.helidon.examples.pico.car.pico".
+ * @see {@link io.helidon.examples.pico.car.pico.Car }
+ */
+ binder.bindTo(io.helidon.examples.pico.car.pico.Car$$picoActivator.INSTANCE)
+ .bind("io.helidon.examples.pico.car.pico.|2(1)",
+ io.helidon.examples.pico.car.pico.EngineProvider$$picoActivator.INSTANCE)
+ .bind("io.helidon.examples.pico.car.pico.|2(2)",
+ io.helidon.examples.pico.car.pico.BrandProvider$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "io.helidon.examples.pico.car.pico".
+ * @see {@link io.helidon.examples.pico.car.pico.EngineProvider }
+ */
+ binder.bindTo(io.helidon.examples.pico.car.pico.EngineProvider$$picoActivator.INSTANCE)
+ .commit();
+
+ }
+}
+```
+
+At initialization time (and using the default configuration) Pico will use the service loader to attempt to find the Application instance and use that instead of resolving the dependency graph at runtime. Generating the application is optional but recommended for production scenarios.
+
+5. The Dagger application is considerably smaller in terms of disk and memory footprints. This makes sense considering that the primary driver for Dagger 2 is to be consumed by Android developers who naturally require a liter footprint. Pico, while still small and lite compared to many other options on the i-net, offers more features including: interceptor/interception capabilities, flexible service registry search & resolution semantics, service meta information as described above, lazy activation, circular dependency detection, extensibility, configuration, etc. The default configuration assumes a production use case where the services registry is assumed to be non-dynamic/static in nature.
+
+On the topic of extensibility - Pico is centered around extensibility of its tooling. The templates use replaceable [handlebars](https://github.com/jknack/handlebars.java), and the generators use service loader. Anyone can either override Pico's reference implementation, or else write an entirely new implementation based upon the API & SPI that Pico provides.
+
+6. Pico requires less coding as compared to Dagger. In this example the BrandProvider and EngineProvider were contrived in order to demonstrate a nuances of the approach. Generally speaking most of the time the @Singleton annotation (or lack thereof) is all that is needed, depending upon the injection scope required.
+
+7. Pico offers lifecycle support (see jakarta.annotation.@PostConstruct, jakarta.annotation.@PreDestroy, Pico's @RunLevel
+ annotations and PicoServices#shutdown()).
+
+8. Pico generates a suggested module-info.java based upon analysis of your injection/dependency model (see ./target/classes/module-info.java.pico).
+
+```./target/classes/module-info.java.pico
+// @Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+module io.helidon.examples.pico.car.pico {
+ exports io.helidon.examples.pico.car.pico;
+ // pico module - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ provides io.helidon.pico.Module with io.helidon.examples.pico.car.pico.Pico$$Module;
+ // pico external contract usage - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ uses jakarta.inject.Provider;
+ uses io.helidon.pico.InjectionPointProvider;
+ // pico services - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ requires transitive io.helidon.pico;
+}
+```
+
+9. Pico can optionally generate the activators (i.e., the DI supporting classes) on an external jar module. See the [logger](../logger) example for details.
+
+# References
+* https://www.baeldung.com/dagger-2
diff --git a/examples/pico/car/dagger2/pom.xml b/examples/pico/car/dagger2/pom.xml
new file mode 100644
index 00000000000..27785c5c81b
--- /dev/null
+++ b/examples/pico/car/dagger2/pom.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+ io.helidon.examples.pico.car
+ helidon-examples-pico-car-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ helidon-examples-pico-car-dagger2
+ Helidon Pico Examples - Car - Dagger2
+
+
+ io.helidon.examples.pico.car.dagger2.Main
+
+
+
+
+ com.google.dagger
+ dagger
+ ${version.lib.dagger}
+
+
+ io.helidon.builder
+ helidon-builder
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+ provided
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ com.google.dagger
+ dagger-compiler
+ ${version.lib.dagger}
+
+
+ io.helidon.builder
+ helidon-builder-processor
+ ${helidon.version}
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+
+
+ ${mainClass}
+
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${version.plugin.jar}
+
+
+
+ true
+ libs
+ ${mainClass}
+ false
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${version.plugin.exec}
+
+ ${mainClass}
+
+
+
+
+
+
diff --git a/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Brand.java b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Brand.java
new file mode 100644
index 00000000000..a5958f0c86e
--- /dev/null
+++ b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Brand.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+import io.helidon.builder.Builder;
+
+@Builder(requireLibraryDependencies = false)
+public interface Brand {
+
+ String name();
+
+}
diff --git a/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Car.java b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Car.java
new file mode 100644
index 00000000000..c876f655200
--- /dev/null
+++ b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Car.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+import javax.inject.Inject;
+
+public class Car implements Vehicle {
+
+ private Engine engine;
+ private Brand brand;
+
+ @Inject
+ public Car(
+ Engine engine,
+ Brand brand) {
+ this.engine = engine;
+ this.brand = brand;
+ }
+
+ @Override
+ public String toString() {
+ return "Car(engine=" + engine() + ",brand=" + brand() + ")";
+ }
+
+ @Override
+ public Engine engine() {
+ return engine;
+ }
+
+ public void engine(Engine engine) {
+ this.engine = engine;
+ }
+
+ @Override
+ public Brand brand() {
+ return brand;
+ }
+
+ public void brand(Brand brand) {
+ this.brand = brand;
+ }
+
+}
diff --git a/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Engine.java b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Engine.java
new file mode 100644
index 00000000000..1fb3165b765
--- /dev/null
+++ b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Engine.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+public class Engine {
+
+ public void start() {
+ System.out.println("Engine started");
+ }
+
+ public void stop() {
+ System.out.println("Engine stopped");
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Main.java b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Main.java
new file mode 100644
index 00000000000..5449dfb45a9
--- /dev/null
+++ b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Main.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+public class Main {
+
+ public static void main(String[] args) {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ if (args.length > 0) {
+ VehiclesModule.brandName = args[0];
+ }
+ VehiclesComponent component = DaggerVehiclesComponent.create();
+ System.out.println("found a car component: " + component);
+ Car car = component.buildCar();
+ System.out.println("found a car: " + car);
+ car.engine().start();
+ car.engine().stop();
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Dagger2 Main memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Dagger2 Main elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Vehicle.java b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Vehicle.java
new file mode 100644
index 00000000000..3a03d858afc
--- /dev/null
+++ b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/Vehicle.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+public interface Vehicle {
+ Engine engine();
+ Brand brand();
+}
diff --git a/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesComponent.java b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesComponent.java
new file mode 100644
index 00000000000..7a1c86d7395
--- /dev/null
+++ b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesComponent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+@Singleton
+@Component(modules = VehiclesModule.class)
+public interface VehiclesComponent {
+ Car buildCar();
+}
diff --git a/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesModule.java b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesModule.java
new file mode 100644
index 00000000000..32db99b5753
--- /dev/null
+++ b/examples/pico/car/dagger2/src/main/java/io/helidon/examples/pico/car/dagger2/VehiclesModule.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class VehiclesModule {
+
+ static String brandName;
+
+ @Provides
+ public Engine provideEngine() {
+ return new Engine();
+ }
+
+ @Provides
+ @Singleton
+ public Brand provideBrand() {
+ return DefaultBrand.builder().name(brandName).build();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/car/dagger2/src/test/java/io/helidon/examples/pico/car/dagger2/Dagger2Test.java b/examples/pico/car/dagger2/src/test/java/io/helidon/examples/pico/car/dagger2/Dagger2Test.java
new file mode 100644
index 00000000000..5c9101da70c
--- /dev/null
+++ b/examples/pico/car/dagger2/src/test/java/io/helidon/examples/pico/car/dagger2/Dagger2Test.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.dagger2;
+
+import org.junit.jupiter.api.Test;
+
+public class Dagger2Test {
+
+ @Test
+ public void testMain() {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ Main.main(new String[] {"Dagger2"});
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Dagger2 JUnit memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Dagger2 JUnit elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/car/pico/pom.xml b/examples/pico/car/pico/pom.xml
new file mode 100644
index 00000000000..e3d1f779d00
--- /dev/null
+++ b/examples/pico/car/pico/pom.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+ io.helidon.examples.pico.car
+ helidon-examples-pico-car-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ helidon-examples-pico-car-pico
+ Helidon Pico Examples - Car - Pico
+
+
+ io.helidon.examples.pico.car.pico.Main
+
+
+
+
+ io.helidon.pico
+ helidon-pico-services
+
+
+ io.helidon.builder
+ helidon-builder
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+
+
+
+
+ io.helidon.pico
+ helidon-pico-processor
+ ${helidon.version}
+
+
+ io.helidon.builder
+ helidon-builder-processor
+ ${helidon.version}
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+
+
+ ${mainClass}
+
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+ io.helidon.pico
+ helidon-pico-maven-plugin
+ ${helidon.version}
+
+
+ compile
+ compile
+
+ application-create
+
+
+
+
+ ALL
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${version.plugin.jar}
+
+
+
+ true
+ libs
+ ${mainClass}
+ false
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${version.plugin.exec}
+
+ ${mainClass}
+
+
+
+
+
+
diff --git a/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Brand.java b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Brand.java
new file mode 100644
index 00000000000..53bba32116e
--- /dev/null
+++ b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Brand.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+import io.helidon.builder.Builder;
+
+@Builder
+public interface Brand {
+
+ String name();
+
+}
diff --git a/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/BrandProvider.java b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/BrandProvider.java
new file mode 100644
index 00000000000..0b70241823f
--- /dev/null
+++ b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/BrandProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+import jakarta.inject.Provider;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class BrandProvider implements Provider {
+ static String brandName;
+
+ @Override
+ public Brand get() {
+ return DefaultBrand.builder().name(brandName).build();
+ }
+
+}
diff --git a/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Car.java b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Car.java
new file mode 100644
index 00000000000..72f6ade9841
--- /dev/null
+++ b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Car.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class Car implements Vehicle {
+
+ private Engine engine;
+ private Brand brand;
+
+ @Inject
+ public Car(Engine engine, Brand brand) {
+ this.engine = engine;
+ this.brand = brand;
+ }
+
+ @Override
+ public String toString() {
+ return "Car(engine=" + engine() + ",brand=" + brand() + ")";
+ }
+
+ @Override
+ public Engine engine() {
+ return engine;
+ }
+
+ public void engine(Engine engine) {
+ this.engine = engine;
+ }
+
+ @Override
+ public Brand brand() {
+ return brand;
+ }
+
+ public void brand(Brand brand) {
+ this.brand = brand;
+ }
+
+}
diff --git a/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Engine.java b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Engine.java
new file mode 100644
index 00000000000..fd77d120508
--- /dev/null
+++ b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Engine.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+public class Engine {
+
+ public void start() {
+ System.out.println("Engine started");
+ }
+
+ public void stop() {
+ System.out.println("Engine stopped");
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/EngineProvider.java b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/EngineProvider.java
new file mode 100644
index 00000000000..dfdca52d282
--- /dev/null
+++ b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/EngineProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+import java.util.Optional;
+
+import io.helidon.pico.ContextualServiceQuery;
+import io.helidon.pico.InjectionPointProvider;
+
+import jakarta.inject.Singleton;
+
+@Singleton
+public class EngineProvider implements InjectionPointProvider {
+
+ @Override
+ public Optional first(ContextualServiceQuery query) {
+ return Optional.of(new Engine());
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Main.java b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Main.java
new file mode 100644
index 00000000000..59de93251f3
--- /dev/null
+++ b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Main.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+import io.helidon.pico.PicoServices;
+import io.helidon.pico.ServiceProvider;
+
+public class Main {
+
+ public static void main(String[] args) {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ if (args.length > 0) {
+ BrandProvider.brandName = args[0];
+ }
+ ServiceProvider carSp = PicoServices.realizedServices().lookupFirst(Car.class);
+ System.out.println("found a car provider: " + carSp.description());
+ Car car = carSp.get();
+ System.out.println("found a car: " + car);
+ car.engine().start();
+ car.engine().stop();
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Pico Main memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Pico Main elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Vehicle.java b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Vehicle.java
new file mode 100644
index 00000000000..793ba69d7ec
--- /dev/null
+++ b/examples/pico/car/pico/src/main/java/io/helidon/examples/pico/car/pico/Vehicle.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+import io.helidon.pico.Contract;
+
+@Contract
+public interface Vehicle {
+ Engine engine();
+ Brand brand();
+}
diff --git a/examples/pico/car/pico/src/test/java/io/helidon/examples/pico/car/pico/PicoTest.java b/examples/pico/car/pico/src/test/java/io/helidon/examples/pico/car/pico/PicoTest.java
new file mode 100644
index 00000000000..7b8a7eb8e11
--- /dev/null
+++ b/examples/pico/car/pico/src/test/java/io/helidon/examples/pico/car/pico/PicoTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.car.pico;
+
+import org.junit.jupiter.api.Test;
+
+public class PicoTest {
+
+ @Test
+ public void testMain() {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ Main.main(new String[] {"Pico"});
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Pico JUnit memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Pico JUnit elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/car/pom.xml b/examples/pico/car/pom.xml
new file mode 100644
index 00000000000..335a2a3dcfb
--- /dev/null
+++ b/examples/pico/car/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ io.helidon.examples.pico
+ helidon-examples-pico-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ io.helidon.examples.pico.car
+ helidon-examples-pico-car-project
+ Helidon Pico Examples - Car
+
+ pom
+
+
+ 2.43
+
+
+
+ dagger2
+ pico
+
+
+
diff --git a/examples/pico/car/run.sh b/examples/pico/car/run.sh
new file mode 100755
index 00000000000..88e71e39cf7
--- /dev/null
+++ b/examples/pico/car/run.sh
@@ -0,0 +1,28 @@
+#!/bin/bash -e
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# mvn clean install
+clear
+echo "RUN 1: (DAGGER2)"
+java -jar dagger2/target/helidon-examples-pico-car-dagger2-4.0.0-SNAPSHOT-jar-with-dependencies.jar dagger2
+echo "RUN 2: (DAGGER2)"
+java -jar dagger2/target/helidon-examples-pico-car-dagger2-4.0.0-SNAPSHOT-jar-with-dependencies.jar dagger2
+echo "========================"
+echo "RUN 1: (PICO)"
+java -jar pico/target/helidon-examples-pico-car-pico-4.0.0-SNAPSHOT-jar-with-dependencies.jar pico
+echo "RUN 2: (PICO)"
+java -jar pico/target/helidon-examples-pico-car-pico-4.0.0-SNAPSHOT-jar-with-dependencies.jar pico
diff --git a/examples/pico/logger/README.md b/examples/pico/logger/README.md
new file mode 100644
index 00000000000..ef02e0e3d8b
--- /dev/null
+++ b/examples/pico/logger/README.md
@@ -0,0 +1,100 @@
+# pico-examples-logger
+
+## Overview
+This example compares Pico to Guice. It was taken from an example found on the internet (see references below). The example is fairly trivial, but it is sufficient to compare the similarities between the two, as well as to demonstrate the performance differences between the two.
+
+[common](common) contains the core application logic.
+[guice](guice) contains the delta for integrating to Guice.
+[pico](pico) contains the delta for integrating with Pico.
+
+Review the code to see the similarities and differences. Note that the generated source code is found under
+"./target/generated-sources". Summaries of similarities and differences are listed below.
+
+# Building and Running
+```
+> mvn clean install
+> ./run.sh
+```
+
+The [run.sh](./run.sh) script as shown above will spawn the Guice and Pico built applications twice (1st iteration for warmup).
+
+:DISCLAIMER: **Results may vary** The below measurements were captured at the time of this revision (see git log)
+
+
+# Notable
+
+1. Pico supports jakarta.inject as well as javax.inject packaging. Guice (at the time of this writing) only supports javax.inject. This example therefore uses javax.inject in order to compare the two models even though it is recommended all switch to jakarta.inject if possible.
+
+2. Guice is based upon reflection and at runtime uses it to determine the injection points and dependency graph. Pico is based upon compile-time code generation to generate (in code) the dependency graph. Both, however, support lazy/dynamic activation of services.
+
+3. Pico provides the ability (as demonstrated in the [pom.xml](./pico/pom.xml)) to bind to the final injection model at assembly time. This option provides several benefits including deterministic behavior, speed & performance, and to helps ensure the completeness & validity of the entire application's dependency graph. Guice does not offer such an option.
+
+4. Guice is considerable larger in terms of its memory consumption footprint.
+
+```run.sh
+...
+Guice Main memory consumption = 13,194,048 bytes
+...
+Pico Main memory consumption = 7,930,352 bytes
+...
+```
+
+5. Both applications are packaged with all of its transitive compile-time dependencies to showcase the differences in disk size. Pico is considerably smaller in terms of its disk consumption footprint:
+```
+> find . | grep dependencies | grep jar | xargs ls -l
+-rw-r--r-- 1 jtrent staff 3975690 Feb 21 20:43 ./guice/target/helidon-examples-pico-logger-guice-4.0.0-SNAPSHOT-jar-with-dependencies.jar
+-rw-r--r-- 1 jtrent staff 403936 Feb 21 20:43 ./pico/target/helidon-examples-pico-logger-pico-4.0.0-SNAPSHOT-jar-with-dependencies.jar
+```
+
+6. Pico is considerably faster in terms of its end-to-end runtime. Mileage will vary.
+
+```run.sh
+...
+Guice Main elapsed time = 293 ms
+...
+Pico Main elapsed time = 184 ms
+...
+```
+
+7. Pico requires less coding as compared to Guice.
+
+```
+jtrent@jtrent-mac logger % find guice -type f -not -path '*/.*' | grep -v target | wc
+ 4 4 230
+jtrent@jtrent-mac logger % find pico -type f -not -path '*/.*' | grep -v target | wc
+ 3 3 149
+```
+
+8. Pico offers lifecycle support (see jakarta.annotation.@PostConstruct, jakarta.annotation.@PreDestroy, Pico's @RunLevel
+ annotations and PicoServices#shutdown()).
+
+9. Pico generates a suggested module-info.java based upon analysis of your injection/dependency model (see /target/classes/module-info.java.pico).
+
+```./target/classes/module-info.java.pico
+// @Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+module helidon.examples.pico.logger.common {
+ exports io.helidon.examples.pico.logger.common;
+ // pico module - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ provides io.helidon.pico.Module with io.helidon.examples.pico.logger.common.Pico$$Module;
+ // pico external contract usage - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ requires helidon.examples.pico.logger.common;
+ uses io.helidon.examples.pico.logger.common.CommunicationMode;
+ uses io.helidon.examples.pico.logger.common.Communicator;
+ uses jakarta.inject.Provider;
+ uses javax.inject.Provider;
+ // pico services - Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+ requires transitive io.helidon.pico.services;
+}
+```
+
+10. Pico can optionally generate the activators (i.e., the DI supporting classes) on an external jar module by using a maven plugin. Notice how [common](./common) is built, and then in the [pico/pom.xml](pico/pom.xml) the maven plugin uses application-create to create the supporting DI around it. That explains why there are no classes other than Main in the pico sub-module. Guice does not offer such an option, and instead requires the developer to write the modules declaring the DI module programmatically.
+
+
+Additionally, and more philosophical in nature, Pico strives to closely adhere to standard JSR-330 constructs as compared to Guice.
+To be productive only requires the use of these packages:
+* [jakarta.inject](https://javadoc.io/doc/jakarta.inject/jakarta.inject-api/latest/index.html)
+* Optionally, [jakarta.annotation](https://javadoc.io/doc/jakarta.annotation/jakarta.annotation-api/latest/jakarta.annotation/jakarta/annotation/package-summary.html)
+* Optionally, a few [pico API](../../pico/src/main/java/io/helidon/pico) / annotations.
+
+# References
+* https://www.baeldung.com/guice
diff --git a/examples/pico/logger/common/pom.xml b/examples/pico/logger/common/pom.xml
new file mode 100644
index 00000000000..7b1dc5d8a01
--- /dev/null
+++ b/examples/pico/logger/common/pom.xml
@@ -0,0 +1,52 @@
+
+
+
+
+ io.helidon.examples.pico.logger
+ helidon-examples-pico-logger-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ helidon-examples-pico-logger-common
+ Helidon Pico Examples - Logger - Common
+
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+
+ javax.inject
+ javax.inject
+ ${javax.injection.version}
+
+
+
+ javax.annotation
+ javax.annotation-api
+ ${javax.annotations.version}
+ provided
+
+
+
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/AnotherCommunicationMode.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/AnotherCommunicationMode.java
new file mode 100644
index 00000000000..24c87bd581f
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/AnotherCommunicationMode.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@javax.inject.Singleton
+public class AnotherCommunicationMode implements CommunicationMode {
+
+ @javax.inject.Inject
+ Logger logger;
+
+ @Override
+ public int sendMessage(String message) {
+ logger.log(Level.INFO, "Sending message '" + message + "' over another (default) communication mode");
+ return 1;
+ }
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/Communication.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/Communication.java
new file mode 100644
index 00000000000..f8055090945
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/Communication.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Communication {
+
+ Logger logger;
+ public Communicator communicator;
+
+ volatile boolean didPostConstruct;
+
+ @javax.inject.Inject
+ public Communication(Logger logger, Communicator communicator) {
+ this.logger = logger;
+ this.communicator = Objects.requireNonNull(communicator);
+ }
+
+ @javax.annotation.PostConstruct
+ public void postConstruct() {
+ logger.log(Level.INFO, "logging is enabled for communication");
+ }
+
+ public int sendMessageViaAllModes(String message) {
+ if (!didPostConstruct) {
+ logger.log(Level.INFO, "explicitly calling postConstruct");
+ postConstruct();
+ }
+
+ return communicator.sendMessage(message, "email")
+ + communicator.sendMessage(message, "sms")
+ + communicator.sendMessage(message, "im")
+ + communicator.sendMessage(message, "another");
+ }
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/CommunicationMode.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/CommunicationMode.java
new file mode 100644
index 00000000000..50965233e87
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/CommunicationMode.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+public interface CommunicationMode {
+
+ int sendMessage(String message);
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/Communicator.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/Communicator.java
new file mode 100644
index 00000000000..293183a8452
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/Communicator.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+public interface Communicator {
+
+ int sendMessage(String message, String preferredMode);
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/DefaultCommunicator.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/DefaultCommunicator.java
new file mode 100644
index 00000000000..ab5e5bd910c
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/DefaultCommunicator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+public class DefaultCommunicator implements Communicator {
+
+ @javax.inject.Inject
+ @javax.inject.Named("sms")
+ public CommunicationMode sms;
+
+ @javax.inject.Inject
+ @javax.inject.Named("email")
+ public CommunicationMode email;
+
+ @javax.inject.Inject
+ @javax.inject.Named("im")
+ public CommunicationMode im;
+
+ public CommunicationMode defaultCommunication;
+
+ @javax.inject.Inject
+ public DefaultCommunicator(CommunicationMode defaultCommunication) {
+ this.defaultCommunication = defaultCommunication;
+ }
+
+ @Override
+ public int sendMessage(String message, String preferredMode) {
+ if ("sms".equals(preferredMode)) {
+ return sms.sendMessage(message);
+ } else if ("email".equals(preferredMode)) {
+ return email.sendMessage(message);
+ } else if ("im".equals(preferredMode)) {
+ return im.sendMessage(message);
+ } else {
+ return defaultCommunication.sendMessage(message);
+ }
+ }
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/EmailCommunicationMode.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/EmailCommunicationMode.java
new file mode 100644
index 00000000000..737fd47b8e9
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/EmailCommunicationMode.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@javax.inject.Singleton
+@javax.inject.Named("email")
+public class EmailCommunicationMode implements CommunicationMode {
+
+ @javax.inject.Inject
+ Logger logger;
+
+ @Override
+ public int sendMessage(String message) {
+ logger.log(Level.INFO, "Sending message '" + message + "' over email");
+ return 1;
+ }
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/ImCommunicationMode.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/ImCommunicationMode.java
new file mode 100644
index 00000000000..47173b74d19
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/ImCommunicationMode.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@javax.inject.Singleton
+@javax.inject.Named("im")
+public class ImCommunicationMode implements CommunicationMode {
+
+ @javax.inject.Inject
+ @jakarta.inject.Inject
+ Logger logger;
+
+ @Override
+ public int sendMessage(String message) {
+ logger.log(Level.INFO, "Sending message '" + message + "' over IM");
+ return 1;
+ }
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/LoggerProvider.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/LoggerProvider.java
new file mode 100644
index 00000000000..5ade91b7ec7
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/LoggerProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+import java.util.logging.Logger;
+
+@javax.inject.Singleton
+public class LoggerProvider implements javax.inject.Provider, jakarta.inject.Provider {
+
+ @Override
+ public Logger get() {
+ return Logger.getAnonymousLogger();
+ }
+
+}
diff --git a/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/SmsCommunicationMode.java b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/SmsCommunicationMode.java
new file mode 100644
index 00000000000..baac623eca7
--- /dev/null
+++ b/examples/pico/logger/common/src/main/java/io/helidon/examples/pico/logger/common/SmsCommunicationMode.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.common;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@javax.inject.Singleton
+@javax.inject.Named("sms")
+public class SmsCommunicationMode implements CommunicationMode {
+
+ @javax.inject.Inject
+ Logger logger;
+
+ @Override
+ public int sendMessage(String message) {
+ logger.log(Level.INFO, "Sending message '" + message + "' over SMS");
+ return 1;
+ }
+
+}
diff --git a/examples/pico/logger/guice/pom.xml b/examples/pico/logger/guice/pom.xml
new file mode 100644
index 00000000000..d12cc6451c1
--- /dev/null
+++ b/examples/pico/logger/guice/pom.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+ io.helidon.examples.pico.logger
+ helidon-examples-pico-logger-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ helidon-examples-pico-logger-guice
+ Helidon Pico Examples - Logger - Guice
+
+
+ io.helidon.examples.pico.logger.guice.Main
+
+
+
+
+ io.helidon.examples.pico.logger
+ helidon-examples-pico-logger-common
+ ${helidon.version}
+
+
+ com.google.inject
+ guice
+ ${version.lib.guice}
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+
+
+ ${mainClass}
+
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${version.plugin.jar}
+
+
+
+ true
+ libs
+ ${mainClass}
+ false
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${version.plugin.exec}
+
+ ${mainClass}
+
+
+
+
+
+
diff --git a/examples/pico/logger/guice/src/main/java/io/helidon/examples/pico/logger/guice/BasicModule.java b/examples/pico/logger/guice/src/main/java/io/helidon/examples/pico/logger/guice/BasicModule.java
new file mode 100644
index 00000000000..df0735ae792
--- /dev/null
+++ b/examples/pico/logger/guice/src/main/java/io/helidon/examples/pico/logger/guice/BasicModule.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.guice;
+
+import io.helidon.examples.pico.logger.common.AnotherCommunicationMode;
+import io.helidon.examples.pico.logger.common.Communication;
+import io.helidon.examples.pico.logger.common.CommunicationMode;
+import io.helidon.examples.pico.logger.common.Communicator;
+import io.helidon.examples.pico.logger.common.DefaultCommunicator;
+import io.helidon.examples.pico.logger.common.EmailCommunicationMode;
+import io.helidon.examples.pico.logger.common.ImCommunicationMode;
+import io.helidon.examples.pico.logger.common.SmsCommunicationMode;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class BasicModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ try {
+ bind(Communicator.class).toConstructor(DefaultCommunicator.class.getConstructor(CommunicationMode.class));
+ bind(Boolean.class).toInstance(true);
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ bind(CommunicationMode.class).to(AnotherCommunicationMode.class);
+ bind(CommunicationMode.class).annotatedWith(Names.named("default")).to(AnotherCommunicationMode.class);
+ bind(CommunicationMode.class).annotatedWith(Names.named("im")).to(ImCommunicationMode.class);
+ bind(CommunicationMode.class).annotatedWith(Names.named("im")).to(ImCommunicationMode.class);
+ bind(CommunicationMode.class).annotatedWith(Names.named("email")).to(EmailCommunicationMode.class);
+ bind(CommunicationMode.class).annotatedWith(Names.named("sms")).to(SmsCommunicationMode.class);
+ bind(Communication.class).asEagerSingleton();
+ }
+
+}
diff --git a/examples/pico/logger/guice/src/main/java/io/helidon/examples/pico/logger/guice/Main.java b/examples/pico/logger/guice/src/main/java/io/helidon/examples/pico/logger/guice/Main.java
new file mode 100644
index 00000000000..c98f25c49ae
--- /dev/null
+++ b/examples/pico/logger/guice/src/main/java/io/helidon/examples/pico/logger/guice/Main.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.guice;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.helidon.examples.pico.logger.common.Communication;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+public class Main {
+
+ static Injector injector;
+ static Communication comms;
+
+ public static void main(String[] args){
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ injector = Guice.createInjector(new BasicModule());
+ comms = injector.getInstance(Communication.class);
+
+ List messages = Arrays.asList(args);
+ if (messages.isEmpty()) {
+ messages = Collections.singletonList("Hello World!");
+ }
+
+ AtomicInteger sent = new AtomicInteger();
+ messages.forEach((message) -> {
+ sent.addAndGet(comms.sendMessageViaAllModes(message));
+ });
+ int count = sent.get();
+ System.out.println("finished sending: " + count);
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Guice Main memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Guice Main elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/logger/guice/src/test/java/io/helidon/examples/pico/logger/guice/GuiceTest.java b/examples/pico/logger/guice/src/test/java/io/helidon/examples/pico/logger/guice/GuiceTest.java
new file mode 100644
index 00000000000..3b608012118
--- /dev/null
+++ b/examples/pico/logger/guice/src/test/java/io/helidon/examples/pico/logger/guice/GuiceTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.guice;
+
+import io.helidon.examples.pico.logger.common.AnotherCommunicationMode;
+import io.helidon.examples.pico.logger.common.DefaultCommunicator;
+import io.helidon.examples.pico.logger.common.EmailCommunicationMode;
+import io.helidon.examples.pico.logger.common.ImCommunicationMode;
+import io.helidon.examples.pico.logger.common.SmsCommunicationMode;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class GuiceTest {
+
+ @Test
+ public void testMain() {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ Main.main(new String[] {"Hello World!"});
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ DefaultCommunicator comms = ((DefaultCommunicator) Main.comms.communicator);
+ assertEquals(SmsCommunicationMode.class, comms.sms.getClass());
+ assertEquals(EmailCommunicationMode.class, comms.email.getClass());
+ assertEquals(ImCommunicationMode.class, comms.im.getClass());
+ assertEquals(AnotherCommunicationMode.class, comms.defaultCommunication.getClass());
+ System.out.println("Guice JUnit memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Guice JUnit elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/logger/pico/pom.xml b/examples/pico/logger/pico/pom.xml
new file mode 100644
index 00000000000..e2f90f21b18
--- /dev/null
+++ b/examples/pico/logger/pico/pom.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+ io.helidon.examples.pico.logger
+ helidon-examples-pico-logger-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ helidon-examples-pico-logger-pico
+ Helidon Pico Examples - Logger - Pico
+
+
+ io.helidon.examples.pico.logger.pico.Main
+
+
+
+
+ io.helidon.examples.pico.logger
+ helidon-examples-pico-logger-common
+ ${helidon.version}
+
+
+ io.helidon.pico
+ helidon-pico-services
+
+
+ io.helidon.builder
+ helidon-builder
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+ provided
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+
+
+ ${mainClass}
+
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+ io.helidon.pico
+ helidon-pico-maven-plugin
+ ${helidon.version}
+
+
+
+ external-module-create
+
+
+
+ compile
+ compile
+
+ application-create
+
+
+
+
+
+ io.helidon.examples.pico.logger.common
+
+ helidon.examples.pico.logger.common
+ ALL
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${version.plugin.jar}
+
+
+
+ true
+ libs
+ ${mainClass}
+ false
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${version.plugin.exec}
+
+ ${mainClass}
+
+
+
+
+
+
diff --git a/examples/pico/logger/pico/src/main/java/io/helidon/examples/pico/logger/pico/Main.java b/examples/pico/logger/pico/src/main/java/io/helidon/examples/pico/logger/pico/Main.java
new file mode 100644
index 00000000000..5f45d3cb052
--- /dev/null
+++ b/examples/pico/logger/pico/src/main/java/io/helidon/examples/pico/logger/pico/Main.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.pico;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.helidon.examples.pico.logger.common.Communication;
+import io.helidon.pico.PicoServices;
+import io.helidon.pico.Services;
+
+public class Main {
+
+ static Services services;
+ static Communication comms;
+
+ public static void main(String[] args){
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ PicoServices picoServices = PicoServices.picoServices().get();
+ services = picoServices.services();
+ comms = services.lookupFirst(Communication.class).get();
+
+ List messages = Arrays.asList(args);
+ if (messages.isEmpty()) {
+ messages = Collections.singletonList("Hello World!");
+ }
+
+ AtomicInteger sent = new AtomicInteger();
+ messages.forEach((message) -> {
+ sent.addAndGet(comms.sendMessageViaAllModes(message));
+ });
+ int count = sent.get();
+ System.out.println("finished sending: " + count);
+
+ final long finish = System.currentTimeMillis();
+ final long memFinish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ System.out.println("Pico Main memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Pico Main elapsed time = " + (finish - start) + " ms");
+ }
+}
diff --git a/examples/pico/logger/pico/src/test/java/io/helidon/examples/pico/logger/pico/PicoTest.java b/examples/pico/logger/pico/src/test/java/io/helidon/examples/pico/logger/pico/PicoTest.java
new file mode 100644
index 00000000000..1017c8918ec
--- /dev/null
+++ b/examples/pico/logger/pico/src/test/java/io/helidon/examples/pico/logger/pico/PicoTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.pico.logger.pico;
+
+import io.helidon.examples.pico.logger.common.AnotherCommunicationMode;
+import io.helidon.examples.pico.logger.common.DefaultCommunicator;
+import io.helidon.examples.pico.logger.common.EmailCommunicationMode;
+import io.helidon.examples.pico.logger.common.ImCommunicationMode;
+import io.helidon.examples.pico.logger.common.SmsCommunicationMode;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PicoTest {
+
+ @Test
+ public void testMain() {
+ final long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long start = System.currentTimeMillis();
+
+ Main.main(new String[] {"Hello World!"});
+
+ final long finish = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ final long memFinish = Runtime.getRuntime().totalMemory();
+ DefaultCommunicator comms = ((DefaultCommunicator) Main.comms.communicator);
+ assertEquals(SmsCommunicationMode.class, comms.sms.getClass());
+ assertEquals(EmailCommunicationMode.class, comms.email.getClass());
+ assertEquals(ImCommunicationMode.class, comms.im.getClass());
+ assertEquals(AnotherCommunicationMode.class, comms.defaultCommunication.getClass());
+ System.out.println("Pico Junit memory consumption = " + (memFinish - memStart) + " bytes");
+ System.out.println("Pico Junit elapsed time = " + (finish - start) + " ms");
+ }
+
+}
diff --git a/examples/pico/logger/pom.xml b/examples/pico/logger/pom.xml
new file mode 100644
index 00000000000..45cd71392be
--- /dev/null
+++ b/examples/pico/logger/pom.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+ io.helidon.examples.pico
+ helidon-examples-pico-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ io.helidon.examples.pico.logger
+ helidon-examples-pico-logger-project
+ Helidon Pico Examples - Logger
+
+ pom
+
+
+ 5.1.0
+
+
+ 1
+ 1.3.2
+
+
+
+ common
+ guice
+ pico
+
+
+
diff --git a/examples/pico/logger/run.sh b/examples/pico/logger/run.sh
new file mode 100755
index 00000000000..6ca82cffa03
--- /dev/null
+++ b/examples/pico/logger/run.sh
@@ -0,0 +1,28 @@
+#!/bin/bash -e
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# mvn clean install
+clear
+echo "RUN 1: (GUICE)"
+java -jar guice/target/helidon-examples-pico-logger-guice-4.0.0-SNAPSHOT-jar-with-dependencies.jar "hello guice"
+echo "RUN 2: (GUICE)"
+java -jar guice/target/helidon-examples-pico-logger-guice-4.0.0-SNAPSHOT-jar-with-dependencies.jar "hello guice"
+echo "========================"
+echo "RUN 1: (PICO)"
+java -jar pico/target/helidon-examples-pico-logger-pico-4.0.0-SNAPSHOT-jar-with-dependencies.jar "hello pico"
+echo "RUN 2: (PICO)"
+java -jar pico/target/helidon-examples-pico-logger-pico-4.0.0-SNAPSHOT-jar-with-dependencies.jar "hello pico"
diff --git a/examples/pico/pom.xml b/examples/pico/pom.xml
new file mode 100644
index 00000000000..55c65642885
--- /dev/null
+++ b/examples/pico/pom.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+ io.helidon.examples
+ helidon-examples-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ io.helidon.examples.pico
+ helidon-examples-pico-project
+ Helidon Examples Pico Project
+ pom
+ 4.0.0
+
+
+ 11
+ 11
+
+
+
+
+
+
+
+
+
+
+ book
+ car
+ logger
+
+
+
diff --git a/examples/pom.xml b/examples/pom.xml
index 532c70d3e3d..f14135d77aa 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -63,6 +63,7 @@
metricsjbatchnima
+ pico
diff --git a/nima/http/pom.xml b/nima/http/pom.xml
index df74714fbdd..8af9da5003d 100644
--- a/nima/http/pom.xml
+++ b/nima/http/pom.xml
@@ -38,6 +38,7 @@
encodingmedia
+ processor
diff --git a/nima/http/processor/pom.xml b/nima/http/processor/pom.xml
new file mode 100644
index 00000000000..abc8f95dbe8
--- /dev/null
+++ b/nima/http/processor/pom.xml
@@ -0,0 +1,54 @@
+
+
+
+ 4.0.0
+
+ io.helidon.nima.http
+ helidon-nima-http-project
+ 4.0.0-SNAPSHOT
+
+
+ helidon-nima-http-processor
+ Helidon NÃma HTTP Annotation Processor
+
+
+ true
+
+
+
+
+ io.helidon.pico
+ helidon-pico-api
+
+
+ io.helidon.pico
+ helidon-pico-processor
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
diff --git a/nima/http/processor/src/main/java/io/helidon/nima/http/processor/HttpEndpointCreator.java b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/HttpEndpointCreator.java
new file mode 100644
index 00000000000..428ed4837fa
--- /dev/null
+++ b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/HttpEndpointCreator.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.nima.http.processor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.pico.tools.CustomAnnotationTemplateRequest;
+import io.helidon.pico.tools.CustomAnnotationTemplateResponse;
+import io.helidon.pico.tools.DefaultGenericTemplateCreatorRequest;
+import io.helidon.pico.tools.GenericTemplateCreatorRequest;
+import io.helidon.pico.tools.spi.CustomAnnotationTemplateCreator;
+
+/**
+ * Annotation processor that generates a service for each class annotated with {@value #PATH_ANNOTATION} annotation.
+ * Service provider implementation of a {@link io.helidon.pico.tools.spi.CustomAnnotationTemplateCreator}.
+ */
+public class HttpEndpointCreator implements CustomAnnotationTemplateCreator {
+ private static final String PATH_ANNOTATION = "io.helidon.common.http.Path";
+
+ /**
+ * Default constructor used by the {@link java.util.ServiceLoader}.
+ */
+ public HttpEndpointCreator() {
+ }
+
+ @Override
+ public Set annoTypes() {
+ return Set.of(PATH_ANNOTATION);
+ }
+
+ @Override
+ public Optional create(CustomAnnotationTemplateRequest request) {
+ TypeInfo enclosingType = request.enclosingTypeInfo();
+ if (!enclosingType.typeKind().equals(TypeInfo.KIND_CLASS)) {
+ // we are only interested in classes, not in methods
+ return Optional.empty();
+ }
+
+ String classname = enclosingType.typeName().className() + "_GeneratedService";
+ TypeName generatedTypeName = DefaultTypeName.create(enclosingType.typeName().packageName(), classname);
+
+ String template = Templates.loadTemplate("nima", "http-endpoint.java.hbs");
+ GenericTemplateCreatorRequest genericCreatorRequest = DefaultGenericTemplateCreatorRequest.builder()
+ .customAnnotationTemplateRequest(request)
+ .template(template)
+ .generatedTypeName(generatedTypeName)
+ .overrideProperties(addProperties(request))
+ .build();
+ return request.genericTemplateCreator().create(genericCreatorRequest);
+ }
+
+ private Map addProperties(CustomAnnotationTemplateRequest request) {
+ Map response = new HashMap<>();
+
+ var annots = request.enclosingTypeInfo().annotations();
+ for (var annot : annots) {
+ if (annot.typeName().name().equals(PATH_ANNOTATION)) {
+ response.put("http", Map.of("path", annot.value().orElse("/")));
+ break;
+ }
+ }
+
+ return response;
+ }
+}
diff --git a/nima/http/processor/src/main/java/io/helidon/nima/http/processor/HttpMethodCreator.java b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/HttpMethodCreator.java
new file mode 100644
index 00000000000..b268375a9c9
--- /dev/null
+++ b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/HttpMethodCreator.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.nima.http.processor;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.lang.model.element.ElementKind;
+
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeInfo;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
+import io.helidon.pico.tools.CustomAnnotationTemplateRequest;
+import io.helidon.pico.tools.CustomAnnotationTemplateResponse;
+import io.helidon.pico.tools.DefaultGenericTemplateCreatorRequest;
+import io.helidon.pico.tools.GenericTemplateCreator;
+import io.helidon.pico.tools.GenericTemplateCreatorRequest;
+import io.helidon.pico.tools.spi.CustomAnnotationTemplateCreator;
+
+/**
+ * Annotation processor that generates a service for each method annotated with an HTTP method annotation.
+ * Service provider implementation of a {@link io.helidon.pico.tools.spi.CustomAnnotationTemplateCreator}.
+ */
+public class HttpMethodCreator implements CustomAnnotationTemplateCreator {
+ private static final String PATH_ANNOTATION = "io.helidon.common.http.Path";
+ private static final String GET_ANNOTATION = "io.helidon.common.http.GET";
+ private static final String HTTP_METHOD_ANNOTATION = "io.helidon.common.http.HttpMethod";
+ private static final String POST_ANNOTATION = "io.helidon.common.http.POST";
+ private static final String PATH_PARAM_ANNOTATION = "io.helidon.common.http.PathParam";
+ private static final String HEADER_PARAM_ANNOTATION = "io.helidon.common.http.HeaderParam";
+ private static final String QUERY_PARAM_ANNOTATION = "io.helidon.common.http.QueryParam";
+ private static final String ENTITY_PARAM_ANNOTATION = "io.helidon.common.http.Entity";
+ private static final String QUERY_NO_DEFAULT = "io.helidon.nima.htp.api.QueryParam_NO_DEFAULT_VALUE";
+
+ private static final Set PARAM_ANNOTATIONS = Set.of(
+ HEADER_PARAM_ANNOTATION,
+ QUERY_PARAM_ANNOTATION,
+ PATH_PARAM_ANNOTATION,
+ ENTITY_PARAM_ANNOTATION
+ );
+
+ /**
+ * Default constructor used by the {@link java.util.ServiceLoader}.
+ */
+ public HttpMethodCreator() {
+ }
+
+ @Override
+ public Set annoTypes() {
+ return Set.of(GET_ANNOTATION,
+ HEADER_PARAM_ANNOTATION,
+ HTTP_METHOD_ANNOTATION,
+ POST_ANNOTATION,
+ PATH_ANNOTATION,
+ QUERY_PARAM_ANNOTATION);
+ }
+
+ @Override
+ public Optional create(CustomAnnotationTemplateRequest request) {
+ TypeInfo enclosingType = request.enclosingTypeInfo();
+
+ if (!ElementKind.METHOD.name().equals(request.targetElement().elementTypeKind())) {
+ // we are only interested in methods, not in classes
+ return Optional.empty();
+ }
+
+ String classname = enclosingType.typeName().className() + "_"
+ + request.annoTypeName().className() + "_"
+ + request.targetElement().elementName();
+ TypeName generatedTypeName = DefaultTypeName.create(enclosingType.typeName().packageName(), classname);
+
+ GenericTemplateCreator genericTemplateCreator = request.genericTemplateCreator();
+ GenericTemplateCreatorRequest genericCreatorRequest = DefaultGenericTemplateCreatorRequest.builder()
+ .customAnnotationTemplateRequest(request)
+ .template(Templates.loadTemplate("nima", "http-method.java.hbs"))
+ .generatedTypeName(generatedTypeName)
+ .overrideProperties(addProperties(request))
+ .build();
+ return genericTemplateCreator.create(genericCreatorRequest);
+ }
+
+ private Map addProperties(CustomAnnotationTemplateRequest request) {
+ TypedElementName targetElement = request.targetElement();
+ Map response = new HashMap<>();
+
+ HttpDef http = new HttpDef();
+ /*
+ Method response
+ http.response.type
+ http.response.isVoid
+ */
+ TypeName returnType = targetElement.typeName();
+ if ("void".equals(returnType.className())) {
+ http.response = new HttpResponse();
+ } else {
+ http.response = new HttpResponse(returnType.name());
+ }
+
+ // http.methodName - name of the method in source code (not HTTP Method)
+ http.methodName = targetElement.elementName();
+
+ // http.params (full string)
+ List headerList = new LinkedList<>();
+ List elementArgs = request.targetElementArgs();
+ LinkedList parameters = new LinkedList<>();
+ int headerCount = 1;
+ for (TypedElementName elementArg : elementArgs) {
+ String type = elementArg.typeName().name();
+
+ switch (type) {
+ case "io.helidon.nima.webserver.http.ServerRequest" -> parameters.add("req,");
+ case "io.helidon.nima.webserver.http.ServerResponse" -> parameters.add("res,");
+ default -> processParameter(http, parameters, headerList, type, elementArg);
+ }
+ }
+
+ if (!parameters.isEmpty()) {
+ String last = parameters.removeLast();
+ last = last.substring(0, last.length() - 1);
+ parameters.addLast(last);
+ }
+ http.params = parameters;
+
+ /*
+ Headers
+ http.headers, field, name
+ */
+ http.headers = headerList;
+
+ /*
+ HTTP Method
+ http.method
+ */
+ if (request.annoTypeName().className().equals(HTTP_METHOD_ANNOTATION)) {
+ http.method = findAnnotValue(targetElement.annotations(), HTTP_METHOD_ANNOTATION, null);
+ if (http.method == null) {
+ throw new IllegalStateException("HTTP method producer called without HTTP Method annotation (such as @GET)");
+ }
+ } else {
+ http.method = request.annoTypeName().className();
+ }
+
+ // HTTP Path (if defined)
+ http.path = findAnnotValue(targetElement.annotations(), PATH_ANNOTATION, "");
+
+ response.put("http", http);
+ return response;
+ }
+
+ private void processParameter(HttpDef httpDef,
+ LinkedList parameters,
+ List headerList,
+ String type,
+ TypedElementName elementArg) {
+ // depending on annotations
+ List annotations = elementArg.annotations();
+ if (annotations.size() == 0) {
+ throw new IllegalStateException("Parameters must be annotated with one of @Entity, @PathParam, @HeaderParam, "
+ + "@QueryParam - parameter "
+ + elementArg.elementName() + " is not annotated at all.");
+ }
+
+ AnnotationAndValue httpAnnotation = null;
+
+ for (AnnotationAndValue annotation : annotations) {
+ if (PARAM_ANNOTATIONS.contains(annotation.typeName().name())) {
+ if (httpAnnotation == null) {
+ httpAnnotation = annotation;
+ } else {
+ throw new IllegalStateException("Parameters must be annotated with one of " + PARAM_ANNOTATIONS
+ + ", - parameter "
+ + elementArg.elementName() + " has more than one annotation.");
+ }
+ }
+ }
+
+ if (httpAnnotation == null) {
+ throw new IllegalStateException("Parameters must be annotated with one of " + PARAM_ANNOTATIONS
+ + ", - parameter "
+ + elementArg.elementName() + " has neither of these.");
+ }
+
+ // todo now we only support String for query, path and header -> add conversions
+ switch (httpAnnotation.typeName().className()) {
+ case ("PathParam") -> parameters.add("req.path().pathParameters().value(\"" + httpAnnotation.value().orElseThrow()
+ + "\"),");
+ case ("Entity") -> parameters.add("req.content().as(" + type + ".class),");
+ case ("HeaderParam") -> {
+ String headerName = "HEADER_" + (headerList.size() + 1);
+ headerList.add(new HeaderDef(headerName, httpAnnotation.value().orElseThrow()));
+ parameters.add("req.headers().get(" + headerName + ").value(),");
+ }
+ case ("QueryParam") -> {
+ httpDef.hasQueryParams = true;
+ String defaultValue = httpAnnotation.value("defaultValue").orElse(null);
+ if (defaultValue == null || QUERY_NO_DEFAULT.equals(defaultValue)) {
+ defaultValue = "null";
+ } else {
+ defaultValue = "\"" + defaultValue + "\"";
+ }
+ String queryParam = httpAnnotation.value().get();
+ // TODO string is hardcoded, we need to add support for mapping
+ parameters.add("query(req, res, \"" + queryParam + "\", " + defaultValue + ", String.class),");
+ }
+ default -> throw new IllegalStateException("Invalid annotation on HTTP parameter: " + elementArg.elementName());
+ }
+ }
+
+ private String findAnnotValue(List elementAnnotations, String name, String defaultValue) {
+ for (AnnotationAndValue elementAnnotation : elementAnnotations) {
+ if (name.equals(elementAnnotation.typeName().name())) {
+ return elementAnnotation.value().orElseThrow();
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ */
+ @Deprecated(since = "1.0.0")
+ public static class HttpDef {
+ private List headers;
+ private LinkedList params;
+ private HttpResponse response;
+ private String methodName;
+ private String method;
+ private String path;
+ private boolean hasQueryParams;
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public List getHeaders() {
+ return headers;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public LinkedList getParams() {
+ return params;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public HttpResponse getResponse() {
+ return response;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public String getMethodName() {
+ return methodName;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public String getMethod() {
+ return method;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public boolean isHasQueryParams() {
+ return hasQueryParams;
+ }
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ */
+ @Deprecated(since = "1.0.0")
+ public static class HttpResponse {
+ private final String type;
+ private final boolean isVoid;
+
+ HttpResponse() {
+ this.type = null;
+ this.isVoid = true;
+ }
+
+ HttpResponse(String type) {
+ this.type = type;
+ this.isVoid = false;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public boolean isVoid() {
+ return isVoid;
+ }
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ */
+ @Deprecated(since = "1.0.0")
+ public static class HeaderDef {
+ private String field;
+ private String name;
+
+ HeaderDef(String field, String name) {
+ this.field = field;
+ this.name = name;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public String getField() {
+ return field;
+ }
+
+ /**
+ * Needed for template processing.
+ * Do not use.
+ * @return do not use
+ */
+ @Deprecated(since = "1.0.0")
+ public String getName() {
+ return name;
+ }
+ }
+}
diff --git a/nima/http/processor/src/main/java/io/helidon/nima/http/processor/Templates.java b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/Templates.java
new file mode 100644
index 00000000000..c5db21e4b48
--- /dev/null
+++ b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/Templates.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.nima.http.processor;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+final class Templates {
+ private Templates() {
+ }
+
+ static String loadTemplate(String templateProfile, String name) {
+ String path = "templates/pico/" + templateProfile + "/" + name;
+ try {
+ InputStream in = Templates.class.getClassLoader().getResourceAsStream(path);
+ if (in == null) {
+ throw new RuntimeException("Could not find template " + path + " on classpath.");
+ }
+ try (in) {
+ return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/nima/http/processor/src/main/java/io/helidon/nima/http/processor/package-info.java b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/package-info.java
new file mode 100644
index 00000000000..44e9b669c09
--- /dev/null
+++ b/nima/http/processor/src/main/java/io/helidon/nima/http/processor/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Annotation processor that generates HTTP Endpoints discoverable by Pico.
+ */
+package io.helidon.nima.http.processor;
diff --git a/nima/http/processor/src/main/java/module-info.java b/nima/http/processor/src/main/java/module-info.java
new file mode 100644
index 00000000000..19a03d18b94
--- /dev/null
+++ b/nima/http/processor/src/main/java/module-info.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import io.helidon.nima.http.processor.HttpEndpointCreator;
+import io.helidon.nima.http.processor.HttpMethodCreator;
+
+/**
+ * Annotation processor that generates HTTP Endpoints.
+ */
+module io.helidon.nima.http.processor {
+ requires io.helidon.pico.api;
+ requires io.helidon.pico.tools;
+ requires io.helidon.pico.processor;
+ requires java.compiler;
+ requires io.helidon.builder.processor.spi;
+
+ exports io.helidon.nima.http.processor;
+
+ provides io.helidon.pico.tools.spi.CustomAnnotationTemplateCreator
+ with HttpEndpointCreator, HttpMethodCreator;
+}
diff --git a/nima/http/processor/src/main/resources/templates/pico/nima/http-endpoint.java.hbs b/nima/http/processor/src/main/resources/templates/pico/nima/http-endpoint.java.hbs
new file mode 100644
index 00000000000..9930b6de300
--- /dev/null
+++ b/nima/http/processor/src/main/resources/templates/pico/nima/http-endpoint.java.hbs
@@ -0,0 +1,57 @@
+{{!
+Copyright (c) 2023 Oracle and/or its affiliates.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+}}
+package {{packageName}};
+
+import java.util.List;
+
+import io.helidon.common.Weight;
+import io.helidon.nima.webserver.http.GeneratedHandler;
+import io.helidon.nima.webserver.http.Handler;
+import io.helidon.nima.webserver.http.HttpFeature;
+import io.helidon.nima.webserver.http.HttpRouting;
+import io.helidon.nima.webserver.http.HttpRules;
+import io.helidon.nima.webserver.http.HttpService;
+
+import io.helidon.pico.ExternalContracts;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.inject.Provider;
+import jakarta.inject.Singleton;
+
+@jakarta.annotation.Generated({{{generatedSticker}}})
+@Singleton
+@Named("{{enclosingClassTypeName.name}}")
+@Weight({{weight}})
+@ExternalContracts(HttpFeature.class)
+public class {{className}} implements HttpFeature {
+ private final List myMethods;
+ private final String path = "{{http.path}}";
+
+ @Inject
+ {{className}}(@Named("{{enclosingClassTypeName.name}}") List myMethods) {
+ this.myMethods = myMethods;
+ }
+
+ @Override
+ public void setup(HttpRouting.Builder routing) {
+ routing.register(path, (HttpService) rules -> {
+ for (GeneratedHandler handler : myMethods) {
+ rules.route(handler.method(), handler.path(), (Handler) handler);
+ }
+ });
+ }
+}
diff --git a/nima/http/processor/src/main/resources/templates/pico/nima/http-method.java.hbs b/nima/http/processor/src/main/resources/templates/pico/nima/http-method.java.hbs
new file mode 100644
index 00000000000..1f8ade21c31
--- /dev/null
+++ b/nima/http/processor/src/main/resources/templates/pico/nima/http-method.java.hbs
@@ -0,0 +1,104 @@
+{{!
+Copyright (c) 2023 Oracle and/or its affiliates.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+}}
+package {{packageName}};
+
+import io.helidon.common.Weight;
+import io.helidon.common.http.Http;
+
+{{#if http.hasQueryParams}}import io.helidon.common.http.HttpException;
+
+import io.helidon.common.uri.UriQuery;
+{{/if}}
+import io.helidon.nima.webserver.http.GeneratedHandler;
+import io.helidon.nima.webserver.http.Handler;
+import io.helidon.nima.webserver.http.ServerRequest;
+import io.helidon.nima.webserver.http.ServerResponse;
+
+import io.helidon.pico.ExternalContracts;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.inject.Provider;
+import jakarta.inject.Singleton;
+
+@jakarta.annotation.Generated({{{generatedSticker}}})
+@Singleton
+@Named("{{enclosingClassTypeName.name}}")
+@Weight({{weight}})
+@ExternalContracts(GeneratedHandler.class)
+public class {{className}} implements GeneratedHandler {
+{{#each http.headers}}private static final Http.HeaderName {{this.field}} = Http.Header.create("{{this.name}}");
+{{/each}}
+ private static final Http.Method METHOD = Http.Method.create("{{http.method}}");
+
+ private final Provider<{{enclosingClassTypeName.className}}> target;
+
+ private {{enclosingClassTypeName.className}} endpoint;
+
+ @Inject
+ {{className}}(Provider<{{enclosingClassTypeName.className}}> target) {
+ this.target = target;
+ }
+
+ @Override
+ public Http.Method method() {
+ return METHOD;
+ }
+
+ @Override
+ public String path() {
+ return "{{http.path}}";
+ }
+
+ @Override
+ public void beforeStart() {
+ this.endpoint = target.get();
+ }
+
+ @Override
+ public void afterStop() {
+ this.endpoint = null;
+ }
+
+ @Override
+ public void handle(ServerRequest req, ServerResponse res) {
+ {{#unless http.response.isVoid}}{{http.response.type}} response = {{/unless}}invokeMethod(req, res);
+ {{#if http.response.isVoid}}if (!res.isSent()) {
+ res.send();
+ }{{else}}res.send(response);{{/if}}
+ }
+
+ private {{#if http.response.isVoid}}void{{else}}{{http.response.type}}{{/if}} invokeMethod(ServerRequest req, ServerResponse res) {
+ {{#unless http.response.isVoid}}return {{/unless}}endpoint.{{http.methodName}}(
+ {{#each http.params}}{{{this}}}
+ {{/each}}
+ );
+ }
+{{#if http.hasQueryParams}}
+ private T query(ServerRequest req, ServerResponse res, String name, String defaultValue, Class type) {
+ UriQuery query = req.query();
+ if (query.contains(name)) {
+ // todo hardcoded type
+ return (T) query.value(name);
+ }
+ if (defaultValue == null) {
+ throw new HttpException("Query parameter \"" + name + "\" is required", Http.Status.BAD_REQUEST_400, true);
+ }
+ // todo also hardcoded type
+ return (T) defaultValue;
+ }
+{{/if}}
+}
diff --git a/nima/http2/webserver/pom.xml b/nima/http2/webserver/pom.xml
index c9ff2b27308..5f4723e18ca 100644
--- a/nima/http2/webserver/pom.xml
+++ b/nima/http2/webserver/pom.xml
@@ -37,8 +37,8 @@
helidon-nima-http2
- io.helidon.pico.builder.config
- helidon-pico-builder-config
+ io.helidon.builder
+ helidon-builder-configio.helidon.common.features
@@ -87,8 +87,8 @@
${helidon.version}
- io.helidon.pico.builder.config
- helidon-pico-builder-config-processor
+ io.helidon.builder
+ helidon-builder-config-processor${helidon.version}
@@ -102,4 +102,3 @@
-
diff --git a/nima/http2/webserver/src/main/java/io/helidon/nima/http2/webserver/Http2Config.java b/nima/http2/webserver/src/main/java/io/helidon/nima/http2/webserver/Http2Config.java
index 5498257146b..a7464b8a4bf 100644
--- a/nima/http2/webserver/src/main/java/io/helidon/nima/http2/webserver/Http2Config.java
+++ b/nima/http2/webserver/src/main/java/io/helidon/nima/http2/webserver/Http2Config.java
@@ -17,15 +17,15 @@
package io.helidon.nima.http2.webserver;
import io.helidon.builder.Builder;
+import io.helidon.builder.config.ConfigBean;
import io.helidon.common.http.RequestedUriDiscoveryContext;
import io.helidon.config.metadata.ConfiguredOption;
-import io.helidon.pico.builder.config.ConfigBean;
/**
* HTTP/2 server configuration.
*/
@Builder
-@ConfigBean(key = "server.connection-providers.http_2")
+@ConfigBean("server.connection-providers.http_2")
public interface Http2Config {
/**
* The size of the largest frame payload that the sender is willing to receive in bytes.
diff --git a/nima/http2/webserver/src/main/java/module-info.java b/nima/http2/webserver/src/main/java/module-info.java
index 36e3d7e12bb..065ed60666b 100644
--- a/nima/http2/webserver/src/main/java/module-info.java
+++ b/nima/http2/webserver/src/main/java/module-info.java
@@ -39,8 +39,8 @@
requires transitive io.helidon.nima.http2;
requires transitive io.helidon.nima.http.encoding;
requires transitive io.helidon.nima.http.media;
- requires io.helidon.pico.builder.config;
requires io.helidon.builder;
+ requires io.helidon.builder.config;
exports io.helidon.nima.http2.webserver;
exports io.helidon.nima.http2.webserver.spi;
diff --git a/nima/nima/src/main/java/io/helidon/nima/Nima.java b/nima/nima/src/main/java/io/helidon/nima/Nima.java
index 879cac489fc..216985aa915 100644
--- a/nima/nima/src/main/java/io/helidon/nima/Nima.java
+++ b/nima/nima/src/main/java/io/helidon/nima/Nima.java
@@ -47,4 +47,13 @@ private Nima() {
public static Config config() {
return CONFIG_INSTANCE.get();
}
+
+ /**
+ * Set global configuration. Once method {@link #config()} is invoked, the configuration cannot be modified.
+ *
+ * @param config configuration to use
+ */
+ public static void config(Config config) {
+ NIMA_CONFIG.set(config);
+ }
}
diff --git a/nima/sse/webserver/pom.xml b/nima/sse/webserver/pom.xml
index 23c62b9c674..c72d758eeb0 100644
--- a/nima/sse/webserver/pom.xml
+++ b/nima/sse/webserver/pom.xml
@@ -71,8 +71,8 @@
${helidon.version}
- io.helidon.pico.builder.config
- helidon-pico-builder-config-processor
+ io.helidon.builder
+ helidon-builder-config-processor${helidon.version}
@@ -86,4 +86,3 @@
-
diff --git a/nima/webserver/webserver/pom.xml b/nima/webserver/webserver/pom.xml
index 318c19e9daf..f248ac01ac8 100644
--- a/nima/webserver/webserver/pom.xml
+++ b/nima/webserver/webserver/pom.xml
@@ -69,25 +69,49 @@
helidon-nima-http-encoding
- io.helidon.pico.builder.config
- helidon-pico-builder-config
+
+ io.helidon.builder
+ helidon-builder-configio.helidon.common.features
- helidon-common-features-api
- provided
+ helidon-common-features
+
+
+
+ io.helidon.pico.configdriven
+ helidon-pico-configdriven-servicestrue
- io.helidon.config
- helidon-config-metadata
- provided
+ io.helidon.pico
+ helidon-pico-api
+
+
+ io.helidon.pico
+ helidon-pico-services
+ true
+
+
+ io.helidon.builder
+ helidon-builder-config-processor
+ providedtrue
-
- io.helidon.pico.builder.config
- helidon-pico-builder-config-processor
+ io.helidon.pico.configdriven
+ helidon-pico-configdriven-processor
+ provided
+ true
+
+
+ jakarta.inject
+ jakarta.inject-api
+ true
+
+
+ io.helidon.config
+ helidon-config-metadataprovidedtrue
@@ -138,6 +162,11 @@
io.helidon.common.testingtest
+
+ io.helidon.pico
+ helidon-pico-testing
+ test
+
@@ -153,8 +182,8 @@
${helidon.version}
- io.helidon.pico.builder.config
- helidon-pico-builder-config-processor
+ io.helidon.builder
+ helidon-builder-config-processor${helidon.version}
@@ -162,6 +191,11 @@
helidon-config-metadata-processor${helidon.version}
+
+ io.helidon.pico.configdriven
+ helidon-pico-configdriven-processor
+ ${helidon.version}
+
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/LoomServer.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/LoomServer.java
index 0d71da500f7..0d2e2838414 100644
--- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/LoomServer.java
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/LoomServer.java
@@ -39,12 +39,21 @@
import io.helidon.common.Version;
import io.helidon.common.context.Context;
+import io.helidon.common.features.HelidonFeatures;
+import io.helidon.common.features.api.HelidonFlavor;
import io.helidon.nima.common.tls.Tls;
import io.helidon.nima.http.encoding.ContentEncodingContext;
import io.helidon.nima.http.media.MediaContext;
import io.helidon.nima.webserver.http.DirectHandlers;
+import io.helidon.nima.webserver.http.HttpFeature;
import io.helidon.nima.webserver.spi.ServerConnectionSelector;
+import io.helidon.pico.configdriven.ConfiguredBy;
+import jakarta.annotation.PostConstruct;
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+
+@ConfiguredBy(ServerConfig.class)
class LoomServer implements WebServer {
private static final System.Logger LOGGER = System.getLogger(LoomServer.class.getName());
private static final String EXIT_ON_STARTED_KEY = "exit.on.started";
@@ -60,7 +69,23 @@ class LoomServer implements WebServer {
private volatile List startFutures;
private volatile boolean alreadyStarted = false;
- LoomServer(Builder builder, DirectHandlers directHandlers) {
+ @Inject
+ LoomServer(ServerConfig serverConfig, List> features) {
+ this(WebServer.builder()
+ .routing(it -> {
+ features.stream()
+ .map(Provider::get)
+ .forEach(it::addFeature);
+ }),
+ serverConfig,
+ DirectHandlers.builder().build());
+ }
+
+ LoomServer(Builder builder, ServerConfig serverConfig, DirectHandlers directHandlers) {
+ // this will be modified once we move everything to config driven
+ builder.host(serverConfig.host());
+ builder.port(serverConfig.port());
+
this.registerShutdownHook = builder.shutdownHook();
this.context = builder.context();
@@ -117,8 +142,12 @@ class LoomServer implements WebServer {
.factory());
}
+ @PostConstruct
@Override
public WebServer start() {
+ HelidonFeatures.flavor(HelidonFlavor.NIMA);
+ HelidonFeatures.print(HelidonFlavor.NIMA, Version.VERSION, false);
+
try {
lifecycleLock.lockInterruptibly();
} catch (InterruptedException e) {
@@ -224,12 +253,12 @@ private void startIt() {
now = System.currentTimeMillis() - now;
long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
- LOGGER.log(System.Logger.Level.INFO, "Helidon NÃma " + Version.VERSION);
LOGGER.log(System.Logger.Level.INFO, "Started all channels in "
+ now + " milliseconds. "
+ uptime + " milliseconds since JVM startup. "
+ "Java " + Runtime.version());
+
if ("!".equals(System.getProperty(EXIT_ON_STARTED_KEY))) {
LOGGER.log(System.Logger.Level.INFO, String.format("Exiting, -D%s set.", EXIT_ON_STARTED_KEY));
System.exit(0);
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ServerConfig.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ServerConfig.java
new file mode 100644
index 00000000000..12a15e4f52b
--- /dev/null
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ServerConfig.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.nima.webserver;
+
+import io.helidon.builder.config.ConfigBean;
+import io.helidon.config.metadata.ConfiguredOption;
+
+/**
+ * Server configuration bean.
+ * There is a generated {@link io.helidon.nima.webserver.DefaultServerConfig} implementing this type, that can be used
+ * to create an instance manually through a builder, or using configuration.
+ */
+@ConfigBean(value = "server", levelType = ConfigBean.LevelType.ROOT, drivesActivation = true)
+public interface ServerConfig {
+ /**
+ * Host of the default socket. Defaults to all host addresses ({@code 0.0.0.0}).
+ *
+ * @return host address to listen on (for the default socket)
+ */
+ @ConfiguredOption("0.0.0.0")
+ String host();
+
+ /**
+ * Port of the default socket.
+ * If configured to {@code 0} (the default), server starts on a random port.
+ *
+ * @return port to listen on (for the default socket)
+ */
+ @ConfiguredOption("0")
+ int port();
+}
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ServerListener.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ServerListener.java
index 3f68aaf7838..3771ea4e1df 100644
--- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ServerListener.java
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ServerListener.java
@@ -237,14 +237,13 @@ void start() {
connectedPort,
socketName));
- if (listenerConfig.writeQueueLength() <= 1) {
- LOGGER.log(System.Logger.Level.INFO, "[" + serverChannelId + "] direct writes");
- } else {
- LOGGER.log(System.Logger.Level.INFO,
- "[" + serverChannelId + "] async writes, queue length: " + listenerConfig.writeQueueLength());
- }
-
if (LOGGER.isLoggable(TRACE)) {
+ if (listenerConfig.writeQueueLength() <= 1) {
+ LOGGER.log(System.Logger.Level.TRACE, "[" + serverChannelId + "] direct writes");
+ } else {
+ LOGGER.log(System.Logger.Level.TRACE,
+ "[" + serverChannelId + "] async writes, queue length: " + listenerConfig.writeQueueLength());
+ }
if (listenerConfig.hasTls()) {
debugTls(serverChannelId, listenerConfig.tls());
}
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/WebServer.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/WebServer.java
index c02683adfc2..b8a81bfebfe 100644
--- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/WebServer.java
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/WebServer.java
@@ -38,10 +38,12 @@
import io.helidon.nima.webserver.http.HttpRouting;
import io.helidon.nima.webserver.spi.ServerConnectionProvider;
import io.helidon.nima.webserver.spi.ServerConnectionSelector;
+import io.helidon.pico.Contract;
/**
* Server that opens server sockets and handles requests through routing.
*/
+@Contract
public interface WebServer {
/**
* The default server socket configuration name. All the default server socket
@@ -165,6 +167,8 @@ class Builder implements io.helidon.common.Builder, Router.R
private final HelidonServiceLoader.Builder connectionProviders
= HelidonServiceLoader.builder(ServiceLoader.load(ServerConnectionProvider.class));
+ private final DefaultServerConfig.Builder configBuilder = DefaultServerConfig.builder();
+
private Config providersConfig = Config.empty();
// MediaContext should be updated with config processing or during final build if not set.
private MediaContext mediaContext;
@@ -197,7 +201,7 @@ public WebServer build() {
mediaContext(MediaContext.create());
}
- return new LoomServer(this, directHandlers.build());
+ return new LoomServer(this, configBuilder.build(), directHandlers.build());
}
/**
@@ -333,6 +337,7 @@ public Builder defaultSocket(Consumer socketBuild
*/
public Builder port(int port) {
socket(DEFAULT_SOCKET_NAME).port(port);
+ configBuilder.port(port);
return this;
}
@@ -344,6 +349,7 @@ public Builder port(int port) {
*/
public Builder host(String host) {
socket(DEFAULT_SOCKET_NAME).host(host);
+ configBuilder.host(host);
return this;
}
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/GeneratedHandler.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/GeneratedHandler.java
new file mode 100644
index 00000000000..d9d90a23f20
--- /dev/null
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/GeneratedHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.nima.webserver.http;
+
+import io.helidon.common.http.Http;
+
+/**
+ * This class is only used by generated code.
+ *
+ * @deprecated please do not use directly, designed for generated code
+ * @see io.helidon.nima.webserver.http1.Http1Route
+ * @see io.helidon.nima.webserver.http.Handler
+ */
+@Deprecated(since = "4.0.0")
+public interface GeneratedHandler extends Handler {
+ /**
+ * HTTP Method of this handler.
+ *
+ * @return method
+ */
+ Http.Method method();
+
+ /**
+ * Path this handler should be registered at.
+ *
+ * @return path, may include path parameter (template)
+ */
+ String path();
+
+
+}
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1Config.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1Config.java
index bfece6ce3db..7a01f0b0c75 100644
--- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1Config.java
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1Config.java
@@ -19,15 +19,15 @@
import io.helidon.builder.Builder;
import io.helidon.builder.Singular;
+import io.helidon.builder.config.ConfigBean;
import io.helidon.common.http.RequestedUriDiscoveryContext;
import io.helidon.config.metadata.ConfiguredOption;
-import io.helidon.pico.builder.config.ConfigBean;
/**
* HTTP/1.1 server configuration.
*/
@Builder(interceptor = Http1BuilderInterceptor.class)
-@ConfigBean(key = "server.connection-providers.http_1_1")
+@ConfigBean("server.connection-providers.http_1_1")
public interface Http1Config {
/**
* Maximal size of received HTTP prologue (GET /path HTTP/1.1).
diff --git a/nima/webserver/webserver/src/main/java/module-info.java b/nima/webserver/webserver/src/main/java/module-info.java
index 999f2a73b3d..d930e5d13a1 100644
--- a/nima/webserver/webserver/src/main/java/module-info.java
+++ b/nima/webserver/webserver/src/main/java/module-info.java
@@ -17,9 +17,6 @@
import io.helidon.common.features.api.Feature;
import io.helidon.common.features.api.HelidonFlavor;
import io.helidon.nima.webserver.http.spi.SinkProvider;
-import io.helidon.nima.webserver.http1.Http1ConnectionProvider;
-import io.helidon.nima.webserver.http1.spi.Http1UpgradeProvider;
-import io.helidon.nima.webserver.spi.ServerConnectionProvider;
/**
* Loom based WebServer.
@@ -39,7 +36,9 @@
requires transitive io.helidon.common.security;
requires io.helidon.logging.common;
requires io.helidon.builder;
- requires io.helidon.pico.builder.config;
+ requires io.helidon.builder.config;
+ requires io.helidon.common.features.api;
+ requires io.helidon.common.features;
requires io.helidon.common.task;
requires java.management;
@@ -49,8 +48,13 @@
requires jakarta.annotation;
requires io.helidon.common.uri;
- requires static io.helidon.common.features.api;
requires static io.helidon.config.metadata;
+ requires static io.helidon.pico.configdriven.services;
+ requires static jakarta.inject;
+
+ // needed to compile pico generated classes
+ requires io.helidon.pico.api;
+ requires static io.helidon.pico.services;
// provides multiple packages due to intentional cyclic dependency
// we want to support HTTP/1.1 by default (we could fully separate it, but the API would be harder to use
@@ -63,9 +67,10 @@
exports io.helidon.nima.webserver.http1;
exports io.helidon.nima.webserver.http1.spi;
- uses Http1UpgradeProvider;
- uses ServerConnectionProvider;
+ uses io.helidon.nima.webserver.http1.spi.Http1UpgradeProvider;
+ uses io.helidon.nima.webserver.spi.ServerConnectionProvider;
uses SinkProvider;
- provides ServerConnectionProvider with Http1ConnectionProvider;
-}
\ No newline at end of file
+ provides io.helidon.nima.webserver.spi.ServerConnectionProvider with io.helidon.nima.webserver.http1.Http1ConnectionProvider;
+ provides io.helidon.pico.Module with io.helidon.nima.webserver.Pico$$Module;
+}
diff --git a/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/WebServerConfigDrivenTest.java b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/WebServerConfigDrivenTest.java
new file mode 100644
index 00000000000..56365fe2de4
--- /dev/null
+++ b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/WebServerConfigDrivenTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.nima.webserver;
+
+import io.helidon.config.Config;
+import io.helidon.pico.DefaultBootstrap;
+import io.helidon.pico.Phase;
+import io.helidon.pico.PicoServices;
+import io.helidon.pico.ServiceProvider;
+import io.helidon.pico.Services;
+import io.helidon.pico.testing.PicoTestingSupport;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+class WebServerConfigDrivenTest {
+ static final boolean NORMAL_PRODUCTION_PATH = false;
+
+ @AfterEach
+ public void reset() {
+ if (!NORMAL_PRODUCTION_PATH) {
+ // requires 'pico.permits-dynamic=true' to be able to reset
+ PicoTestingSupport.resetAll();
+ }
+ }
+
+ @Test
+ void testConfigDriven() {
+ // This will pick up application.yaml from the classpath as default configuration file
+ Config config = Config.create();
+
+ if (NORMAL_PRODUCTION_PATH) {
+ // bootstrap Pico with our config tree when it initializes
+ PicoServices.globalBootstrap(DefaultBootstrap.builder().config(config).build());
+ }
+
+ // initialize Pico, and drive all activations based upon what has been configured
+ Services services;
+ if (NORMAL_PRODUCTION_PATH) {
+ services = PicoServices.realizedServices();
+ } else {
+ PicoServices picoServices = PicoTestingSupport.testableServices(config);
+ services = picoServices.services();
+ }
+
+ ServiceProvider webServerSp = services.lookupFirst(WebServer.class);
+ assertThat(webServerSp.currentActivationPhase(), is(Phase.ACTIVE));
+ }
+
+}
diff --git a/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/WebServerConfigTest.java b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/WebServerConfigTest.java
index 88440ee24c0..32a95b51b80 100644
--- a/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/WebServerConfigTest.java
+++ b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/WebServerConfigTest.java
@@ -37,6 +37,8 @@
import io.helidon.nima.http.media.jsonp.JsonpMediaSupportProvider;
import io.helidon.nima.webserver.spi.ServerConnectionSelector;
+import org.junit.jupiter.api.Test;
+
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
diff --git a/nima/webserver/webserver/src/test/resources/application.yaml b/nima/webserver/webserver/src/test/resources/application.yaml
index d7971c309d4..a75727823e1 100644
--- a/nima/webserver/webserver/src/test/resources/application.yaml
+++ b/nima/webserver/webserver/src/test/resources/application.yaml
@@ -41,3 +41,6 @@ server2:
media-support:
content-encoding:
+
+pico:
+ permits-dynamic: true
diff --git a/pico/README.md b/pico/README.md
new file mode 100644
index 00000000000..f3ebd0d300e
--- /dev/null
+++ b/pico/README.md
@@ -0,0 +1,335 @@
+# helidon-pico
+
+Helidon Pico is an optional feature in Helidon. At its core it provides these main features:
+
+1. A service registry. The service registry holds service providers and are (or otherwise can produce) services. Each service provider in the registry advertises its meta-information for what each service provides, and what it requires in the way of dependencies.
+
+2. A lifecycle engine. Each service provider in the registry remains dormant until there is a "demand for activation". This "demand for activation" is also known as "lazy activation" that can come in different ways. One way is to simply "get" a service (or services) from the registry that matches the meta-information criteria you provide programmatically. If the service (or services) need other services as part of the activation then those services are chain activated, recursively. Pico provides graceful startup and shutdown at a micro service-level as well as at a macro application level for all services in the service registry.
+
+3. Integrations and Extensibility. More will be mentioned on this later.
+
+Over the foundation of these three main features of "services registry", "lifecycle", and "extensibility" there are a number of other tooling and layers that are delivered from various Pico submodules that provide the following additional features and benefits:
+
+1. A minimalist, compile-time generated dependency injection framework that is free from reflection, and compliant to the JSR-330 injection specification. Compile-time source code generation has a number of advantages, including: (a) pre-runtime validation of the DI model, (b) visibility into your application by providing "less magic", which in turn fosters understandability and debug-ability of your application, (c) deterministic behavior (instead of depending on reflection and classpath ordering, etc.) and (c) performance, since binding the model at compile-time is more efficient than computing it at runtime. Pico (through its tooling) provides you with a mix of declarative and programmatic ways to build your application. It doesn't have to be just one or the other like other popular frameworks in use today require. Inspiration for Pico, however, did come from many libraries and frameworks that came before it (e.g., Jakarta Hk2, Google Guice, Spring, CDI, and even OSGi). Foundationally, Pico provides a way to develop declarative code using standard (i.e., javax/jakarta, not Helidon specific) annotation types.
+
+2. Integrations. Blending services from different providers (e.g., Helidon services like WebServer, your application service, 3rd party service, etc.) becomes natural in the Pico framework, and enables you to build fully-integrated, feature-rich applications more easily.
+
+3. Extensibility. At the micro level developers can provide their own templates for things like code generation, or even provide an entirely different implementation from this reference implementation Helidon provides. At a macro level (and post the initial release), Pico will be providing a foundation to extend your Guice, Spring, Hk2, CDI, application naturally into one application runtime. The Helidon team fields a number of support questions that involve this area involving "battling DI frameworks" like CDI w/ Hk2. In time, Pico aims to smooth out this area through the integrations and extensibility features that it will be providing.
+
+4. Interception. Annotations are provided, that in conjunction with Pico's code-generation annotation processors, allow services in the service registry to support interception and decoration patterns - without the use of reflection at runtime, which is conducive to native image.
+
+***
+__Pico currently support Java 11+, using jakarta.inject or javax.inject, jakarta.annotations or javax.annotations.__
+***
+
+The Helidon Team believes that the above features help developers achieve the following goals:
+* More IoC options. With Pico... developers can choose to use an imperative coding style or a declarative IoC style previously only available with CDI using Helidon MP. At the initial release (Helidon 4.0), however, Pico will only be available with Helidon Nima support.
+* Compile-time benefits. With Pico... developers can decide to use compile-time code generation into their build process, thereby statically and deterministically wiring their injection model while still enjoying the benefits of a declarative approach for writing their application. Added to this, all code-generated artifacts are in source form instead of bytecode thereby making your application more readable, understandable, consistent, and debuggable. Furthermore, DI model inconsistencies can be found during compile-time instead of at runtime.
+* Improved performance. Pushing more into compile-time helps reduce what otherwise would need to occur (often times via reflection) to built/compile-time processing. Native code is generated that is further optimized by the compiler. Additionally, with lazy activation of services, only what is needed is activated. Anything not used may be in the classpath is available, but unless and until there is demand for those services they remain dormant. You control the lifecycle in your application code.
+* Additional lifecycle options. Pico can handle micro, service-level activations for your services, as well as offer controlled shutdown if desired.
+
+Many DI frameworks start simple and over time become bloated with "bells and whistle" type features - the majority of which most developers don't need and will never use; especially in today's world of microservices where the application scope is the JVM process itself.
+
+***
+The Helidon Pico Framework is a reset back to basics, and perfect for such use cases requiring minimalism but yet still be extensible. This is why Pico intentionally chose to implement the earlier JSR-330 specification at its foundation. Application Scope == Singleton Scope in a microservices world.
+***
+
+Request and Session scopes are simply not made available in Pico. We believe that scoping is a recipe for undo complexity, confusion, and bugs for the many developers today.
+
+## Terminology
+* DI - Dependency Injection.
+* Inject - The assignment of a service instance to a field or method setter that has been annotated with @Inject - also referred to as an injection point. In Spring this would be referred to as 'Autowired'.
+* Injection Plan - The act of determining how your application will resolve each injection point. In Pico this can optionally be performed at compile-time. But even when the injection plan is deferred to runtime it is resolved without using reflection, and is therefore conducive to native image restrictions and enhanced performance.
+* Service (aka Bean) - In Spring this would be referred to as a bean with a @Service annotation; These are concrete class types in your application that represents some sort of business logic.
+* Scope - This refers to the cardinality of a service instance in your application.
+* Singleton - jakarta.inject.Singleton or javax.inject.Singleton - This is the default scope for services in Pico just like it is in Spring.
+* Provided - jakarta.inject.Provider or javax.inject.Provider - If the scope of a service is not Singleton then it is considered to be a Provided scope - and the cardinality will be ascribed to the implementation of the Provider to determine its cardinality. The provider can optionally use the injection point context to determine the appropriate instance and/or cardinality it provides.
+* Contract - These are how a service can alias itself for injection. Contracts are typically the interface or abstract base class definitions of a service implementation. Injection points must be based upon either using a contract or service that pico is aware of, usually through annotation processing at compile time.
+* Qualifier - jakarta.inject.qualifier or javax.inject.qualifier - These are meta annotations that can be ascribed to other annotations. One built-in qualifier type is @Named in the same package.
+* Dependency - An injection point represents what is considered to be a dependency, perhaps qualified or Optional, on another service or contract. This is just another what to describe an injection point.
+* Activator (aka ServiceProvider) - This is what is code generated by Pico to lazily activate your service instance(s) in the Pico services registry, and it handles resolving all dependencies it has, along with injecting the fields, methods, etc. that are required to be satisfied as part of that activation process.
+* Services (aka services registry) - This is the collection of all services that are known to the JVM/runtime in Pico.
+* Module - This is where your application will "bind" services into the services registry - typically code generated, and typically with one module per jar/module in your application.
+* Application - The fully realized set of modules and services/service providers that constitute your application, and code-generated using Helidon Pico Tooling.
+
+## Getting Started
+As stated in the introduction above, the Pico framework aims to provide a minimalist API implementation. As a result, it might be surprising to learn how small the actual API is for Pico - see [pico api](./pico) and the API/annotation types at [pico api](./api/src/main/java/io/helidon/pico). If you are already familiar with [jakarta.inject](https://javadoc.io/doc/jakarta.inject/jakarta.inject-api/latest/index.html) and optionally, [jakarta.annotation](https://javadoc.io/doc/jakarta.annotation/jakarta.annotation-api/latest/jakarta.annotation/jakarta/annotation/package-summary.html) then basically you are ready to go. But if you've never used DI before then first review the basics of [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection).
+
+The prerequisites are familiarity with dependency injection, Java 11+, and maven 3.8.5+.
+
+The best way to learn Helidon Pico is by looking at [the examples](../examples/pico). But if you want to immediately get started here are the basics steps:
+
+1. Put these in your pom.xml or gradle.build file:
+ Annotation processor dependency / path:
+```
+ io.helidon.pico
+ helidon-pico-processor
+ ${helidon.version}
+```
+Compile-time dependency:
+```
+
+ io.helidon.pico
+ helidon-pico-services
+ ${helidon.version}
+
+```
+
+2. Write your application using w/ standard jakarta.inject.* and jakarta.annotation.* types. Again, see any of [the examples](./examples/README.md) for pointers as needed.
+
+3. Build and run. In a DI-based framework, the frameworks "owns" the creation of services in accordance with the Scope each service is declared as. You therefore need to get things started by creating demand for the initial service(s) instead of ever calling new directly in your application code. Generally speaking, there are two such ways to get things started at runtime:
+
+* If you know the class you want to create then look it up directly using the Services SPI. Here is a sample excerpt from [the book example](./examples/book/README.md):
+
+```
+ Services services = PicoServices.realizedServices();
+ // query
+ ServiceProvider serviceProvider = services.lookupFirst(MyService.class);
+ // lazily activate
+ MyService myLazyActivatedService = serviceProvider.get();
+```
+
+* If there are a collection of services requiring activation at startup then we recommend annotating those service implementation types with RunLevel(RunLevel.STARTUP) and then use code below in main() to lazily activate those services. Note that whenever List-based injection is used in Pico all services matching the injection criteria will be in the injected (and immutable) list. The list will always be in order according to the Weight annotation value, ranking from the highest weight to the lowest weight. If services are not weighted explicitly, then a default weight is assigned. If the weight is the same for two services, then the secondary ordering will be based on the FN class name of the service types. While Weight determines list order, the RunLevel annotation is used to rank the startup ordering, from the lowest value to the highest value, where RunLevel.STARTUP == 0. The developer is expected to activate these directly using code like the following (the get() lazily creates & activates the underlying service type):
+
+```
+ List> startupServices = services
+ .lookup(DefaultServiceInfoCriteria.builder().runLevel(RunLevel.STARTUP).build());
+ startupServices.stream().forEach(ServiceProvider::get);
+```
+
+* If the ordering of the list of services is important, remember to use the Weight and/or RunLevel annotations to establish the priority / weighted ordering, and startup ordering.
+
+## More Advanced Features
+
+* Pico provides a means to generate "Activators" (the DI supporting types) for externally built modules as well as supporting javax annotated types. See [the logger example](../examples/pico/logger) for use of these features.
+
+* Pico offers services the ability to be intercepted. If your service contains any annotation that itself is annotated with InterceptorTrigger then the code generated for that service will support interception. The Helidon Nima project provides these types of examples.
+
+* Pico provides meta-information for each service in its service registry, including such information as what contracts are provided by each service as well as describing its dependencies.
+
+* Java Module System support / generation. Pico generates a proposed module-info.java.pico file for your module (look for module-info.java.pico under ./target/pico).
+
+* Pico provides a maven-plugin that allows the injection graph to be (a) validated for completeness, and (b) deterministically bound to the service implementation - at compile-time. This is demonstrated in each of the examples, the result of which leads to early detection of issues at compile-time instead of at runtime as well as a marked performance enhancement.
+
+* Testability. The [testing](./testing) module offers a set of types in order to facility for creating fake/mock services for various testing scenarios.
+
+* Extensibility. The entire Pico Framework is designed to by extended either at a micro level (developers can override mustache/handlebar templates) to the macro level (developers can provide their own implementation of any SPI). Another example of internal extensibility is via our [config-driven](./configdriven) services.
+
+* Determinism. Pico strives to keep your application as deterministic as possible. Dynamically adding services post-initialization will not be allowed, and will even result in a runtime exception (configurable). Any service that is "a provider" that dynamically creates a service in runtime code will issue build failures or warnings (configurable). All services are always ordered first according to Weight and secondarily according to type name (instead of relying on classpath ordering). Essentially, all areas of Pico attempts to keep your application as deterministic as possible at production runtime.
+
+## Modules
+
+* [api](./api) - the Pico API and SPI; depends on jakarta-inject and jakarta-annotations. Required as a maven compile-time dependency for runtime consumption.
+* [services](./services) - contains the default runtime implementation of the Pico API/SPI; depends on the pico api module above. Requires as a maven compile-time dependency for runtime consumption.
+* [config-driven](./configdriven) - Extensions to Pico to integrate directly with the [Helidon Config](../config) subsystem.
+* [tools](./tools) - contains the libraries and template-based codegen mustache resources as well as model validation tooling; depends on runtime services. Only required at build time and is not required for Pico at runtime.
+* [processor](./processor) - contains the libraries for annotation processing; depends on tools. Only required at build time and is not required for Pico at runtime.
+* [maven-plugin](./maven-plugin) - provides code generation Mojo wrappers for maven; depends on tools. Only required at build time and is not required for Pico at runtime. This is what would be used to create your Application.
+* [testing](./testing) - provides testing types useful for Pico unit & integration testing.
+* [tests](./tests) - used internally for testing Pico.
+* [examples](../examples/pico) - providing examples for how to use Pico as well as side-by-side comparisons for Pico compared to Guice, Dagger2, Hk2, etc.
+
+## How Pico Works
+
+* The Pico annotation [processor](./processor) will look for standard jakarta/javax inject and jakarta/javax annotation types. When these types are found in a class that is being compiled by javac, Pico will trigger the creation of an Activator for that service class/type. For example, if you have a FooImpl class implementing Foo interface, and the FooImpl either contains "@Inject" or "@Singleton" then the presence of either of these annotations will trigger the creation of a FooImpl$$picoActivator to be created. The Activator is used to (a) describe the service in terms of what service contracts (i.e., interfaces) are advertised by FooImpl - in this case Foo (if Foo is annotated with @Contract or if "-Aio.helidon.pico.autoAddNonContractInterfaces=true" is used at compile-time), (b) lifecycle of services including creation, calling injection-based setters, and any PostConstruct or PreDestroy methods.
+
+* If one or more activators are created at compile-time, then a Pico$$Module is also created to aggregate the services for the given module. Below is an example if a picoModule from [examples/logger](./examples/logger). At initialization time of Pico, all Modules will be located using the ServiceLocator and each service will be binded into the Pico service registry.
+
+```java
+@Generated(value = "io.helidon.pico.tools.creator.impl.DefaultActivatorCreator", comments = "version = 1")
+@Singleton @Named(Pico$$Module.NAME)
+public class Pico$$Module implements Module {
+ static final String NAME = "pico.examples.logger.common";
+
+ @Override
+ public Optional getName() {
+ return Optional.of(NAME);
+ }
+
+ @Override
+ public String toString() {
+ return NAME + ":" + getClass().getName();
+ }
+
+ @Override
+ public void configure(ServiceBinder binder) {
+ binder.bind(io.helidon.pico.examples.logger.common.AnotherCommunicationMode$$picoActivator.INSTANCE);
+ binder.bind(io.helidon.pico.examples.logger.common.Communication$$picoActivator.INSTANCE);
+ binder.bind(io.helidon.pico.examples.logger.common.DefaultCommunicator$$picoActivator.INSTANCE);
+ binder.bind(io.helidon.pico.examples.logger.common.EmailCommunicationMode$$picoActivator.INSTANCE);
+ binder.bind(io.helidon.pico.examples.logger.common.ImCommunicationMode$$picoActivator.INSTANCE);
+ binder.bind(io.helidon.pico.examples.logger.common.LoggerProvider$$picoActivator.INSTANCE);
+ binder.bind(io.helidon.pico.examples.logger.common.SmsCommunicationMode$$picoActivator.INSTANCE);
+ }
+}
+```
+
+And just randomly taking one of the generated Activators:
+```java
+@Generated(value = "io.helidon.pico.tools.DefaultActivatorCreator", comments = "version=1")
+public class SmsCommunicationMode$$Pico$$Activator
+ extends io.helidon.pico.services.AbstractServiceProvider {
+ private static final DefaultServiceInfo serviceInfo =
+ DefaultServiceInfo.builder()
+ .serviceTypeName(io.helidon.examples.pico.logger.common.SmsCommunicationMode.class.getName())
+ .addExternalContractsImplemented(io.helidon.examples.pico.logger.common.CommunicationMode.class.getName())
+ .activatorTypeName(SmsCommunicationMode$$Pico$$Activator.class.getName())
+ .addScopeTypeName(jakarta.inject.Singleton.class.getName())
+ .addQualifier(io.helidon.pico.DefaultQualifierAndValue.create(jakarta.inject.Named.class, "sms"))
+ .build();
+
+ /**
+ * The global singleton instance for this service provider activator.
+ */
+ public static final SmsCommunicationMode$$Pico$$Activator INSTANCE = new SmsCommunicationMode$$Pico$$Activator();
+
+ /**
+ * Default activator constructor.
+ */
+ protected SmsCommunicationMode$$Pico$$Activator() {
+ serviceInfo(serviceInfo);
+ }
+
+ /**
+ * The service type of the managed service.
+ *
+ * @return the service type of the managed service
+ */
+ public Class> serviceType() {
+ return io.helidon.examples.pico.logger.common.SmsCommunicationMode.class;
+ }
+
+ @Override
+ public DependenciesInfo dependencies() {
+ DependenciesInfo deps = Dependencies.builder(io.helidon.examples.pico.logger.common.SmsCommunicationMode.class.getName())
+ .add("logger", java.util.logging.Logger.class, ElementKind.FIELD, Access.PACKAGE_PRIVATE)
+ .build();
+ return Dependencies.combine(super.dependencies(), deps);
+ }
+
+ @Override
+ protected SmsCommunicationMode createServiceProvider(Map deps) {
+ return new io.helidon.examples.pico.logger.common.SmsCommunicationMode();
+ }
+
+ @Override
+ protected void doInjectingFields(Object t, Map deps, Set injections, String forServiceType) {
+ super.doInjectingFields(t, deps, injections, forServiceType);
+ SmsCommunicationMode target = (SmsCommunicationMode) t;
+ target.logger = (java.util.logging.Logger) get(deps, "io.helidon.examples.pico.logger.common.logger");
+ }
+
+}
+```
+
+* As you can see from above example, the Activators are effectively managing the lifecycle and injection of your classes. These generated Activator types are placed in the same package as your class(es). Since Pico is avoiding reflection, however, it means that only public, protected, and package private injection points are supported. private and static injection points are not supported by the framework.
+
+* If an annotation in your service is meta-annotated with InterceptedTrigger, then an extra service type is created that will trigger interceptor service code generation. For example, if FooImpl was found to have one such annotation then FooImpl$$Pico$$Interceptor would also be created along with an activator for that interceptor. The interceptor would be created with a higher weight than your FooImpl, and would therefore be "preferred" when a single @Inject is used for Foo or FooImpl. If a list is injected then it would appear towards the head of the list. Once again, all reflection is avoided in these generated classes. Any calls to Foo/FooImpl will be interceptable for any Interceptor that is @Named to handle that type name. Search the test code and Nima code for such examples as this is an advanced feature.
+
+* The [maven-plugin](./maven-plugin) can optionally be used to avoid Pico lookup resolutions at runtime within each service activation. At startup Pico will attempt to first use the Application to avoid lookups. The best practice is to apply the maven-plugin to create-application on your maven assembly - this is usually your "final" application module that depends upon every other service / module in your entire deployed application. Here is the Pico$$Application from [examples/logger](../examples/pico/logger):
+
+```java
+@Generated({"generator=io.helidon.pico.maven.plugin.ApplicationCreatorMojo", "ver=1"})
+@Singleton @Named(Pico$$Application.NAME)
+public class Pico$$Application implements Application {
+ static final String NAME = "unnamed";
+
+ @Override
+ public Optional getName() {
+ return Optional.of(NAME);
+ }
+
+ @Override
+ public String toString() {
+ return NAME + ":" + getClass().getName();
+ }
+
+ @Override
+ public void configure(ServiceInjectionPlanBinder binder) {
+ /**
+ * In module name "pico.examples.logger.common".
+ * @see {@link io.helidon.pico.examples.logger.common.AnotherCommunicationMode }
+ */
+ binder.bindTo(io.helidon.pico.examples.logger.common.AnotherCommunicationMode$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.logger",
+ io.helidon.pico.examples.logger.common.LoggerProvider$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "pico.examples.logger.common".
+ * @see {@link io.helidon.pico.examples.logger.common.Communication }
+ */
+ binder.bindTo(io.helidon.pico.examples.logger.common.Communication$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.|2(1)",
+ io.helidon.pico.examples.logger.common.LoggerProvider$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.|2(2)",
+ io.helidon.pico.examples.logger.common.DefaultCommunicator$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "pico.examples.logger.common".
+ * @see {@link io.helidon.pico.examples.logger.common.DefaultCommunicator }
+ */
+ binder.bindTo(io.helidon.pico.examples.logger.common.DefaultCommunicator$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.sms",
+ io.helidon.pico.examples.logger.common.SmsCommunicationMode$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.email",
+ io.helidon.pico.examples.logger.common.EmailCommunicationMode$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.im",
+ io.helidon.pico.examples.logger.common.ImCommunicationMode$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.|1(1)",
+ io.helidon.pico.examples.logger.common.AnotherCommunicationMode$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "pico.examples.logger.common".
+ * @see {@link io.helidon.pico.examples.logger.common.EmailCommunicationMode }
+ */
+ binder.bindTo(io.helidon.pico.examples.logger.common.EmailCommunicationMode$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.logger",
+ io.helidon.pico.examples.logger.common.LoggerProvider$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "pico.examples.logger.common".
+ * @see {@link io.helidon.pico.examples.logger.common.ImCommunicationMode }
+ */
+ binder.bindTo(io.helidon.pico.examples.logger.common.ImCommunicationMode$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.logger",
+ io.helidon.pico.examples.logger.common.LoggerProvider$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "pico.examples.logger.common".
+ * @see {@link io.helidon.pico.examples.logger.common.LoggerProvider }
+ */
+ binder.bindTo(io.helidon.pico.examples.logger.common.LoggerProvider$$picoActivator.INSTANCE)
+ .commit();
+
+ /**
+ * In module name "pico.examples.logger.common".
+ * @see {@link io.helidon.pico.examples.logger.common.SmsCommunicationMode }
+ */
+ binder.bindTo(io.helidon.pico.examples.logger.common.SmsCommunicationMode$$picoActivator.INSTANCE)
+ .bind("io.helidon.pico.examples.logger.common.logger",
+ io.helidon.pico.examples.logger.common.LoggerProvider$$picoActivator.INSTANCE)
+ .commit();
+ }
+}
+```
+
+* The maven-plugin can additionally be used to create the Pico DI supporting types (Activators, Modules, Interceptors, Applications, etc.) from introspecting an external jar - see the [examples](../examples/pico) for details.
+
+That is basically all there is to know to get started and become productive using Pico.
+
+## Special Notes to Providers & Contributors
+Pico aims to provide an extensible, SPI-based mechanism. There are many ways Pico can be overridden, extended, or even replaced with a different implementation than what is provided out of the built-in reference implementation modules included. Of course, you can also contribute directly by becoming a committer. However, if you are looking to fork the implementation then you are strongly encouraged to honor the "spirit of this framework" and follow this as a high-level guide:
+
+* In order to be a Pico provider implementation, the provider must supply an implementation for PicoServices discoverable by the
+ ServiceLoader with a higher-than-default Weight.
+* All SPI class definitions from the io.helidon.pico.spi package are considered primordial and therefore should not participate in injection or conventionally be considered injectable.
+* All service classes that are not targets for injection should be represented under
+ /META-INF/services/ to be found by the standard ServiceLocator.
+* Providers are encouraged to fail-fast during compile time - this implies a sophisticated set of tooling that can and should be applied to create and validate the integrity of the dependency graph at compile time instead of at runtime.
+* Providers are encouraged to avoid reflection completely at runtime.
+* Providers are encouraged to advertise capabilities and configuration using PicoServicesConfig.
diff --git a/pico/api/README.md b/pico/api/README.md
new file mode 100644
index 00000000000..22fd2ed6605
--- /dev/null
+++ b/pico/api/README.md
@@ -0,0 +1,112 @@
+This module contains all the API and SPI types that are applicable to a Helidon Pico based application.
+
+The API can logically be broken up into two categories - declarative types and imperative/programmatic types. The declarative form is the most common approach for using Pico.
+
+The declarative API is small and based upon annotations. This is because most of the supporting annotation types actually come directly from both of the standard javax/jakarta inject and javax/jakarta annotation modules. These standard annotations are supplemented with these proprietary annotation types offered here from Pico:
+
+* [@Contract](src/main/java/io/helidon/pico/Contract.java)
+* [@ExteralContracts](src/main/java/io/helidon/pico/ExternalContracts.java)
+* [@RunLevel](src/main/java/io/helidon/pico/RunLevel.java)
+
+The programmatic API is typically used to manually lookup and activate services (those that are typically annotated with @jakarta.inject.Singleton for example) directly. The main entry points for programmatic access can start from one of these two types:
+
+* [PicoServices](src/main/java/io/helidon/pico/PicoServices.java)
+* [Services](src/main/java/io/helidon/pico/Services.java)
+
+Note that this module only contains the common types for a Helidon Pico services provider. See the [pico-services](../services) module for the default reference implementation for this API / SPI.
+
+## Declaring a Service
+
+### Singleton
+In this example the service is declared to be one-per JVM. Also note that ApplicationScoped is effectively the same as Singleton scoped services in a (micro)services framework such as Helidon.
+
+```java
+@jakarta.inject.Singleton
+class MySingletonService implements ServiceContract {
+}
+
+```
+
+Also note that in the above example ServiceContract is typically the Contract or ExternalContract definition - which is a way to signify lookup capabilities within the Services registry.
+
+### Provider
+Provider extends the Singleton to delegate dynamic behavior to service creation. In other frameworks this would typically be called a Factory, Producer, or PerLookup.
+
+```java
+@jakarta.inject.Singleton
+class MySingletonProvider implements jakarta.inject.Provider {
+ @Override
+ ServiceContract get() {
+ ...
+ }
+}
+```
+
+Pico delegates the cardinality of to the provider implementation for which instance to return to the caller. However, note that the instances returned are not "owned" by Pico - unless those instances are looked up out of the Services registry.
+
+### InjectionPointProvider
+Here the standard jakarta.inject.Provider<> from above is extended to support contextual knowledge of "who is asking" to be injected with the service. In this way the provider implementation can provide the "right" instance based upon the caller's context.
+
+```java
+@Singleton
+@Named("*")
+public class BladeProvider implements InjectionPointProvider {
+ @Override
+ public Optional first(
+ ContextualServiceQuery query) {
+ ServiceInfoCriteria criteria = query.serviceInfoCriteria();
+
+ AbstractBlade blade;
+ if (criteria.qualifiers().contains(all) || criteria.qualifiers().contains(coarse)) {
+ blade = new CoarseBlade();
+ } else if (criteria.qualifiers().contains(fine)) {
+ blade = new FineBlade();
+ } else {
+ assert (criteria.qualifiers().isEmpty());
+ blade = new DullBlade();
+ }
+
+ return Optional.of(blade);
+ }
+}
+```
+
+## Injectable Constructs
+Any service can declare field, method, or constructor injection points. The only caveat is that these injectable elements must either be public or package private. Generally speaking, it is considered a best practice to (a) use only an injectable constructor, and (b) only inject Provider instances. Here is an example for best practice depicting all possible usages for injection types supported by Pico.
+
+```java
+@Singleton
+public class MainToolBox implements ToolBox {
+
+ // generally not recommended
+ @Inject
+ Provider anyHammerProvider;
+
+ // the best practice is to generally to use only constructor injection with Provider-wrapped types
+ @Inject
+ MainToolBox(
+ @Preferred Screwdriver screwdriver, // the qualifier restricts to the "preferred" screwdriver
+ List> allTools, // all tools in proper weighted/ranked order
+ @Named("big") Provider bigHammerProvider, // only the hammer provider qualified with name "big"
+ List> allHammers, // all hammers in proper weighted/ranked order
+ Optional maybeAChisel) // optionally a Chisel, activated
+ {
+ }
+
+ // generally not recommended
+ @Inject
+ void setScrewdriver(Screwdriver screwdriver) {
+ }
+
+ // called after construction by Pico's lifecycle startup management
+ @PostConstruct
+ void postConstruct() {
+ }
+
+ // called before shutdown by Pico's lifecycle shutdown management
+ @PreDestroy
+ void preDestroy() {
+ }
+}
+
+```
diff --git a/pico/pico/pom.xml b/pico/api/pom.xml
similarity index 90%
rename from pico/pico/pom.xml
rename to pico/api/pom.xml
index 755f625f61a..b2c712c47bf 100644
--- a/pico/pico/pom.xml
+++ b/pico/api/pom.xml
@@ -1,6 +1,5 @@
4.0.0
- helidon-pico
+ helidon-pico-apiHelidon Pico API / SPI
@@ -38,8 +36,8 @@
- io.helidon.pico
- helidon-pico-types
+ io.helidon.common
+ helidon-common-typesio.helidon.common
@@ -49,6 +47,11 @@
io.helidon.commonhelidon-common-config
+
+ io.helidon.config
+ helidon-config
+ test
+ jakarta.injectjakarta.inject-api
@@ -107,13 +110,6 @@
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
- true
-
-
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationLog.java b/pico/api/src/main/java/io/helidon/pico/ActivationLog.java
similarity index 91%
rename from pico/pico/src/main/java/io/helidon/pico/ActivationLog.java
rename to pico/api/src/main/java/io/helidon/pico/ActivationLog.java
index 0ae772183a5..8eb69a8c4f9 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ActivationLog.java
+++ b/pico/api/src/main/java/io/helidon/pico/ActivationLog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ public interface ActivationLog {
* @param entry the log entry to record
* @return the (perhaps decorated) activation log entry
*/
- ActivationLogEntry> recordActivationEvent(ActivationLogEntry> entry);
+ ActivationLogEntry record(ActivationLogEntry entry);
/**
* Optionally provide a means to query the activation log, if query is possible. If query is not possible then an empty
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationLogEntry.java b/pico/api/src/main/java/io/helidon/pico/ActivationLogEntry.java
similarity index 51%
rename from pico/pico/src/main/java/io/helidon/pico/ActivationLogEntry.java
rename to pico/api/src/main/java/io/helidon/pico/ActivationLogEntry.java
index ec0ee0ed6b8..9ba609222d7 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ActivationLogEntry.java
+++ b/pico/api/src/main/java/io/helidon/pico/ActivationLogEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
import java.util.Optional;
import io.helidon.builder.Builder;
+import io.helidon.builder.BuilderInterceptor;
/**
* Log entry for lifecycle related events (i.e., activation startup and deactivation shutdown).
@@ -27,32 +28,9 @@
* @see ActivationLog
* @see Activator
* @see DeActivator
- * @param the service type
*/
-@Builder
-public interface ActivationLogEntry {
-
- /**
- * The activation event.
- */
- enum Event {
- /**
- * Starting.
- */
- STARTING,
-
- /**
- * Finished.
- */
- FINISHED
- }
-
- /**
- * The managing service provider.
- *
- * @return the managing service provider
- */
- ServiceProvider serviceProvider();
+@Builder(interceptor = ActivationLogEntry.Interceptor.class)
+public interface ActivationLogEntry {
/**
* The event.
@@ -62,32 +40,32 @@ enum Event {
Event event();
/**
- * The starting activation phase.
+ * Optionally, any special message being logged.
*
- * @return the starting activation phase
+ * @return the message
*/
- ActivationPhase startingActivationPhase();
+ Optional message();
/**
- * The eventual/desired/target activation phase.
+ * Optionally, when this log entry pertains to a service provider activation.
*
- * @return the eventual/desired/target activation phase
+ * @return the activation result
*/
- ActivationPhase targetActivationPhase();
+ Optional activationResult();
/**
- * The finishing phase at the time of this event's log entry.
+ * Optionally, the managing service provider the event pertains to.
*
- * @return the actual finishing phase
+ * @return the managing service provider
*/
- ActivationPhase finishingActivationPhase();
+ Optional> serviceProvider();
/**
- * The finishing activation status at the time of this event's log entry.
+ * Optionally, the injection point that the event pertains to.
*
- * @return the activation status
+ * @return the injection point
*/
- ActivationStatus finishingStatus();
+ Optional injectionPoint();
/**
* The time this event was generated.
@@ -110,4 +88,31 @@ enum Event {
*/
long threadId();
+
+ /**
+ * Ensures that the non-nullable fields are populated with default values.
+ */
+ class Interceptor implements BuilderInterceptor {
+
+ Interceptor() {
+ }
+
+ @Override
+ public DefaultActivationLogEntry.Builder intercept(DefaultActivationLogEntry.Builder b) {
+ if (b.time() == null) {
+ b.time(Instant.now());
+ }
+
+ if (b.threadId() == 0) {
+ b.threadId(Thread.currentThread().getId());
+ }
+
+ if (b.event() == null) {
+ b.event(Event.FINISHED);
+ }
+
+ return b;
+ }
+ }
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DependenciesInfo.java b/pico/api/src/main/java/io/helidon/pico/ActivationLogQuery.java
similarity index 53%
rename from pico/pico/src/main/java/io/helidon/pico/DependenciesInfo.java
rename to pico/api/src/main/java/io/helidon/pico/ActivationLogQuery.java
index 88899a271f4..f776f5b67e2 100644
--- a/pico/pico/src/main/java/io/helidon/pico/DependenciesInfo.java
+++ b/pico/api/src/main/java/io/helidon/pico/ActivationLogQuery.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,26 +17,28 @@
package io.helidon.pico;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
/**
- * Represents a per {@link ServiceInfo} mapping of {@link DependencyInfo}'s.
+ * Provide a means to query the activation log.
+ *
+ * @see ActivationLog
*/
-public interface DependenciesInfo {
+public interface ActivationLogQuery extends Resettable {
/**
- * Represents the set of dependencies for each {@link ServiceInfo}.
+ * Clears the activation log.
*
- * @return map from the service info to its dependencies
+ * @param deep ignored
+ * @return true if the log was cleared, false if the log was previously empty
*/
- Map> serviceInfoDependencies();
+ @Override
+ boolean reset(boolean deep);
/**
- * Represents a flattened list of all dependencies.
+ * The full transcript of all services phase transitions being managed.
*
- * @return the flattened list of all dependencies
+ * @return the activation log if log capture is enabled
*/
- List extends DependencyInfo> allDependencies();
+ List fullActivationLog();
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/EventReceiver.java b/pico/api/src/main/java/io/helidon/pico/ActivationPhaseReceiver.java
similarity index 51%
rename from pico/pico/src/main/java/io/helidon/pico/EventReceiver.java
rename to pico/api/src/main/java/io/helidon/pico/ActivationPhaseReceiver.java
index 4516ea1be83..f59624dda2d 100644
--- a/pico/pico/src/main/java/io/helidon/pico/EventReceiver.java
+++ b/pico/api/src/main/java/io/helidon/pico/ActivationPhaseReceiver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,42 +17,22 @@
package io.helidon.pico;
/**
- * A receiver of events from the {@link Services} registry.
+ * A receiver of events from the {@link Services} registry and providers held by the service registry.
*
* Note that only {@link ServiceProvider}'s implement this contract that are also bound to the global
* {@link io.helidon.pico.Services} registry are currently capable of receiving events.
*
* @see ServiceProviderBindable
*/
-public interface EventReceiver {
+public interface ActivationPhaseReceiver {
/**
- * Events issued from the framework.
- */
- enum Event {
-
- /**
- * Called after all modules and services from those modules are initially loaded into the service registry.
- */
- POST_BIND_ALL_MODULES,
-
- /**
- * Called after {@link #POST_BIND_ALL_MODULES} to resolve any latent bindings, prior to {@link #SERVICES_READY}.
- */
- FINAL_RESOLVE,
-
- /**
- * The service registry is fully populated and ready.
- */
- SERVICES_READY
-
- }
-
- /**
- * Called at the end of module and service bindings, when all the services in the service registry have been populated.
+ * Called when there is an event transition within the service registry.
*
* @param event the event
+ * @param phase the phase
*/
- void onEvent(Event event);
+ void onPhaseEvent(Event event,
+ Phase phase);
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationRequest.java b/pico/api/src/main/java/io/helidon/pico/ActivationRequest.java
similarity index 61%
rename from pico/pico/src/main/java/io/helidon/pico/ActivationRequest.java
rename to pico/api/src/main/java/io/helidon/pico/ActivationRequest.java
index 8d426dcb6da..6cb57543eaf 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ActivationRequest.java
+++ b/pico/api/src/main/java/io/helidon/pico/ActivationRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,32 +23,31 @@
/**
* Request to activate a service.
- *
- * @param service type
*/
@Builder
-public interface ActivationRequest {
+public interface ActivationRequest {
/**
- * Target service provider.
+ * Optionally, the injection point context information.
*
- * @return service provider
+ * @return injection point info
*/
- ServiceProvider serviceProvider();
+ Optional injectionPoint();
/**
- * Injection point context information.
+ * The phase to start activation. Typically, this should be left as the default (i.e., PENDING).
*
- * @return injection point info
+ * @return phase to start
*/
- Optional injectionPoint();
+ Optional startingPhase();
/**
* Ultimate target phase for activation.
*
* @return phase to target
*/
- ActivationPhase targetPhase();
+ @ConfiguredOption("ACTIVE")
+ Phase targetPhase();
/**
* Whether to throw an exception on failure to activate, or return an error activation result on activation.
@@ -56,6 +55,18 @@ public interface ActivationRequest {
* @return whether to throw on failure
*/
@ConfiguredOption("true")
- boolean throwOnFailure();
+ boolean throwIfError();
+
+ /**
+ * Creates a new activation request.
+ *
+ * @param targetPhase the target phase
+ * @return the activation request
+ */
+ static ActivationRequest create(Phase targetPhase) {
+ return DefaultActivationRequest.builder()
+ .targetPhase(targetPhase)
+ .build();
+ }
}
diff --git a/pico/api/src/main/java/io/helidon/pico/ActivationResult.java b/pico/api/src/main/java/io/helidon/pico/ActivationResult.java
new file mode 100644
index 00000000000..4fa3600e182
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/ActivationResult.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Future;
+
+import io.helidon.builder.Builder;
+import io.helidon.pico.spi.InjectionPlan;
+
+/**
+ * Represents the result of a service activation or deactivation.
+ *
+ * @see Activator
+ * @see DeActivator
+ **/
+@Builder
+public interface ActivationResult {
+
+ /**
+ * The service provider undergoing activation or deactivation.
+ *
+ * @return the service provider generating the result
+ */
+ ServiceProvider> serviceProvider();
+
+ /**
+ * Optionally, given by the implementation provider to indicate the future completion when the provider's
+ * {@link ActivationStatus} is {@link ActivationStatus#WARNING_SUCCESS_BUT_NOT_READY}.
+ *
+ * @return the future result, assuming how activation can be async in nature
+ */
+ Optional> finishedActivationResult();
+
+ /**
+ * The activation phase that was found at onset of the phase transition.
+ *
+ * @return the starting phase
+ */
+ Phase startingActivationPhase();
+
+ /**
+ * The activation phase that was requested at the onset of the phase transition.
+ *
+ * @return the target, desired, ultimate phase requested
+ */
+ Phase targetActivationPhase();
+
+ /**
+ * The activation phase we finished successfully on, or are otherwise currently in if not yet finished.
+ *
+ * @return the finishing phase
+ */
+ Phase finishingActivationPhase();
+
+ /**
+ * How did the activation finish.
+ * Will only be populated if the lifecycle event has completed - see {@link #finishedActivationResult()}.
+ *
+ * @return the finishing status
+ */
+ Optional finishingStatus();
+
+ /**
+ * The injection plan that was found or determined, key'ed by each element's {@link ServiceProvider#id()}.
+ *
+ * @return the resolved injection plan map
+ */
+ Map injectionPlans();
+
+ /**
+ * The dependencies that were resolved or loaded, key'ed by each element's {@link ServiceProvider#id()}.
+ *
+ * @return the resolved dependency map
+ */
+ Map resolvedDependencies();
+
+ /**
+ * Set to true if the injection plan in {@link #resolvedDependencies()} has been resolved and can be "trusted" as being
+ * complete and accurate.
+ *
+ * @return true if was resolved
+ */
+ boolean wasResolved();
+
+ /**
+ * Any throwable/exceptions that were observed during activation.
+ *
+ * @return any captured error
+ */
+ Optional error();
+
+ /**
+ * Returns true if this result is finished.
+ *
+ * @return true if finished
+ */
+ default boolean finished() {
+ Future f = finishedActivationResult().orElse(null);
+ return (f == null || f.isDone());
+ }
+
+ /**
+ * Returns true if this result was successful.
+ *
+ * @return true if successful
+ */
+ default boolean success() {
+ return finishingStatus().orElse(null) != ActivationStatus.FAILURE;
+ }
+
+ /**
+ * Returns true if this result was unsuccessful.
+ *
+ * @return true if unsuccessful
+ */
+ default boolean failure() {
+ return !success();
+ }
+
+ /**
+ * Creates a successful result.
+ *
+ * @param serviceProvider the service provider
+ * @return the result
+ */
+ static ActivationResult createSuccess(ServiceProvider> serviceProvider) {
+ Phase phase = serviceProvider.currentActivationPhase();
+ return DefaultActivationResult.builder()
+ .serviceProvider(serviceProvider)
+ .startingActivationPhase(phase)
+ .finishingActivationPhase(phase)
+ .targetActivationPhase(phase)
+ .finishingStatus(ActivationStatus.SUCCESS)
+ .build();
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationStatus.java b/pico/api/src/main/java/io/helidon/pico/ActivationStatus.java
similarity index 95%
rename from pico/pico/src/main/java/io/helidon/pico/ActivationStatus.java
rename to pico/api/src/main/java/io/helidon/pico/ActivationStatus.java
index b1e12fb21b7..bce05ad9d7f 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ActivationStatus.java
+++ b/pico/api/src/main/java/io/helidon/pico/ActivationStatus.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/pico/pico/src/main/java/io/helidon/pico/Activator.java b/pico/api/src/main/java/io/helidon/pico/Activator.java
similarity index 80%
rename from pico/pico/src/main/java/io/helidon/pico/Activator.java
rename to pico/api/src/main/java/io/helidon/pico/Activator.java
index 4d2c5129641..a7553480179 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Activator.java
+++ b/pico/api/src/main/java/io/helidon/pico/Activator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@
/**
* Activators are responsible for lifecycle creation and lazy activation of service providers. They are responsible for taking the
- * {@link ServiceProvider}'s manage service instance from {@link ActivationPhase#PENDING}
- * through {@link ActivationPhase#POST_CONSTRUCTING} (i.e., including any
+ * {@link ServiceProvider}'s manage service instance from {@link Phase#PENDING}
+ * through {@link Phase#POST_CONSTRUCTING} (i.e., including any
* {@link PostConstructMethod} invocations, etc.), and finally into the
- * {@link ActivationPhase#ACTIVE} phase.
+ * {@link Phase#ACTIVE} phase.
*
Management of the service's {@link ActivationPhase}.
+ *
Management of the service's {@link Phase}.
*
Control over creation (i.e., invoke the constructor non-reflectively).
*
Control over gathering the service requisite dependencies (ctor, field, setters) and optional activation of those.
*
Invocation of any {@link PostConstructMethod}.
*
Responsible to logging to the {@link ActivationLog} - see {@link PicoServices#activationLog()}.
*
*
- * @param the managed service type being activated
* @see DeActivator
*/
-@Contract
-public interface Activator {
+public interface Activator {
/**
* Activate a managed service/provider.
@@ -51,5 +49,6 @@ public interface Activator {
* @param activationRequest activation request
* @return the result of the activation
*/
- ActivationResult activate(ActivationRequest activationRequest);
+ ActivationResult activate(ActivationRequest activationRequest);
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Application.java b/pico/api/src/main/java/io/helidon/pico/Application.java
similarity index 91%
rename from pico/pico/src/main/java/io/helidon/pico/Application.java
rename to pico/api/src/main/java/io/helidon/pico/Application.java
index ada9c3d3819..0361ebda5d3 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Application.java
+++ b/pico/api/src/main/java/io/helidon/pico/Application.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
* @see Module
*/
@Contract
-public interface Application extends Named {
+public interface Application extends OptionallyNamed {
/**
* Called by the provider implementation at bootstrapping time to bind all injection plans to each and every service provider.
diff --git a/pico/pico/src/main/java/io/helidon/pico/Bootstrap.java b/pico/api/src/main/java/io/helidon/pico/Bootstrap.java
similarity index 56%
rename from pico/pico/src/main/java/io/helidon/pico/Bootstrap.java
rename to pico/api/src/main/java/io/helidon/pico/Bootstrap.java
index b62912256cc..83dc4f37766 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Bootstrap.java
+++ b/pico/api/src/main/java/io/helidon/pico/Bootstrap.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,17 +25,27 @@
* This is the bootstrap needed to provide to {@code Pico} initialization.
*
* @see io.helidon.pico.spi.PicoServicesProvider
+ * @see io.helidon.pico.PicoServices#globalBootstrap()
*/
@Builder
public interface Bootstrap {
/**
* Provides the base primordial bootstrap configuration to the {@link io.helidon.pico.spi.PicoServicesProvider}.
- * The provider will then bootstrap its {@link io.helidon.pico.PicoServicesConfig} to any provided bootstrap
- * configuration instance provided, etc.
+ * The provider will then bootstrap {@link io.helidon.pico.PicoServices} using this bootstrap instance.
+ * then default values will be used accordingly.
*
- * @return the bootstrap configuration
+ * @return the bootstrap helidon configuration
*/
Optional config();
+ /**
+ * In certain conditions Pico services should be initialized but not started (i.e., avoiding calls to {@code PostConstruct}
+ * etc.). This can be used in special cases where the normal Pico startup should limit lifecycle up to a given phase. Normally
+ * one should not use this feature - it is mainly used in Pico tooling (e.g., the pico-maven-plugin).
+ *
+ * @return the phase to stop at during lifecycle
+ */
+ Optional limitRuntimePhase();
+
}
diff --git a/pico/api/src/main/java/io/helidon/pico/CallingContext.java b/pico/api/src/main/java/io/helidon/pico/CallingContext.java
new file mode 100644
index 00000000000..e3e6de172b0
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/CallingContext.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import io.helidon.builder.Builder;
+
+import static io.helidon.pico.PicoServicesConfig.TAG_DEBUG;
+
+/**
+ * For internal use only to Helidon. Applicable when {@link io.helidon.pico.PicoServicesConfig#TAG_DEBUG} is enabled.
+ */
+@Builder(interceptor = CallingContext.BuilderInterceptor.class)
+public abstract class CallingContext {
+
+ /**
+ * Helpful hint to give developers needing to see more debug info.
+ */
+ public static final String DEBUG_HINT = "use the (-D and/or -A) tag '" + TAG_DEBUG + "=true' to see full trace output.";
+
+ private static CallingContext defaultCallingContext;
+
+ /**
+ * This needs to be private since a generated builder will be extending this.
+ */
+ protected CallingContext() {
+ }
+
+ @Override
+ public String toString() {
+ String prettyPrintStackTrace = String.join("\n", stackTraceOf(trace()));
+ return "module name: " + moduleName() + "; thread name: " + threadName()
+ + "; trace:\n" + prettyPrintStackTrace;
+ }
+
+ /**
+ * Only populated when {@link io.helidon.pico.PicoServicesConfig#TAG_DEBUG} is set.
+ *
+ * @return the stack trace for who initialized
+ */
+ public abstract StackTraceElement[] trace();
+
+ /**
+ * Only populated when {@link io.helidon.pico.PicoServicesConfig#TAG_MODULE_NAME} is set.
+ *
+ * @return the module name
+ */
+ public abstract Optional moduleName();
+
+ /**
+ * The thread that created the calling context.
+ *
+ * @return thread creating the calling context
+ */
+ public abstract String threadName();
+
+ /**
+ * Returns a stack trace as a list of strings.
+ *
+ * @param trace the trace
+ * @return the list of strings for the stack trace
+ */
+ static List stackTraceOf(StackTraceElement[] trace) {
+ List result = new ArrayList<>();
+ for (StackTraceElement e : trace) {
+ result.add(e.toString());
+ }
+ return result;
+ }
+
+ /**
+ * Sets the default global calling context.
+ *
+ * @param callingContext the default global context
+ * @param throwIfAlreadySet should an exception be thrown if the global calling context was already set
+ * @throws java.lang.IllegalStateException if context was already set and the throwIfAlreadySet is active
+ */
+ public static void globalCallingContext(CallingContext callingContext,
+ boolean throwIfAlreadySet) {
+ Objects.requireNonNull(callingContext);
+
+ CallingContext global = defaultCallingContext;
+ if (global != null && throwIfAlreadySet) {
+ CallingContext currentCallingContext = CallingContextFactory.create(true).orElseThrow();
+ throw new IllegalStateException("Expected to be the owner of the calling context. This context is: "
+ + currentCallingContext + "\n Context previously set was: " + global);
+ }
+
+ CallingContext.defaultCallingContext = callingContext;
+ }
+
+ /**
+ * Convenience method for producing an error message that may involve advising the user to apply a debug mode.
+ *
+ * @param callingContext the calling context (caller can be using a custom calling context, which is why we accept it here
+ * instead of using the global one)
+ * @param msg the base message to display
+ * @return the message appropriate for any exception being thrown
+ */
+ public static String toErrorMessage(CallingContext callingContext,
+ String msg) {
+ return msg + " - previous calling context: " + callingContext;
+ }
+
+ /**
+ * Convenience method for producing an error message that may involve advising the user to apply a debug mode. Use
+ * {@link #toErrorMessage(CallingContext, String)} iinstead f a calling context is available.
+ *
+ * @param msg the base message to display
+ * @return the message appropriate for any exception being thrown
+ * @see #toErrorMessage(CallingContext, String)
+ */
+ public static String toErrorMessage(String msg) {
+ return msg + " - " + DEBUG_HINT;
+ }
+
+
+ static class BuilderInterceptor implements io.helidon.builder.BuilderInterceptor {
+ @Override
+ public DefaultCallingContext.Builder intercept(DefaultCallingContext.Builder target) {
+ if (target.threadName() == null) {
+ target.threadName(Thread.currentThread().getName());
+ }
+ return target;
+ }
+ }
+
+}
diff --git a/pico/api/src/main/java/io/helidon/pico/CallingContextFactory.java b/pico/api/src/main/java/io/helidon/pico/CallingContextFactory.java
new file mode 100644
index 00000000000..3c4e08109ed
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/CallingContextFactory.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import java.util.Optional;
+
+/**
+ * Factory for creating {@link CallingContext} and builders for the calling context.
+ * After a calling context builder is created, it should be amended with as much contextual information as possible, and then
+ * optionally set globally using {@link CallingContext#globalCallingContext(CallingContext, boolean)}.
+ */
+public class CallingContextFactory {
+
+ private CallingContextFactory() {
+ }
+
+ /**
+ * Creates a new calling context instance. Normally this method will return a context optionally only when debug is
+ * enabled. This behavior can be overridden by passing the {@code force=true} flag.
+ *
+ * @param force forces the creation of the calling context even when debug is disabled
+ * @return a new calling context if there is an indication that debug mode is enabled, or if the force flag is set
+ * @see io.helidon.pico.PicoServices#isDebugEnabled()
+ */
+ public static Optional create(boolean force) {
+ Optional optBuilder = createBuilder(force);
+ return optBuilder.map(DefaultCallingContext.Builder::build);
+
+ }
+
+ /**
+ * Creates a new calling context builder instance. Normally this method will return a context builder optionally only when
+ * debug is enabled. This behavior can be overridden by passing the {@code force=true} flag.
+ *
+ * @param force forces the creation of the calling context even when debug is disabled
+ * @return a new calling context builder if there is an indication that debug mode is enabled, or if the force flag is set
+ * @see io.helidon.pico.PicoServices#isDebugEnabled()
+ */
+ public static Optional createBuilder(boolean force) {
+ boolean createIt = (force || PicoServices.isDebugEnabled());
+ if (!createIt) {
+ return Optional.empty();
+ }
+
+ return Optional.of(DefaultCallingContext.builder()
+ .trace(new RuntimeException().getStackTrace()));
+ }
+
+}
diff --git a/pico/api/src/main/java/io/helidon/pico/CommonQualifiers.java b/pico/api/src/main/java/io/helidon/pico/CommonQualifiers.java
new file mode 100644
index 00000000000..362a894a606
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/CommonQualifiers.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeName;
+
+import jakarta.inject.Named;
+
+/**
+ * Commonly used {@link QualifierAndValue} types.
+ */
+public final class CommonQualifiers {
+
+ /**
+ * Represents a {@link jakarta.inject.Named} type name with no value.
+ */
+ public static final TypeName NAMED = DefaultTypeName.create(Named.class);
+
+ /**
+ * Represents a wildcard {@link #NAMED} qualifier.
+ */
+ public static final QualifierAndValue WILDCARD_NAMED = DefaultQualifierAndValue.createNamed("*");
+
+ private CommonQualifiers() {
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ContextualServiceQuery.java b/pico/api/src/main/java/io/helidon/pico/ContextualServiceQuery.java
similarity index 50%
rename from pico/pico/src/main/java/io/helidon/pico/ContextualServiceQuery.java
rename to pico/api/src/main/java/io/helidon/pico/ContextualServiceQuery.java
index 63b23da1150..cc5553d1b41 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ContextualServiceQuery.java
+++ b/pico/api/src/main/java/io/helidon/pico/ContextualServiceQuery.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +16,16 @@
package io.helidon.pico;
+import java.util.Objects;
import java.util.Optional;
import io.helidon.builder.Builder;
/**
- * Combines the {@link io.helidon.pico.ServiceInfo} criteria along with the {@link io.helidon.pico.InjectionPointInfo} context
+ * Combines the {@link ServiceInfo} criteria along with the {@link InjectionPointInfo} context
* that the query applies to.
*
- * @see io.helidon.pico.InjectionPointProvider
+ * @see InjectionPointProvider
*/
@Builder
public interface ContextualServiceQuery {
@@ -34,14 +35,14 @@ public interface ContextualServiceQuery {
*
* @return the service info criteria
*/
- ServiceInfoCriteria serviceInfo();
+ ServiceInfoCriteria serviceInfoCriteria();
/**
- * The injection point context this search applies to.
+ * Optionally, the injection point context this search applies to.
*
- * @return the injection point context info
+ * @return the optional injection point context info
*/
- Optional ipInfo();
+ Optional injectionPointInfo();
/**
* Set to true if there is an expectation that there is at least one match result from the search.
@@ -50,4 +51,21 @@ public interface ContextualServiceQuery {
*/
boolean expected();
+ /**
+ * Creates a contextual service query given the injection point info.
+ *
+ * @param ipInfo the injection point info
+ * @param expected true if the query is expected to at least have a single match
+ * @return the query
+ */
+ static ContextualServiceQuery create(InjectionPointInfo ipInfo,
+ boolean expected) {
+ Objects.requireNonNull(ipInfo);
+ return DefaultContextualServiceQuery.builder()
+ .expected(expected)
+ .injectionPointInfo(ipInfo)
+ .serviceInfoCriteria(ipInfo.dependencyToServiceInfo())
+ .build();
+ }
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Contract.java b/pico/api/src/main/java/io/helidon/pico/Contract.java
similarity index 69%
rename from pico/pico/src/main/java/io/helidon/pico/Contract.java
rename to pico/api/src/main/java/io/helidon/pico/Contract.java
index 70c10b499f2..ef3e0902cf8 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Contract.java
+++ b/pico/api/src/main/java/io/helidon/pico/Contract.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,11 +22,11 @@
import java.lang.annotation.Target;
/**
- * The {@code Contract} annotation is used to relay significance to the type. While remaining optional in its use, it is typically
- * placed on an interface definition to signify that the given type can be used for lookup in the {@link io.helidon.pico.Services}
- * registry, and be eligible for injection via standard {@code @Inject}. While normally places on interface types, it can also be
- * placed on other types (e.g., abstract class) as well. The main point is that a contract is the focal point for service lookup
- * and injection.
+ * The {@code Contract} annotation is used to relay significance to the type that it annotates. While remaining optional in its
+ * use, it is typically placed on an interface definition to signify that the given type can be used for lookup in the
+ * {@link io.helidon.pico.Services} registry, and be eligible for injection via standard {@code @Inject}.
+ * While normally placed on interface types, it can also be placed on abstract and concrete class as well. The main point is that
+ * a {@code Contract} is the focal point for service lookup and injection.
*
* If the developer does not have access to the source to place this annotation on the interface definition directly then consider
* using {@link ExternalContracts} instead - this annotation can be placed on the implementation class implementing the given
@@ -36,7 +36,7 @@
* @see io.helidon.pico.ServiceInfo#externalContractsImplemented()
*/
@Documented
-@Retention(RetentionPolicy.RUNTIME)
+@Retention(RetentionPolicy.CLASS)
@Target(java.lang.annotation.ElementType.TYPE)
public @interface Contract {
diff --git a/pico/pico/src/main/java/io/helidon/pico/DeActivationRequest.java b/pico/api/src/main/java/io/helidon/pico/DeActivationRequest.java
similarity index 53%
rename from pico/pico/src/main/java/io/helidon/pico/DeActivationRequest.java
rename to pico/api/src/main/java/io/helidon/pico/DeActivationRequest.java
index 67facfacbe2..9cc72e0dcd3 100644
--- a/pico/pico/src/main/java/io/helidon/pico/DeActivationRequest.java
+++ b/pico/api/src/main/java/io/helidon/pico/DeActivationRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,41 +17,39 @@
package io.helidon.pico;
import io.helidon.builder.Builder;
+import io.helidon.common.LazyValue;
import io.helidon.config.metadata.ConfiguredOption;
/**
- * Request to {@link io.helidon.pico.DeActivator#deactivate(DeActivationRequest)}.
- *
- * @param type to deactivate
+ * Request to deactivate a {@link io.helidon.pico.ServiceProvider}.
*/
@Builder
-public interface DeActivationRequest {
+public abstract class DeActivationRequest {
- /**
- * Create a request with defaults.
- *
- * @param provider service provider responsible for invoking deactivate
- * @return a new request
- * @param type to deactivate
- */
- @SuppressWarnings("unchecked")
- static DeActivationRequest create(ServiceProvider provider) {
- return DefaultDeActivationRequest.builder().serviceProvider(provider).build();
+ DeActivationRequest() {
}
/**
- * Service provider responsible for invoking deactivate.
+ * Whether to throw an exception on failure, or return it as part of the result.
*
- * @return service provider
+ * @return throw on failure
*/
- ServiceProvider serviceProvider();
+ @ConfiguredOption("true")
+ public abstract boolean throwIfError();
/**
- * Whether to throw an exception on failure, or return it as part of the result.
+ * A standard/default deactivation request, without any additional options placed on the request.
*
- * @return throw on failure
+ * @return a standard/default deactivation request.
*/
- @ConfiguredOption("true")
- boolean throwOnFailure();
+ public static DeActivationRequest defaultDeactivationRequest() {
+ return Init.DEFAULT.get();
+ }
+
+
+ static class Init {
+ static final LazyValue DEFAULT =
+ LazyValue.create(() -> DefaultDeActivationRequest.builder().build());
+ }
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DeActivator.java b/pico/api/src/main/java/io/helidon/pico/DeActivator.java
similarity index 73%
rename from pico/pico/src/main/java/io/helidon/pico/DeActivator.java
rename to pico/api/src/main/java/io/helidon/pico/DeActivator.java
index 6978ef3ccae..57841f117d5 100644
--- a/pico/pico/src/main/java/io/helidon/pico/DeActivator.java
+++ b/pico/api/src/main/java/io/helidon/pico/DeActivator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,15 +18,13 @@
/**
* DeActivators are responsible for lifecycle, transitioning a {@link ServiceProvider} through its
- * {@link io.helidon.pico.ActivationPhase}'s, notably including any
+ * {@link Phase}'s, notably including any
* {@link jakarta.annotation.PreDestroy} method invocations, and finally into the terminal
- * {@link ActivationPhase#DESTROYED} phase. These phase transitions are the inverse of {@link Activator}.
+ * {@link Phase#DESTROYED} phase. These phase transitions are the inverse of {@link Activator}.
*
- * @param the type to deactivate
* @see Activator
*/
-@Contract
-public interface DeActivator {
+public interface DeActivator {
/**
* Deactivate a managed service. This will trigger any {@link jakarta.annotation.PreDestroy} method on the
@@ -35,5 +33,6 @@ public interface DeActivator {
* @param request deactivation request
* @return the result
*/
- ActivationResult deactivate(DeActivationRequest request);
+ ActivationResult deactivate(DeActivationRequest request);
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java b/pico/api/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java
similarity index 84%
rename from pico/pico/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java
rename to pico/api/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java
index b4c22725b25..ea0a659f3d1 100644
--- a/pico/pico/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java
+++ b/pico/api/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,13 @@
package io.helidon.pico;
import java.lang.annotation.Annotation;
+import java.util.Map;
import java.util.Objects;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.DefaultAnnotationAndValue;
-import io.helidon.pico.types.DefaultTypeName;
-import io.helidon.pico.types.TypeName;
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.DefaultAnnotationAndValue;
+import io.helidon.common.types.DefaultTypeName;
+import io.helidon.common.types.TypeName;
/**
* Describes a {@link jakarta.inject.Qualifier} type annotation associated with a service being provided or dependant upon.
@@ -31,16 +32,6 @@
public class DefaultQualifierAndValue extends DefaultAnnotationAndValue
implements QualifierAndValue, Comparable {
- /**
- * Represents a {@link jakarta.inject.Named} type name with no value.
- */
- public static final TypeName NAMED = DefaultTypeName.create(Named.class);
-
- /**
- * Represents a wildcard {@link #NAMED} qualifier.
- */
- public static final QualifierAndValue WILDCARD_NAMED = DefaultQualifierAndValue.createNamed("*");
-
/**
* Constructor using the builder.
*
@@ -59,7 +50,7 @@ protected DefaultQualifierAndValue(Builder b) {
*/
public static DefaultQualifierAndValue createNamed(String name) {
Objects.requireNonNull(name);
- return (DefaultQualifierAndValue) builder().typeName(NAMED).value(name).build();
+ return (DefaultQualifierAndValue) builder().typeName(CommonQualifiers.NAMED).value(name).build();
}
/**
@@ -114,7 +105,21 @@ public static DefaultQualifierAndValue create(TypeName qualifierType, String val
}
/**
- * Converts from an {@link io.helidon.pico.types.AnnotationAndValue} to a {@link QualifierAndValue}.
+ * Creates a qualifier.
+ *
+ * @param qualifierType the qualifier
+ * @param vals the values
+ * @return qualifier
+ */
+ public static DefaultQualifierAndValue create(TypeName qualifierType, Map vals) {
+ return (DefaultQualifierAndValue) builder()
+ .typeName(qualifierType)
+ .values(vals)
+ .build();
+ }
+
+ /**
+ * Converts from an {@link AnnotationAndValue} to a {@link QualifierAndValue}.
*
* @param annotationAndValue the annotation and value
* @return the qualifier and value equivalent
diff --git a/pico/api/src/main/java/io/helidon/pico/DependenciesInfo.java b/pico/api/src/main/java/io/helidon/pico/DependenciesInfo.java
new file mode 100644
index 00000000000..17c0ce353ff
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/DependenciesInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import io.helidon.builder.Builder;
+import io.helidon.builder.Singular;
+
+/**
+ * Represents a per {@link ServiceInfo} mapping of {@link DependencyInfo}'s. These are typically assigned to a
+ * {@link ServiceProvider} via compile-time code generation within the Pico framework.
+ */
+@Builder
+public interface DependenciesInfo {
+
+ /**
+ * Represents the set of dependencies for each {@link ServiceInfo}.
+ *
+ * @return map from the service info to its dependencies
+ */
+ @Singular("serviceInfoDependency")
+ Map> serviceInfoDependencies();
+
+ /**
+ * Optionally, the service type name aggregating {@link #allDependencies()}.
+ *
+ * @return the optional service type name for which these dependencies belong
+ */
+ Optional fromServiceTypeName();
+
+ /**
+ * Represents a flattened set of all dependencies.
+ *
+ * @return the flattened set of all dependencies
+ */
+ default Set allDependencies() {
+ Set all = new TreeSet<>(comparator());
+ serviceInfoDependencies().values()
+ .forEach(all::addAll);
+ return all;
+ }
+
+ /**
+ * Represents the list of all dependencies for a given injection point element name ordered by the element position.
+ *
+ * @param elemName the element name of the injection point
+ * @return the list of all dependencies got a given element name of a given injection point
+ */
+ default List allDependenciesFor(String elemName) {
+ Objects.requireNonNull(elemName);
+ return allDependencies().stream()
+ .flatMap(dep -> dep.injectionPointDependencies().stream()
+ .filter(ipi -> elemName.equals(ipi.elementName()))
+ .map(ipi -> DefaultDependencyInfo.toBuilder(dep)
+ .injectionPointDependencies(Set.of(ipi))
+ .build()))
+ .sorted(comparator())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Comparator appropriate for {@link DependencyInfo}.
+ */
+ class Comparator implements java.util.Comparator, Serializable {
+ private Comparator() {
+ }
+
+ @Override
+ public int compare(DependencyInfo o1,
+ DependencyInfo o2) {
+ InjectionPointInfo ipi1 = o1.injectionPointDependencies().iterator().next();
+ InjectionPointInfo ipi2 = o2.injectionPointDependencies().iterator().next();
+
+ java.util.Comparator idComp = java.util.Comparator.comparing(InjectionPointInfo::baseIdentity);
+ java.util.Comparator posComp = java.util.Comparator.comparing(Comparator::elementOffsetOf);
+
+ return idComp.thenComparing(posComp).compare(ipi1, ipi2);
+ }
+
+ private static int elementOffsetOf(InjectionPointInfo ipi) {
+ return ipi.elementOffset().orElse(0);
+ }
+ }
+
+ /**
+ * Provides a comparator appropriate for {@link io.helidon.pico.DependencyInfo}.
+ *
+ * @return a comparator for dependency info
+ */
+ static java.util.Comparator comparator() {
+ return new Comparator();
+ }
+
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DependencyInfo.java b/pico/api/src/main/java/io/helidon/pico/DependencyInfo.java
similarity index 68%
rename from pico/pico/src/main/java/io/helidon/pico/DependencyInfo.java
rename to pico/api/src/main/java/io/helidon/pico/DependencyInfo.java
index 9e7b417a50f..65c75bb965e 100644
--- a/pico/pico/src/main/java/io/helidon/pico/DependencyInfo.java
+++ b/pico/api/src/main/java/io/helidon/pico/DependencyInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,11 @@
package io.helidon.pico;
+import java.util.Optional;
import java.util.Set;
import io.helidon.builder.Builder;
+import io.helidon.builder.Singular;
/**
* Aggregates the set of {@link InjectionPointInfo}'s that are dependent upon a specific and common
@@ -32,13 +34,22 @@ public interface DependencyInfo {
*
* @return the service info dependency
*/
- ServiceInfo dependencyTo();
+ ServiceInfoCriteria dependencyTo();
/**
* The set of injection points that depends upon {@link #dependencyTo()}.
*
* @return the set of dependencies
*/
+ @Singular("injectionPointDependency")
Set extends InjectionPointInfo> injectionPointDependencies();
+ /**
+ * The {@link io.helidon.pico.ServiceProvider} that this dependency is optional resolved and bound to. All dependencies
+ * from {@link #injectionPointDependencies()} will be bound to this resolution.
+ *
+ * @return the optional resolved and bounded service provider
+ */
+ Optional> resolvedTo();
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ElementInfo.java b/pico/api/src/main/java/io/helidon/pico/ElementInfo.java
similarity index 83%
rename from pico/pico/src/main/java/io/helidon/pico/ElementInfo.java
rename to pico/api/src/main/java/io/helidon/pico/ElementInfo.java
index 40df5a34650..4646174eb61 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ElementInfo.java
+++ b/pico/api/src/main/java/io/helidon/pico/ElementInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,8 @@
import java.util.Set;
import io.helidon.builder.Builder;
-import io.helidon.pico.types.AnnotationAndValue;
+import io.helidon.builder.Singular;
+import io.helidon.common.types.AnnotationAndValue;
/**
* Abstractly describes method or field elements of a managed service type (i.e., fields, constructors, injectable methods, etc.).
@@ -113,6 +114,13 @@ enum Access {
*/
Optional elementOffset();
+ /**
+ * If the element is a method or constructor then this is the total argument count for that method.
+ *
+ * @return total argument count
+ */
+ Optional elementArgs();
+
/**
* True if the injection point is static.
*
@@ -132,6 +140,15 @@ enum Access {
*
* @return the annotations on this element
*/
+ @Singular
Set annotations();
+ /**
+ * The qualifier type annotations on this element.
+ *
+ * @return the qualifier type annotations on this element
+ */
+ @Singular
+ Set qualifiers();
+
}
diff --git a/pico/api/src/main/java/io/helidon/pico/Event.java b/pico/api/src/main/java/io/helidon/pico/Event.java
new file mode 100644
index 00000000000..48bfee6fbf5
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/Event.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+/**
+ * A lifecycle activation event.
+ */
+public enum Event {
+
+ /**
+ * Starting.
+ */
+ STARTING,
+
+ /**
+ * Finished.
+ */
+ FINISHED,
+
+ /**
+ * Other, informational.
+ */
+ OTHER
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ExternalContracts.java b/pico/api/src/main/java/io/helidon/pico/ExternalContracts.java
similarity index 94%
rename from pico/pico/src/main/java/io/helidon/pico/ExternalContracts.java
rename to pico/api/src/main/java/io/helidon/pico/ExternalContracts.java
index 9001c25243a..5041d788b5d 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ExternalContracts.java
+++ b/pico/api/src/main/java/io/helidon/pico/ExternalContracts.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
* from a 3rd party library provider.
*/
@Documented
-@Retention(RetentionPolicy.RUNTIME)
+@Retention(RetentionPolicy.CLASS)
@Target(java.lang.annotation.ElementType.TYPE)
public @interface ExternalContracts {
diff --git a/pico/pico/src/main/java/io/helidon/pico/InjectionException.java b/pico/api/src/main/java/io/helidon/pico/InjectionException.java
similarity index 75%
rename from pico/pico/src/main/java/io/helidon/pico/InjectionException.java
rename to pico/api/src/main/java/io/helidon/pico/InjectionException.java
index 21c07c1f206..02162885f9c 100644
--- a/pico/pico/src/main/java/io/helidon/pico/InjectionException.java
+++ b/pico/api/src/main/java/io/helidon/pico/InjectionException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package io.helidon.pico;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -27,9 +28,9 @@ public class InjectionException extends PicoServiceProviderException {
/**
* The optional activation log (configure to enabled).
*
- * @see io.helidon.pico.PicoServicesConfig
+ * @see PicoServicesConfig#activationLogs()
*/
- private final ActivationLog activationLog;
+ private ActivationLog activationLog;
/**
* Injection, or a required service lookup related exception.
@@ -38,7 +39,6 @@ public class InjectionException extends PicoServiceProviderException {
*/
public InjectionException(String msg) {
super(msg);
- this.activationLog = null;
}
/**
@@ -48,25 +48,21 @@ public InjectionException(String msg) {
* @param cause the root cause
* @param serviceProvider the service provider
*/
- public InjectionException(String msg, Throwable cause, ServiceProvider> serviceProvider) {
+ public InjectionException(String msg,
+ Throwable cause,
+ ServiceProvider> serviceProvider) {
super(msg, cause, serviceProvider);
- this.activationLog = null;
}
/**
* Injection, or a required service lookup related exception.
*
* @param msg the message
- * @param cause the root cause
* @param serviceProvider the service provider
- * @param log the optional activity log
*/
public InjectionException(String msg,
- Throwable cause,
- ServiceProvider> serviceProvider,
- ActivationLog log) {
- super(msg, cause, serviceProvider);
- this.activationLog = log;
+ ServiceProvider> serviceProvider) {
+ super(msg, serviceProvider);
}
/**
@@ -78,4 +74,15 @@ public Optional activationLog() {
return Optional.ofNullable(activationLog);
}
+ /**
+ * Sets the activation log on this exception instance.
+ *
+ * @param log the activation log
+ * @return this exception instance
+ */
+ public InjectionException activationLog(ActivationLog log) {
+ this.activationLog = Objects.requireNonNull(log);
+ return this;
+ }
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InjectionPointInfo.java b/pico/api/src/main/java/io/helidon/pico/InjectionPointInfo.java
similarity index 78%
rename from pico/pico/src/main/java/io/helidon/pico/InjectionPointInfo.java
rename to pico/api/src/main/java/io/helidon/pico/InjectionPointInfo.java
index 5df97f2b3d8..cf3c9fe41a0 100644
--- a/pico/pico/src/main/java/io/helidon/pico/InjectionPointInfo.java
+++ b/pico/api/src/main/java/io/helidon/pico/InjectionPointInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
package io.helidon.pico;
-import java.util.Set;
-
import io.helidon.builder.Builder;
/**
@@ -27,14 +25,14 @@
public interface InjectionPointInfo extends ElementInfo {
/**
- * The identifying name for this injection point. The identity should be unique for the service type it is contained within.
+ * The identity (aka id) for this injection point. The id should be unique for the service type it is contained within.
*
* This method will return the {@link #baseIdentity()} when {@link #elementOffset()} is null. If not null
* then the elemOffset is part of the returned identity.
*
* @return the unique identity
*/
- String identity();
+ String id();
/**
* The base identifying name for this injection point. If the element represents a function, then the function arguments
@@ -44,13 +42,6 @@ public interface InjectionPointInfo extends ElementInfo {
*/
String baseIdentity();
- /**
- * The qualifiers on this element.
- *
- * @return The qualifiers on this element.
- */
- Set qualifiers();
-
/**
* True if the injection point is of type {@link java.util.List}.
*
@@ -73,9 +64,10 @@ public interface InjectionPointInfo extends ElementInfo {
boolean providerWrapped();
/**
- * The dependency this is dependent upon.
+ * The service info criteria/dependency this is dependent upon.
*
- * @return The service info we are dependent upon.
+ * @return the service info criteria we are dependent upon
*/
- ServiceInfo dependencyToServiceInfo();
+ ServiceInfoCriteria dependencyToServiceInfo();
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InjectionPointProvider.java b/pico/api/src/main/java/io/helidon/pico/InjectionPointProvider.java
similarity index 85%
rename from pico/pico/src/main/java/io/helidon/pico/InjectionPointProvider.java
rename to pico/api/src/main/java/io/helidon/pico/InjectionPointProvider.java
index 09b2945dd30..d35303902fd 100644
--- a/pico/pico/src/main/java/io/helidon/pico/InjectionPointProvider.java
+++ b/pico/api/src/main/java/io/helidon/pico/InjectionPointProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
* @param the type that the provider produces
*/
public interface InjectionPointProvider extends Provider {
+
/**
* Get (or create) an instance of this service type using default/empty criteria and context.
*
@@ -42,7 +43,7 @@ public interface InjectionPointProvider extends Provider {
@Override
default T get() {
return first(PicoServices.SERVICE_QUERY_REQUIRED)
- .orElseThrow(() -> new PicoException("Could not resolve a match for " + this));
+ .orElseThrow(() -> couldNotFindMatch());
}
/**
@@ -66,4 +67,12 @@ default List list(ContextualServiceQuery query) {
return first(query).map(List::of).orElseGet(List::of);
}
+ @SuppressWarnings("rawtypes")
+ private PicoException couldNotFindMatch() {
+ if (this instanceof ServiceProvider) {
+ return new PicoServiceProviderException("expected to find a match", (ServiceProvider) this);
+ }
+ return new PicoException("expected to find a match for " + this);
+ }
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Injector.java b/pico/api/src/main/java/io/helidon/pico/Injector.java
similarity index 63%
rename from pico/pico/src/main/java/io/helidon/pico/Injector.java
rename to pico/api/src/main/java/io/helidon/pico/Injector.java
index e6636d6b1a8..c19b81adf74 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Injector.java
+++ b/pico/api/src/main/java/io/helidon/pico/Injector.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,6 @@
* implementations to extend the model to perform other types of injection point resolution.
*/
public interface Injector {
- /**
- * Empty options is the same as passing no options, taking all the default values.
- */
- InjectorOptions EMPTY_OPTIONS = DefaultInjectorOptions.builder().build();
/**
* The strategy the injector should attempt to apply. The reference implementation for Pico provider only handles
@@ -55,39 +51,31 @@ enum Strategy {
/**
* Called to activate and inject a manage service instance or service provider, putting it into
- * {@link ActivationPhase#ACTIVE}.
- *
- * Note that if a {@link ServiceProvider} is passed in then the {@link Activator}
- * will be used instead. In this case, then any {@link InjectorOptions#startAtPhase()} and
- * {@link InjectorOptions#finishAtPhase()} arguments will be ignored.
+ * {@link Phase#ACTIVE}.
*
* @param serviceOrServiceProvider the target instance or service provider being activated and injected
- * @param opts the injector options, or use {@link #EMPTY_OPTIONS}
- * @param the managed service instance type
+ * @param opts the injector options
+ * @param the managed service type
* @return the result of the activation
* @throws io.helidon.pico.PicoServiceProviderException if an injection or activation problem occurs
* @see Activator
*/
- ActivationResult activateInject(T serviceOrServiceProvider,
- InjectorOptions opts) throws PicoServiceProviderException;
+ ActivationResult activateInject(T serviceOrServiceProvider,
+ InjectorOptions opts) throws PicoServiceProviderException;
/**
- * Called to deactivate a managed service or service provider, putting it into {@link ActivationPhase#DESTROYED}.
+ * Called to deactivate a managed service or service provider, putting it into {@link Phase#DESTROYED}.
* If a managed service has a {@link jakarta.annotation.PreDestroy} annotated method then it will be called during
* this lifecycle event.
- *
- * Note that if a {@link ServiceProvider} is passed in then the {@link DeActivator}
- * will be used instead. In this case, then any {@link InjectorOptions#startAtPhase()} and
- * {@link InjectorOptions#finishAtPhase()} arguments will be ignored.
*
* @param serviceOrServiceProvider the service provider or instance registered and being managed
- * @param opts the injector options, or use {@link #EMPTY_OPTIONS}
- * @param the managed service instance type
+ * @param opts the injector options
+ * @param the managed service type
* @return the result of the deactivation
* @throws io.helidon.pico.PicoServiceProviderException if a problem occurs
* @see DeActivator
*/
- ActivationResult deactivate(T serviceOrServiceProvider,
- InjectorOptions opts) throws PicoServiceProviderException;
+ ActivationResult deactivate(T serviceOrServiceProvider,
+ InjectorOptions opts) throws PicoServiceProviderException;
}
diff --git a/pico/api/src/main/java/io/helidon/pico/InjectorOptions.java b/pico/api/src/main/java/io/helidon/pico/InjectorOptions.java
new file mode 100644
index 00000000000..6badfe72e40
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/InjectorOptions.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import io.helidon.builder.Builder;
+import io.helidon.builder.BuilderInterceptor;
+import io.helidon.config.metadata.ConfiguredOption;
+
+/**
+ * Provides optional, contextual tunings to the {@link Injector}.
+ *
+ * @see Injector
+ */
+@Builder(interceptor = InjectorOptions.Interceptor.class)
+public abstract class InjectorOptions {
+
+ InjectorOptions() {
+ }
+
+ /**
+ * The strategy the injector should apply. The default is {@link Injector.Strategy#ANY}.
+ *
+ * @return the injector strategy to use
+ */
+ @ConfiguredOption("ANY")
+ public abstract Injector.Strategy strategy();
+
+ /**
+ * Optionally, customized activator options to use for the {@link io.helidon.pico.Activator}.
+ *
+ * @return activator options, or leave blank to use defaults
+ */
+ public abstract ActivationRequest activationRequest();
+
+
+ /**
+ * This will ensure that the activation request is populated.
+ */
+ static class Interceptor implements BuilderInterceptor {
+ Interceptor() {
+ }
+
+ @Override
+ public DefaultInjectorOptions.Builder intercept(DefaultInjectorOptions.Builder target) {
+ if (target.activationRequest() == null) {
+ target.activationRequest(PicoServices.createDefaultActivationRequest());
+ }
+ return target;
+ }
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Intercepted.java b/pico/api/src/main/java/io/helidon/pico/Intercepted.java
similarity index 92%
rename from pico/pico/src/main/java/io/helidon/pico/Intercepted.java
rename to pico/api/src/main/java/io/helidon/pico/Intercepted.java
index eb984cf7b39..9578a262588 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Intercepted.java
+++ b/pico/api/src/main/java/io/helidon/pico/Intercepted.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
* @see io.helidon.pico.Interceptor
*/
@Documented
-@Retention(RetentionPolicy.RUNTIME)
+@Retention(RetentionPolicy.CLASS)
@Inherited
@Qualifier
@Target(java.lang.annotation.ElementType.TYPE)
diff --git a/pico/pico/src/main/java/io/helidon/pico/InterceptedTrigger.java b/pico/api/src/main/java/io/helidon/pico/InterceptedTrigger.java
similarity index 85%
rename from pico/pico/src/main/java/io/helidon/pico/InterceptedTrigger.java
rename to pico/api/src/main/java/io/helidon/pico/InterceptedTrigger.java
index 60fa62a87dc..4e549bd7bc2 100644
--- a/pico/pico/src/main/java/io/helidon/pico/InterceptedTrigger.java
+++ b/pico/api/src/main/java/io/helidon/pico/InterceptedTrigger.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,9 +24,12 @@
/**
* Meta-annotation for an annotation that will trigger services annotated with it to become intercepted.
+ *
+ * @see io.helidon.pico.Interceptor
+ * @see io.helidon.pico.Intercepted
*/
@Documented
-@Retention(RetentionPolicy.RUNTIME)
+@Retention(RetentionPolicy.CLASS)
@Target(ElementType.ANNOTATION_TYPE)
public @interface InterceptedTrigger {
diff --git a/pico/pico/src/main/java/io/helidon/pico/Interceptor.java b/pico/api/src/main/java/io/helidon/pico/Interceptor.java
similarity index 81%
rename from pico/pico/src/main/java/io/helidon/pico/Interceptor.java
rename to pico/api/src/main/java/io/helidon/pico/Interceptor.java
index 103f78ac081..26f5f48bfcf 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Interceptor.java
+++ b/pico/api/src/main/java/io/helidon/pico/Interceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,14 +25,15 @@ public interface Interceptor {
/**
* Called during interception of the target V. The implementation typically should finish with the call to
- * {@link Interceptor.Chain#proceed()}.
+ * {@link Interceptor.Chain#proceed}.
*
* @param ctx the invocation context
* @param chain the chain to call proceed on
+ * @param args the arguments to the call
* @param the return value type (or {@link Void} for void method elements)
* @return the return value to the caller
*/
- V proceed(InvocationContext ctx, Chain chain);
+ V proceed(InvocationContext ctx, Chain chain, Object... args);
/**
@@ -44,9 +45,10 @@ interface Chain {
/**
* Call the next interceptor in line, or finishing with the call to the service provider.
*
- * @return the result of the call.
+ * @param args the arguments pass
+ * @return the result of the call
*/
- V proceed();
+ V proceed(Object[] args);
}
}
diff --git a/pico/api/src/main/java/io/helidon/pico/InternalBootstrap.java b/pico/api/src/main/java/io/helidon/pico/InternalBootstrap.java
new file mode 100644
index 00000000000..72cb603ce69
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/InternalBootstrap.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import java.util.Optional;
+
+import io.helidon.builder.Builder;
+
+/**
+ * Internal bootstrap is what we store when {@link io.helidon.pico.PicoServices#globalBootstrap(Bootstrap)} is used.
+ */
+@Builder
+abstract class InternalBootstrap {
+
+ /**
+ * The user established bootstrap.
+ *
+ * @return user establised bootstrap
+ */
+ abstract Bootstrap bootStrap();
+
+ /**
+ * Only populated when {@link io.helidon.pico.PicoServicesConfig#TAG_DEBUG} is set.
+ *
+ * @return the calling context
+ */
+ abstract Optional callingContext();
+
+ /**
+ * Creates an internal bootstrap.
+ * See the notes in {@link CallingContextFactory#create(boolean)}.
+ *
+ * @param bootstrap Optionally, the user-defined bootstrap - one will be created if passed as null
+ * @param callingContext Optionally, the calling context if known
+ * @return a newly created internal bootstrap instance
+ */
+ static InternalBootstrap create(Bootstrap bootstrap,
+ CallingContext callingContext) {
+ if (callingContext == null) {
+ callingContext = CallingContextFactory.create(false).orElse(null);
+ }
+ return DefaultInternalBootstrap.builder()
+ .bootStrap((bootstrap == null) ? DefaultBootstrap.builder().build() : bootstrap)
+ .callingContext(Optional.ofNullable(callingContext))
+ .build();
+ }
+
+ /**
+ * Creates a calling context when nothing is known from the caller's perspective.
+ *
+ * @return a newly created internal bootstrap instance
+ */
+ static InternalBootstrap create() {
+ return create(null, null);
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InvocationContext.java b/pico/api/src/main/java/io/helidon/pico/InvocationContext.java
similarity index 71%
rename from pico/pico/src/main/java/io/helidon/pico/InvocationContext.java
rename to pico/api/src/main/java/io/helidon/pico/InvocationContext.java
index c037f9dc6d4..c178ad4b9af 100644
--- a/pico/pico/src/main/java/io/helidon/pico/InvocationContext.java
+++ b/pico/api/src/main/java/io/helidon/pico/InvocationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,22 +18,27 @@
import java.util.List;
import java.util.Map;
+import java.util.Optional;
-import io.helidon.pico.types.AnnotationAndValue;
-import io.helidon.pico.types.TypeName;
-import io.helidon.pico.types.TypedElementName;
+import io.helidon.builder.Builder;
+import io.helidon.common.types.AnnotationAndValue;
+import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypedElementName;
+
+import jakarta.inject.Provider;
/**
* Used by {@link Interceptor}.
*/
+@Builder
public interface InvocationContext {
/**
- * The root service provider being intercepted.
+ * The service provider being intercepted.
*
- * @return the root service provider being intercepted
+ * @return the service provider being intercepted
*/
- ServiceProvider> rootServiceProvider();
+ ServiceProvider> serviceProvider();
/**
* The service type name for the root service provider.
@@ -59,16 +64,16 @@ public interface InvocationContext {
/**
* The method/element argument info.
*
- * @return the method/element argument info.
+ * @return the method/element argument info
*/
- TypedElementName[] elementArgInfo();
+ Optional elementArgInfo();
/**
- * The arguments to the method.
+ * The interceptor chain.
*
- * @return the read/write method/element arguments
+ * @return the interceptor chain
*/
- Object[] elementArgs();
+ List> interceptors();
/**
* The contextual info that can be shared between interceptors.
diff --git a/pico/pico/src/main/java/io/helidon/pico/InvocationException.java b/pico/api/src/main/java/io/helidon/pico/InvocationException.java
similarity index 95%
rename from pico/pico/src/main/java/io/helidon/pico/InvocationException.java
rename to pico/api/src/main/java/io/helidon/pico/InvocationException.java
index 99a6538852a..c26f5bbeba5 100644
--- a/pico/pico/src/main/java/io/helidon/pico/InvocationException.java
+++ b/pico/api/src/main/java/io/helidon/pico/InvocationException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/pico/api/src/main/java/io/helidon/pico/Metrics.java b/pico/api/src/main/java/io/helidon/pico/Metrics.java
new file mode 100644
index 00000000000..198c71f2698
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/Metrics.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import java.util.Optional;
+
+import io.helidon.builder.Builder;
+
+/**
+ * Pico Metrics.
+ */
+@Builder
+public interface Metrics {
+
+ /**
+ * The total service count in the registry.
+ *
+ * @return total service count
+ */
+ Optional serviceCount();
+
+ /**
+ * The total number of {@code Services::lookup()} calls since jvm start, or since last reset.
+ *
+ * @return lookup count
+ */
+ Optional lookupCount();
+
+ /**
+ * The total number of {@code Services::lookup()} calls that were attempted against the lookup cache. This will be empty
+ * if caching is disabled.
+ *
+ * @see io.helidon.pico.PicoServicesConfig
+ * @return cache lookup count
+ */
+ Optional cacheLookupCount();
+
+ /**
+ * The total number of {@code Services:lookup()} calls that were successfully resolved via cache. This will be a value less
+ * than or equal to {@link #cacheLookupCount()}.
+ *
+ * @return cache hit count
+ */
+ Optional cacheHitCount();
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Module.java b/pico/api/src/main/java/io/helidon/pico/Module.java
similarity index 84%
rename from pico/pico/src/main/java/io/helidon/pico/Module.java
rename to pico/api/src/main/java/io/helidon/pico/Module.java
index 906efb40574..d35f3dad139 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Module.java
+++ b/pico/api/src/main/java/io/helidon/pico/Module.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package io.helidon.pico;
+import java.util.Optional;
+
/**
* Provides aggregation of services to the "containing" (jar) module.
*
@@ -27,7 +29,7 @@
* @see Application
*/
@Contract
-public interface Module extends Named {
+public interface Module extends OptionallyNamed {
/**
* Called by the provider implementation at bootstrapping time to bind all services / service providers to the
@@ -37,4 +39,9 @@ public interface Module extends Named {
*/
void configure(ServiceBinder binder);
+ @Override
+ default Optional named() {
+ return Optional.empty();
+ }
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Named.java b/pico/api/src/main/java/io/helidon/pico/OptionallyNamed.java
similarity index 76%
rename from pico/pico/src/main/java/io/helidon/pico/Named.java
rename to pico/api/src/main/java/io/helidon/pico/OptionallyNamed.java
index 2e50c17a44f..0994ab3dfb8 100644
--- a/pico/pico/src/main/java/io/helidon/pico/Named.java
+++ b/pico/api/src/main/java/io/helidon/pico/OptionallyNamed.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,19 +19,17 @@
import java.util.Optional;
/**
- * Provides a means to identify if the instance is named.
+ * Provides a means to identify if the instance is optionally named.
*
* @see jakarta.inject.Named
*/
-public interface Named {
+public interface OptionallyNamed {
/**
* The optional name for this instance.
*
- * @return the name associated with this instance or empty if not available or known.
+ * @return the name associated with this instance or empty if not available or known
*/
- default Optional name() {
- return Optional.empty();
- }
+ Optional named();
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationPhase.java b/pico/api/src/main/java/io/helidon/pico/Phase.java
similarity index 78%
rename from pico/pico/src/main/java/io/helidon/pico/ActivationPhase.java
rename to pico/api/src/main/java/io/helidon/pico/Phase.java
index 325eef96edd..7c5a3d299a6 100644
--- a/pico/pico/src/main/java/io/helidon/pico/ActivationPhase.java
+++ b/pico/api/src/main/java/io/helidon/pico/Phase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
/**
* Forms a progression of full activation and deactivation.
*/
-public enum ActivationPhase {
+public enum Phase {
/**
* Starting state before anything happens activation-wise.
@@ -66,10 +66,25 @@ public enum ActivationPhase {
*/
ACTIVE(true),
+ /**
+ * Called after all modules and services loaded into the service registry.
+ */
+ POST_BIND_ALL_MODULES(true),
+
+ /**
+ * Called after {@link #POST_BIND_ALL_MODULES} to resolve any latent bindings, prior to {@link #SERVICES_READY}.
+ */
+ FINAL_RESOLVE(true),
+
+ /**
+ * The service registry is fully populated and ready.
+ */
+ SERVICES_READY(true),
+
/**
* About to call pre-destroy.
*/
- PRE_DESTROYING(false),
+ PRE_DESTROYING(true),
/**
* Destroyed (after calling any pre-destroy).
@@ -84,15 +99,15 @@ public enum ActivationPhase {
/**
* Determines whether this phase passes the gate for whether deactivation (PreDestroy) can be called.
*
- * @return true if this phase is eligible to be included in shutdown processing.
- *
+ * @return true if this phase is eligible to be included in shutdown processing
* @see PicoServices#shutdown()
*/
public boolean eligibleForDeactivation() {
return eligibleForDeactivation;
}
- ActivationPhase(boolean eligibleForDeactivation) {
+ Phase(boolean eligibleForDeactivation) {
this.eligibleForDeactivation = eligibleForDeactivation;
}
+
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/PicoException.java b/pico/api/src/main/java/io/helidon/pico/PicoException.java
similarity index 90%
rename from pico/pico/src/main/java/io/helidon/pico/PicoException.java
rename to pico/api/src/main/java/io/helidon/pico/PicoException.java
index 6e5d44f3df4..9c72dd4ac2f 100644
--- a/pico/pico/src/main/java/io/helidon/pico/PicoException.java
+++ b/pico/api/src/main/java/io/helidon/pico/PicoException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
package io.helidon.pico;
/**
- * A general purpose exception.
+ * A general exception indicating that something failed related to Pico.
*
* @see PicoServiceProviderException
* @see InjectionException
diff --git a/pico/pico/src/main/java/io/helidon/pico/PicoServiceProviderException.java b/pico/api/src/main/java/io/helidon/pico/PicoServiceProviderException.java
similarity index 76%
rename from pico/pico/src/main/java/io/helidon/pico/PicoServiceProviderException.java
rename to pico/api/src/main/java/io/helidon/pico/PicoServiceProviderException.java
index 2f66fb07f8d..8038416dffd 100644
--- a/pico/pico/src/main/java/io/helidon/pico/PicoServiceProviderException.java
+++ b/pico/api/src/main/java/io/helidon/pico/PicoServiceProviderException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
import java.util.Optional;
/**
- * A general purpose exception from Pico.
+ * An exception relative to a {@link io.helidon.pico.ServiceProvider}.
*/
public class PicoServiceProviderException extends PicoException {
@@ -49,11 +49,24 @@ public PicoServiceProviderException(String msg,
Throwable cause) {
super(msg, cause);
- if (cause instanceof PicoServiceProviderException) {
+ if (cause instanceof PicoServiceProviderException) {
this.serviceProvider = ((PicoServiceProviderException) cause).serviceProvider().orElse(null);
- } else {
+ } else {
this.serviceProvider = null;
- }
+ }
+ }
+
+ /**
+ * A general purpose exception from Pico.
+ *
+ * @param msg the message
+ * @param serviceProvider the service provider
+ */
+ public PicoServiceProviderException(String msg,
+ ServiceProvider> serviceProvider) {
+ super(msg);
+ Objects.requireNonNull(serviceProvider);
+ this.serviceProvider = serviceProvider;
}
/**
@@ -83,8 +96,7 @@ public Optional> serviceProvider() {
@Override
public String getMessage() {
return super.getMessage()
- + (Objects.isNull(serviceProvider)
- ? "" : (": service provider: " + serviceProvider));
+ + (serviceProvider == null ? "" : (": service provider: " + serviceProvider));
}
}
diff --git a/pico/api/src/main/java/io/helidon/pico/PicoServices.java b/pico/api/src/main/java/io/helidon/pico/PicoServices.java
new file mode 100644
index 00000000000..da0b2544533
--- /dev/null
+++ b/pico/api/src/main/java/io/helidon/pico/PicoServices.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.pico;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+
+/**
+ * Abstract factory for all services provided by a single Helidon Pico provider implementation.
+ * An implementation of this interface must minimally supply a "services registry" - see {@link #services()}.
+ *
+ * The global singleton instance is accessed via {@link #picoServices()}. Note that optionally one can provide a
+ * primordial bootstrap configuration to the {@code Pico} services provider. One must establish any bootstrap instance
+ * prior to the first call to {@link #picoServices()} as it will use a default configuration if not explicitly set. Once
+ * the bootstrap has been set it cannot be changed for the lifespan of the JVM.
+ */
+public interface PicoServices {
+
+ /**
+ * Empty criteria will match anything and everything.
+ */
+ ServiceInfoCriteria EMPTY_CRITERIA = DefaultServiceInfoCriteria.builder().build();
+
+ /**
+ * Denotes a match to any (default) service, but required to be matched to at least one.
+ */
+ ContextualServiceQuery SERVICE_QUERY_REQUIRED = DefaultContextualServiceQuery.builder()
+ .serviceInfoCriteria(EMPTY_CRITERIA)
+ .expected(true)
+ .build();
+
+ /**
+ * Returns the {@link io.helidon.pico.Bootstrap} configuration instance that was used to initialize this instance.
+ *
+ * @return the bootstrap configuration instance
+ */
+ Bootstrap bootstrap();
+
+ /**
+ * Returns true if debugging is enabled.
+ *
+ * @return true if debugging is enabled
+ * @see io.helidon.pico.PicoServicesConfig#TAG_DEBUG
+ */
+ // Note that here in Pico at this level we don't have much access to information.
+ //
+ //
we don't have access to full config, just common config - so no config sources from system properties, etc.
+ //
we don't have access to annotation processing options that may be passed
+ //
+ static boolean isDebugEnabled() {
+ Supplier lastResortSupplier = () -> Boolean.getBoolean(PicoServicesConfig.TAG_DEBUG);
+ Optional bootstrap = globalBootstrap();
+ if (bootstrap.isPresent()) {
+ return PicoServicesConfig.asBoolean(PicoServicesConfig.TAG_DEBUG, lastResortSupplier);
+ } else {
+ // last resort
+ return lastResortSupplier.get();
+ }
+ }
+
+ /**
+ * Retrieves any primordial bootstrap configuration that previously set.
+ *
+ * @return the bootstrap primordial configuration already assigned
+ * @see #globalBootstrap(Bootstrap)
+ */
+ static Optional globalBootstrap() {
+ return PicoServicesHolder.bootstrap(false);
+ }
+
+ /**
+ * First attempts to locate and return the {@link #globalBootstrap()} and if not found will create a new bootstrap instance.
+ *
+ * @return a bootstrap
+ */
+ static Bootstrap realizedGlobalBootStrap() {
+ Optional bootstrap = globalBootstrap();
+ return bootstrap.orElseGet(() -> PicoServicesHolder.bootstrap(true).orElseThrow());
+ }
+
+ /**
+ * Sets the primordial bootstrap configuration that will supply {@link #picoServices()} during global
+ * singleton initialization.
+ *
+ * @param bootstrap the primordial global bootstrap configuration
+ * @see #globalBootstrap()
+ */
+ static void globalBootstrap(Bootstrap bootstrap) {
+ Objects.requireNonNull(bootstrap);
+ PicoServicesHolder.bootstrap(bootstrap);
+ }
+
+ /**
+ * Get {@link PicoServices} instance if available. The highest {@link io.helidon.common.Weighted} service will be loaded
+ * and returned. Remember to optionally configure any primordial {@link Bootstrap} configuration prior to the
+ * first call to get {@code PicoServices}.
+ *
+ * @return the Pico services instance
+ */
+ static Optional picoServices() {
+ return PicoServicesHolder.picoServices();
+ }
+
+ /**
+ * Short-cut for the following code block. During the first invocation the {@link io.helidon.pico.Services} registry
+ * will be initialized.
+ *
+ *
+ *
+ * @return the services instance
+ */
+ static Services realizedServices() {
+ return picoServices().orElseThrow().services();
+ }
+
+ /**
+ * Similar to {@link #services()}, but here if Pico is not available or the services registry has not yet been initialized
+ * then this method will return {@code Optional.empty()}. This is convenience for users who conditionally want to use Pico's
+ * service registry if it is currently available and in active use, but if not do alternative processing or allocations
+ * directly, etc.
+ *
+ * @return the services instance if it has already been activated and initialized, empty otherwise
+ */
+ @SuppressWarnings("unchecked")
+ static Optional unrealizedServices() {
+ PicoServices picoServices = picoServices().orElse(null);
+ if (picoServices == null) {
+ return Optional.empty();
+ }
+
+ return (Optional) picoServices.services(false);
+ }
+
+ /**
+ * The service registry. The first call typically loads and initializes the service registry. To avoid automatic loading
+ * and initialization on any first request then consider using {@link #unrealizedServices()} or {@link #services(boolean)}.
+ *
+ * @return the services registry
+ */
+ default Services services() {
+ return services(true).orElseThrow();
+ }
+
+ /**
+ * The service registry. The first call typically loads and initializes the service registry.
+ *
+ * @param initialize true to allow initialization applicable for the 1st request, false to prevent 1st call initialization
+ * @return the services registry if it is available and already has been initialized, empty if not yet initialized
+ */
+ Optional extends Services> services(boolean initialize);
+
+ /**
+ * The governing configuration.
+ *
+ * @return the config
+ */
+ PicoServicesConfig config();
+
+ /**
+ * Optionally, the injector.
+ *
+ * @return the injector, or empty if not available
+ */
+ Optional injector();
+
+ /**
+ * Attempts to perform a graceful {@link Injector#deactivate(Object, InjectorOptions)} on all managed
+ * service instances in the {@link Services} registry.
+ * Deactivation is handled within the current thread.
+ *
+ * If the service provider does not support shutdown an empty is returned.
+ *
+ * The default reference implementation for Pico will return a map of all service types that were deactivated to any
+ * throwable that was observed during that services shutdown sequence.
+ *
+ * The order in which services are deactivated is dependent upon whether the {@link #activationLog()} is available.
+ * If the activation log is available, then services will be shutdown in reverse chronological order as how they
+ * were started. If the activation log is not enabled or found to be empty then the deactivation will be in reverse
+ * order of {@link RunLevel} from the highest value down to the lowest value. If two services share
+ * the same {@link RunLevel} value then the ordering will be based upon the implementation's comparator.
+ *
+ * When shutdown returns, it is guaranteed that all services were shutdown, or failed to achieve shutdown.
+ *
+ * @return a map of all managed service types deactivated to results of deactivation, or empty if shutdown is not supported
+ */
+ Optional