Skip to content

Commit

Permalink
Prototype builder update (#7281)
Browse files Browse the repository at this point in the history
- builder getters use Optional where can be null
- builder no longer implements prototype
- interceptor changed to decorator
  • Loading branch information
tomas-langer authored Aug 1, 2023
1 parent 39b644b commit 6153086
Show file tree
Hide file tree
Showing 70 changed files with 744 additions and 820 deletions.
33 changes: 6 additions & 27 deletions builder/api/src/main/java/io/helidon/builder/api/Prototype.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,6 @@ public interface Builder<BUILDER, PROTOTYPE> {
default BUILDER self() {
return (BUILDER) this;
}

/**
* Resolve an instance to a built instance (if it is a builder).
* This method is used by generated code to ensure that instances passed to us have gone through
* builder interceptors and required validation.
*
* @param instance instance to check
* @return the same instance if not a builder, or a built instance
* @param <T> type of the parameter
*/
@SuppressWarnings("unchecked")
default <T> T resolveBuilder(T instance) {
if (instance instanceof Builder<?, ?> builder) {
return (T) builder.buildPrototype();
}
if (instance instanceof io.helidon.common.Builder<?, ?> builder) {
return (T) builder.build();
}
return instance;
}
}

/**
Expand Down Expand Up @@ -214,14 +194,14 @@ public interface Factory<T> {
boolean beanStyle() default false;

/**
* Used to intercept the builder, right before method build is called.
* Used to decorate the builder, right before method build is called.
* Validations are done AFTER the interceptor is handled.
* This class may be package local if located in the same package as blueprint.
* The class must have accessible constructor with no parameters.
*
* @return interceptor type
* @return decorator type
*/
Class<? extends BuilderInterceptor> builderInterceptor() default BuilderInterceptor.class;
Class<? extends BuilderDecorator> decorator() default BuilderDecorator.class;
}

