Skip to content

Commit 4fb65f2

Browse files
authored
4.x: Supplier in builder (#7402)
* Support for suppliers in builder, using config suppliers when configured. * Test for supplier * Support for supplier of optional values
1 parent 91d269c commit 4fb65f2

File tree

7 files changed

+466
-8
lines changed

7 files changed

+466
-8
lines changed

builder/processor/src/main/java/io/helidon/builder/processor/TypeHandler.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ static TypeHandler create(String name, String getterName, String setterName, Typ
5656
if (TypeNames.OPTIONAL.equals(returnType)) {
5757
return new TypeHandlerOptional(name, getterName, setterName, returnType);
5858
}
59+
if (TypeNames.SUPPLIER.equals(returnType)) {
60+
return new TypeHandlerSupplier(name, getterName, setterName, returnType);
61+
}
5962
if (TypeNames.SET.equals(returnType)) {
6063
return new TypeHandlerSet(name, getterName, setterName, returnType);
6164
}
@@ -202,7 +205,7 @@ String configGet(PrototypeProperty.ConfiguredOption configured) {
202205

203206
String generateFromConfig(FactoryMethods factoryMethods) {
204207
if (actualType().fqName().equals("char[]")) {
205-
return ".asString().map(String::toCharArray)";
208+
return ".asString().as(String::toCharArray)";
206209
}
207210

208211
TypeName boxed = actualType().boxed();
@@ -214,7 +217,7 @@ String generateFromConfig(FactoryMethods factoryMethods) {
214217

215218
void generateFromConfig(Method.Builder method, FactoryMethods factoryMethods) {
216219
if (actualType().fqName().equals("char[]")) {
217-
method.add(".asString().map(").typeName(String.class).add("::toCharArray)");
220+
method.add(".asString().as(").typeName(String.class).add("::toCharArray)");
218221
return;
219222
}
220223

@@ -296,10 +299,10 @@ boolean builderGetterOptional(boolean required, boolean hasDefault) {
296299

297300
}
298301

299-
private void declaredSetter(InnerClass.Builder classBuilder,
300-
PrototypeProperty.ConfiguredOption configured,
301-
TypeName returnType,
302-
Javadoc blueprintJavadoc) {
302+
protected void declaredSetter(InnerClass.Builder classBuilder,
303+
PrototypeProperty.ConfiguredOption configured,
304+
TypeName returnType,
305+
Javadoc blueprintJavadoc) {
303306
Method.Builder builder = Method.builder()
304307
.name(setterName())
305308
.returnType(returnType, "updated builder instance")

builder/processor/src/main/java/io/helidon/builder/processor/TypeHandlerMap.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,8 @@ private void declaredSetterAdd(InnerClass.Builder classBuilder, PrototypePropert
416416
.addLine("return self();"));
417417
}
418418

419-
private void declaredSetter(InnerClass.Builder classBuilder,
419+
@Override
420+
protected void declaredSetter(InnerClass.Builder classBuilder,
420421
PrototypeProperty.ConfiguredOption configured,
421422
TypeName returnType,
422423
Javadoc blueprintJavadoc) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
* Copyright (c) 2023 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.builder.processor;
18+
19+
import java.util.Objects;
20+
import java.util.function.Consumer;
21+
22+
import io.helidon.common.processor.classmodel.Field;
23+
import io.helidon.common.processor.classmodel.InnerClass;
24+
import io.helidon.common.processor.classmodel.Javadoc;
25+
import io.helidon.common.processor.classmodel.Method;
26+
import io.helidon.common.types.TypeName;
27+
28+
import static io.helidon.builder.processor.Types.CHAR_ARRAY_TYPE;
29+
import static io.helidon.builder.processor.Types.STRING_TYPE;
30+
import static io.helidon.common.types.TypeNames.SUPPLIER;
31+
32+
class TypeHandlerSupplier extends TypeHandler.OneTypeHandler {
33+
34+
TypeHandlerSupplier(String name, String getterName, String setterName, TypeName declaredType) {
35+
super(name, getterName, setterName, declaredType);
36+
}
37+
38+
@Override
39+
Field.Builder fieldDeclaration(PrototypeProperty.ConfiguredOption configured, boolean isBuilder, boolean alwaysFinal) {
40+
Field.Builder builder = Field.builder()
41+
.type(declaredType())
42+
.name(name())
43+
.isFinal(alwaysFinal || !isBuilder);
44+
45+
if (isBuilder && configured.hasDefault()) {
46+
builder.defaultValue("() -> " + configured.defaultValue());
47+
}
48+
49+
return builder;
50+
}
51+
52+
@Override
53+
TypeName argumentTypeName() {
54+
return TypeName.builder(SUPPLIER)
55+
.addTypeArgument(toWildcard(actualType()))
56+
.build();
57+
}
58+
59+
@Override
60+
void generateFromConfig(Method.Builder method, PrototypeProperty.ConfiguredOption configured, FactoryMethods factoryMethods) {
61+
if (configured.provider()) {
62+
return;
63+
}
64+
if (factoryMethods.createFromConfig().isPresent()) {
65+
method.addLine(configGet(configured)
66+
+ generateFromConfig(factoryMethods)
67+
+ ".ifPresent(this::" + setterName() + ");");
68+
} else if (actualType().isOptional()) {
69+
method.add(setterName() + "(");
70+
method.add(configGet(configured));
71+
method.add(generateFromConfigOptional(factoryMethods));
72+
method.addLine(".optionalSupplier());");
73+
} else {
74+
method.add(setterName() + "(");
75+
method.add(configGet(configured));
76+
method.add(generateFromConfig(factoryMethods));
77+
method.addLine(".supplier());");
78+
}
79+
}
80+
81+
String generateFromConfigOptional(FactoryMethods factoryMethods) {
82+
TypeName optionalType = actualType().typeArguments().get(0);
83+
if (optionalType.fqName().equals("char[]")) {
84+
return ".asString().as(String::toCharArray)";
85+
}
86+
87+
TypeName boxed = optionalType.boxed();
88+
return factoryMethods.createFromConfig()
89+
.map(it -> ".map(" + it.typeWithFactoryMethod().genericTypeName().fqName() + "::" + it.createMethodName() + ")")
90+
.orElseGet(() -> ".as(" + boxed.fqName() + ".class)");
91+
92+
}
93+
94+
@Override
95+
void setters(InnerClass.Builder classBuilder,
96+
PrototypeProperty.ConfiguredOption configured,
97+
PrototypeProperty.Singular singular,
98+
FactoryMethods factoryMethod,
99+
TypeName returnType,
100+
Javadoc blueprintJavadoc) {
101+
102+
declaredSetter(classBuilder, configured, returnType, blueprintJavadoc);
103+
104+
// and add the setter with the actual type
105+
Method.Builder method = Method.builder()
106+
.name(setterName())
107+
.description(blueprintJavadoc.content())
108+
.returnType(returnType, "updated builder instance")
109+
.addParameter(param -> param.name(name())
110+
.type(actualType())
111+
.description(blueprintJavadoc.returnDescription()))
112+
.addJavadocTag("see", "#" + getterName() + "()")
113+
.typeName(Objects.class)
114+
.addLine(".requireNonNull(" + name() + ");")
115+
.addLine("this." + name() + " = () -> " + name() + ";")
116+
.addLine("return self();");
117+
classBuilder.addMethod(method);
118+
119+
if (actualType().equals(CHAR_ARRAY_TYPE)) {
120+
classBuilder.addMethod(builder -> builder.name(setterName())
121+
.returnType(returnType, "updated builder instance")
122+
.description(blueprintJavadoc.content())
123+
.addJavadocTag("see", "#" + getterName() + "()")
124+
.addParameter(param -> param.name(name())
125+
.type(STRING_TYPE)
126+
.description(blueprintJavadoc.returnDescription()))
127+
.accessModifier(setterAccessModifier(configured))
128+
.typeName(Objects.class).addLine(".requireNonNull(" + name() + ");")
129+
.addLine("this." + name() + " = () -> " + name() + ".toCharArray();")
130+
.addLine("return self();"));
131+
}
132+
133+
if (factoryMethod.createTargetType().isPresent()) {
134+
// if there is a factory method for the return type, we also have setters for the type (probably config object)
135+
FactoryMethods.FactoryMethod fm = factoryMethod.createTargetType().get();
136+
String argumentName = name() + "Config";
137+
138+
classBuilder.addMethod(builder -> builder.name(setterName())
139+
.accessModifier(setterAccessModifier(configured))
140+
.description(blueprintJavadoc.content())
141+
.returnType(returnType, "updated builder instance")
142+
.addParameter(param -> param.name(argumentName)
143+
.type(fm.argumentType())
144+
.description(blueprintJavadoc.returnDescription()))
145+
.addJavadocTag("see", "#" + getterName() + "()")
146+
.typeName(Objects.class)
147+
.addLine(".requireNonNull(" + argumentName + ");")
148+
.add("this." + name() + " = ")
149+
.typeName(fm.typeWithFactoryMethod().genericTypeName())
150+
.addLine("." + fm.createMethodName() + "(" + argumentName + ");")
151+
.addLine("return self();"));
152+
}
153+
154+
if (factoryMethod.builder().isPresent()) {
155+
// if there is a factory method for the return type, we also have setters for the type (probably config object)
156+
FactoryMethods.FactoryMethod fm = factoryMethod.builder().get();
157+
158+
TypeName builderType;
159+
String className = fm.factoryMethodReturnType().className();
160+
if (className.equals("Builder") || className.endsWith(".Builder")) {
161+
builderType = fm.factoryMethodReturnType();
162+
} else {
163+
builderType = TypeName.create(fm.factoryMethodReturnType().fqName() + ".Builder");
164+
}
165+
String argumentName = "consumer";
166+
TypeName argumentType = TypeName.builder()
167+
.type(Consumer.class)
168+
.addTypeArgument(builderType)
169+
.build();
170+
171+
classBuilder.addMethod(builder -> builder.name(setterName())
172+
.accessModifier(setterAccessModifier(configured))
173+
.description(blueprintJavadoc.content())
174+
.returnType(returnType, "updated builder instance")
175+
.addParameter(param -> param.name(argumentName)
176+
.type(argumentType)
177+
.description(blueprintJavadoc.returnDescription()))
178+
.addJavadocTag("see", "#" + getterName() + "()")
179+
.typeName(Objects.class)
180+
.addLine(".requireNonNull(" + argumentName + ");")
181+
.add("var builder = ")
182+
.typeName(fm.typeWithFactoryMethod().genericTypeName())
183+
.addLine("." + fm.createMethodName() + "();")
184+
.addLine("consumer.accept(builder);")
185+
.addLine("this." + name() + "(builder.build());")
186+
.addLine("return self();"));
187+
}
188+
}
189+
190+
protected void declaredSetter(InnerClass.Builder classBuilder,
191+
PrototypeProperty.ConfiguredOption configured,
192+
TypeName returnType,
193+
Javadoc blueprintJavadoc) {
194+
classBuilder.addMethod(method -> method.name(setterName())
195+
.returnType(returnType, "updated builder instance")
196+
.description(blueprintJavadoc.content())
197+
.addJavadocTag("see", "#" + getterName() + "()")
198+
.addParameter(param -> param.name(name())
199+
.type(argumentTypeName())
200+
.description(blueprintJavadoc.returnDescription()))
201+
.accessModifier(setterAccessModifier(configured))
202+
.typeName(Objects.class).addLine(".requireNonNull(" + name() + ");")
203+
.addLine("this." + name() + " = " + name() + "::get;")
204+
.addLine("return self();"));
205+
}
206+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2023 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.builder.test.testsubjects;
18+
19+
import java.util.Optional;
20+
import java.util.function.Supplier;
21+
22+
import io.helidon.builder.api.Prototype;
23+
import io.helidon.config.metadata.Configured;
24+
import io.helidon.config.metadata.ConfiguredOption;
25+
26+
/**
27+
* Blueprint with a supplier from configuration.
28+
*/
29+
@Prototype.Blueprint
30+
@Configured
31+
interface SupplierBeanBlueprint {
32+
/**
33+
* This value is either explicitly configured, or uses config to get the supplier.
34+
* If config source with change support is changed, the supplier should provide the latest value from configuration.
35+
*
36+
* @return supplier with latest value
37+
*/
38+
@ConfiguredOption
39+
Supplier<String> stringSupplier();
40+
41+
@ConfiguredOption(key = "string-supplier")
42+
Supplier<char[]> charSupplier();
43+
44+
@ConfiguredOption
45+
Supplier<Optional<String>> optionalSupplier();
46+
47+
@ConfiguredOption(key = "optional-supplier")
48+
Supplier<Optional<char[]>> optionalCharSupplier();
49+
}

0 commit comments

Comments
 (0)