Add flag to add concrete setters for optionals (#94)
* Add flag to add concrete setters for optionals * Add another test with concrete optionals disabled
This commit is contained in:
@@ -144,6 +144,11 @@ public @interface RecordBuilder {
|
|||||||
*/
|
*/
|
||||||
boolean emptyDefaultForOptional() default true;
|
boolean emptyDefaultForOptional() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add non-optional setter methods for optional record components.
|
||||||
|
*/
|
||||||
|
boolean addConcreteSettersForOptional() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add not-null checks for record components annotated with any annotation named either "NotNull",
|
* Add not-null checks for record components annotated with any annotation named either "NotNull",
|
||||||
* "NoNull", or "NonNull" (see {@link #interpretNotNullsPattern()} for the actual regex matching pattern).
|
* "NoNull", or "NonNull" (see {@link #interpretNotNullsPattern()} for the actual regex matching pattern).
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ class InternalRecordBuilderProcessor {
|
|||||||
private final CollectionBuilderUtils collectionBuilderUtils;
|
private final CollectionBuilderUtils collectionBuilderUtils;
|
||||||
|
|
||||||
private static final TypeName overrideType = TypeName.get(Override.class);
|
private static final TypeName overrideType = TypeName.get(Override.class);
|
||||||
private static final TypeName optionalType = TypeName.get(Optional.class);
|
|
||||||
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
|
|
||||||
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
|
|
||||||
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
|
|
||||||
private static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator", "RecordBuilderValidator");
|
private static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator", "RecordBuilderValidator");
|
||||||
private static final TypeVariableName rType = TypeVariableName.get("R");
|
private static final TypeVariableName rType = TypeVariableName.get("R");
|
||||||
private final ProcessingEnvironment processingEnv;
|
private final ProcessingEnvironment processingEnv;
|
||||||
@@ -99,6 +95,9 @@ class InternalRecordBuilderProcessor {
|
|||||||
if (metaData.enableGetters()) {
|
if (metaData.enableGetters()) {
|
||||||
add1GetterMethod(component);
|
add1GetterMethod(component);
|
||||||
}
|
}
|
||||||
|
if (metaData.addConcreteSettersForOptional()) {
|
||||||
|
add1ConcreteOptionalSetterMethod(component);
|
||||||
|
}
|
||||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, EXCLUDE_WILDCARD_TYPES);
|
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, EXCLUDE_WILDCARD_TYPES);
|
||||||
collectionMetaData.ifPresent(meta -> add1CollectionBuilders(meta, component));
|
collectionMetaData.ifPresent(meta -> add1CollectionBuilders(meta, component));
|
||||||
});
|
});
|
||||||
@@ -708,31 +707,17 @@ class InternalRecordBuilderProcessor {
|
|||||||
*/
|
*/
|
||||||
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
|
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
|
||||||
if (metaData.emptyDefaultForOptional()) {
|
if (metaData.emptyDefaultForOptional()) {
|
||||||
TypeName thisOptionalType = null;
|
Optional<OptionalType> thisOptionalType = OptionalType.fromClassType(component);
|
||||||
if (isOptional(component)) {
|
if (thisOptionalType.isPresent()) {
|
||||||
thisOptionalType = optionalType;
|
var codeBlock = CodeBlock.builder()
|
||||||
} else if (component.typeName().equals(optionalIntType)) {
|
.add("$T.empty()", thisOptionalType.get().typeName())
|
||||||
thisOptionalType = optionalIntType;
|
.build();
|
||||||
} else if (component.typeName().equals(optionalLongType)) {
|
|
||||||
thisOptionalType = optionalLongType;
|
|
||||||
} else if (component.typeName().equals(optionalDoubleType)) {
|
|
||||||
thisOptionalType = optionalDoubleType;
|
|
||||||
}
|
|
||||||
if (thisOptionalType != null) {
|
|
||||||
var codeBlock = CodeBlock.builder().add("$T.empty()", thisOptionalType).build();
|
|
||||||
fieldSpecBuilder.initializer(codeBlock);
|
fieldSpecBuilder.initializer(codeBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.addField(fieldSpecBuilder.build());
|
builder.addField(fieldSpecBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOptional(ClassType component) {
|
|
||||||
if (component.typeName().equals(optionalType)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName) && parameterizedTypeName.rawType.equals(optionalType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component, String methodName) {
|
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component, String methodName) {
|
||||||
/*
|
/*
|
||||||
For a single record component, add a getter similar to:
|
For a single record component, add a getter similar to:
|
||||||
@@ -944,6 +929,33 @@ class InternalRecordBuilderProcessor {
|
|||||||
builder.addMethod(methodSpec.build());
|
builder.addMethod(methodSpec.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void add1ConcreteOptionalSetterMethod(RecordClassType component) {
|
||||||
|
/*
|
||||||
|
For a single optional record component, add a concrete setter similar to:
|
||||||
|
|
||||||
|
public MyRecordBuilder p(T p) {
|
||||||
|
this.p = p;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var optionalType = OptionalType.fromClassType(component);
|
||||||
|
if (optionalType.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var type = optionalType.get();
|
||||||
|
var methodSpec = MethodSpec.methodBuilder(prefixedName(component, false))
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.returns(builderClassType.typeName());
|
||||||
|
|
||||||
|
var parameterSpecBuilder = ParameterSpec.builder(type.valueType(), component.name());
|
||||||
|
methodSpec.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||||
|
.addStatement("this.$L = $T.of($L)", component.name(), type.typeName(), component.name());
|
||||||
|
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||||
|
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
|
||||||
|
builder.addMethod(methodSpec.build());
|
||||||
|
}
|
||||||
|
|
||||||
private List<TypeVariableName> typeVariablesWithReturn() {
|
private List<TypeVariableName> typeVariablesWithReturn() {
|
||||||
var variables = new ArrayList<TypeVariableName>();
|
var variables = new ArrayList<TypeVariableName>();
|
||||||
variables.add(rType);
|
variables.add(rType);
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* 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.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalDouble;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
import com.squareup.javapoet.ParameterizedTypeName;
|
||||||
|
import com.squareup.javapoet.TypeName;
|
||||||
|
|
||||||
|
public record OptionalType(TypeName typeName, TypeName valueType) {
|
||||||
|
|
||||||
|
private static final TypeName optionalType = TypeName.get(Optional.class);
|
||||||
|
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
|
||||||
|
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
|
||||||
|
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
|
||||||
|
|
||||||
|
private static boolean isOptional(ClassType component) {
|
||||||
|
if (component.typeName().equals(optionalType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)
|
||||||
|
&& parameterizedTypeName.rawType.equals(optionalType);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<OptionalType> fromClassType(final ClassType component) {
|
||||||
|
if (isOptional(component)) {
|
||||||
|
if (!(component.typeName() instanceof ParameterizedTypeName parameterizedType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalType, TypeName.get(Object.class)));
|
||||||
|
}
|
||||||
|
final TypeName containingType = parameterizedType.typeArguments.isEmpty()
|
||||||
|
? TypeName.get(Object.class)
|
||||||
|
: parameterizedType.typeArguments.get(0);
|
||||||
|
return Optional.of(new OptionalType(optionalType, containingType));
|
||||||
|
}
|
||||||
|
if (component.typeName().equals(optionalIntType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalIntType, TypeName.get(int.class)));
|
||||||
|
}
|
||||||
|
if (component.typeName().equals(optionalLongType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalLongType, TypeName.get(long.class)));
|
||||||
|
}
|
||||||
|
if (component.typeName().equals(optionalDoubleType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalDoubleType, TypeName.get(double.class)));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,6 @@ import java.util.OptionalDouble;
|
|||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
@RecordBuilder.Options(emptyDefaultForOptional = true, addConcreteSettersForOptional = true)
|
||||||
@RecordBuilder
|
@RecordBuilder
|
||||||
public record RecordWithOptional(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
public record RecordWithOptional(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* 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.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalDouble;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
||||||
|
@RecordBuilder
|
||||||
|
public record RecordWithOptional2(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||||
@@ -33,4 +33,36 @@ class TestOptional {
|
|||||||
Assertions.assertEquals(OptionalLong.empty(), record.l());
|
Assertions.assertEquals(OptionalLong.empty(), record.l());
|
||||||
Assertions.assertEquals(OptionalDouble.empty(), record.d());
|
Assertions.assertEquals(OptionalDouble.empty(), record.d());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRawSetters() {
|
||||||
|
var record = RecordWithOptionalBuilder.builder()
|
||||||
|
.value("value")
|
||||||
|
.raw("rawValue")
|
||||||
|
.i(42)
|
||||||
|
.l(424242L)
|
||||||
|
.d(42.42)
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(Optional.of("value"), record.value());
|
||||||
|
Assertions.assertEquals(Optional.of("rawValue"), record.raw());
|
||||||
|
Assertions.assertEquals(OptionalInt.of(42), record.i());
|
||||||
|
Assertions.assertEquals(OptionalLong.of(424242L), record.l());
|
||||||
|
Assertions.assertEquals(OptionalDouble.of(42.42), record.d());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOptionalSetters() {
|
||||||
|
var record = RecordWithOptional2Builder.builder()
|
||||||
|
.value(Optional.of("value"))
|
||||||
|
.raw(Optional.of("rawValue"))
|
||||||
|
.i(OptionalInt.of(42))
|
||||||
|
.l(OptionalLong.of(424242L))
|
||||||
|
.d(OptionalDouble.of(42.42))
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(Optional.of("value"), record.value());
|
||||||
|
Assertions.assertEquals(Optional.of("rawValue"), record.raw());
|
||||||
|
Assertions.assertEquals(OptionalInt.of(42), record.i());
|
||||||
|
Assertions.assertEquals(OptionalLong.of(424242L), record.l());
|
||||||
|
Assertions.assertEquals(OptionalDouble.of(42.42), record.d());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user