/**
Expand All @@ -235,17 +215,16 @@ public interface Factory<T> {
* by your very builder interceptor.
*
* @param <T> the type of the bean builder to intercept
* @see Prototype.Blueprint#builderInterceptor()
* @see io.helidon.builder.api.Prototype.Blueprint#decorator()
*/
@FunctionalInterface
public interface BuilderInterceptor<T> {
public interface BuilderDecorator<T> {
/**
* Provides the ability to intercept (i.e., including decoration or mutation) the target.
*
* @param target the target being intercepted
* @return the mutated or replaced target (must not be null)
*/
T intercept(T target);
void decorate(T target);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,8 @@ static void generate(PrintWriter pw,
} else {
pw.print(PROTOTYPE_BUILDER);
}
pw.print("<BUILDER, PROTOTYPE>,");
pw.print(prototypeWithTypes);
pw.println(" {");
pw.println("<BUILDER, PROTOTYPE> {");

// builder fields
fields(pw, typeContext, true);

Expand Down Expand Up @@ -143,57 +142,12 @@ static void generate(PrintWriter pw,
fromInstanceMethod(pw, typeContext, prototypeWithTypes);
fromBuilderMethod(pw, typeContext, typeArgumentNames);

// method preBuildPrototype() - handles providers, interceptor
// method preBuildPrototype() - handles providers, decorator
preBuildPrototypeMethod(pw, typeContext);
validatePrototypeMethod(pw, typeContext);

CustomMethods customMethods = typeContext.customMethods();

for (CustomMethods.CustomMethod customMethod : customMethods.prototypeMethods()) {
// todo these sections should be moved to CustomMethod implementation once we have class model
// builder - custom implementation methods for new prototype interface methods
CustomMethods.Method generated = customMethod.generatedMethod().method();
// public TypeName boxed() - with implementation
if (!generated.javadoc().isEmpty()) {
Javadoc parsed = Javadoc.parse(generated.javadoc()).removeFirstParam();
pw.print(SOURCE_SPACING);
pw.println("/**");
for (String docLine : parsed.toLines()) {
pw.print(SOURCE_SPACING);
pw.print(" *");
pw.println(docLine);
}
pw.print(SOURCE_SPACING);
pw.println(" */");
}
for (String annotation : customMethod.generatedMethod().annotations()) {
pw.print(SOURCE_SPACING);
pw.print('@');
pw.println(annotation);
}
if (!customMethod.generatedMethod().annotations().contains(OVERRIDE)) {
pw.print(SOURCE_SPACING);
pw.println("@Override");
}
pw.print(SOURCE_SPACING);
pw.print("public ");
pw.print(generated.returnType().fqName());
pw.print(" ");
pw.print(generated.name());
pw.print("(");
pw.print(generated.arguments()
.stream()
.map(it -> it.typeName().fqName() + " " + it.name())
.collect(Collectors.joining(", ")));
pw.println(") {");
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print(customMethod.generatedMethod().callCode());
pw.println(";");
pw.print(SOURCE_SPACING);
pw.println("}");
pw.println();
}
for (CustomMethods.CustomMethod customMethod : customMethods.builderMethods()) {
// builder specific custom methods (not part of interface)
CustomMethods.Method generated = customMethod.generatedMethod().method();
Expand Down Expand Up @@ -402,7 +356,7 @@ public BUILDER config(Config config) {
}

TypeName returnType = TypeName.createFromGenericDeclaration("BUILDER");
// first setters (implement interface)
// first setters
for (PrototypeProperty child : properties) {
for (GeneratedMethod setter : child.setters(returnType, child.configuredOption().description())) {
// this is builder setters
Expand Down Expand Up @@ -454,6 +408,12 @@ public BUILDER config(Config config) {
}

// then getters
/*
If has default value - return type
If primitive & optional - return type
If collection - return type
Otherwise return Optional<x>
*/
for (PrototypeProperty child : properties) {
String getterName = child.getterName();
/*
Expand Down Expand Up @@ -481,11 +441,8 @@ String host() {
}
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.println("@Override");
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print("public ");
pw.print(child.typeName().fqName());
pw.print(child.builderGetterType().fqName());
pw.print(" ");
pw.print(getterName);
pw.println("() {");
Expand Down Expand Up @@ -630,36 +587,24 @@ private static void fromBuilderMethod(PrintWriter pw, TypeContext typeContext, S
pw.print(SOURCE_SPACING);
TypeName declaredType = property.typeHandler().declaredType();

if (declaredType.primitive() || declaredType.isOptional()) {
pw.print(property.typeHandler().setterName());
pw.print("(builder.");
pw.print(property.typeHandler().getterName());
pw.println("());");
} else if (declaredType.isSet() || declaredType.isList() || declaredType.isMap()) {
pw.print("add");
pw.print(capitalize(property.name()));
pw.print("(builder.");
pw.print(property.typeHandler().getterName());
pw.println("());");
} else {
if (property.builderGetterOptional()) {
// property that is either mandatory or internally nullable
pw.print("if (builder.");
pw.print("builder.");
pw.print(property.typeHandler().getterName());
pw.println("() != null) {");
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print("().ifPresent(this::");
pw.print(property.typeHandler().setterName());
pw.println(");");
} else {
if (declaredType.isSet() || declaredType.isList() || declaredType.isMap()) {
pw.print("add");
pw.print(capitalize(property.name()));
} else {
pw.print(property.typeHandler().setterName());
}
pw.print("(builder.");
pw.print(property.typeHandler().getterName());
pw.println("());");
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.println("}");
}

}
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
Expand Down Expand Up @@ -709,7 +654,7 @@ private static void preBuildPrototypeMethod(PrintWriter pw,
pw.println("/**");
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.println(" * Handles providers and interceptors.");
pw.println(" * Handles providers and decorators.");
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.println(" */");
Expand Down Expand Up @@ -803,14 +748,13 @@ private static void preBuildPrototypeMethod(PrintWriter pw,
}
}
}
if (typeContext.typeInfo().builderInterceptor().isPresent()) {
// TODO we should use the resulting builder, or remove the return type from interceptor
if (typeContext.typeInfo().decorator().isPresent()) {
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.print("new ");
pw.print(typeContext.typeInfo().builderInterceptor().get().fqName());
pw.println("().intercept(this);");
pw.print(typeContext.typeInfo().decorator().get().fqName());
pw.println("().decorate(this);");
pw.println();
}
pw.print(SOURCE_SPACING);
Expand Down Expand Up @@ -1252,23 +1196,31 @@ private static void implAssignToFields(PrintWriter pw, TypeContext typeContext)
pw.print("this.");
pw.print(child.name());
pw.print(" = ");
if (child.typeHandler().declaredType().genericTypeName().equals(LIST)) {
TypeName declaredType = child.typeHandler().declaredType();
if (declaredType.genericTypeName().equals(LIST)) {
pw.print("java.util.List.copyOf(builder.");
pw.print(child.getterName());
pw.println("());");
} else if (child.typeHandler().declaredType().genericTypeName().equals(SET)) {
} else if (declaredType.genericTypeName().equals(SET)) {
pw.print("java.util.Collections.unmodifiableSet(new java.util.LinkedHashSet<>(builder.");
pw.print(child.getterName());
pw.println("()));");
} else if (child.typeHandler().declaredType().genericTypeName().equals(MAP)) {
} else if (declaredType.genericTypeName().equals(MAP)) {
pw.print("java.util.Collections.unmodifiableMap(new java.util.LinkedHashMap<>(builder.");
pw.print(child.getterName());
pw.println("()));");
} else {
// optional and other types are just plainly assigned
pw.print(" builder.");
pw.print(child.getterName());
pw.println("();");
if (child.builderGetterOptional() && !declaredType.isOptional()) {
// builder getter optional, but type not, we call get (must be present - is validated)
pw.print(" builder.");
pw.print(child.getterName());
pw.println("().get();");
} else {
// optional and other types are just plainly assigned
pw.print(" builder.");
pw.print(child.getterName());
pw.println("();");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,18 @@ TypeName typeName() {
return typeHandler.declaredType();
}

TypeName builderGetterType() {
return typeHandler.builderGetterType(configuredOption.required(),
configuredOption.hasDefault());
}
String builderGetter() {
return typeHandler().generateBuilderGetter();
return typeHandler.generateBuilderGetter(configuredOption.required(),
configuredOption.hasDefault());
}

boolean builderGetterOptional() {
return typeHandler.builderGetterOptional(configuredOption.required(),
configuredOption.hasDefault());
}

public String fieldDeclaration(boolean isBuilder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import io.helidon.common.types.TypeValues;

import static io.helidon.builder.processor.Types.BLUEPRINT_TYPE;
import static io.helidon.builder.processor.Types.BUILDER_INTERCEPTOR;
import static io.helidon.builder.processor.Types.BUILDER_DECORATOR;
import static io.helidon.builder.processor.Types.CONFIGURED_OPTION_TYPE;
import static io.helidon.builder.processor.Types.CONFIGURED_TYPE;
import static io.helidon.builder.processor.Types.IMPLEMENT_TYPE;
Expand Down Expand Up @@ -191,8 +191,8 @@ static TypeContext create(ProcessingContext processingContext,
.filter(it -> it) // filter our falses
.findFirst()
.orElse(false);
Optional<TypeName> builderInterceptor = blueprintAnnotation.getValue("builderInterceptor")
.filter(Predicate.not(BUILDER_INTERCEPTOR::equals))
Optional<TypeName> decorator = blueprintAnnotation.getValue("decorator")
.filter(Predicate.not(BUILDER_DECORATOR::equals))
.map(TypeName::create);

// factory is if the blueprint implements Factory<RuntimeContractType>
Expand Down Expand Up @@ -225,7 +225,7 @@ static TypeContext create(ProcessingContext processingContext,
prototypeBuilder,
prototypeImpl,
runtimeObject,
builderInterceptor,
decorator,
superPrototype,
annotationsToGenerate(blueprint));

Expand Down Expand Up @@ -448,7 +448,7 @@ record TypeInformation(
TypeName prototypeBuilder,
TypeName prototypeImpl,
Optional<TypeName> runtimeObject,
Optional<TypeName> builderInterceptor,
Optional<TypeName> decorator,
Optional<TypeName> superPrototype,
List<String> annotationsToGenerate) {
public TypeName prototypeBuilderBase() {
Expand Down
Loading

0 comments on commit 6153086

Please sign in to comment.