diff --git a/bom/pom.xml b/bom/pom.xml
index 5f05f10f071..aa97afec567 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -1394,7 +1394,7 @@
io.helidon.pico
- helidon-pico-api
+ helidon-pico${helidon.version}
diff --git a/pico/api/pom.xml b/pico/api/pom.xml
deleted file mode 100644
index 334daa33d0e..00000000000
--- a/pico/api/pom.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
- io.helidon.pico
- helidon-pico-project
- 4.0.0-SNAPSHOT
- ../pom.xml
-
- 4.0.0
-
- helidon-pico-api
- Helidon Pico API
-
-
diff --git a/pico/api/src/main/java/io/helidon/pico/api/package-info.java b/pico/api/src/main/java/io/helidon/pico/api/package-info.java
deleted file mode 100644
index 1da67056a63..00000000000
--- a/pico/api/src/main/java/io/helidon/pico/api/package-info.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (c) 2022 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.
- */
-
-/**
- * Types that are Pico API/consumer facing. The Pico API provide types that are generally useful at compile time
- * to assign special meaning to the type. In this way it also helps with readability and intentions of the code itself.
- *
- *
{@link io.helidon.pico.api.Contract} - signifies that the type can be used for lookup in the service registry.
- *
{@link io.helidon.pico.api.ExternalContracts} - same as Contract, but applied to the implementation class instead.
- *
{@link io.helidon.pico.api.RunLevel} - ascribes meaning for when the service should start.
- *
- */
-package io.helidon.pico.api;
diff --git a/pico/builder/builder/README.md b/pico/builder/builder/README.md
index 2f98cf90362..1d96528cbd4 100644
--- a/pico/builder/builder/README.md
+++ b/pico/builder/builder/README.md
@@ -2,4 +2,4 @@
This module can either be used compile-time only or at runtime as well depending upon your usage.
-See the [main](../README.md) document for details.
+See the [main](../README.md) for details.
diff --git a/pico/builder/builder/src/main/java/io/helidon/pico/builder/spi/RequiredAttributeVisitor.java b/pico/builder/builder/src/main/java/io/helidon/pico/builder/spi/RequiredAttributeVisitor.java
index 1b0740bf455..2a87b9958be 100644
--- a/pico/builder/builder/src/main/java/io/helidon/pico/builder/spi/RequiredAttributeVisitor.java
+++ b/pico/builder/builder/src/main/java/io/helidon/pico/builder/spi/RequiredAttributeVisitor.java
@@ -16,6 +16,7 @@
package io.helidon.pico.builder.spi;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -38,7 +39,7 @@
*/
@Deprecated
public class RequiredAttributeVisitor implements AttributeVisitor {
- private List errors;
+ private final List errors = new ArrayList<>();
/**
* Default constructor.
@@ -64,20 +65,17 @@ public void visit(String attrName,
return;
}
- if (Objects.isNull(errors)) {
- errors = new java.util.LinkedList<>();
- }
errors.add("'" + attrName + "' is a required attribute and should not be null");
}
/**
* Performs the validation. Any errors will result in a thrown error.
*
- * @throws java.lang.AssertionError when any attributes are in violation with the validation policy
+ * @throws java.lang.IllegalStateException when any attributes are in violation with the validation policy
*/
public void validate() {
- if (Objects.nonNull(errors) && !errors.isEmpty()) {
- throw new AssertionError(String.join(", ", errors));
+ if (!errors.isEmpty()) {
+ throw new IllegalStateException(String.join(", ", errors));
}
}
diff --git a/pico/builder/processor-spi/src/main/java/io/helidon/pico/builder/processor/spi/DefaultTypeInfo.java b/pico/builder/processor-spi/src/main/java/io/helidon/pico/builder/processor/spi/DefaultTypeInfo.java
index 8b0fbf028fa..258e30a0f77 100644
--- a/pico/builder/processor-spi/src/main/java/io/helidon/pico/builder/processor/spi/DefaultTypeInfo.java
+++ b/pico/builder/processor-spi/src/main/java/io/helidon/pico/builder/processor/spi/DefaultTypeInfo.java
@@ -16,12 +16,11 @@
package io.helidon.pico.builder.processor.spi;
+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;
import java.util.Optional;
@@ -48,13 +47,20 @@ public class DefaultTypeInfo implements TypeInfo {
protected DefaultTypeInfo(Builder b) {
this.typeName = b.typeName;
this.typeKind = b.typeKind;
- this.annotations = Objects.isNull(b.annotations)
- ? Collections.emptyList() : Collections.unmodifiableList(new LinkedList<>(b.annotations));
- this.elementInfo = Objects.isNull(b.elementInfo)
- ? Collections.emptyList() : Collections.unmodifiableList(new LinkedList<>(b.elementInfo));
+ this.annotations = Collections.unmodifiableList(new LinkedList<>(b.annotations));
+ this.elementInfo = Collections.unmodifiableList(new LinkedList<>(b.elementInfo));
this.superTypeInfo = b.superTypeInfo;
}
+ /**
+ * Creates a new builder for this type.
+ *
+ * @return the fluent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public TypeName typeName() {
return typeName;
@@ -96,26 +102,16 @@ protected String toStringInner() {
+ ", superTypeInfo=" + superTypeInfo();
}
-
- /**
- * Creates a new builder for this type.
- *
- * @return the fluent builder
- */
- public static Builder builder() {
- return new Builder();
- }
-
-
/**
* Builder for this type.
*/
- public static class Builder {
+ public static class Builder implements io.helidon.common.Builder {
+ private final List annotations = new ArrayList<>();
+ private final List elementInfo = new ArrayList<>();
+
private TypeName typeName;
private String typeKind;
- private List annotations;
- private List elementInfo;
- private Map defaultValueMap;
+
private TypeInfo superTypeInfo;
/**
@@ -124,6 +120,16 @@ public static class Builder {
protected Builder() {
}
+ /**
+ * Builds the instance.
+ *
+ * @return the built instance
+ */
+ @Override
+ public DefaultTypeInfo build() {
+ return new DefaultTypeInfo(this);
+ }
+
/**
* Sets the typeName to val.
*
@@ -153,7 +159,9 @@ public Builder typeKind(String val) {
* @return this fluent builder
*/
public Builder annotations(Collection val) {
- this.annotations = new LinkedList<>(Objects.requireNonNull(val));
+ Objects.requireNonNull(val);
+ this.annotations.clear();
+ this.annotations.addAll(val);
return this;
}
@@ -164,9 +172,7 @@ public Builder annotations(Collection val) {
* @return this fluent builder
*/
public Builder addAnnotation(AnnotationAndValue val) {
- if (Objects.isNull(annotations)) {
- annotations = new LinkedList<>();
- }
+ Objects.requireNonNull(val);
annotations.add(Objects.requireNonNull(val));
return this;
}
@@ -178,7 +184,9 @@ public Builder addAnnotation(AnnotationAndValue val) {
* @return this fluent builder
*/
public Builder elementInfo(Collection val) {
- this.elementInfo = new LinkedList<>(Objects.requireNonNull(val));
+ Objects.requireNonNull(val);
+ this.elementInfo.clear();
+ this.elementInfo.addAll(val);
return this;
}
@@ -189,39 +197,11 @@ public Builder elementInfo(Collection val) {
* @return this fluent builder
*/
public Builder addElementInfo(TypedElementName val) {
- if (Objects.isNull(elementInfo)) {
- elementInfo = new LinkedList<>();
- }
+ Objects.requireNonNull(val);
elementInfo.add(Objects.requireNonNull(val));
return this;
}
- /**
- * Sets the defaultValueMap to val.
- *
- * @param val the value
- * @return this fluent builder
- */
- public Builder defaultValueMap(Map val) {
- this.defaultValueMap = new LinkedHashMap<>(Objects.requireNonNull(val));
- return this;
- }
-
- /**
- * Adds a singular defaultValue val.
- *
- * @param key the key
- * @param val the value
- * @return this fluent builder
- */
- public Builder addDefaultValue(TypedElementName key, String val) {
- if (Objects.isNull(defaultValueMap)) {
- defaultValueMap = new LinkedHashMap<>();
- }
- defaultValueMap.put(key, val);
- return this;
- }
-
/**
* Sets the superTypeInfo to val.
*
@@ -229,18 +209,10 @@ public Builder addDefaultValue(TypedElementName key, String val) {
* @return this fluent builder
*/
public Builder superTypeInfo(TypeInfo val) {
+ Objects.requireNonNull(val);
this.superTypeInfo = val;
return this;
}
-
- /**
- * Builds the instance.
- *
- * @return the built instance
- */
- public DefaultTypeInfo build() {
- return new DefaultTypeInfo(this);
- }
}
}
diff --git a/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/BodyContext.java b/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/BodyContext.java
new file mode 100644
index 00000000000..a5cd612e864
--- /dev/null
+++ b/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/BodyContext.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (c) 2022 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.builder.processor.tools;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.helidon.pico.builder.processor.spi.TypeInfo;
+import io.helidon.pico.builder.spi.BeanUtils;
+import io.helidon.pico.types.AnnotationAndValue;
+import io.helidon.pico.types.DefaultAnnotationAndValue;
+import io.helidon.pico.types.TypeName;
+import io.helidon.pico.types.TypedElementName;
+
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.BUILDER_ANNO_TYPE_NAME;
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.DEFAULT_INCLUDE_META_ATTRIBUTES;
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.DEFAULT_LIST_TYPE;
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.DEFAULT_MAP_TYPE;
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES;
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.DEFAULT_SET_TYPE;
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.SUPPORT_STREAMS_ON_BUILDER;
+import static io.helidon.pico.builder.processor.tools.DefaultBuilderCreator.SUPPORT_STREAMS_ON_IMPL;
+
+/**
+ * Represents the context of the body being code generated.
+ */
+public class BodyContext {
+ private final boolean doingConcreteType;
+ private final TypeName implTypeName;
+ private final TypeInfo typeInfo;
+ private final AnnotationAndValue builderAnnotation;
+ 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;
+ private final boolean requireLibraryDependencies;
+ private final boolean isBeanStyleRequired;
+ private final String listType;
+ private final String mapType;
+ private final String setType;
+ private final boolean hasParent;
+ private final TypeName ctorBuilderAcceptTypeName;
+ private final String genericBuilderClassDecl;
+ private final String genericBuilderAliasDecl;
+ private final String genericBuilderAcceptAliasDecl;
+
+ /**
+ * Constructor.
+ *
+ * @param doingConcreteType true if the concrete type is being generated, otherwise the abstract class
+ * @param implTypeName the impl type name
+ * @param typeInfo the type info
+ * @param builderAnnotation the builder annotation
+ */
+ BodyContext(boolean doingConcreteType,
+ TypeName implTypeName,
+ TypeInfo typeInfo,
+ AnnotationAndValue builderAnnotation) {
+ this.doingConcreteType = doingConcreteType;
+ this.implTypeName = implTypeName;
+ this.typeInfo = typeInfo;
+ this.builderAnnotation = builderAnnotation;
+ this.hasStreamSupportOnImpl = hasStreamSupportOnImpl(doingConcreteType, builderAnnotation);
+ this.hasStreamSupportOnBuilder = hasStreamSupportOnBuilder(doingConcreteType, builderAnnotation);
+ this.includeMetaAttributes = toIncludeMetaAttributes(builderAnnotation, typeInfo);
+ this.requireLibraryDependencies = toRequireLibraryDependencies(builderAnnotation, typeInfo);
+ this.isBeanStyleRequired = toRequireBeanStyle(builderAnnotation, typeInfo);
+ this.listType = toListImplType(builderAnnotation, typeInfo);
+ this.mapType = toMapImplType(builderAnnotation, typeInfo);
+ this.setType = toSetImplType(builderAnnotation, typeInfo);
+ gatherAllAttributeNames(this, typeInfo);
+ assert (allTypeInfos.size() == allAttributeNames.size());
+ this.hasParent = Objects.nonNull(parentTypeName.get());
+ this.ctorBuilderAcceptTypeName = (hasParent)
+ ? typeInfo.typeName()
+ : (
+ Objects.nonNull(parentAnnotationType.get()) && typeInfo.elementInfo().isEmpty()
+ ? typeInfo.superTypeInfo().get().typeName() : typeInfo.typeName());
+ this.genericBuilderClassDecl = "Builder";
+ this.genericBuilderAliasDecl = ("B".equals(typeInfo.typeName().className())) ? "BU" : "B";
+ this.genericBuilderAcceptAliasDecl = ("T".equals(typeInfo.typeName().className())) ? "TY" : "T";
+ }
+
+ /**
+ * Returns true if we are currently processing the concrete builder type.
+ *
+ * @return true if we are processing the concrete type
+ */
+ protected boolean doingConcreteType() {
+ return doingConcreteType;
+ }
+
+ /**
+ * Returns the impl type name.
+ *
+ * @return the type name
+ */
+ protected TypeName implTypeName() {
+ return implTypeName;
+ }
+
+ /**
+ * Returns the type info.
+ *
+ * @return the type info
+ */
+ protected TypeInfo typeInfo() {
+ return typeInfo;
+ }
+
+ /**
+ * Returns the builder annotation that triggers things.
+ *
+ * @return the builder annotation
+ */
+ protected AnnotationAndValue builderAnnotation() {
+ return builderAnnotation;
+ }
+
+ /**
+ * Returns the map of all type elements in the entire hierarchy.
+ *
+ * @return the map of elements by name
+ */
+ protected Map map() {
+ return map;
+ }
+
+ /**
+ * Returns the list of all type elements.
+ *
+ * @return the list of type elements
+ */
+ protected List allTypeInfos() {
+ return allTypeInfos;
+ }
+
+ /**
+ * Returns the list of all attributes names.
+ *
+ * @return the list of attribute names
+ */
+ protected List allAttributeNames() {
+ return allAttributeNames;
+ }
+
+ /**
+ * Returns the parent type name of the builder.
+ *
+ * @return the parent type name
+ */
+ protected AtomicReference parentTypeName() {
+ return parentTypeName;
+ }
+
+ /**
+ * Returns the parent annotation type.
+ *
+ * @return the parent annotation type
+ */
+ protected AtomicReference parentAnnotationType() {
+ return parentAnnotationType;
+ }
+
+ /**
+ * Returns true if there is stream support included on the generated class.
+ *
+ * @return true if stream support enabled
+ */
+ protected boolean hasStreamSupportOnImpl() {
+ return hasStreamSupportOnImpl;
+ }
+
+ /**
+ * Returns true if there is stream support included on the builder generated class.
+ *
+ * @return true if stream support enabled
+ */
+ protected boolean hasStreamSupportOnBuilder() {
+ return hasStreamSupportOnBuilder;
+ }
+
+ /**
+ * Returns true if meta attributes should be generated.
+ *
+ * @return true if meta attributes should be generated
+ */
+ protected boolean includeMetaAttributes() {
+ return includeMetaAttributes;
+ }
+
+ /**
+ * Returns true if Helidon library dependencies should be expected.
+ *
+ * @return true if Helidon library dependencies are expected
+ */
+ protected boolean requireLibraryDependencies() {
+ return requireLibraryDependencies;
+ }
+
+ /**
+ * Returns true if bean "getter" and "is" style is required.
+ *
+ * @return true if bean style is required
+ */
+ protected boolean isBeanStyleRequired() {
+ return isBeanStyleRequired;
+ }
+
+ /**
+ * Returns the list type generated.
+ *
+ * @return the list type
+ */
+ protected String listType() {
+ return listType;
+ }
+
+ /**
+ * Returns the map type generated.
+ *
+ * @return the map type
+ */
+ protected String mapType() {
+ return mapType;
+ }
+
+ /**
+ * Returns the set type generated.
+ *
+ * @return the set type
+ */
+ protected String setType() {
+ return setType;
+ }
+
+ /**
+ * Returns true if the current type has a parent.
+ *
+ * @return true if current has parent
+ */
+ protected boolean hasParent() {
+ return hasParent;
+ }
+
+ /**
+ * Returns the streamable accept type of the builder and constructor.
+ *
+ * @return the builder accept type
+ */
+ protected TypeName ctorBuilderAcceptTypeName() {
+ return ctorBuilderAcceptTypeName;
+ }
+
+ /**
+ * Returns the generic declaration for the builder class type.
+ *
+ * @return the generic declaration
+ */
+ protected String genericBuilderClassDecl() {
+ return genericBuilderClassDecl;
+ }
+
+ /**
+ * Returns the builder generics alias name for the type being built.
+ *
+ * @return the builder generics alias name
+ */
+ protected String genericBuilderAliasDecl() {
+ return genericBuilderAliasDecl;
+ }
+
+ /**
+ * Returns the builder generics alias name for the builder itself.
+ *
+ * @return the builder generics alias name
+ */
+ protected String genericBuilderAcceptAliasDecl() {
+ return genericBuilderAcceptAliasDecl;
+ }
+
+ /**
+ * returns the bean attribute name of a particular method.
+ *
+ * @param method the method
+ * @param isBeanStyleRequired is bean style required
+ * @return the bean attribute 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();
+ if (!isBeanStyleRequired) {
+ return (!attrNames.isEmpty()) ? attrNames.get(0) : method.elementName();
+ }
+ return Objects.requireNonNull(attrNames.get(0));
+ }
+
+ private static boolean hasStreamSupportOnImpl(boolean ignoreDoingConcreteClass,
+ AnnotationAndValue ignoreBuilderAnnotation) {
+ return SUPPORT_STREAMS_ON_IMPL;
+ }
+
+ private static boolean hasStreamSupportOnBuilder(boolean ignoreDoingConcreteClass,
+ AnnotationAndValue ignoreBuilderAnnotation) {
+ return SUPPORT_STREAMS_ON_BUILDER;
+ }
+
+ /**
+ * In support of {@link io.helidon.pico.builder.Builder#includeMetaAttributes()}.
+ */
+ private static boolean toIncludeMetaAttributes(AnnotationAndValue builderAnnotation,
+ TypeInfo typeInfo) {
+ String val = searchForBuilderAnnotation("includeMetaAttributes", builderAnnotation, typeInfo);
+ return val == null ? DEFAULT_INCLUDE_META_ATTRIBUTES : Boolean.parseBoolean(val);
+ }
+
+ /**
+ * In support of {@link io.helidon.pico.builder.Builder#requireLibraryDependencies()}.
+ */
+ private static boolean toRequireLibraryDependencies(AnnotationAndValue builderAnnotation,
+ TypeInfo typeInfo) {
+ String val = searchForBuilderAnnotation("requireLibraryDependencies", builderAnnotation, typeInfo);
+ return val == null ? DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES : Boolean.parseBoolean(val);
+ }
+
+ /**
+ * In support of {@link io.helidon.pico.builder.Builder#requireBeanStyle()}.
+ */
+ private static boolean toRequireBeanStyle(AnnotationAndValue builderAnnotation,
+ TypeInfo typeInfo) {
+ String val = searchForBuilderAnnotation("requireBeanStyle", builderAnnotation, typeInfo);
+ return Boolean.parseBoolean(val);
+ }
+
+ /**
+ * In support of {@link io.helidon.pico.builder.Builder#listImplType()}.
+ */
+ private static String toListImplType(AnnotationAndValue builderAnnotation,
+ TypeInfo typeInfo) {
+ String type = searchForBuilderAnnotation("listImplType", builderAnnotation, typeInfo);
+ return (!BuilderTypeTools.hasNonBlankValue(type)) ? DEFAULT_LIST_TYPE : type;
+ }
+
+ /**
+ * In support of {@link io.helidon.pico.builder.Builder#mapImplType()} ()}.
+ */
+ private static String toMapImplType(AnnotationAndValue builderAnnotation,
+ TypeInfo typeInfo) {
+ String type = searchForBuilderAnnotation("mapImplType", builderAnnotation, typeInfo);
+ return (!BuilderTypeTools.hasNonBlankValue(type)) ? DEFAULT_MAP_TYPE : type;
+ }
+
+ /**
+ * In support of {@link io.helidon.pico.builder.Builder#setImplType()}.
+ */
+ private static String toSetImplType(AnnotationAndValue builderAnnotation,
+ TypeInfo typeInfo) {
+ String type = searchForBuilderAnnotation("setImplType", builderAnnotation, typeInfo);
+ return (!BuilderTypeTools.hasNonBlankValue(type)) ? DEFAULT_SET_TYPE : type;
+ }
+
+ private static String searchForBuilderAnnotation(String key,
+ AnnotationAndValue builderAnnotation,
+ TypeInfo typeInfo) {
+ String val = builderAnnotation.value(key).orElse(null);
+ if (val != null) {
+ return val;
+ }
+
+ if (!builderAnnotation.typeName().equals(BUILDER_ANNO_TYPE_NAME)) {
+ builderAnnotation = DefaultAnnotationAndValue
+ .findFirst(BUILDER_ANNO_TYPE_NAME.name(), typeInfo.annotations()).orElse(null);
+ if (Objects.nonNull(builderAnnotation)) {
+ val = builderAnnotation.value(key).orElse(null);
+ }
+ }
+
+ return val;
+ }
+
+ private static void gatherAllAttributeNames(BodyContext ctx,
+ TypeInfo typeInfo) {
+ TypeInfo superTypeInfo = typeInfo.superTypeInfo().orElse(null);
+ if (Objects.nonNull(superTypeInfo)) {
+ Optional extends AnnotationAndValue> superBuilderAnnotation = DefaultAnnotationAndValue
+ .findFirst(ctx.builderAnnotation.typeName().name(), superTypeInfo.annotations());
+ if (superBuilderAnnotation.isEmpty()) {
+ gatherAllAttributeNames(ctx, superTypeInfo);
+ } else {
+ populateMap(ctx.map, superTypeInfo, ctx.isBeanStyleRequired);
+ }
+
+ if (Objects.isNull(ctx.parentTypeName.get())
+ && superTypeInfo.typeKind().equals("INTERFACE")) {
+ ctx.parentTypeName.set(superTypeInfo.typeName());
+ } else if (Objects.isNull(ctx.parentAnnotationType.get())
+ && superTypeInfo.typeKind().equals("ANNOTATION_TYPE")) {
+ ctx.parentAnnotationType.set(superTypeInfo.typeName());
+ }
+ }
+
+ for (TypedElementName method : typeInfo.elementInfo()) {
+ String beanAttributeName = toBeanAttributeName(method, ctx.isBeanStyleRequired);
+ TypedElementName existing = ctx.map.get(beanAttributeName);
+ if (Objects.nonNull(existing)
+ && BeanUtils.isBooleanType(method.typeName().name())
+ && method.elementName().startsWith("is")) {
+ AtomicReference>> alternateNames = new AtomicReference<>();
+ BeanUtils.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()
+ .filter(it -> !it.equals(currentAttrName))
+ .findFirst();
+ if (alternateName.isPresent() && !ctx.map.containsKey(alternateName.get())) {
+ beanAttributeName = alternateName.get();
+ existing = ctx.map.get(beanAttributeName);
+ }
+ }
+
+ if (Objects.nonNull(existing)) {
+ 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);
+ if (pos >= 0) {
+ ctx.allTypeInfos.set(pos, method);
+ }
+ continue;
+ }
+
+ Object prev = ctx.map.put(beanAttributeName, method);
+ assert (Objects.isNull(prev));
+
+ ctx.allTypeInfos.add(method);
+ if (ctx.allAttributeNames.contains(beanAttributeName)) {
+ throw new IllegalStateException("duplicate attribute name: " + beanAttributeName + " processing " + typeInfo);
+ }
+ ctx.allAttributeNames.add(beanAttributeName);
+ }
+ }
+
+ private static void populateMap(Map map,
+ TypeInfo typeInfo,
+ boolean isBeanStyleRequired) {
+ if (typeInfo.superTypeInfo().isPresent()) {
+ populateMap(map, typeInfo.superTypeInfo().get(), isBeanStyleRequired);
+ }
+
+ for (TypedElementName method : typeInfo.elementInfo()) {
+ String beanAttributeName = toBeanAttributeName(method, isBeanStyleRequired);
+ TypedElementName existing = map.get(beanAttributeName);
+ if (Objects.nonNull(existing)) {
+ 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(map.put(beanAttributeName, method));
+ } else {
+ Object prev = map.put(beanAttributeName, method);
+ assert (Objects.isNull(prev));
+ }
+ }
+ }
+
+}
diff --git a/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/BuilderTypeTools.java b/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/BuilderTypeTools.java
index d61817bd93e..9b28b8ebcb1 100644
--- a/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/BuilderTypeTools.java
+++ b/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/BuilderTypeTools.java
@@ -17,6 +17,7 @@
package io.helidon.pico.builder.processor.tools;
import java.lang.annotation.Annotation;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -61,9 +62,6 @@
*/
@Weight(Weighted.DEFAULT_WEIGHT - 1)
public class BuilderTypeTools implements TypeInfoCreator {
-
- private final System.Logger logger = System.getLogger(getClass().getName());
-
/**
* Default constructor.
*/
@@ -109,7 +107,7 @@ public Optional createTypeInfo(AnnotationAndValue annotation,
.typeKind(String.valueOf(element.getKind()))
.annotations(BuilderTypeTools.createAnnotationAndValueListFromElement(element, processingEnv.getElementUtils()))
.elementInfo(elementInfo)
- .superTypeInfo(toTypeInfo(annotation, element, processingEnv).orElse(null))
+ .update(it -> toTypeInfo(annotation, element, processingEnv).ifPresent(it::superTypeInfo))
.build());
}
@@ -191,15 +189,19 @@ public static Optional createTypeNameFromElement(Element type)
return createTypeNameFromMirror(((ExecutableElement) type).getReturnType());
}
- String className = type.getSimpleName().toString();
+ List classNames = new ArrayList<>();
+ classNames.add(type.getSimpleName().toString());
while (Objects.nonNull(type.getEnclosingElement())
&& ElementKind.PACKAGE != type.getEnclosingElement().getKind()) {
- className = type.getEnclosingElement().getSimpleName() + "." + className;
+ classNames.add(type.getEnclosingElement().getSimpleName().toString());
type = type.getEnclosingElement();
}
- return Optional.of(Objects.isNull(type.getEnclosingElement())
- ? DefaultTypeName.create(type.toString(), className)
- : DefaultTypeName.create(type.getEnclosingElement().toString(), className));
+ Collections.reverse(classNames);
+ String className = String.join(".", classNames);
+
+ Element packageName = type.getEnclosingElement() == null ? type : type.getEnclosingElement();
+
+ return Optional.of(DefaultTypeName.create(packageName.toString(), className));
}
/**
@@ -238,7 +240,7 @@ public static Optional createTypeNameFromMirror(TypeMirror type
type = double.class;
break;
default:
- throw new AssertionError("unknown primitive type: " + kind);
+ throw new IllegalStateException("unknown primitive type: " + kind);
}
return Optional.of(DefaultTypeName.create(type));
@@ -261,20 +263,21 @@ public static Optional createTypeNameFromMirror(TypeMirror type
if (typeMirror instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) typeMirror;
- DefaultTypeName result = createTypeNameFromElement(declaredType.asElement()).orElse(null);
- List typeParams = declaredType.getTypeArguments().stream()
+ List typeParams = declaredType.getTypeArguments()
+ .stream()
.map(BuilderTypeTools::createTypeNameFromMirror)
- .filter(Optional::isPresent)
- .map(Optional::orElseThrow)
- .filter(Objects::nonNull)
+ .flatMap(Optional::stream)
.collect(Collectors.toList());
- if (!typeParams.isEmpty()) {
- result = result.toBuilder().typeArguments(typeParams).build();
+
+ DefaultTypeName result = createTypeNameFromElement(declaredType.asElement()).orElse(null);
+ if (typeParams.isEmpty() || result == null) {
+ return Optional.ofNullable(result);
}
- return Optional.of(result);
+
+ return Optional.of(result.toBuilder().typeArguments(typeParams).build());
}
- throw new AssertionError("unknown type mirror: " + typeMirror);
+ throw new IllegalStateException("Unknown type mirror: " + typeMirror);
}
/**
@@ -301,10 +304,8 @@ public static Optional extends AnnotationMirror> findAnnotationMirror(String a
public static Optional createAnnotationAndValueFromMirror(AnnotationMirror am,
Elements elements) {
Optional val = createTypeNameFromMirror(am.getAnnotationType());
- if (val.isEmpty()) {
- return Optional.empty();
- }
- return Optional.ofNullable(DefaultAnnotationAndValue.create(val.get(), extractValues(am, Optional.of(elements))));
+
+ return val.map(it -> DefaultAnnotationAndValue.create(it, extractValues(am, elements)));
}
/**
@@ -330,12 +331,8 @@ public static List createAnnotationAndValueListFromElement(E
* @return the extracted values
*/
public static Map extractValues(AnnotationMirror am,
- Optional elements) {
- if (elements.isPresent()) {
- return extractValues(elements.get().getElementValuesWithDefaults(am));
- }
-
- return extractValues(am.getElementValues());
+ Elements elements) {
+ return extractValues(elements.getElementValuesWithDefaults(am));
}
/**
@@ -384,14 +381,16 @@ public static TypedElementName createTypedElementNameFromElement(Element v,
createAnnotationAndValueListFromElement(((DeclaredType) returnType).asElement(), elements);
}
AnnotationValue annotationValue = ee.getDefaultValue();
- defaultValue = Objects.isNull(annotationValue)
- ? null : annotationValue.accept(new ToStringAnnotationValueVisitor()
+ defaultValue = annotationValue == null
+ ? null
+ : annotationValue.accept(new ToStringAnnotationValueVisitor()
.mapBooleanToNull(true)
.mapVoidToNull(true)
.mapBlankArrayToNull(true)
.mapEmptyStringToNull(true)
.mapToSourceDeclaration(true), null);
}
+ componentTypeNames = componentTypeNames == null ? List.of() : componentTypeNames;
return DefaultTypedElementName.builder()
.typeName(type)
diff --git a/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/DefaultBuilderCreator.java b/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/DefaultBuilderCreator.java
index 54d26bed6bc..7483543ffa2 100644
--- a/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/DefaultBuilderCreator.java
+++ b/pico/builder/processor-tools/src/main/java/io/helidon/pico/builder/processor/tools/DefaultBuilderCreator.java
@@ -20,7 +20,6 @@
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;
@@ -28,7 +27,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -51,22 +49,24 @@
import io.helidon.pico.types.TypeName;
import io.helidon.pico.types.TypedElementName;
+import static io.helidon.pico.builder.processor.tools.BodyContext.toBeanAttributeName;
+
/**
* Default implementation for {@link io.helidon.pico.builder.processor.spi.BuilderCreator}.
*/
@Weight(Weighted.DEFAULT_WEIGHT - 1) // allow all other creators to take precedence over us...
public class DefaultBuilderCreator implements BuilderCreator {
- private static final boolean DEFAULT_INCLUDE_META_ATTRIBUTES = true;
- private static final boolean DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES = true;
- private static final String DEFAULT_IMPL_PREFIX = Builder.DEFAULT_IMPL_PREFIX;
- private static final String DEFAULT_ABSTRACT_IMPL_PREFIX = Builder.DEFAULT_ABSTRACT_IMPL_PREFIX;
- private static final String DEFAULT_SUFFIX = Builder.DEFAULT_SUFFIX;
- private static final String DEFAULT_LIST_TYPE = Builder.DEFAULT_LIST_TYPE.getName();
- private static final String DEFAULT_MAP_TYPE = Builder.DEFAULT_MAP_TYPE.getName();
- private static final String DEFAULT_SET_TYPE = Builder.DEFAULT_SET_TYPE.getName();
- private static final TypeName BUILDER_ANNO_TYPE_NAME = DefaultTypeName.create(Builder.class);
- private static final boolean SUPPORT_STREAMS_ON_IMPL = false;
- private static final boolean SUPPORT_STREAMS_ON_BUILDER = true;
+ 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;
+ static final String DEFAULT_ABSTRACT_IMPL_PREFIX = Builder.DEFAULT_ABSTRACT_IMPL_PREFIX;
+ static final String DEFAULT_SUFFIX = Builder.DEFAULT_SUFFIX;
+ static final String DEFAULT_LIST_TYPE = Builder.DEFAULT_LIST_TYPE.getName();
+ static final String DEFAULT_MAP_TYPE = Builder.DEFAULT_MAP_TYPE.getName();
+ static final String DEFAULT_SET_TYPE = Builder.DEFAULT_SET_TYPE.getName();
+ static final TypeName BUILDER_ANNO_TYPE_NAME = DefaultTypeName.create(Builder.class);
+ static final boolean SUPPORT_STREAMS_ON_IMPL = false;
+ static final boolean SUPPORT_STREAMS_ON_BUILDER = true;
/**
* Default constructor.
@@ -133,125 +133,6 @@ protected List postValidate(List builds) {
return builds;
}
- /**
- * In support of {@link io.helidon.pico.builder.Builder#packageName()}.
- */
- private String toPackageName(String packageName,
- AnnotationAndValue builderAnnotation) {
- String packageNameFromAnno = builderAnnotation.value("packageName").orElse(null);
- if (Objects.isNull(packageNameFromAnno) || packageNameFromAnno.isBlank()) {
- return packageName;
- } else if (packageNameFromAnno.startsWith(".")) {
- return packageName + packageNameFromAnno;
- } else {
- return packageNameFromAnno;
- }
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#abstractImplPrefix()}.
- */
- private String toAbstractImplTypePrefix(AnnotationAndValue builderAnnotation) {
- return builderAnnotation.value("abstractImplPrefix").orElse(DEFAULT_ABSTRACT_IMPL_PREFIX);
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#implPrefix()}.
- */
- private String toImplTypePrefix(AnnotationAndValue builderAnnotation) {
- return builderAnnotation.value("implPrefix").orElse(DEFAULT_IMPL_PREFIX);
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#implSuffix()}.
- */
- private String toImplTypeSuffix(AnnotationAndValue builderAnnotation) {
- return builderAnnotation.value("implSuffix").orElse(DEFAULT_SUFFIX);
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#includeMetaAttributes()}.
- */
- private static boolean toIncludeMetaAttributes(AnnotationAndValue builderAnnotation,
- TypeInfo typeInfo) {
- String val = searchForBuilderAnnotation("includeMetaAttributes", builderAnnotation, typeInfo);
- return Objects.isNull(val) ? DEFAULT_INCLUDE_META_ATTRIBUTES : Boolean.parseBoolean(val);
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#requireLibraryDependencies()}.
- */
- private static boolean toRequireLibraryDependencies(AnnotationAndValue builderAnnotation,
- TypeInfo typeInfo) {
- String val = searchForBuilderAnnotation("requireLibraryDependencies", builderAnnotation, typeInfo);
- return Objects.isNull(val) ? DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES : Boolean.parseBoolean(val);
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#requireBeanStyle()}.
- */
- private static boolean toRequireBeanStyle(AnnotationAndValue builderAnnotation,
- TypeInfo typeInfo) {
- String val = searchForBuilderAnnotation("requireBeanStyle", builderAnnotation, typeInfo);
- return Boolean.parseBoolean(val);
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#listImplType()}.
- */
- private static String toListImplType(AnnotationAndValue builderAnnotation,
- TypeInfo typeInfo) {
- String type = searchForBuilderAnnotation("listImplType", builderAnnotation, typeInfo);
- return (!BuilderTypeTools.hasNonBlankValue(type)) ? DEFAULT_LIST_TYPE : type;
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#mapImplType()} ()}.
- */
- private static String toMapImplType(AnnotationAndValue builderAnnotation,
- TypeInfo typeInfo) {
- String type = searchForBuilderAnnotation("mapImplType", builderAnnotation, typeInfo);
- return (!BuilderTypeTools.hasNonBlankValue(type)) ? DEFAULT_MAP_TYPE : type;
- }
-
- /**
- * In support of {@link io.helidon.pico.builder.Builder#setImplType()}.
- */
- private static String toSetImplType(AnnotationAndValue builderAnnotation,
- TypeInfo typeInfo) {
- String type = searchForBuilderAnnotation("setImplType", builderAnnotation, typeInfo);
- return (!BuilderTypeTools.hasNonBlankValue(type)) ? DEFAULT_SET_TYPE : type;
- }
-
- private static boolean hasStreamSupportOnImpl(boolean ignoreDoingConcreteClass,
- AnnotationAndValue ignoreBuilderAnnotation) {
- return SUPPORT_STREAMS_ON_IMPL;
- }
-
- private static boolean hasStreamSupportOnBuilder(boolean ignoreDoingConcreteClass,
- AnnotationAndValue ignoreBuilderAnnotation) {
- return SUPPORT_STREAMS_ON_BUILDER;
- }
-
- private static String searchForBuilderAnnotation(String key,
- AnnotationAndValue builderAnnotation,
- TypeInfo typeInfo) {
- String val = builderAnnotation.value(key).orElse(null);
- if (Objects.nonNull(val)) {
- return val;
- }
-
- if (!builderAnnotation.typeName().equals(BUILDER_ANNO_TYPE_NAME)) {
- builderAnnotation = DefaultAnnotationAndValue
- .findFirst(BUILDER_ANNO_TYPE_NAME, typeInfo.annotations(), false).orElse(null);
- if (Objects.nonNull(builderAnnotation)) {
- val = builderAnnotation.value(key).orElse(null);
- }
- }
-
- return val;
- }
-
/**
* Constructs the abstract implementation type name for what is code generated.
*
@@ -282,70 +163,6 @@ protected Optional toImplTypeName(TypeName typeName,
return Optional.of(DefaultTypeName.create(toPackageName, prefix + typeName.className() + suffix));
}
- /**
- * Represents the context of the body being code generated.
- */
- protected static class BodyContext {
- private final boolean doingConcreteType;
- private final TypeName implTypeName;
- private final TypeInfo typeInfo;
- private final AnnotationAndValue builderAnnotation;
- 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;
- private final boolean requireLibraryDependencies;
- private final boolean isBeanStyleRequired;
- private final String listType;
- private final String mapType;
- private final String setType;
- private final boolean hasParent;
- private final TypeName ctorBuilderAcceptTypeName;
- private final String genericBuilderClassDecl;
- private final String genericBuilderAliasDecl;
- private final String genericBuilderAcceptAliasDecl;
-
- /**
- * Constructor.
- *
- * @param doingConcreteType true if the concrete type is being generated, otherwise the abstract class
- * @param implTypeName the impl type name
- * @param typeInfo the type info
- * @param builderAnnotation the builder annotation
- */
- protected BodyContext(boolean doingConcreteType,
- TypeName implTypeName,
- TypeInfo typeInfo,
- AnnotationAndValue builderAnnotation) {
- this.doingConcreteType = doingConcreteType;
- this.implTypeName = implTypeName;
- this.typeInfo = typeInfo;
- this.builderAnnotation = builderAnnotation;
- this.hasStreamSupportOnImpl = hasStreamSupportOnImpl(doingConcreteType, builderAnnotation);
- this.hasStreamSupportOnBuilder = hasStreamSupportOnBuilder(doingConcreteType, builderAnnotation);
- this.includeMetaAttributes = toIncludeMetaAttributes(builderAnnotation, typeInfo);
- this.requireLibraryDependencies = toRequireLibraryDependencies(builderAnnotation, typeInfo);
- this.isBeanStyleRequired = toRequireBeanStyle(builderAnnotation, typeInfo);
- this.listType = toListImplType(builderAnnotation, typeInfo);
- this.mapType = toMapImplType(builderAnnotation, typeInfo);
- this.setType = toSetImplType(builderAnnotation, typeInfo);
- gatherAllAttributeNames(this, typeInfo);
- assert (allTypeInfos.size() == allAttributeNames.size());
- this.hasParent = Objects.nonNull(parentTypeName.get());
- this.ctorBuilderAcceptTypeName = (hasParent)
- ? typeInfo.typeName()
- : (Objects.nonNull(parentAnnotationType.get()) && typeInfo.elementInfo().isEmpty()
- ? typeInfo.superTypeInfo().get().typeName() : typeInfo.typeName());
- this.genericBuilderClassDecl = "Builder";
- this.genericBuilderAliasDecl = ("B".equals(typeInfo.typeName().className())) ? "BU" : "B";
- this.genericBuilderAcceptAliasDecl = ("T".equals(typeInfo.typeName().className())) ? "TY" : "T";
- }
- }
-
/**
* Creates the context for the class being built.
*
@@ -400,602 +217,169 @@ protected void appendFooter(StringBuilder builder,
builder.append("}\n");
}
- private void appendBuilder(StringBuilder builder,
- BodyContext ctx) {
- appendBuilderHeader(builder, ctx);
- appendExtraBuilderFields(builder, ctx.genericBuilderClassDecl, ctx.builderAnnotation,
- ctx.typeInfo, ctx.parentTypeName.get(), ctx.allAttributeNames, ctx.allTypeInfos);
- appendBuilderBody(builder, ctx);
-
- appendExtraBuilderMethods(builder, ctx);
+ /**
+ * Appends the simple {@link io.helidon.config.metadata.ConfiguredOption#required()} validation inside the build() method.
+ *
+ * @param builder the builder
+ * @param ctx the context
+ */
+ protected void appendRequiredValidator(StringBuilder builder,
+ BodyContext ctx) {
+ if (ctx.includeMetaAttributes()) {
+ builder.append("\t\t\tRequiredAttributeVisitor visitor = new RequiredAttributeVisitor();\n"
+ + "\t\t\tvisitAttributes(visitor, null);\n"
+ + "\t\t\tvisitor.validate();\n");
+ }
+ }
- if (ctx.doingConcreteType) {
- if (ctx.hasParent) {
- builder.append("\t\t@Override\n");
- } else {
- builder.append("\t\t/**\n"
- + "\t\t * Builds the instance.\n"
- + "\t\t *\n"
- + "\t\t * @return the built instance\n"
- + "\t\t * @throws java.lang.AssertionError if any required attributes are missing\n"
- + "\t\t */\n");
- }
- builder.append("\t\tpublic ").append(ctx.implTypeName).append(" build() {\n");
- appendRequiredValidator(builder, ctx);
- appendBuilderBuildPreSteps(builder, ctx);
- builder.append("\t\t\treturn new ").append(ctx.implTypeName.className()).append("(this);\n");
- builder.append("\t\t}\n");
- } else {
- int i = 0;
- for (String beanAttributeName : ctx.allAttributeNames) {
- TypedElementName method = ctx.allTypeInfos.get(i);
- boolean isList = isList(method);
- boolean isMap = !isList && isMap(method);
- boolean isSet = !isMap && isSet(method);
- boolean ignoredUpLevel = isSet || isList;
- appendSetter(builder, beanAttributeName, Optional.empty(), method, ctx);
- if (isList || isMap || isSet) {
- // NOP
- } else {
- boolean isBoolean = BeanUtils.isBooleanType(method.typeName().name());
- if (isBoolean && beanAttributeName.startsWith("is")) {
- // possibly overload setter to strip the "is"...
- String basicAttributeName = ""
- + Character.toLowerCase(beanAttributeName.charAt(2))
- + beanAttributeName.substring(3);
- if (!ctx.allAttributeNames.contains(basicAttributeName)) {
- appendSetter(builder, beanAttributeName, Optional.of(basicAttributeName), method, ctx);
- }
- }
- }
+ /**
+ * Adds the basic getters to the generated builder output.
+ *
+ * @param builder the builder
+ * @param ctx the context
+ */
+ protected void appendBasicGetters(StringBuilder builder,
+ BodyContext ctx) {
+ if (ctx.doingConcreteType()) {
+ return;
+ }
- maybeAppendSingularSetter(builder, method, beanAttributeName, isList, isMap, isSet, ctx);
- i++;
- }
+ if (Objects.nonNull(ctx.parentAnnotationType().get())) {
+ builder.append("\t@Override\n");
+ builder.append("\tpublic Class extends java.lang.annotation.Annotation> annotationType() {\n");
+ builder.append("\t\treturn ").append(ctx.typeInfo().superTypeInfo().get().typeName()).append(".class;\n");
+ builder.append("\t}\n\n");
+ }
- if (!ctx.hasParent && !ctx.requireLibraryDependencies) {
- builder.append("\t\t/**\n"
- + "\t\t * Build the instance from this builder.\n"
- + "\t\t *\n"
- + "\t\t * @return instance of the built type\n"
- + "\t\t */\n"
- + "\t\tpublic abstract ").append(ctx.genericBuilderAcceptAliasDecl)
- .append(" build();\n\n");
+ if (!ctx.hasParent() && ctx.hasStreamSupportOnImpl()) {
+ builder.append("\t@Override\n"
+ + "\tpublic T get() {\n"
+ + "\t\treturn (T) this;\n"
+ + "\t}\n\n");
+ }
+ }
- if (ctx.hasStreamSupportOnBuilder) {
- builder.append("\t\t/**\n"
- + "\t\t * Update the builder in a fluent API way.\n"
- + "\t\t *\n"
- + "\t\t * @param consumer consumer of the builder instance\n"
- + "\t\t * @return updated builder instance\n"
- + "\t\t */\n");
- builder.append("\t\tpublic B update(Consumer<")
- .append(ctx.genericBuilderAcceptAliasDecl)
- .append("> consumer) {\n"
- + "\t\t\tconsumer.accept(get());\n"
- + "\t\t\treturn identity();\n"
- + "\t\t}\n\n");
- }
+ /**
+ * Appends meta attribute related methods.
+ *
+ * @param builder the builder
+ * @param ctx the context
+ */
+ protected void appendMetaAttributes(StringBuilder builder,
+ BodyContext ctx) {
+ if (!ctx.doingConcreteType() && ctx.includeMetaAttributes()) {
+ builder.append("\tprivate static Map> __calcMeta() {\n");
+ builder.append("\t\tMap> metaProps = new java.util.LinkedHashMap<>();\n");
- if (!ctx.requireLibraryDependencies) {
- builder.append("\t\t/**\n"
- + "\t\t * Instance of this builder as the correct type.\n"
- + "\t\t *\n"
- + "\t\t * @return this instance typed to correct type\n"
- + "\t\t */\n");
- builder.append("\t\t@SuppressWarnings(\"unchecked\")\n");
- builder.append("\t\tprotected ").append(ctx.genericBuilderAliasDecl).append(" identity() {\n"
- + "\t\t\treturn (")
- .append(ctx.genericBuilderAliasDecl).append(") this;\n"
- + "\t\t}\n\n"
- + "\t\t@Override\n"
- + "\t\tpublic ")
- .append(ctx.genericBuilderAcceptAliasDecl).append(" get() {\n"
- + "\t\t\treturn (")
- .append(ctx.genericBuilderAcceptAliasDecl).append(") build();\n"
- + "\t\t}\n\n");
- }
- }
+ AtomicBoolean needsCustomMapOf = new AtomicBoolean();
+ appendMetaProps(builder, "metaProps",
+ ctx.typeInfo(), ctx.map(), ctx.allAttributeNames(), ctx.allTypeInfos(), needsCustomMapOf);
+ builder.append("\t\treturn metaProps;\n");
+ builder.append("\t}\n\n");
- if (ctx.hasStreamSupportOnBuilder || ctx.requireLibraryDependencies) {
- builder.append("\t\t@Override\n");
- }
- builder.append("\t\tpublic void accept(").append(ctx.genericBuilderAcceptAliasDecl).append(" val) {\n");
- if (ctx.hasParent) {
- builder.append("\t\t\tsuper.accept(val);\n");
+ if (needsCustomMapOf.get()) {
+ appendCustomMapOf(builder);
}
- builder.append("\t\t\tacceptThis(val);\n");
- builder.append("\t\t}\n\n");
- builder.append("\t\tprivate void acceptThis(").append(ctx.genericBuilderAcceptAliasDecl).append(" val) {\n");
- builder.append("\t\t\tif (Objects.isNull(val)) {\n"
- + "\t\t\t\treturn;\n"
- + "\t\t\t}\n");
- i = 0;
- for (String beanAttributeName : ctx.allAttributeNames) {
- TypedElementName method = ctx.allTypeInfos.get(i++);
- String getterName = method.elementName();
- builder.append("\t\t\t").append(beanAttributeName).append("(");
- boolean isList = isList(method);
- boolean isMap = !isList && isMap(method);
- boolean isSet = !isMap && isSet(method);
- if (isList || isSet) {
- builder.append("(java.util.Collection) ");
- }
- builder.append("val.").append(getterName).append("());\n");
- }
- builder.append("\t\t}\n");
+ GenerateMethod.internalMetaAttributes(builder);
}
-
- // end of the generated builder inner class here
- builder.append("\t}\n");
}
- private void appendBuilderBody(StringBuilder builder, BodyContext ctx) {
- if (!ctx.doingConcreteType) {
- int i = 0;
- for (String beanAttributeName : ctx.allAttributeNames) {
- TypedElementName method = ctx.allTypeInfos.get(i);
- TypeName type = method.typeName();
- builder.append("\t\t/**\n"
- + "\t\t * field value for {@code " + method + "()}.\n"
- + "\t\t */\n");
- builder.append("\t\tprotected ").append(type.array() ? type.fqName() : type.name()).append(" ")
- .append(beanAttributeName);
- Optional defaultVal = toConfiguredOptionValue(method, true, true);
- if (defaultVal.isPresent()) {
- builder.append(" = ");
- appendDefaultValueAssignment(builder, method, defaultVal.get());
- }
- builder.append(";\n");
- i++;
- }
- builder.append("\n");
+ /**
+ * Adds the fields part of the generated builder.
+ *
+ * @param builder the builder
+ * @param ctx the context
+ */
+ protected void appendFields(StringBuilder builder,
+ BodyContext ctx) {
+ if (ctx.doingConcreteType()) {
+ return;
}
- builder.append("\t\t/**\n"
- + "\t\t * The fluent builder constructor.\n"
- + "\t\t *\n"
- + "\t\t * @param val the value to copy to initialize the builder attributes\n"
- + "\t\t */\n");
- if (ctx.doingConcreteType) {
- builder.append("\t\tprotected ").append(ctx.genericBuilderClassDecl).append("(");
- builder.append(ctx.ctorBuilderAcceptTypeName).append(" val) {\n");
- builder.append("\t\t\tsuper(val);\n");
- } else {
- builder.append("\t\tprotected ").append(ctx.genericBuilderClassDecl).append("(")
- .append(ctx.genericBuilderAcceptAliasDecl).append(" val) {\n");
- if (ctx.hasParent) {
- builder.append("\t\t\tsuper(val);\n");
- }
- appendOverridesOfDefaultValues(builder, ctx);
- builder.append("\t\t\tacceptThis(val);\n");
+ for (int i = 0; i < ctx.allTypeInfos().size(); i++) {
+ TypedElementName method = ctx.allTypeInfos().get(i);
+ String beanAttributeName = ctx.allAttributeNames().get(i);
+ appendAnnotations(builder, method.annotations(), "\t");
+ builder.append("\tprivate ");
+ builder.append(getFieldModifier());
+ builder.append(toGenerics(method, false)).append(" ");
+ builder.append(beanAttributeName).append(";\n");
}
- builder.append("\t\t}\n\n");
}
- private void appendBuilderHeader(StringBuilder builder,
- BodyContext ctx) {
- builder.append("\n\t/**\n"
- + "\t * The fluent builder for this type.\n"
- + "\t *\n");
- if (!ctx.doingConcreteType) {
- builder.append("\t * @param <").append(ctx.genericBuilderAliasDecl).append(">\tthe type of the builder\n");
- builder.append("\t * @param <").append(ctx.genericBuilderAcceptAliasDecl)
- .append(">\tthe type of the built instance\n");
- }
- builder.append("\t */\n");
- builder.append("\tpublic ");
- if (!ctx.doingConcreteType) {
+ /**
+ * Adds the header part of the generated builder.
+ *
+ * @param builder the builder
+ * @param ctx the context
+ */
+ protected void appendHeader(StringBuilder builder,
+ BodyContext ctx) {
+ builder.append("package ").append(ctx.implTypeName().packageName()).append(";\n\n");
+ builder.append("import java.util.Collections;\n");
+ builder.append("import java.util.List;\n");
+ builder.append("import java.util.Map;\n");
+ builder.append("import java.util.Set;\n");
+ builder.append("import java.util.Objects;\n\n");
+ appendExtraImports(builder, ctx);
+
+ builder.append("/**\n");
+ String type = (ctx.doingConcreteType()) ? "Concrete" : "Abstract";
+ builder.append(" * ").append(type).append(" implementation w/ builder for {@link ");
+ builder.append(ctx.typeInfo().typeName()).append("}.\n");
+ builder.append(" */\n");
+ builder.append(BuilderTemplateHelper.getDefaultGeneratedSticker(getClass().getSimpleName())).append("\n");
+ builder.append("@SuppressWarnings(\"unchecked\")\t\n");
+ appendAnnotations(builder, ctx.typeInfo().annotations(), "");
+ builder.append("public ");
+ if (!ctx.doingConcreteType()) {
builder.append("abstract ");
}
- builder.append("static class ").append(ctx.genericBuilderClassDecl);
+ builder.append("class ").append(ctx.implTypeName().className());
- if (ctx.doingConcreteType) {
+ if (ctx.hasParent() || ctx.doingConcreteType()) {
builder.append(" extends ");
- builder.append(toAbstractImplTypeName(ctx.typeInfo.typeName(), ctx.builderAnnotation).get());
- builder.append(".").append(ctx.genericBuilderClassDecl);
- builder.append("<").append(ctx.genericBuilderClassDecl).append(", ").append(ctx.ctorBuilderAcceptTypeName)
- .append("> {\n");
+ }
+
+ if (ctx.doingConcreteType()) {
+ builder.append(toAbstractImplTypeName(ctx.typeInfo().typeName(), ctx.builderAnnotation()).get());
} else {
- builder.append("<").append(ctx.genericBuilderAliasDecl).append(" extends ").append(ctx.genericBuilderClassDecl);
- builder.append("<").append(ctx.genericBuilderAliasDecl).append(", ");
- builder.append(ctx.genericBuilderAcceptAliasDecl).append(">, ").append(ctx.genericBuilderAcceptAliasDecl)
- .append(" extends ");
- builder.append(ctx.ctorBuilderAcceptTypeName).append("> ");
- if (ctx.hasParent) {
- builder.append("extends ").append(toAbstractImplTypeName(ctx.parentTypeName.get(), ctx.builderAnnotation).get())
- .append(".").append(ctx.genericBuilderClassDecl);
- builder.append("<").append(ctx.genericBuilderAliasDecl).append(", ").append(ctx.genericBuilderAcceptAliasDecl);
- builder.append(">");
- } else if (ctx.hasStreamSupportOnBuilder) {
- builder.append("implements Supplier<").append(ctx.genericBuilderAcceptAliasDecl)
- .append(">, Consumer<").append(ctx.genericBuilderAcceptAliasDecl).append(">");
+ if (ctx.hasParent()) {
+ builder.append(toAbstractImplTypeName(ctx.parentTypeName().get(), ctx.builderAnnotation()).get());
}
- if (!ctx.hasParent) {
- if (ctx.requireLibraryDependencies) {
- builder.append(", io.helidon.common.Builder<").append(ctx.genericBuilderAliasDecl)
- .append(", ").append(ctx.genericBuilderAcceptAliasDecl).append(">");
- } else {
- builder.append("/*, io.helidon.common.Builder<").append(ctx.genericBuilderAliasDecl)
- .append(", ").append(ctx.genericBuilderAcceptAliasDecl).append("> */");
- }
+
+ if (!ctx.hasParent() && ctx.hasStreamSupportOnImpl()) {
+ builder.append("<").append(ctx.genericBuilderAcceptAliasDecl()).append(" extends ")
+ .append(ctx.implTypeName().className()).append(">");
}
- builder.append(" {\n");
+ builder.append(" implements ").append(ctx.typeInfo().typeName());
+ if (!ctx.hasParent() && ctx.hasStreamSupportOnImpl()) {
+ builder.append(", Supplier<").append(ctx.genericBuilderAcceptAliasDecl()).append(">");
+ }
}
+
+ builder.append(" {\n");
}
/**
- * Appends the simple {@link io.helidon.config.metadata.ConfiguredOption#required()} validation inside the build() method.
- *
- * @param builder the builder
- * @param ctx the context
- */
- protected void appendRequiredValidator(StringBuilder builder,
- BodyContext ctx) {
- if (ctx.includeMetaAttributes) {
- builder.append("\t\t\tRequiredAttributeVisitor visitor = new RequiredAttributeVisitor();\n"
- + "\t\t\tvisitAttributes(visitor, null);\n"
- + "\t\t\tvisitor.validate();\n");
- }
- }
-
- private void appendToBuilderMethods(StringBuilder builder,
- BodyContext ctx) {
- if (!ctx.doingConcreteType) {
- return;
- }
-
- builder.append("\t/**\n"
- + "\t * Creates a builder for this type.\n"
- + "\t *\n");
- builder.append("\t * @return A builder for {@link ");
- builder.append(ctx.typeInfo.typeName());
- builder.append("}\n\t */\n");
- builder.append("\tpublic static ").append(ctx.genericBuilderClassDecl);
- builder.append(" builder() {\n");
- builder.append("\t\treturn new Builder((").append(ctx.typeInfo.typeName()).append(") null);\n");
- builder.append("\t}\n\n");
-
- builder.append("\t/**\n"
- + "\t * Creates a builder for this type, initialized with the attributes from the values passed"
- + ".\n\n");
- builder.append("\t * @param val the value to copy to initialize the builder attributes\n");
- builder.append("\t * @return A builder for {@link ").append(ctx.typeInfo.typeName());
- builder.append("}\n\t */\n");
-
- builder.append("\tpublic static ").append(ctx.genericBuilderClassDecl);
- builder.append(" toBuilder(").append(ctx.ctorBuilderAcceptTypeName).append(" val) {\n");
- builder.append("\t\treturn new Builder(val);\n");
- builder.append("\t}\n\n");
-
- String decl = "public static Builder toBuilder({args}) {";
- appendExtraToBuilderBuilderFunctions(builder, decl, ctx);
- }
-
- private void appendInterfaceBasedGetters(StringBuilder builder,
- BodyContext ctx) {
- if (ctx.doingConcreteType) {
- return;
- }
-
- int i = 0;
- for (String beanAttributeName : ctx.allAttributeNames) {
- TypedElementName method = ctx.allTypeInfos.get(i);
- appendAnnotations(builder, method.annotations(), "\t");
- builder.append("\t@Override\n");
- builder.append("\tpublic ").append(toGenerics(method, false)).append(" ").append(method.elementName())
- .append("() {\n");
- builder.append("\t\treturn ").append(beanAttributeName).append(";\n");
- builder.append("\t}\n\n");
- i++;
- }
- }
-
- private void appendCtor(StringBuilder builder,
- BodyContext ctx) {
- builder.append("\n\t/**\n"
- + "\t * Constructor using the builder argument.\n"
- + "\t *\n"
- + "\t * @param b\tthe builder\n"
- + "\t */\n");
- builder.append("\tprotected ").append(ctx.implTypeName.className());
- builder.append("(");
- builder.append(ctx.genericBuilderClassDecl);
- if (ctx.doingConcreteType) {
- builder.append(" b) {\n");
- builder.append("\t\tsuper(b);\n");
- } else {
- if (!ctx.doingConcreteType) {
- builder.append(", ?>");
- }
- builder.append(" b) {\n");
- appendExtraCtorCode(builder, ctx.hasParent, "b", ctx.typeInfo);
- appendCtorCode(builder, "b", ctx);
- }
-
- builder.append("\t}\n\n");
- }
-
- private void appendHashCodeAndEquals(StringBuilder builder,
- BodyContext ctx) {
- if (ctx.doingConcreteType) {
- return;
- }
-
- builder.append("\t@Override\n");
- builder.append("\tpublic int hashCode() {\n");
- if (ctx.hasParent) {
- builder.append("\t\tint hashCode = super.hashCode();\n");
- } else {
- builder.append("\t\tint hashCode = 0;\n");
- }
- for (TypedElementName method : ctx.allTypeInfos) {
- builder.append("\t\thashCode ^= Objects.hashCode(").append(method.elementName()).append("());\n");
- }
- builder.append("\t\treturn hashCode;\n");
- builder.append("\t}\n\n");
-
- builder.append("\t@Override\n");
- builder.append("\tpublic boolean equals(Object another) {\n");
- builder.append("\t\tif (this == another) {\n\t\t\treturn true;\n\t\t}\n");
- builder.append("\t\tif (!(another instanceof ").append(ctx.typeInfo.typeName()).append(")) {\n");
- builder.append("\t\t\treturn false;\n");
- builder.append("\t\t}\n");
- builder.append("\t\t").append(ctx.typeInfo.typeName()).append(" other = (")
- .append(ctx.typeInfo.typeName()).append(") another;\n");
- if (ctx.hasParent) {
- builder.append("\t\tboolean equals = super.equals(other);\n");
- } else {
- builder.append("\t\tboolean equals = true;\n");
- }
- for (TypedElementName method : ctx.allTypeInfos) {
- builder.append("\t\tequals &= Objects.equals(").append(method.elementName()).append("(), other.")
- .append(method.elementName()).append("());\n");
- }
- builder.append("\t\treturn equals;\n");
- builder.append("\t}\n\n");
- }
-
- private void appendInnerToStringMethod(StringBuilder builder,
- BodyContext ctx) {
- if (ctx.doingConcreteType) {
- return;
- }
-
- builder.append("\t/**\n"
- + "\t * Produces the inner portion of the toString() output (i.e., what is between the parens).\n"
- + "\t *\n"
- + "\t * @return portion of the toString output\n"
- + "\t */\n");
- if (ctx.hasParent) {
- builder.append("\t@Override\n");
- }
- builder.append("\tprotected String toStringInner() {\n");
- if (ctx.hasParent) {
- builder.append("\t\tString result = super.toStringInner();\n");
- if (!ctx.allAttributeNames.isEmpty()) {
- builder.append("\t\tif (!result.isEmpty() && !result.endsWith(\", \")) {\n");
- builder.append("\t\t\tresult += \", \";\n");
- builder.append("\t\t}\n");
- }
- } else {
- builder.append("\t\tString result = \"\";\n");
- }
-
- int i = 0;
- for (String beanAttributeName : ctx.allAttributeNames) {
- TypedElementName method = ctx.allTypeInfos.get(i++);
- TypeName typeName = method.typeName();
- builder.append("\t\tresult += \"").append(beanAttributeName).append("=\" + ");
- if (typeName.array()) {
- builder.append("(Objects.isNull(").append(beanAttributeName).append(") ? null : ");
- if (typeName.primitive()) {
- builder.append("\"not-null\"");
- } else {
- builder.append("java.util.Arrays.asList(");
- builder.append(method.elementName()).append("())");
- }
- builder.append(")");
- } else {
- builder.append(method.elementName()).append("()");
- }
- if (i < ctx.allAttributeNames.size()) {
- builder.append(" + \", \"");
- }
- builder.append(";\n");
- }
- builder.append("\t\treturn result;\n");
- builder.append("\t}\n\n");
- }
-
- /**
- * Adds the basic getters to the generated builder output.
- *
- * @param builder the builder
- * @param ctx the context
- */
- protected void appendBasicGetters(StringBuilder builder,
- BodyContext ctx) {
- if (ctx.doingConcreteType) {
- return;
- }
-
- if (Objects.nonNull(ctx.parentAnnotationType.get())) {
- builder.append("\t@Override\n");
- builder.append("\tpublic Class extends java.lang.annotation.Annotation> annotationType() {\n");
- builder.append("\t\treturn ").append(ctx.typeInfo.superTypeInfo().get().typeName()).append(".class;\n");
- builder.append("\t}\n\n");
- }
-
- if (!ctx.hasParent && ctx.hasStreamSupportOnImpl) {
- builder.append("\t@Override\n"
- + "\tpublic T get() {\n"
- + "\t\treturn (T) this;\n"
- + "\t}\n\n");
- }
- }
-
- /**
- * Appends meta attribute related methods.
- *
- * @param builder the builder
- * @param ctx the context
- */
- protected void appendMetaAttributes(StringBuilder builder,
- BodyContext ctx) {
- if (!ctx.doingConcreteType && ctx.includeMetaAttributes) {
- builder.append("\tprivate static Map> __calcMeta() {\n");
- builder.append("\t\tMap> metaProps = new java.util.LinkedHashMap<>();\n");
-
- AtomicBoolean needsCustomMapOf = new AtomicBoolean();
- appendMetaProps(builder, "metaProps",
- ctx.typeInfo, ctx.map, ctx.allAttributeNames, ctx.allTypeInfos, needsCustomMapOf);
- builder.append("\t\treturn metaProps;\n");
- builder.append("\t}\n\n");
-
- if (needsCustomMapOf.get()) {
- appendCustomMapOf(builder);
- }
-
- builder.append("\t/**\n"
- + "\t * The map of meta attributes describing each element of this type.\n"
- + "\t *\n"
- + "\t * @return the map of meta attributes using the key being the attribute name\n"
- + "\t */\n");
- builder.append("\tpublic static Map> __metaAttributes() {\n"
- + "\t\treturn META_PROPS;\n"
- + "\t}\n\n");
- }
- }
-
- /**
- * Adds the fields part of the generated builder.
- *
- * @param builder the builder
- * @param ctx the context
- */
- protected void appendFields(StringBuilder builder,
- BodyContext ctx) {
- if (ctx.doingConcreteType) {
- return;
- }
-
- for (int i = 0; i < ctx.allTypeInfos.size(); i++) {
- TypedElementName method = ctx.allTypeInfos.get(i);
- String beanAttributeName = ctx.allAttributeNames.get(i);
- appendAnnotations(builder, method.annotations(), "\t");
- builder.append("\tprivate ");
- builder.append(getFieldModifier());
- builder.append(toGenerics(method, false)).append(" ");
- builder.append(beanAttributeName).append(";\n");
- }
- }
-
- /**
- * Adds the header part of the generated builder.
- *
- * @param builder the builder
- * @param ctx the context
- */
- protected void appendHeader(StringBuilder builder,
- BodyContext ctx) {
- builder.append("package ").append(ctx.implTypeName.packageName()).append(";\n\n");
- builder.append("import java.util.Collections;\n");
- builder.append("import java.util.List;\n");
- builder.append("import java.util.Map;\n");
- builder.append("import java.util.Objects;\n\n");
- appendExtraImports(builder, ctx);
-
- builder.append("/**\n");
- String type = (ctx.doingConcreteType) ? "Concrete" : "Abstract";
- builder.append(" * ").append(type).append(" implementation w/ builder for {@link ");
- builder.append(ctx.typeInfo.typeName()).append("}.\n");
- builder.append(" */\n");
- builder.append(BuilderTemplateHelper.getDefaultGeneratedSticker(getClass().getSimpleName())).append("\n");
- builder.append("@SuppressWarnings(\"unchecked\")\t\n");
- appendAnnotations(builder, ctx.typeInfo.annotations(), "");
- builder.append("public ");
- if (!ctx.doingConcreteType) {
- builder.append("abstract ");
- }
- builder.append("class ").append(ctx.implTypeName.className());
-
- if (ctx.hasParent || ctx.doingConcreteType) {
- builder.append(" extends ");
- }
-
- if (ctx.doingConcreteType) {
- builder.append(toAbstractImplTypeName(ctx.typeInfo.typeName(), ctx.builderAnnotation).get());
- } else {
- if (ctx.hasParent) {
- builder.append(toAbstractImplTypeName(ctx.parentTypeName.get(), ctx.builderAnnotation).get());
- }
-
- if (!ctx.hasParent && ctx.hasStreamSupportOnImpl) {
- builder.append("<").append(ctx.genericBuilderAcceptAliasDecl).append(" extends ")
- .append(ctx.implTypeName.className()).append(">");
- }
-
- builder.append(" implements ").append(ctx.typeInfo.typeName());
- if (!ctx.hasParent && ctx.hasStreamSupportOnImpl) {
- builder.append(", Supplier<").append(ctx.genericBuilderAcceptAliasDecl).append(">");
- }
- }
-
- builder.append(" {\n");
- }
-
- private void appendDefaultValueAssignment(StringBuilder builder,
- TypedElementName method,
- String defaultVal) {
- TypeName type = method.typeName();
- boolean isOptional = type.name().equals(Optional.class.getName());
- if (isOptional) {
- builder.append(Optional.class.getName()).append(".of(");
- if (!type.typeArguments().isEmpty()) {
- type = type.typeArguments().get(0);
- }
- }
-
- boolean isString = type.name().equals(String.class.getName()) && !type.array();
- boolean isCharArr = type.fqName().equals("char[]");
- if ((isString || isCharArr) && !defaultVal.startsWith("\"")) {
- builder.append("\"");
- }
-
- builder.append(defaultVal);
-
- if ((isString || isCharArr) && !defaultVal.endsWith("\"")) {
- builder.append("\"");
- if (isCharArr) {
- builder.append(".toCharArray()");
- }
- }
-
- if (isOptional) {
- builder.append(")");
- }
- }
-
- /**
- * Adds extra imports to the generated builder.
+ * Adds extra imports to the generated builder.
*
* @param builder the builder
* @param ctx the context
*/
protected void appendExtraImports(StringBuilder builder,
BodyContext ctx) {
- if (!ctx.doingConcreteType) {
+ if (!ctx.doingConcreteType()) {
builder.append("import java.util.function.Consumer;\n");
builder.append("import java.util.function.Supplier;\n");
builder.append("\n");
}
- if (ctx.requireLibraryDependencies) {
+ if (ctx.requireLibraryDependencies()) {
builder.append("import ").append(AttributeVisitor.class.getName()).append(";\n");
- if (ctx.doingConcreteType) {
+ if (ctx.doingConcreteType()) {
builder.append("import ").append(RequiredAttributeVisitor.class.getName()).append(";\n");
}
builder.append("\n");
@@ -1010,13 +394,13 @@ protected void appendExtraImports(StringBuilder builder,
*/
protected void appendToStringMethod(StringBuilder builder,
BodyContext ctx) {
- if (ctx.doingConcreteType) {
+ if (ctx.doingConcreteType()) {
return;
}
builder.append("\t@Override\n");
builder.append("\tpublic String toString() {\n");
- builder.append("\t\treturn ").append(ctx.typeInfo.typeName());
+ builder.append("\t\treturn ").append(ctx.typeInfo().typeName());
builder.append(".class.getSimpleName() + \"(\" + toStringInner() + \")\";\n");
builder.append("\t}\n\n");
}
@@ -1030,7 +414,7 @@ protected void appendToStringMethod(StringBuilder builder,
*/
protected void appendExtraMethods(StringBuilder builder,
BodyContext ctx) {
- if (ctx.includeMetaAttributes) {
+ if (ctx.includeMetaAttributes()) {
appendVisitAttributes(builder, "", false, ctx);
}
}
@@ -1044,80 +428,7 @@ protected void appendExtraMethods(StringBuilder builder,
*/
protected void appendExtraInnerClasses(StringBuilder builder,
BodyContext ctx) {
- if (ctx.doingConcreteType) {
- return;
- }
-
- if (!ctx.hasParent
- && ctx.includeMetaAttributes
- && !ctx.requireLibraryDependencies) {
- builder.append("\n\n\t/**\n"
- + "\t * A functional interface that can be used to visit all attributes of this type.\n"
- + "\t */\n");
- builder.append("\t@FunctionalInterface\n"
- + "\tpublic static interface AttributeVisitor {\n"
- + "\t\t/**\n"
- + "\t\t * Visits the attribute named 'attrName'.\n"
- + "\t\t *\n"
- + "\t\t * @param attrName\t\tthe attribute name\n"
- + "\t\t * @param valueSupplier\tthe attribute value supplier\n"
- + "\t\t * @param meta\t\t\tthe meta information for the attribute\n"
- + "\t\t * @param userDefinedCtx a user defined context that can be used for holding an "
- + "object of your choosing\n"
- + "\t\t * @param type\t\t\tthe type of the attribute\n"
- + "\t\t * @param typeArgument\tthe type arguments (if type is a parameterized / generic "
- + "type)\n"
- + "\t\t */\n"
- + "\t\tvoid visit(String attrName, Supplier
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
diff --git a/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ChildInterfaceIsABuilder.java b/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ChildInterfaceIsABuilder.java
index 391cad89a97..7191b9ec576 100644
--- a/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ChildInterfaceIsABuilder.java
+++ b/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ChildInterfaceIsABuilder.java
@@ -16,6 +16,8 @@
package io.helidon.pico.builder.test.testsubjects;
+import java.util.Optional;
+
import io.helidon.config.metadata.ConfiguredOption;
import io.helidon.pico.builder.Builder;
@@ -49,6 +51,15 @@ public interface ChildInterfaceIsABuilder extends ParentInterfaceNotABuilder {
*/
@Override
@ConfiguredOption("override")
+ Optional maybeOverrideMe();
+
+ /**
+ * Used for testing {@link io.helidon.config.metadata.ConfiguredOption} default values.
+ *
+ * @return ignored, here for testing purposes only
+ */
+ @Override
+ @ConfiguredOption("override2")
char[] overrideMe();
}
diff --git a/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/Level1.java b/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/Level1.java
index 93f32ad4c2f..c4c3518d990 100644
--- a/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/Level1.java
+++ b/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/Level1.java
@@ -16,6 +16,8 @@
package io.helidon.pico.builder.test.testsubjects;
+import java.util.Optional;
+
import io.helidon.config.metadata.ConfiguredOption;
import io.helidon.pico.builder.Builder;
@@ -65,6 +67,6 @@ public interface Level1 extends Level0 {
*
* @return ignored, here for testing purposes only
*/
- Boolean getLevel1BooleanAttribute();
+ Optional getLevel1BooleanAttribute();
}
diff --git a/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ParentInterfaceNotABuilder.java b/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ParentInterfaceNotABuilder.java
index bb23617d13b..fed89f82d90 100644
--- a/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ParentInterfaceNotABuilder.java
+++ b/pico/builder/tests/builder/src/main/java/io/helidon/pico/builder/test/testsubjects/ParentInterfaceNotABuilder.java
@@ -16,6 +16,8 @@
package io.helidon.pico.builder.test.testsubjects;
+import java.util.Optional;
+
/**
* Used for demonstrating (and testing) multi-inheritance of interfaces and the builders that are produced.
*
@@ -29,13 +31,22 @@ public interface ParentInterfaceNotABuilder extends ParentOfParentInterfaceIsABu
default void ignoreMe() {
}
+ /**
+ * The Pico Builder will ignore {@code default} and {@code static} functions.
+ *
+ * @return ignored, here for testing purposes only
+ */
+ default Optional maybeOverrideMe() {
+ return Optional.empty();
+ }
+
/**
* The Pico Builder will ignore {@code default} and {@code static} functions.
*
* @return ignored, here for testing purposes only
*/
default char[] overrideMe() {
- return new char[] {};
+ return "default".toCharArray();
}
/**
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ComplexCaseTest.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ComplexCaseTest.java
index a6044c2d10b..1d2470e037f 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ComplexCaseTest.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ComplexCaseTest.java
@@ -17,8 +17,12 @@
package io.helidon.pico.builder.api.test;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import io.helidon.pico.builder.test.testsubjects.ComplexCaseImpl;
+import io.helidon.pico.builder.test.testsubjects.MyConfigBean;
import org.junit.jupiter.api.Test;
@@ -29,15 +33,17 @@ class ComplexCaseTest {
@Test
void testIt() {
+ Map> mapWithNull = new HashMap<>();
+ mapWithNull.put("key", null);
+
ComplexCaseImpl val = ComplexCaseImpl.builder()
.name("name")
- .addConfigBean(null)
- .addKeyToConfigBean("key", null)
+ .mapOfKeyToConfigBeans(mapWithNull)
.setOfLists(Collections.singleton(Collections.singletonList(null)))
.build();
assertThat(val.toString(),
equalTo("ComplexCase(name=name, enabled=false, port=8080, mapOfKeyToConfigBeans={key=null}, "
- + "listOfConfigBeans=[null], setOfLists=[[null]])"));
+ + "listOfConfigBeans=[], setOfLists=[[null]])"));
}
}
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/EdgeCasesTest.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/EdgeCasesTest.java
index bbe3a759187..de91e7d2031 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/EdgeCasesTest.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/EdgeCasesTest.java
@@ -28,7 +28,7 @@ class EdgeCasesTest {
@Test
void testIt() {
- DefaultEdgeCases val = DefaultEdgeCases.toBuilder(null).build();
+ DefaultEdgeCases val = DefaultEdgeCases.builder().build();
assertThat(val.optionalIntegerWithDefault().get(), is(-1));
assertThat(val.optionalStringWithDefault().get(), equalTo("test"));
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/LevelTest.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/LevelTest.java
index f03dcee70cf..e876a848dc6 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/LevelTest.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/LevelTest.java
@@ -50,12 +50,12 @@ void manualGeneric() {
.build();
assertThat(val.toString(),
equalTo("Level2ManualImpl(level0StringAttribute=a, level1intAttribute=1, level1IntegerAttribute=null, "
- + "level1booleanAttribute=false, level1BooleanAttribute=null, "
+ + "level1booleanAttribute=false, level1BooleanAttribute=Optional.empty, "
+ "level2Level0Info=[Level0ManualImpl(level0StringAttribute=1)], "
+ "level2ListOfLevel0s=[Level0ManualImpl(level0StringAttribute=1)], "
+ "level2MapOfStringToLevel1s={key=Level1ManualImpl(level0StringAttribute=1, "
+ "level1intAttribute=1, level1IntegerAttribute=1, level1booleanAttribute=true, "
- + "level1BooleanAttribute=null)})"));
+ + "level1BooleanAttribute=Optional.empty)})"));
Level2 val2 = Level2ManualImpl.builder()
.level0StringAttribute("a")
@@ -84,12 +84,12 @@ void manualNonGeneric() {
.addStringToLevel1("key", Level1ManualImpl.builder().build())
.build();
assertThat(val.toString(), equalTo("Level2ManualImpl(level0StringAttribute=a, level1intAttribute=1, level1IntegerAttribute=null, "
- + "level1booleanAttribute=false, level1BooleanAttribute=null, "
+ + "level1booleanAttribute=false, level1BooleanAttribute=Optional.empty, "
+ "level2Level0Info=[Level0ManualImpl(level0StringAttribute=1)], "
+ "level2ListOfLevel0s=[Level0ManualImpl(level0StringAttribute=1)], "
+ "level2MapOfStringToLevel1s={key=Level1ManualImpl(level0StringAttribute=1, "
+ "level1intAttribute=1, level1IntegerAttribute=1, level1booleanAttribute=true, "
- + "level1BooleanAttribute=null)})"));
+ + "level1BooleanAttribute=Optional.empty)})"));
Level2 val2 = Level2ManualImpl.toBldr(val).build();
assertThat(val, equalTo(val2));
@@ -100,26 +100,22 @@ void codeGen() {
Level2 val = Level2Impl.builder()
.level0StringAttribute("a")
.level1booleanAttribute(false)
- .level1IntegerAttribute(null)
- .level2Level0Info(null)
.level2Level0Info(Collections.singleton(Level0Impl.builder().build()))
.addLevel0(Level0Impl.builder().build())
.addStringToLevel1("key", Level1Impl.builder().build())
.build();
assertThat(val.toString(),
- equalTo("Level2(level0StringAttribute=a, level1intAttribute=1, level1IntegerAttribute=null, "
- + "level1booleanAttribute=false, level1BooleanAttribute=null, "
+ equalTo("Level2(level0StringAttribute=a, level1intAttribute=1, level1IntegerAttribute=1, "
+ + "level1booleanAttribute=false, level1BooleanAttribute=Optional.empty, "
+ "level2Level0Info=[Level0(level0StringAttribute=1)], "
+ "level2ListOfLevel0s=[Level0(level0StringAttribute=1)], "
+ "level2MapOfStringToLevel1s={key=Level1(level0StringAttribute=1, "
+ "level1intAttribute=1, level1IntegerAttribute=1, level1booleanAttribute=true, "
- + "level1BooleanAttribute=null)})"));
+ + "level1BooleanAttribute=Optional.empty)})"));
Level2 val2 = Level2Impl.builder()
.level0StringAttribute("a")
.level1booleanAttribute(false)
- .level1IntegerAttribute(null)
- .level2Level0Info(null)
.level2Level0Info(Collections.singleton(Level0Impl.builder().build()))
.addLevel0(Level0Impl.builder().build())
.addStringToLevel1("key", Level1Impl.builder().build())
@@ -133,8 +129,6 @@ void toBuilderAndEquals() {
Level2 val = Level2Impl.builder()
.level0StringAttribute("a")
.level1booleanAttribute(false)
- .level1IntegerAttribute(null)
- .level2Level0Info(null)
.level2Level0Info(Collections.singleton(Level0Impl.builder().build()))
.addLevel0(Level0Impl.builder().build())
.addStringToLevel1("key", Level1Impl.builder().build())
@@ -150,7 +144,7 @@ void streams() {
m2.accept(m1.get());
assertThat(m2.build().toString(),
equalTo("Level1(level0StringAttribute=hello, level1intAttribute=1, level1IntegerAttribute=1, "
- + "level1booleanAttribute=true, level1BooleanAttribute=null)"));
+ + "level1booleanAttribute=true, level1BooleanAttribute=Optional.empty)"));
}
@Test
@@ -158,13 +152,13 @@ void levelDefaults() {
Level2 val2 = Level2Impl.builder().build();
assertThat(val2.toString(),
equalTo("Level2(level0StringAttribute=2, level1intAttribute=1, level1IntegerAttribute=1, "
- + "level1booleanAttribute=true, level1BooleanAttribute=null, level2Level0Info=[], "
+ + "level1booleanAttribute=true, level1BooleanAttribute=Optional.empty, level2Level0Info=[], "
+ "level2ListOfLevel0s=[], level2MapOfStringToLevel1s={})"));
Level1 val1 = Level1Impl.builder().build();
assertThat(val1.toString(),
equalTo("Level1(level0StringAttribute=1, level1intAttribute=1, level1IntegerAttribute=1, "
- + "level1booleanAttribute=true, level1BooleanAttribute=null)"));
+ + "level1booleanAttribute=true, level1BooleanAttribute=Optional.empty)"));
Level0 val0 = Level0Impl.builder().build();
assertThat(val0.toString(),
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/MyConfigBeanTest.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/MyConfigBeanTest.java
index 0505dafeb89..9906b54e3f9 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/MyConfigBeanTest.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/MyConfigBeanTest.java
@@ -60,7 +60,7 @@ void codeGen() {
@Test
void mixed() {
- MyConfigBean val1 = MyConfigBeanManualImpl.builder().build();
+ MyConfigBean val1 = MyConfigBeanManualImpl.builder().name("initial").build();
val1 = MyConfigBeanImpl.toBuilder(val1)
.name("test")
.enabled(true)
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ParentParentChildTest.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ParentParentChildTest.java
index 1ecf306f2da..d21e019efea 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ParentParentChildTest.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/ParentParentChildTest.java
@@ -17,12 +17,14 @@
package io.helidon.pico.builder.api.test;
import java.net.URI;
+import java.util.Optional;
import io.helidon.pico.builder.test.testsubjects.ChildInterfaceIsABuilder;
import io.helidon.pico.builder.test.testsubjects.ChildInterfaceIsABuilderImpl;
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;
@@ -35,9 +37,9 @@ void collapsedMiddleType() {
.childLevel(100)
.parentLevel(99)
.uri(URI.create("http://localhost"))
- .empty((String) null)
+ .empty(Optional.empty())
.build();
- assertThat(new String(child.overrideMe()), equalTo("override"));
+ assertThat(new String(child.maybeOverrideMe().get()), equalTo("override"));
assertThat(child.uri().get().toString(), equalTo("http://localhost"));
assertThat(child.empty().isEmpty(), is(true));
assertThat(child.childLevel(), is(100L));
@@ -51,16 +53,22 @@ void collapsedMiddleType() {
@Test
void ensureCharArraysAreHiddenFromToStringOutput() {
ChildInterfaceIsABuilderImpl val = ChildInterfaceIsABuilderImpl.builder()
- .overrideMe("password")
.build();
assertThat(val.toString(),
- equalTo("ChildInterfaceIsABuilder(uri=null, empty=null, parentLevel=0, childLevel=0, isChildLevel=true, "
- + "overrideMe=not-null)"));
+ equalTo("ChildInterfaceIsABuilder(uri=Optional.empty, empty=Optional.empty, parentLevel=0, childLevel=0, "
+ + "isChildLevel=true, maybeOverrideMe=not-empty, overrideMe=not-null)"));
+ assertThat(val.overrideMe(), equalTo("override2".toCharArray()));
+ assertThat(val.maybeOverrideMe().orElseThrow(), equalTo("override".toCharArray()));
- val = ChildInterfaceIsABuilderImpl.toBuilder(val).overrideMe((char[]) null).build();
+ val = ChildInterfaceIsABuilderImpl.toBuilder(val)
+ .maybeOverrideMe(Optional.empty())
+ .overrideMe("pwd")
+ .build();
assertThat(val.toString(),
- equalTo("ChildInterfaceIsABuilder(uri=null, empty=null, parentLevel=0, childLevel=0, isChildLevel=true, "
- + "overrideMe=null)"));
+ equalTo("ChildInterfaceIsABuilder(uri=Optional.empty, empty=Optional.empty, parentLevel=0, childLevel=0, "
+ + "isChildLevel=true, maybeOverrideMe=Optional.empty, overrideMe=not-null)"));
+ assertThat(val.overrideMe(), equalTo("pwd".toCharArray()));
+ assertThat(val.maybeOverrideMe(), optionalEmpty());
}
}
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/PickleBarrelTest.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/PickleBarrelTest.java
index f62cbca0b28..96efe668b59 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/PickleBarrelTest.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/PickleBarrelTest.java
@@ -34,7 +34,7 @@ class PickleBarrelTest {
@Test
void testIt() {
DefaultPickle.Builder pickleBuilder = DefaultPickle.builder().size(Optional.of(Pickle.Size.MEDIUM));
- AssertionError e = assertThrows(AssertionError.class, pickleBuilder::build);
+ Exception e = assertThrows(IllegalStateException.class, pickleBuilder::build);
assertThat(e.getMessage(),
equalTo("'type' is a required attribute and should not be null"));
@@ -44,12 +44,12 @@ void testIt() {
equalTo("Pickle(type=DILL, size=Optional[MEDIUM])"));
DefaultPickleBarrel.Builder pickleBarrelBuilder = DefaultPickleBarrel.builder();
- e = assertThrows(AssertionError.class, pickleBarrelBuilder::build);
+ e = assertThrows(IllegalStateException.class, pickleBarrelBuilder::build);
assertThat(e.getMessage(),
equalTo("'id' is a required attribute and should not be null"));
PickleBarrel pickleBarrel = pickleBarrelBuilder.addPickle(pickle).id("123").build();
assertThat(pickleBarrel.toString(),
- equalTo("PickleBarrel(id=123, type=null, pickles=[Pickle(type=DILL, size=Optional[MEDIUM])])"));
+ equalTo("PickleBarrel(id=123, type=Optional.empty, pickles=[Pickle(type=DILL, size=Optional[MEDIUM])])"));
}
}
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/Level1ManualImpl.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/Level1ManualImpl.java
index 8225083b2b0..0bdd3c11b3e 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/Level1ManualImpl.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/Level1ManualImpl.java
@@ -17,6 +17,7 @@
package io.helidon.pico.builder.api.test.testsubjects;
import java.util.Objects;
+import java.util.Optional;
import io.helidon.pico.builder.test.testsubjects.Level1;
@@ -119,8 +120,8 @@ public boolean getLevel1booleanAttribute() {
* @return ignored, here for testing only
*/
@Override
- public Boolean getLevel1BooleanAttribute() {
- return level1BooleanAttribute;
+ public Optional getLevel1BooleanAttribute() {
+ return Optional.ofNullable(level1BooleanAttribute);
}
/**
@@ -176,7 +177,7 @@ private void acceptThis(T val) {
this.level1intAttribute = val.getLevel1intAttribute();
this.level1IntegerAttribute = val.getLevel1IntegerAttribute();
this.level1booleanAttribute = val.getLevel1booleanAttribute();
- this.level1BooleanAttribute = val.getLevel1BooleanAttribute();
+ val.getLevel1BooleanAttribute().ifPresent(this::level1BooleanAttribute);
}
/**
diff --git a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/MyConfigBeanManualImpl.java b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/MyConfigBeanManualImpl.java
index c2584d59cb9..36762d30918 100644
--- a/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/MyConfigBeanManualImpl.java
+++ b/pico/builder/tests/builder/src/test/java/io/helidon/pico/builder/api/test/testsubjects/MyConfigBeanManualImpl.java
@@ -60,10 +60,8 @@ public String toString() {
@Override
public int hashCode() {
- int hashCode = 0; // super.hashCode();
- hashCode ^= Objects.hashCode(getName());
- hashCode ^= Objects.hashCode(isEnabled());
- hashCode ^= Objects.hashCode(getPort());
+ int hashCode = 1; // super.hashCode();
+ hashCode = 31 * hashCode + Objects.hash(getName(), isEnabled(), getPort());
return hashCode;
}
diff --git a/pico/pico/README.md b/pico/pico/README.md
new file mode 100644
index 00000000000..c9d39785dca
--- /dev/null
+++ b/pico/pico/README.md
@@ -0,0 +1,16 @@
+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)
+* [@ExteralContract](src/main/java/io/helidon/pico/ExternalContract.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 module for the default reference implementation for this API / SPI.
diff --git a/pico/pico/pom.xml b/pico/pico/pom.xml
new file mode 100644
index 00000000000..9e4ea08b8ad
--- /dev/null
+++ b/pico/pico/pom.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+ io.helidon.pico
+ helidon-pico-project
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ helidon-pico
+ Helidon Pico API / SPI
+
+
+
+ 11
+
+
+
+
+ io.helidon.pico
+ helidon-pico-types
+
+
+ io.helidon.common
+ helidon-common
+
+
+ io.helidon.common
+ helidon-common-config
+
+
+ jakarta.inject
+ jakarta.inject-api
+ compile
+
+
+ io.helidon.config
+ helidon-config-metadata
+ provided
+ true
+
+
+ io.helidon.pico.builder
+ helidon-pico-builder
+ provided
+
+
+ io.helidon.pico.builder
+ helidon-pico-builder-processor
+ provided
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+ provided
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.helidon.pico.builder
+ helidon-pico-builder-processor
+ ${helidon.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationLog.java b/pico/pico/src/main/java/io/helidon/pico/ActivationLog.java
new file mode 100644
index 00000000000..0ae772183a5
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ActivationLog.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Tracks the transformations of {@link ServiceProvider}'s {@link ActivationStatus} in lifecycle activity (i.e., activation
+ * startup and deactivation shutdown).
+ *
+ * @see Activator
+ * @see DeActivator
+ */
+public interface ActivationLog {
+
+ /**
+ * Expected to be called during service creation and activation to capture the activation log transcripts.
+ *
+ * @param entry the log entry to record
+ * @return the (perhaps decorated) activation log entry
+ */
+ ActivationLogEntry> recordActivationEvent(ActivationLogEntry> entry);
+
+ /**
+ * Optionally provide a means to query the activation log, if query is possible. If query is not possible then an empty
+ * will be returned.
+ *
+ * @return the optional query API of log activation records
+ */
+ default Optional toQuery() {
+ return Optional.empty();
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationLogEntry.java b/pico/pico/src/main/java/io/helidon/pico/ActivationLogEntry.java
new file mode 100644
index 00000000000..3daacb8b4da
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ActivationLogEntry.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2022 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.time.Instant;
+import java.util.Optional;
+
+import io.helidon.pico.builder.Builder;
+
+/**
+ * Log entry for lifecycle related events (i.e., activation startup and deactivation shutdown).
+ *
+ * @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();
+
+ /**
+ * The event.
+ *
+ * @return the event
+ */
+ Event event();
+
+ /**
+ * The starting activation phase.
+ *
+ * @return the starting activation phase
+ */
+ ActivationPhase startingActivationPhase();
+
+ /**
+ * The eventual/desired/target activation phase.
+ *
+ * @return the eventual/desired/target activation phase
+ */
+ ActivationPhase targetActivationPhase();
+
+ /**
+ * The finishing phase at the time of this event's log entry.
+ *
+ * @return the actual finishing phase
+ */
+ ActivationPhase finishingActivationPhase();
+
+ /**
+ * The finishing activation status at the time of this event's log entry.
+ *
+ * @return the activation status
+ */
+ ActivationStatus finishingStatus();
+
+ /**
+ * The time this event was generated.
+ *
+ * @return the time of the event
+ */
+ Instant time();
+
+ /**
+ * Any observed error during activation.
+ *
+ * @return any observed error
+ */
+ Optional error();
+
+ /**
+ * The thread id that the event occurred on.
+ *
+ * @return the thread id
+ */
+ long threadId();
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationLogQuery.java b/pico/pico/src/main/java/io/helidon/pico/ActivationLogQuery.java
new file mode 100644
index 00000000000..bf43e8aad96
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ActivationLogQuery.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2022 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.List;
+
+/**
+ * Provide a means to query the activation log.
+ *
+ * @see ActivationLog
+ */
+public interface ActivationLogQuery {
+
+ /**
+ * Clears the activation log.
+ */
+ void clear();
+
+ /**
+ * The full transcript of all services phase transitions being managed.
+ *
+ * @return the activation log if log capture is enabled
+ */
+ List> fullActivationLog();
+
+ /**
+ * A filtered list only including service providers.
+ *
+ * @param serviceProviders the filter
+ * @return the filtered activation log if log capture is enabled
+ */
+ List> serviceProviderActivationLog(ServiceProvider>... serviceProviders);
+
+ /**
+ * A filtered list only including service providers.
+ *
+ * @param serviceTypeNames the filter
+ * @return the filtered activation log if log capture is enabled
+ */
+ List> serviceProviderActivationLog(String... serviceTypeNames);
+
+ /**
+ * A filtered list only including service providers.
+ *
+ * @param instances the filter
+ * @return the filtered activation log if log capture is enabled
+ */
+ List> managedServiceActivationLog(Object... instances);
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationPhase.java b/pico/pico/src/main/java/io/helidon/pico/ActivationPhase.java
new file mode 100644
index 00000000000..325eef96edd
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ActivationPhase.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Forms a progression of full activation and deactivation.
+ */
+public enum ActivationPhase {
+
+ /**
+ * Starting state before anything happens activation-wise.
+ */
+ INIT(false),
+
+ /**
+ * Planned to be activated.
+ */
+ PENDING(true),
+
+ /**
+ * Starting to be activated.
+ */
+ ACTIVATION_STARTING(true),
+
+ /**
+ * Gathering dependencies.
+ */
+ GATHERING_DEPENDENCIES(true),
+
+ /**
+ * Constructing.
+ */
+ CONSTRUCTING(true),
+
+ /**
+ * Injecting (fields then methods).
+ */
+ INJECTING(true),
+
+ /**
+ * Calling any post construct method.
+ */
+ POST_CONSTRUCTING(true),
+
+ /**
+ * Finishing post construct method.
+ */
+ ACTIVATION_FINISHING(true),
+
+ /**
+ * Service is active.
+ */
+ ACTIVE(true),
+
+ /**
+ * About to call pre-destroy.
+ */
+ PRE_DESTROYING(false),
+
+ /**
+ * Destroyed (after calling any pre-destroy).
+ */
+ DESTROYED(false);
+
+ /**
+ * True if this phase is eligible for deactivation/shutdown.
+ */
+ private final boolean eligibleForDeactivation;
+
+ /**
+ * 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.
+ *
+ * @see PicoServices#shutdown()
+ */
+ public boolean eligibleForDeactivation() {
+ return eligibleForDeactivation;
+ }
+
+ ActivationPhase(boolean eligibleForDeactivation) {
+ this.eligibleForDeactivation = eligibleForDeactivation;
+ }
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationRequest.java b/pico/pico/src/main/java/io/helidon/pico/ActivationRequest.java
new file mode 100644
index 00000000000..daecb5267cf
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ActivationRequest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 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.config.metadata.ConfiguredOption;
+import io.helidon.pico.builder.Builder;
+
+/**
+ * Request to activate a service.
+ *
+ * @param service type
+ */
+@Builder
+public interface ActivationRequest {
+ /**
+ * Target service provider.
+ *
+ * @return service provider
+ */
+ ServiceProvider serviceProvider();
+
+ /**
+ * Injection point context information.
+ *
+ * @return injection point info
+ */
+ Optional injectionPoint();
+
+ /**
+ * Ultimate target phase for activation.
+ *
+ * @return phase to target
+ */
+ ActivationPhase targetPhase();
+
+ /**
+ * Whether to throw an exception on failure to activate, or return an error activation result on activation.
+ *
+ * @return whether to throw on failure
+ */
+ @ConfiguredOption("true")
+ boolean throwOnFailure();
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationResult.java b/pico/pico/src/main/java/io/helidon/pico/ActivationResult.java
new file mode 100644
index 00000000000..bd0d600c1af
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ActivationResult.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2022 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.Objects;
+import java.util.Optional;
+import java.util.concurrent.Future;
+
+import io.helidon.pico.builder.Builder;
+
+/**
+ * Represents the result of a service activation or deactivation.
+ *
+ * @see Activator
+ * @see DeActivator
+ *
+ * @param The type of the associated activator
+ */
+@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
+ */
+ ActivationPhase startingActivationPhase();
+
+ /**
+ * The activation phase that was requested at the onset of the phase transition.
+ *
+ * @return the target, desired, ultimate phase requested
+ */
+ ActivationPhase ultimateTargetActivationPhase();
+
+ /**
+ * The activation phase we finished successfully on.
+ *
+ * @return the actual finishing phase
+ */
+ ActivationPhase finishingActivationPhase();
+
+ /**
+ * How did the activation finish.
+ *
+ * @return the finishing status
+ */
+ ActivationStatus finishingStatus();
+
+ /**
+ * The containing activation log that tracked this result.
+ *
+ * @return the activation log
+ */
+ Optional activationLog();
+
+ /**
+ * The services registry that was used.
+ *
+ * @return the services registry
+ */
+ Optional services();
+
+ /**
+ * Any vendor/provider implementation specific codes.
+ *
+ * @return the status code, 0 being the normal/default value
+ */
+ int statusCode();
+
+ /**
+ * Any vendor/provider implementation specific description.
+ *
+ * @return a developer friendly description (useful if an error occurs)
+ */
+ Optional statusDescription();
+
+ /**
+ * Any throwable/exceptions that were observed during activation.
+ *
+ * @return the captured error
+ */
+ Optional error();
+
+ /**
+ * Returns true if this result is finished.
+ *
+ * @return true if finished
+ */
+ default boolean finished() {
+ Future> f = finishedActivationResult().orElse(null);
+ return (Objects.isNull(f) || f.isDone());
+ }
+
+ /**
+ * Returns true if this result is successful.
+ *
+ * @return true if successful
+ */
+ default boolean success() {
+ return finishingStatus() != ActivationStatus.FAILURE;
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ActivationStatus.java b/pico/pico/src/main/java/io/helidon/pico/ActivationStatus.java
new file mode 100644
index 00000000000..b1e12fb21b7
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ActivationStatus.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * The activation status. This status applies to the {@link ActivationLogEntry} record.
+ *
+ * @see Activator
+ */
+public enum ActivationStatus {
+
+ /**
+ * The service has been activated and is fully ready to receive requests.
+ */
+ SUCCESS,
+
+ /**
+ * The service has been activated but is still being started asynchronously, and is not fully ready yet to receive requests.
+ * Important note: This is NOT health related - Health is orthogonal to service bindings/activation and readiness.
+ */
+ WARNING_SUCCESS_BUT_NOT_READY,
+
+ /**
+ * A general warning during lifecycle.
+ */
+ WARNING_GENERAL,
+
+ /**
+ * Failed to activate to the given phase.
+ */
+ FAILURE
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Activator.java b/pico/pico/src/main/java/io/helidon/pico/Activator.java
new file mode 100644
index 00000000000..4d2c5129641
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/Activator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * 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 PostConstructMethod} invocations, etc.), and finally into the
+ * {@link ActivationPhase#ACTIVE} phase.
+ *
+ * Assumption:
+ *
+ *
Each {@link ServiceProvider} managing its backing service will have an activator strategy conforming to the DI
+ * specification.
+ *
Each services activation is expected to be non-blocking, but may in fact require deferred blocking activities to become
+ * fully ready for runtime operation.
+ *
+ * Activation includes:
+ *
+ *
Management of the service's {@link ActivationPhase}.
+ *
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 {
+
+ /**
+ * Activate a managed service/provider.
+ *
+ * @param activationRequest activation request
+ * @return the result of the activation
+ */
+ ActivationResult activate(ActivationRequest activationRequest);
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Application.java b/pico/pico/src/main/java/io/helidon/pico/Application.java
new file mode 100644
index 00000000000..ada9c3d3819
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/Application.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * An Application instance, if available at runtime, will be expected to provide a blueprint for all service provider's injection
+ * points.
+ *
+ * Implementations of this contract are normally code generated, although then can be programmatically written by the developer
+ * for special cases.
+ *
+ * Note: instances of this type are not eligible for injection.
+ *
+ * @see Module
+ */
+@Contract
+public interface Application extends Named {
+
+ /**
+ * Called by the provider implementation at bootstrapping time to bind all injection plans to each and every service provider.
+ *
+ * @param binder the binder used to register the service provider injection plans
+ */
+ void configure(ServiceInjectionPlanBinder binder);
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ContextualServiceQuery.java b/pico/pico/src/main/java/io/helidon/pico/ContextualServiceQuery.java
new file mode 100644
index 00000000000..40d35485ef8
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ContextualServiceQuery.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2022 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.pico.builder.Builder;
+
+/**
+ * Combines the {@link io.helidon.pico.ServiceInfo} criteria along with the {@link io.helidon.pico.InjectionPointInfo} context
+ * that the query applies to.
+ *
+ * @see io.helidon.pico.InjectionPointProvider
+ */
+@Builder
+public interface ContextualServiceQuery {
+
+ /**
+ * The criteria to use for the lookup into {@link io.helidon.pico.Services}.
+ *
+ * @return the service info criteria
+ */
+ ServiceInfoCriteria serviceInfo();
+
+ /**
+ * The injection point context this search applies to.
+ *
+ * @return the injection point context info
+ */
+ InjectionPointInfo ipInfo();
+
+ /**
+ * Set to true if there is an expectation that there is at least one match result from the search.
+ *
+ * @return true if it is expected there is at least a single match result
+ */
+ boolean expected();
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Contract.java b/pico/pico/src/main/java/io/helidon/pico/Contract.java
new file mode 100644
index 00000000000..70c10b499f2
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/Contract.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2022 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.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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.
+ *
+ * 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
+ * {@code Contract} interface(s).
+ *
+ * @see io.helidon.pico.ServiceInfo#contractsImplemented()
+ * @see io.helidon.pico.ServiceInfo#externalContractsImplemented()
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.TYPE)
+public @interface Contract {
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DeActivationRequest.java b/pico/pico/src/main/java/io/helidon/pico/DeActivationRequest.java
new file mode 100644
index 00000000000..e1e60e8926c
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/DeActivationRequest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022 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.config.metadata.ConfiguredOption;
+import io.helidon.pico.builder.Builder;
+
+/**
+ * Request to {@link io.helidon.pico.DeActivator#deactivate(DeActivationRequest)}.
+ *
+ * @param type to deactivate
+ */
+@Builder
+public interface 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();
+ }
+
+ /**
+ * Service provider responsible for invoking deactivate.
+ *
+ * @return service provider
+ */
+ ServiceProvider serviceProvider();
+
+ /**
+ * Whether to throw an exception on failure, or return it as part of the result.
+ *
+ * @return throw on failure
+ */
+ @ConfiguredOption("true")
+ boolean throwOnFailure();
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DeActivator.java b/pico/pico/src/main/java/io/helidon/pico/DeActivator.java
new file mode 100644
index 00000000000..6978ef3ccae
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/DeActivator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * DeActivators are responsible for lifecycle, transitioning a {@link ServiceProvider} through its
+ * {@link io.helidon.pico.ActivationPhase}'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}.
+ *
+ * @param the type to deactivate
+ * @see Activator
+ */
+@Contract
+public interface DeActivator {
+
+ /**
+ * Deactivate a managed service. This will trigger any {@link jakarta.annotation.PreDestroy} method on the
+ * underlying service type instance.
+ *
+ * @param request deactivation request
+ * @return the result
+ */
+ ActivationResult deactivate(DeActivationRequest request);
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java b/pico/pico/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java
new file mode 100644
index 00000000000..b4c22725b25
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/DefaultQualifierAndValue.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2022 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.lang.annotation.Annotation;
+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;
+
+/**
+ * Describes a {@link jakarta.inject.Qualifier} type annotation associated with a service being provided or dependant upon.
+ * In Pico these are generally determined at compile time to avoid any use of reflection at runtime.
+ */
+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.
+ *
+ * @param b the builder
+ * @see #builder()
+ */
+ protected DefaultQualifierAndValue(Builder b) {
+ super(b);
+ }
+
+ /**
+ * Creates a {@link jakarta.inject.Named} qualifier.
+ *
+ * @param name the name
+ * @return named qualifier
+ */
+ public static DefaultQualifierAndValue createNamed(String name) {
+ Objects.requireNonNull(name);
+ return (DefaultQualifierAndValue) builder().typeName(NAMED).value(name).build();
+ }
+
+ /**
+ * Creates a qualifier from an annotation.
+ *
+ * @param qualifierType the qualifier type
+ * @return qualifier
+ */
+ public static DefaultQualifierAndValue create(Class extends Annotation> qualifierType) {
+ Objects.requireNonNull(qualifierType);
+ return (DefaultQualifierAndValue) builder().typeName(DefaultTypeName.create(qualifierType)).build();
+ }
+
+ /**
+ * Creates a qualifier.
+ *
+ * @param qualifierType the qualifier type
+ * @param val the value
+ * @return qualifier
+ */
+ public static DefaultQualifierAndValue create(Class extends Annotation> qualifierType, String val) {
+ Objects.requireNonNull(qualifierType);
+ return (DefaultQualifierAndValue) builder().typeName(DefaultTypeName.create(qualifierType)).value(val).build();
+ }
+
+ /**
+ * Creates a qualifier.
+ *
+ * @param qualifierTypeName the qualifier
+ * @param val the value
+ * @return qualifier
+ */
+ public static DefaultQualifierAndValue create(String qualifierTypeName, String val) {
+ return (DefaultQualifierAndValue) builder()
+ .typeName(DefaultTypeName.createFromTypeName(qualifierTypeName))
+ .value(val)
+ .build();
+ }
+
+ /**
+ * Creates a qualifier.
+ *
+ * @param qualifierType the qualifier
+ * @param val the value
+ * @return qualifier
+ */
+ public static DefaultQualifierAndValue create(TypeName qualifierType, String val) {
+ return (DefaultQualifierAndValue) builder()
+ .typeName(qualifierType)
+ .value(val)
+ .build();
+ }
+
+ /**
+ * Converts from an {@link io.helidon.pico.types.AnnotationAndValue} to a {@link QualifierAndValue}.
+ *
+ * @param annotationAndValue the annotation and value
+ * @return the qualifier and value equivalent
+ */
+ public static QualifierAndValue convert(AnnotationAndValue annotationAndValue) {
+ if (annotationAndValue instanceof QualifierAndValue) {
+ return (QualifierAndValue) annotationAndValue;
+ }
+
+ return (QualifierAndValue) builder()
+ .typeName(annotationAndValue.typeName())
+ .values(annotationAndValue.values())
+ .update(it -> annotationAndValue.value().ifPresent(it::value))
+ .build();
+ }
+
+ @Override
+ public int compareTo(AnnotationAndValue other) {
+ return typeName().compareTo(other.typeName());
+ }
+
+
+ /**
+ * Creates a builder for {@link QualifierAndValue}.
+ *
+ * @return a fluent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+
+ /**
+ * The fluent builder.
+ */
+ public static class Builder extends DefaultAnnotationAndValue.Builder {
+ /**
+ * Fluent builder constructor.
+ */
+ protected Builder() {
+ }
+
+ /**
+ * Build the instance.
+ *
+ * @return the built instance
+ */
+ @Override
+ public DefaultQualifierAndValue build() {
+ return new DefaultQualifierAndValue(this);
+ }
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DefaultServiceInfo.java b/pico/pico/src/main/java/io/helidon/pico/DefaultServiceInfo.java
new file mode 100644
index 00000000000..b1700e54ba4
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/DefaultServiceInfo.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2022 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.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import io.helidon.pico.types.AnnotationAndValue;
+
+/**
+ * The default/reference implementation for {@link ServiceInfo}.
+ */
+public class DefaultServiceInfo implements ServiceInfo {
+
+ private final String serviceTypeName;
+ private final Set contractsImplemented;
+ private final Set externalContractsImplemented;
+ private final Set scopeTypeNames;
+ private final Set qualifiers;
+ private final String activatorTypeName;
+ private final int runLevel;
+ private final Double weight;
+ private final String moduleName;
+
+ /**
+ * Copy constructor.
+ *
+ * @param src the source to copy
+ */
+ protected DefaultServiceInfo(ServiceInfo src) {
+ this.serviceTypeName = src.serviceTypeName();
+ this.contractsImplemented = new TreeSet<>(src.contractsImplemented());
+ this.externalContractsImplemented = new LinkedHashSet<>(src.externalContractsImplemented());
+ this.scopeTypeNames = new LinkedHashSet<>(src.scopeTypeNames());
+ this.qualifiers = new LinkedHashSet<>(src.qualifiers());
+ this.activatorTypeName = src.activatorTypeName();
+ this.runLevel = src.runLevel();
+ this.moduleName = src.moduleName().orElse(null);
+ this.weight = src.declaredWeight().orElse(null);
+ }
+
+ /**
+ * Constructor using the builder result.
+ *
+ * @param b the builder
+ * @see #builder()
+ */
+ @SuppressWarnings("unchecked")
+ protected DefaultServiceInfo(Builder b) {
+ this.serviceTypeName = b.serviceTypeName;
+ this.contractsImplemented = Collections.unmodifiableSet(new TreeSet(b.contractsImplemented));
+ this.externalContractsImplemented = Collections.unmodifiableSet(b.externalContractsImplemented);
+ this.scopeTypeNames = Collections.unmodifiableSet(b.scopeTypeNames);
+ this.qualifiers = Collections.unmodifiableSet(b.qualifiers);
+ this.activatorTypeName = b.activatorTypeName;
+ this.runLevel = b.runLevel;
+ this.moduleName = b.moduleName;
+ this.weight = b.weight;
+ }
+
+ /**
+ * Provides a facade over {@link java.util.Objects#equals(Object, Object)}.
+ *
+ * @param o1 an object
+ * @param o2 an object to compare with a1 for equality
+ * @return true if a1 equals a2
+ */
+ public static boolean equals(Object o1, Object o2) {
+ return Objects.equals(o1, o2);
+ }
+
+ /**
+ * Creates a fluent builder for this type.
+ *
+ * @return A builder for {@link DefaultServiceInfo}.
+ */
+ @SuppressWarnings("unchecked")
+ public static Builder extends DefaultServiceInfo, ? extends Builder, ?>> builder() {
+ return new Builder();
+ }
+
+ @Override
+ public Set externalContractsImplemented() {
+ return externalContractsImplemented;
+ }
+
+ @Override
+ public String activatorTypeName() {
+ return activatorTypeName;
+ }
+
+ @Override
+ public Optional moduleName() {
+ return Optional.ofNullable(moduleName);
+ }
+
+ /**
+ * Matches is a looser form of equality check than {@link #equals(Object, Object)}. If a service matches criteria
+ * it is generally assumed to be viable for assignability.
+ *
+ * @param criteria the criteria to compare against
+ * @return true if the criteria provided matches this instance
+ * @see Services#lookup(ServiceInfo)
+ */
+ @Override
+ public boolean matches(ServiceInfoCriteria criteria) {
+ if (criteria == PicoServices.EMPTY_CRITERIA) {
+ return true;
+ }
+
+ boolean matches = matches(this.serviceTypeName(), criteria.serviceTypeName());
+ if (matches && criteria.serviceTypeName().isEmpty()) {
+ matches = this.contractsImplemented().containsAll(criteria.contractsImplemented())
+ || criteria.contractsImplemented().contains(this.serviceTypeName());
+ }
+ return matches
+ && this.scopeTypeNames().containsAll(criteria.scopeTypeNames())
+ && matchesQualifiers(this.qualifiers(), criteria.qualifiers())
+ && matches(this.activatorTypeName(), criteria.activatorTypeName())
+ && matches(this.runLevel(), criteria.runLevel())
+ && matchesWeight(this, criteria)
+ && matches(this.moduleName(), criteria.moduleName());
+ }
+
+ @Override
+ public String serviceTypeName() {
+ return serviceTypeName;
+ }
+
+ @Override
+ public Set scopeTypeNames() {
+ return scopeTypeNames;
+ }
+
+ @Override
+ public Set qualifiers() {
+ return qualifiers;
+ }
+
+ @Override
+ public Set contractsImplemented() {
+ return contractsImplemented;
+ }
+
+ @Override
+ public int runLevel() {
+ return runLevel;
+ }
+
+ @Override
+ public Optional declaredWeight() {
+ return Optional.ofNullable(weight);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(serviceTypeName(), contractsImplemented());
+ }
+
+ @Override
+ public boolean equals(Object another) {
+ if (!(another instanceof ServiceInfo)) {
+ return false;
+ }
+
+ return equals(serviceTypeName(), ((ServiceInfo) another).serviceTypeName())
+ && equals(contractsImplemented(), ((ServiceInfo) another).contractsImplemented())
+ && equals(qualifiers(), ((ServiceInfo) another).qualifiers())
+ && equals(activatorTypeName(), ((ServiceInfo) another).activatorTypeName())
+ && equals(runLevel(), ((ServiceInfo) another).runLevel())
+ && equals(realizedWeight(), ((ServiceInfo) another).realizedWeight())
+ && equals(moduleName(), ((ServiceInfo) another).moduleName());
+ }
+
+ /**
+ * Creates a fluent builder initialized with the current values of this instance.
+ *
+ * @return A builder initialized with the current attributes.
+ */
+ @SuppressWarnings("unchecked")
+ public Builder extends DefaultServiceInfo, ? extends Builder, ?>> toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Weight matching is always less or equal to criteria specified.
+ *
+ * @param src the item being considered
+ * @param criteria the criteria
+ * @return true if there is a match
+ */
+ protected static boolean matchesWeight(ServiceInfoBasics src, ServiceInfoCriteria criteria) {
+ if (criteria.weight().isEmpty()) {
+ return true;
+ }
+
+ Double srcWeight = src.realizedWeight();
+ return (srcWeight.compareTo(criteria.weight().get()) <= 0);
+ }
+
+ /**
+ * Matches qualifier collections.
+ *
+ * @param src the target service info to evaluate
+ * @param criteria the criteria to compare against
+ * @return true if the criteria provided matches this instance
+ */
+ private static boolean matchesQualifiers(Set src, Set criteria) {
+ if (criteria.isEmpty()) {
+ return true;
+ }
+
+ if (src.isEmpty()) {
+ return false;
+ }
+
+ if (src.contains(DefaultQualifierAndValue.WILDCARD_NAMED)) {
+ return true;
+ }
+
+ for (QualifierAndValue criteriaQualifier : criteria) {
+ if (src.contains(criteriaQualifier)) {
+ // NOP;
+ continue;
+ } else if (criteriaQualifier.typeName().equals(DefaultQualifierAndValue.NAMED)) {
+ if (criteriaQualifier.equals(DefaultQualifierAndValue.WILDCARD_NAMED)
+ || criteriaQualifier.value().isEmpty()) {
+ // any Named qualifier will match ...
+ boolean hasSameTypeAsCriteria = src.stream()
+ .anyMatch(q -> q.typeName().equals(criteriaQualifier.typeName()));
+ if (hasSameTypeAsCriteria) {
+ continue;
+ }
+ } else if (src.contains(DefaultQualifierAndValue.WILDCARD_NAMED)) {
+ continue;
+ }
+ return false;
+ } else if (criteriaQualifier.value().isEmpty()) {
+ Set sameTypeAsCriteriaSet = src.stream()
+ .filter(q -> q.typeName().equals(criteriaQualifier.typeName()))
+ .collect(Collectors.toSet());
+ if (sameTypeAsCriteriaSet.isEmpty()) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean matches(Object src, Optional> criteria) {
+ if (criteria.isEmpty()) {
+ return true;
+ }
+
+ return equals(src, criteria.get());
+ }
+
+ /**
+ * The fluent builder for {@link ServiceInfo}.
+ *
+ * @param the builder type
+ * @param the concrete type being build
+ */
+ public static class Builder>
+ implements io.helidon.common.Builder {
+ private final Set contractsImplemented = new LinkedHashSet<>();
+ private final Set externalContractsImplemented = new LinkedHashSet<>();
+ private final Set scopeTypeNames = new LinkedHashSet<>();
+ private final Set qualifiers = new LinkedHashSet<>();
+
+ private String serviceTypeName;
+ private String activatorTypeName;
+ private Integer runLevel;
+ private String moduleName;
+ private Double weight;
+
+ /**
+ * Builder Constructor.
+ *
+ * @see #builder()
+ */
+ protected Builder() {
+ }
+
+ /**
+ * Builder Copy Constructor.
+ *
+ * @param c the existing value object
+ * @see #toBuilder()
+ */
+ protected Builder(C c) {
+ this.serviceTypeName = c.serviceTypeName();
+ this.contractsImplemented.addAll(c.contractsImplemented());
+ this.externalContractsImplemented.addAll(c.externalContractsImplemented());
+ this.scopeTypeNames.addAll(c.scopeTypeNames());
+ this.qualifiers.addAll(c.qualifiers());
+ this.activatorTypeName = c.activatorTypeName();
+ this.runLevel = c.runLevel();
+ this.moduleName = c.moduleName().orElse(null);
+ this.weight = c.declaredWeight().orElse(null);
+ }
+
+ /**
+ * Builds the {@link DefaultServiceInfo}.
+ *
+ * @return the fluent builder instance
+ */
+ @SuppressWarnings("unchecked")
+ public C build() {
+ Objects.requireNonNull(serviceTypeName);
+
+ return (C) new DefaultServiceInfo(this);
+ }
+
+ /**
+ * Sets the mandatory serviceTypeName for this {@link ServiceInfo}.
+ *
+ * @param serviceTypeName the service type name
+ * @return this fluent builder
+ */
+ public B serviceTypeName(String serviceTypeName) {
+ this.serviceTypeName = serviceTypeName;
+ return identity();
+ }
+
+ /**
+ * Sets the mandatory serviceTypeName for this {@link ServiceInfo}.
+ *
+ * @param serviceType the service type
+ * @return this fluent builder
+ */
+ public B serviceType(Class> serviceType) {
+ return serviceTypeName(serviceType.getName());
+ }
+
+ /**
+ * Sets the optional name for this {@link ServiceInfo}.
+ *
+ * @param name the name
+ * @return this fluent builder
+ */
+ public B named(String name) {
+ return addQualifier(DefaultQualifierAndValue.createNamed(name));
+ }
+
+ /**
+ * Adds a singular qualifier for this {@link ServiceInfo}.
+ *
+ * @param qualifier the qualifier
+ * @return this fluent builder
+ */
+ public B addQualifier(QualifierAndValue qualifier) {
+ Objects.requireNonNull(qualifier);
+ qualifiers.add(qualifier);
+ return identity();
+ }
+
+ /**
+ * Sets the collection of qualifiers for this {@link ServiceInfo}.
+ *
+ * @param qualifiers the qualifiers
+ * @return this fluent builder
+ */
+ public B qualifiers(Collection qualifiers) {
+ Objects.requireNonNull(qualifiers);
+ qualifiers.clear();
+ this.qualifiers.addAll(qualifiers);
+ return identity();
+ }
+
+ /**
+ * Adds a singular contract implemented for this {@link ServiceInfo}.
+ *
+ * @param contractImplemented the contract implemented
+ * @return this fluent builder
+ */
+ public B contractImplemented(String contractImplemented) {
+ Objects.requireNonNull(contractImplemented);
+ contractsImplemented.add(contractImplemented);
+ return identity();
+ }
+
+ /**
+ * Adds a contract implemented.
+ *
+ * @param contract the contract type
+ * @return this fluent builder
+ */
+ public B contractTypeImplemented(Class> contract) {
+ return contractImplemented(contract.getName());
+ }
+
+ /**
+ * Sets the collection of contracts implemented for this {@link ServiceInfo}.
+ *
+ * @param contractsImplemented the contract names implemented
+ * @return this fluent builder
+ */
+ public B contractsImplemented(Collection contractsImplemented) {
+ Objects.requireNonNull(contractsImplemented);
+ this.contractsImplemented.clear();
+ this.contractsImplemented.addAll(contractsImplemented);
+ return identity();
+ }
+
+ /**
+ * Adds a singular external contract implemented for this {@link ServiceInfo}.
+ *
+ * @param contractImplemented the type name of the external contract implemented
+ * @return this fluent builder
+ */
+ public B addExternalContractImplemented(String contractImplemented) {
+ Objects.requireNonNull(contractImplemented);
+ this.externalContractsImplemented.add(contractImplemented);
+ return contractImplemented(contractImplemented);
+ }
+
+ /**
+ * Adds an external contract implemented.
+ *
+ * @param contract the external contract type
+ * @return this fluent builder
+ */
+ public B externalContractTypeImplemented(Class> contract) {
+ return addExternalContractImplemented(contract.getName());
+ }
+
+ /**
+ * Sets the collection of contracts implemented for this {@link ServiceInfo}.
+ *
+ * @param contractsImplemented the external contract names implemented
+ * @return this fluent builder
+ */
+ public B externalContractsImplemented(Collection contractsImplemented) {
+ Objects.requireNonNull(contractsImplemented);
+ this.externalContractsImplemented.clear();
+ this.externalContractsImplemented.addAll(contractsImplemented);
+ return identity();
+ }
+
+ /**
+ * Adds a singular scope type name for this {@link ServiceInfo}.
+ *
+ * @param scopeTypeName the scope type name
+ * @return this fluent builder
+ */
+ public B addScopeTypeName(String scopeTypeName) {
+ Objects.requireNonNull(scopeTypeName);
+ this.scopeTypeNames.add(scopeTypeName);
+ return identity();
+ }
+
+ /**
+ * Sets the scope type.
+ *
+ * @param scopeType the scope type
+ * @return this fluent builder
+ */
+ public B scopeType(Class> scopeType) {
+ return addScopeTypeName(scopeType.getName());
+ }
+
+ /**
+ * sets the collection of scope type names declared for this {@link ServiceInfo}.
+ *
+ * @param scopeTypeNames the contract names implemented
+ * @return this fluent builder
+ */
+ public B scopeTypeNames(Collection scopeTypeNames) {
+ Objects.requireNonNull(scopeTypeNames);
+ this.scopeTypeNames.clear();
+ this.scopeTypeNames.addAll(scopeTypeNames);
+ return identity();
+ }
+
+ /**
+ * Sets the activator type name.
+ *
+ * @param activatorTypeName the activator type name
+ * @return this fluent builder
+ */
+ public B activatorTypeName(String activatorTypeName) {
+ this.activatorTypeName = activatorTypeName;
+ return identity();
+ }
+
+ /**
+ * Sets the activator type.
+ *
+ * @param activatorType the activator type
+ * @return this fluent builder
+ */
+ public B activatorType(Class> activatorType) {
+ return activatorTypeName(activatorType.getName());
+ }
+
+ /**
+ * Sets the run level value.
+ *
+ * @param runLevel the run level
+ * @return this fluent builder
+ */
+ public B runLevel(Integer runLevel) {
+ this.runLevel = runLevel;
+ return identity();
+ }
+
+ /**
+ * Sets the module name value.
+ *
+ * @param moduleName the module name
+ * @return this fluent builder
+ */
+ public B moduleName(String moduleName) {
+ this.moduleName = moduleName;
+ return identity();
+ }
+
+ /**
+ * Sets the weight value.
+ *
+ * @param weight the weight (aka priority)
+ * @return this fluent builder
+ */
+ public B weight(Double weight) {
+ this.weight = weight;
+ return identity();
+ }
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DependenciesInfo.java b/pico/pico/src/main/java/io/helidon/pico/DependenciesInfo.java
new file mode 100644
index 00000000000..88899a271f4
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/DependenciesInfo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022 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.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a per {@link ServiceInfo} mapping of {@link DependencyInfo}'s.
+ */
+public interface DependenciesInfo {
+
+ /**
+ * Represents the set of dependencies for each {@link ServiceInfo}.
+ *
+ * @return map from the service info to its dependencies
+ */
+ Map> serviceInfoDependencies();
+
+ /**
+ * Represents a flattened list of all dependencies.
+ *
+ * @return the flattened list of all dependencies
+ */
+ List extends DependencyInfo> allDependencies();
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/DependencyInfo.java b/pico/pico/src/main/java/io/helidon/pico/DependencyInfo.java
new file mode 100644
index 00000000000..593f273a326
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/DependencyInfo.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 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.Set;
+
+import io.helidon.pico.builder.Builder;
+
+/**
+ * Aggregates the set of {@link InjectionPointInfo}'s that are dependent upon a specific and common
+ * {@link ServiceInfo} definition.
+ */
+@Builder
+public interface DependencyInfo {
+
+ /**
+ * The service info describing what the injection point dependencies are dependent upon.
+ *
+ * @return the service info dependency
+ */
+ ServiceInfo dependencyTo();
+
+ /**
+ * The set of injection points that depends upon {@link #dependencyTo()}.
+ *
+ * @return the set of dependencies
+ */
+ Set extends InjectionPointInfo> injectionPointDependencies();
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/ElementInfo.java b/pico/pico/src/main/java/io/helidon/pico/ElementInfo.java
new file mode 100644
index 00000000000..e17c2263c59
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/ElementInfo.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2022 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 java.util.Set;
+
+import io.helidon.pico.builder.Builder;
+import io.helidon.pico.types.AnnotationAndValue;
+
+/**
+ * Abstractly describes method or field elements of a managed service type (i.e., fields, constructors, injectable methods, etc.).
+ */
+@Builder
+public interface ElementInfo {
+
+ /**
+ * The name assigned to constructors.
+ */
+ String CONSTRUCTOR = "";
+
+ /**
+ * The kind of injection target.
+ */
+ enum ElementKind {
+ /**
+ * The injectable constructor. Note that there can be at most 1 injectable constructor.
+ */
+ CONSTRUCTOR,
+
+ /**
+ * A field.
+ */
+ FIELD,
+
+ /**
+ * A method.
+ */
+ METHOD
+ }
+
+ /**
+ * The access describing the target injection point.
+ */
+ enum Access {
+ /**
+ * public.
+ */
+ PUBLIC,
+
+ /**
+ * protected.
+ */
+ PROTECTED,
+
+ /**
+ * package private.
+ */
+ PACKAGE_PRIVATE,
+
+ /**
+ * private.
+ */
+ PRIVATE
+ }
+
+ /**
+ * The injection point/receiver kind.
+ *
+ * @return the kind
+ */
+ ElementKind elementKind();
+
+ /**
+ * The access modifier on the injection point/receiver.
+ *
+ * @return the access
+ */
+ Access access();
+
+ /**
+ * The element type name (e.g., method type or field type).
+ *
+ * @return the target receiver type name
+ */
+ String elementTypeName();
+
+ /**
+ * The element name (e.g., method name or field name).
+ *
+ * @return the target receiver name
+ */
+ String elementName();
+
+ /**
+ * If the element is a method or constructor then this is the ordinal argument position of that argument.
+ *
+ * @return the offset argument, 0 based, or empty if field type
+ */
+ Optional elementOffset();
+
+ /**
+ * True if the injection point is static.
+ *
+ * @return true if static receiver
+ */
+ boolean staticDeclaration();
+
+ /**
+ * The enclosing class name for the element.
+ *
+ * @return service type name
+ */
+ String serviceTypeName();
+
+ /**
+ * The annotations on this element.
+ *
+ * @return the annotations on this element
+ */
+ Set annotations();
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/EventReceiver.java b/pico/pico/src/main/java/io/helidon/pico/EventReceiver.java
new file mode 100644
index 00000000000..4516ea1be83
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/EventReceiver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2022 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 receiver of events from the {@link Services} 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 {
+
+ /**
+ * 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.
+ *
+ * @param event the event
+ */
+ void onEvent(Event event);
+
+}
diff --git a/pico/api/src/main/java/io/helidon/pico/api/ExternalContracts.java b/pico/pico/src/main/java/io/helidon/pico/ExternalContracts.java
similarity index 95%
rename from pico/api/src/main/java/io/helidon/pico/api/ExternalContracts.java
rename to pico/pico/src/main/java/io/helidon/pico/ExternalContracts.java
index 9e6a9945c9f..9001c25243a 100644
--- a/pico/api/src/main/java/io/helidon/pico/api/ExternalContracts.java
+++ b/pico/pico/src/main/java/io/helidon/pico/ExternalContracts.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.pico.api;
+package io.helidon.pico;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -25,7 +25,7 @@
* Placed on the implementation of a service as an alternative to using a {@link Contract}.
*
* Use this annotation when it is impossible to place an annotation on the interface itself - for instance of the interface comes
- * from a 3rd party library/provider.
+ * from a 3rd party library provider.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
diff --git a/pico/pico/src/main/java/io/helidon/pico/InjectionException.java b/pico/pico/src/main/java/io/helidon/pico/InjectionException.java
new file mode 100644
index 00000000000..21c07c1f206
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/InjectionException.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Represents an injection exception. These might be thrown either at compile time or at runtime depending upon how the
+ * application is built.
+ */
+public class InjectionException extends PicoServiceProviderException {
+
+ /**
+ * The optional activation log (configure to enabled).
+ *
+ * @see io.helidon.pico.PicoServicesConfig
+ */
+ private final ActivationLog activationLog;
+
+ /**
+ * Injection, or a required service lookup related exception.
+ *
+ * @param msg the message
+ */
+ public InjectionException(String msg) {
+ super(msg);
+ 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
+ */
+ 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;
+ }
+
+ /**
+ * Returns the activation log if available.
+ *
+ * @return the optional activation log
+ */
+ public Optional activationLog() {
+ return Optional.ofNullable(activationLog);
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InjectionPointInfo.java b/pico/pico/src/main/java/io/helidon/pico/InjectionPointInfo.java
new file mode 100644
index 00000000000..82fddc705e7
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/InjectionPointInfo.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2022 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.Set;
+
+import io.helidon.pico.builder.Builder;
+
+/**
+ * Describes a receiver for injection - identifies who/what is requesting an injection that needs to be satisfied.
+ */
+@Builder
+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.
+ *
+ * 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();
+
+ /**
+ * The base identifying name for this injection point. If the element represents a function, then the function arguments
+ * are encoded in its base identity.
+ *
+ * @return the base identity of the element
+ */
+ 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}.
+ *
+ * @return true if list based receiver
+ */
+ boolean listWrapped();
+
+ /**
+ * True if the injection point is of type {@link java.util.Optional}.
+ *
+ * @return true if optional based receiver
+ */
+ boolean optionalWrapped();
+
+ /**
+ * True if the injection point is of type Provider (or Supplier).
+ *
+ * @return true if provider based receiver
+ */
+ boolean providerWrapped();
+
+ /**
+ * The dependency this is dependent upon.
+ *
+ * @return The service info we are dependent upon.
+ */
+ ServiceInfo dependencyToServiceInfo();
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InjectionPointProvider.java b/pico/pico/src/main/java/io/helidon/pico/InjectionPointProvider.java
new file mode 100644
index 00000000000..09b2945dd30
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/InjectionPointProvider.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 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.List;
+import java.util.Optional;
+
+import jakarta.inject.Provider;
+
+/**
+ * Provides ability to contextualize the injected service by the target receiver of the injection point dynamically
+ * at runtime. This API will provide service instances of type {@code T}. These services may be singleton, or created based upon
+ * scoping cardinality that is defined by the provider implementation of the given type. This is why the javadoc reads "get (or
+ * create)".
+ *
+ * The ordering of services, and the preferred service itself, is determined by the same as documented for
+ * {@link io.helidon.pico.Services}.
+ *
+ * @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.
+ *
+ * @return the best service provider matching the criteria
+ * @throws io.helidon.pico.PicoException if resolution fails to resolve a match
+ */
+ @Override
+ default T get() {
+ return first(PicoServices.SERVICE_QUERY_REQUIRED)
+ .orElseThrow(() -> new PicoException("Could not resolve a match for " + this));
+ }
+
+ /**
+ * Get (or create) an instance of this service type for the given injection point context. This is logically the same
+ * as using the first element of the result from calling {@link #list(ContextualServiceQuery)}.
+ *
+ * @param query the service query
+ * @return the best service provider matching the criteria
+ * @throws io.helidon.pico.PicoException if expected=true and resolution fails to resolve a match
+ */
+ Optional first(ContextualServiceQuery query);
+
+ /**
+ * Get (or create) a list of instances matching the criteria for the given injection point context.
+ *
+ * @param query the service query
+ * @return the resolved services matching criteria for the injection point in order of weight, or null if the context is not
+ * supported
+ */
+ default List list(ContextualServiceQuery query) {
+ return first(query).map(List::of).orElseGet(List::of);
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Injector.java b/pico/pico/src/main/java/io/helidon/pico/Injector.java
new file mode 100644
index 00000000000..e6636d6b1a8
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/Injector.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Used to perform programmatic activation and injection.
+ *
+ * Note that the reference implementation of Pico only performs non-reflective, compile-time generation of service activators
+ * for services that it manages. This Injector contract is mainly provided in order to allow other library extension
+ * 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
+ * {@link Injector.Strategy#ACTIVATOR} type.
+ */
+ enum Strategy {
+
+ /**
+ * Activator based implies compile-time injection strategy. This is the preferred / default strategy.
+ */
+ ACTIVATOR,
+
+ /**
+ * Reflection based implies runtime injection strategy. Note: This is available for other 3rd parties of Pico that choose
+ * to use reflection as a strategy.
+ */
+ REFLECTION,
+
+ /**
+ * Any. Defers the strategy to the provider implementation's capabilities and configuration.
+ */
+ ANY
+
+ }
+
+ /**
+ * 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.
+ *
+ * @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
+ * @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;
+
+ /**
+ * Called to deactivate a managed service or service provider, putting it into {@link ActivationPhase#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
+ * @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;
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InjectorOptions.java b/pico/pico/src/main/java/io/helidon/pico/InjectorOptions.java
new file mode 100644
index 00000000000..ac35dfb938d
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/InjectorOptions.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2022 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.pico.builder.Builder;
+
+/**
+ * Provides optional, contextual tunings to the {@link Injector}.
+ *
+ * @see Injector
+ */
+@Builder
+public interface InjectorOptions {
+
+ /**
+ * The optional starting phase for the {@link Activator} behind the {@link Injector}.
+ * The default is the current phase that the managed {@link ServiceProvider} is currently in.
+ *
+ * @return the optional target finish phase
+ */
+ Optional startAtPhase();
+
+ /**
+ * The optional target finishing phase for the {@link Activator} behind the {@link Injector}.
+ * The default is {@link ActivationPhase#ACTIVE}.
+ *
+ * @return the optional target finish phase
+ */
+ Optional finishAtPhase();
+
+ /**
+ * The optional recipient target, describing who and what is being injected.
+ *
+ * @return the optional target injection point info
+ */
+ Optional ipInfo();
+
+ /**
+ * The optional services registry to use, defaulting to {@link PicoServices#services()}.
+ *
+ * @return the optional services registry to use
+ */
+ Optional services();
+
+ /**
+ * The optional activation log that the injection should record its activity on.
+ *
+ * @return the optional activation log to use
+ */
+ Optional log();
+
+ /**
+ * The optional injection strategy the injector should apply. The default is {@link Injector.Strategy#ANY}.
+ *
+ * @return the optional injector strategy to use
+ */
+ Optional strategy();
+
+}
diff --git a/pico/api/src/main/java/io/helidon/pico/api/Contract.java b/pico/pico/src/main/java/io/helidon/pico/Intercepted.java
similarity index 53%
rename from pico/api/src/main/java/io/helidon/pico/api/Contract.java
rename to pico/pico/src/main/java/io/helidon/pico/Intercepted.java
index ad0027b02d9..eb984cf7b39 100644
--- a/pico/api/src/main/java/io/helidon/pico/api/Contract.java
+++ b/pico/pico/src/main/java/io/helidon/pico/Intercepted.java
@@ -14,26 +14,33 @@
* limitations under the License.
*/
-package io.helidon.pico.api;
+package io.helidon.pico;
import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import jakarta.inject.Qualifier;
+
/**
- * The 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 service registry, or otherwise be
- * eligible for injection via standard @Inject. It could also be used on other types (e.g., abstract class) as well.
- *
+ * Indicates that type identified by {@link #value()} is being intercepted.
*
- * If the developer does not have access to the source to place this annotation on the interface definition then consider using
- * {@link ExternalContracts} instead - this annotation can be placed on the implementation class implementing the given interface.
- * See io.helidon.pico.spi.ServiceInfo#getContractsImplemented()
+ * @see io.helidon.pico.Interceptor
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Qualifier
@Target(java.lang.annotation.ElementType.TYPE)
-public @interface Contract {
+public @interface Intercepted {
+
+ /**
+ * The target being intercepted.
+ *
+ * @return the target class being intercepted
+ */
+ Class> value();
}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InterceptedTrigger.java b/pico/pico/src/main/java/io/helidon/pico/InterceptedTrigger.java
new file mode 100644
index 00000000000..60fa62a87dc
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/InterceptedTrigger.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 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.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Meta-annotation for an annotation that will trigger services annotated with it to become intercepted.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface InterceptedTrigger {
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Interceptor.java b/pico/pico/src/main/java/io/helidon/pico/Interceptor.java
new file mode 100644
index 00000000000..103f78ac081
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/Interceptor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Implementors of this contract must be {@link jakarta.inject.Named} according to the {@link Intercepted}
+ * annotation they support.
+ */
+@Contract
+public interface Interceptor {
+
+ /**
+ * Called during interception of the target V. The implementation typically should finish with the call to
+ * {@link Interceptor.Chain#proceed()}.
+ *
+ * @param ctx the invocation context
+ * @param chain the chain to call proceed on
+ * @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);
+
+
+ /**
+ * Represents the next in line for interception, terminating with a call to the wrapped service provider.
+ *
+ * @param the return value
+ */
+ interface Chain {
+ /**
+ * Call the next interceptor in line, or finishing with the call to the service provider.
+ *
+ * @return the result of the call.
+ */
+ V proceed();
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InvocationContext.java b/pico/pico/src/main/java/io/helidon/pico/InvocationContext.java
new file mode 100644
index 00000000000..c037f9dc6d4
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/InvocationContext.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2022 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.List;
+import java.util.Map;
+
+import io.helidon.pico.types.AnnotationAndValue;
+import io.helidon.pico.types.TypeName;
+import io.helidon.pico.types.TypedElementName;
+
+/**
+ * Used by {@link Interceptor}.
+ */
+public interface InvocationContext {
+
+ /**
+ * The root service provider being intercepted.
+ *
+ * @return the root service provider being intercepted
+ */
+ ServiceProvider> rootServiceProvider();
+
+ /**
+ * The service type name for the root service provider.
+ *
+ * @return the service type name for the root service provider
+ */
+ TypeName serviceTypeName();
+
+ /**
+ * The annotations on the enclosing type.
+ *
+ * @return the annotations on the enclosing type
+ */
+ List classAnnotations();
+
+ /**
+ * The element info represents the method (or the constructor) being invoked.
+ *
+ * @return the element info represents the method (or the constructor) being invoked
+ */
+ TypedElementName elementInfo();
+
+ /**
+ * The method/element argument info.
+ *
+ * @return the method/element argument info.
+ */
+ TypedElementName[] elementArgInfo();
+
+ /**
+ * The arguments to the method.
+ *
+ * @return the read/write method/element arguments
+ */
+ Object[] elementArgs();
+
+ /**
+ * The contextual info that can be shared between interceptors.
+ *
+ * @return the read/write contextual data that is passed between each chained interceptor
+ */
+ Map contextData();
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/InvocationException.java b/pico/pico/src/main/java/io/helidon/pico/InvocationException.java
new file mode 100644
index 00000000000..99a6538852a
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/InvocationException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Wraps any checked exceptions that are thrown during the {@link Interceptor} invocations.
+ */
+public class InvocationException extends PicoServiceProviderException {
+
+ /**
+ * Constructor.
+ *
+ * @param msg the message
+ * @param cause the root cause
+ * @param serviceProvider the service provider
+ */
+ public InvocationException(String msg, Throwable cause, ServiceProvider> serviceProvider) {
+ super(msg, cause, serviceProvider);
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Module.java b/pico/pico/src/main/java/io/helidon/pico/Module.java
new file mode 100644
index 00000000000..906efb40574
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/Module.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Provides aggregation of services to the "containing" (jar) module.
+ *
+ * Implementations of this contract are normally code generated, although then can be programmatically written by the developer
+ * for special cases.
+ *
+ * Note: instances of this type are not eligible for injection.
+ *
+ * @see Application
+ */
+@Contract
+public interface Module extends Named {
+
+ /**
+ * Called by the provider implementation at bootstrapping time to bind all services / service providers to the
+ * service registry.
+ *
+ * @param binder the binder used to register the services to the registry
+ */
+ void configure(ServiceBinder binder);
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/Named.java b/pico/pico/src/main/java/io/helidon/pico/Named.java
new file mode 100644
index 00000000000..2e50c17a44f
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/Named.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Provides a means to identify if the instance is named.
+ *
+ * @see jakarta.inject.Named
+ */
+public interface Named {
+
+ /**
+ * The optional name for this instance.
+ *
+ * @return the name associated with this instance or empty if not available or known.
+ */
+ default Optional name() {
+ return Optional.empty();
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/PicoException.java b/pico/pico/src/main/java/io/helidon/pico/PicoException.java
new file mode 100644
index 00000000000..6e5d44f3df4
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/PicoException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022 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 general purpose exception.
+ *
+ * @see PicoServiceProviderException
+ * @see InjectionException
+ * @see InvocationException
+ */
+public class PicoException extends RuntimeException {
+
+ /**
+ * A general purpose exception from Pico.
+ *
+ * @param msg the message
+ */
+ public PicoException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * A general purpose exception from Pico.
+ *
+ * @param msg the message
+ * @param cause the root cause
+ */
+ public PicoException(String msg,
+ Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/PicoServiceProviderException.java b/pico/pico/src/main/java/io/helidon/pico/PicoServiceProviderException.java
new file mode 100644
index 00000000000..2f66fb07f8d
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/PicoServiceProviderException.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2022 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.Objects;
+import java.util.Optional;
+
+/**
+ * A general purpose exception from Pico.
+ */
+public class PicoServiceProviderException extends PicoException {
+
+ /**
+ * The service provider this exception pertains.
+ */
+ private final ServiceProvider> serviceProvider;
+
+ /**
+ * A general purpose exception from Pico.
+ *
+ * @param msg the message
+ */
+ public PicoServiceProviderException(String msg) {
+ super(msg);
+ this.serviceProvider = null;
+ }
+
+ /**
+ * A general purpose exception from Pico.
+ *
+ * @param msg the message
+ * @param cause the root cause
+ */
+ public PicoServiceProviderException(String msg,
+ Throwable cause) {
+ super(msg, cause);
+
+ if (cause instanceof PicoServiceProviderException) {
+ this.serviceProvider = ((PicoServiceProviderException) cause).serviceProvider().orElse(null);
+ } else {
+ this.serviceProvider = null;
+ }
+ }
+
+ /**
+ * A general purpose exception from Pico.
+ *
+ * @param msg the message
+ * @param cause the root cause
+ * @param serviceProvider the service provider
+ */
+ public PicoServiceProviderException(String msg,
+ Throwable cause,
+ ServiceProvider> serviceProvider) {
+ super(msg, cause);
+ Objects.requireNonNull(serviceProvider);
+ this.serviceProvider = serviceProvider;
+ }
+
+ /**
+ * The service provider that this exception pertains to, or empty if not related to any particular provider.
+ *
+ * @return the optional / contextual service provider
+ */
+ public Optional> serviceProvider() {
+ return Optional.ofNullable(serviceProvider);
+ }
+
+ @Override
+ public String getMessage() {
+ return super.getMessage()
+ + (Objects.isNull(serviceProvider)
+ ? "" : (": service provider: " + serviceProvider));
+ }
+
+}
diff --git a/pico/pico/src/main/java/io/helidon/pico/PicoServices.java b/pico/pico/src/main/java/io/helidon/pico/PicoServices.java
new file mode 100644
index 00000000000..1e37b059d30
--- /dev/null
+++ b/pico/pico/src/main/java/io/helidon/pico/PicoServices.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * 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()}.
+ */
+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()
+ .serviceInfo(EMPTY_CRITERIA)
+ .expected(true)
+ .build();
+
+ /**
+ * Get {@link PicoServices} instance if available. The highest {@link io.helidon.common.Weighted} service will be loaded
+ * and returned.
+ *
+ * @return the Pico services instance
+ */
+ static Optional picoServices() {
+ return PicoServicesHolder.picoServices();
+ }
+
+ /**
+ * The service registry.
+ *
+ * @return the services registry
+ */
+ Services services();
+
+ /**
+ * Creates a service binder instance for a specified module.
+ *
+ * @param module the module to offer binding to dynamically, and typically only at early startup initialization
+ *
+ * @return the service binder capable of binding, or empty if not permitted/available
+ */
+ default Optional createServiceBinder(Module module) {
+ return Optional.empty();
+ }
+
+ /**
+ * Optionally, the injector.
+ *
+ * @return the injector, or empty if not available
+ */
+ default Optional injector() {
+ return Optional.empty();
+ }
+
+ /**
+ * Optionally, the service providers' configuration.
+ *
+ * @return the config, or empty if not available
+ */
+ default Optional config() {
+ return Optional.empty();
+ }
+
+ /**
+ * 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 shutdown.
+ *
+ * @return a map of all managed service types deactivated to results of deactivation
+ */
+ default Optional