Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype builder update #7281

Merged
merged 4 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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