Add configurable method name prefixes to builders (#86)
Add configurable Bean interface to add prefixed getters to record
This commit is contained in:
@@ -187,6 +187,38 @@ public @interface RecordBuilder {
|
||||
* When enabled, adds functional methods to the nested "With" class (such as {@code map()} and {@code accept()}).
|
||||
*/
|
||||
boolean addFunctionalMethodsToWith() default false;
|
||||
|
||||
/**
|
||||
* If set, all builder setter methods will be prefixed with this string. Camel-casing will
|
||||
* still be enforced, so if this option is set to "set" a field named "myField" will get
|
||||
* a corresponding setter named "setMyField".
|
||||
*/
|
||||
String setterPrefix() default "";
|
||||
|
||||
/**
|
||||
* If set, all builder getter methods will be prefixed with this string. Camel-casing will
|
||||
* still be enforced, so if this option is set to "get", a field named "myField" will get
|
||||
* a corresponding getter named "getMyField".
|
||||
*/
|
||||
String getterPrefix() default "";
|
||||
|
||||
/**
|
||||
* If set, all boolean builder getter methods will be prefixed with this string.
|
||||
* Camel-casing will still be enforced, so if this option is set to "is", a field named
|
||||
* "myField" will get a corresponding getter named "isMyField".
|
||||
*/
|
||||
String booleanPrefix() default "";
|
||||
|
||||
/**
|
||||
* If set, the Builder will contain an internal interface with this name. This interface
|
||||
* contains getters for all the fields in the Record prefixed with the value supplied in
|
||||
* {@link this.getterPrefix} and {@link this.booleanPrefix}. This interface can be
|
||||
* implemented by the original Record to have proper bean-style prefixed getters.
|
||||
*
|
||||
* Please note that unless either of the aforementioned prefixes are set,
|
||||
* this option does nothing.
|
||||
*/
|
||||
String beanClassName() default "";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -72,6 +73,9 @@ class InternalRecordBuilderProcessor {
|
||||
.addTypeVariables(typeVariables);
|
||||
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
|
||||
addWithNestedClass();
|
||||
if (!metaData.beanClassName().isEmpty()) {
|
||||
addBeanNestedClass();
|
||||
}
|
||||
addDefaultConstructor();
|
||||
addStaticBuilder();
|
||||
if (recordComponents.size() > 0) {
|
||||
@@ -147,7 +151,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addJavadoc("Add withers to {@code $L}\n", recordClassType.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addTypeVariables(typeVariables);
|
||||
recordComponents.forEach(component -> addNestedGetterMethod(classBuilder, component));
|
||||
recordComponents.forEach(component -> addNestedGetterMethod(classBuilder, component, prefixedName(component, true)));
|
||||
addWithBuilderMethod(classBuilder);
|
||||
addWithSuppliedBuilderMethod(classBuilder);
|
||||
IntStream.range(0, recordComponents.size()).forEach(index -> add1WithMethod(classBuilder, recordComponents.get(index), index));
|
||||
@@ -160,6 +164,31 @@ class InternalRecordBuilderProcessor {
|
||||
builder.addType(classBuilder.build());
|
||||
}
|
||||
|
||||
private void addBeanNestedClass() {
|
||||
/*
|
||||
Adds a nested interface that adds getters similar to:
|
||||
|
||||
public class MyRecordBuilder {
|
||||
public interface Bean {
|
||||
// getter methods
|
||||
}
|
||||
}
|
||||
*/
|
||||
var classBuilder = TypeSpec.interfaceBuilder(metaData.beanClassName())
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addJavadoc("Add getters to {@code $L}\n", recordClassType.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addTypeVariables(typeVariables);
|
||||
recordComponents.forEach(component -> {
|
||||
if (prefixedName(component, true).equals(component.name())) {
|
||||
return;
|
||||
}
|
||||
addNestedGetterMethod(classBuilder, component, component.name());
|
||||
add1PrefixedGetterMethod(classBuilder, component);
|
||||
});
|
||||
builder.addType(classBuilder.build());
|
||||
}
|
||||
|
||||
private void addWithSuppliedBuilderMethod(TypeSpec.Builder classBuilder) {
|
||||
/*
|
||||
Adds a method that returns a pre-filled copy builder similar to:
|
||||
@@ -257,6 +286,28 @@ class InternalRecordBuilderProcessor {
|
||||
classBuilder.addMethod(methodSpec);
|
||||
}
|
||||
|
||||
private void add1PrefixedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component) {
|
||||
/*
|
||||
Adds a get method for the component similar to:
|
||||
|
||||
default MyRecord getName() {
|
||||
return name();
|
||||
}
|
||||
*/
|
||||
var codeBlockBuilder = CodeBlock.builder();
|
||||
codeBlockBuilder.add("$[return $L()$];", component.name());
|
||||
|
||||
var methodName = prefixedName(component, true);
|
||||
var methodSpec = MethodSpec.methodBuilder(methodName)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addJavadoc("Returns the value of {@code $L}\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||
.addCode(codeBlockBuilder.build())
|
||||
.returns(component.typeName())
|
||||
.build();
|
||||
classBuilder.addMethod(methodSpec);
|
||||
}
|
||||
|
||||
private void addComponentCallsAsArguments(int index, CodeBlock.Builder codeBlockBuilder) {
|
||||
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
|
||||
if (parameterIndex > 0) {
|
||||
@@ -266,7 +317,7 @@ class InternalRecordBuilderProcessor {
|
||||
if (parameterIndex == index) {
|
||||
collectionBuilderUtils.add(codeBlockBuilder, parameterComponent);
|
||||
} else {
|
||||
codeBlockBuilder.add("$L()", parameterComponent.name());
|
||||
codeBlockBuilder.add("$L()", prefixedName(parameterComponent, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -530,7 +581,7 @@ class InternalRecordBuilderProcessor {
|
||||
codeBuilder.add("\n");
|
||||
}
|
||||
codeBuilder.add("@Override\n")
|
||||
.add("public $T $L() {\n", component.typeName(), component.name())
|
||||
.add("public $T $L() {\n", component.typeName(), prefixedName(component, true))
|
||||
.indent()
|
||||
.addStatement("return from.$L()", component.name())
|
||||
.unindent()
|
||||
@@ -664,13 +715,13 @@ class InternalRecordBuilderProcessor {
|
||||
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName) && parameterizedTypeName.rawType.equals(optionalType);
|
||||
}
|
||||
|
||||
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component) {
|
||||
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component, String methodName) {
|
||||
/*
|
||||
For a single record component, add a getter similar to:
|
||||
|
||||
T p();
|
||||
*/
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(component.name())
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(methodName)
|
||||
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
@@ -834,7 +885,7 @@ class InternalRecordBuilderProcessor {
|
||||
return p;
|
||||
}
|
||||
*/
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(component.name())
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(prefixedName(component, true))
|
||||
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
@@ -853,8 +904,7 @@ class InternalRecordBuilderProcessor {
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
|
||||
var methodSpec = MethodSpec.methodBuilder(component.name())
|
||||
var methodSpec = MethodSpec.methodBuilder(prefixedName(component, false))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(builderClassType.typeName());
|
||||
@@ -940,5 +990,18 @@ class InternalRecordBuilderProcessor {
|
||||
.addMethod(methodBuilder.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String prefixedName(RecordClassType component, boolean isGetter) {
|
||||
BiFunction<String, String, String> prefixer = (p, s) -> p.isEmpty()
|
||||
? s : p + Character.toUpperCase(s.charAt(0)) + s.substring(1);
|
||||
boolean isBool = component.typeName().toString().toLowerCase(Locale.ROOT).equals("boolean");
|
||||
if (isGetter) {
|
||||
if (isBool) {
|
||||
return prefixer.apply(metaData.booleanPrefix(), component.name());
|
||||
}
|
||||
return prefixer.apply(metaData.getterPrefix(), component.name());
|
||||
}
|
||||
return prefixer.apply(metaData.setterPrefix(), component.name());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.List;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import io.soabase.recordbuilder.test.CustomMethodNamesBuilder.Bean;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
setterPrefix = "set", getterPrefix = "get", booleanPrefix = "is", beanClassName = "Bean")
|
||||
public record CustomMethodNames(
|
||||
int theValue,
|
||||
List<Integer> theList,
|
||||
boolean theBoolean) implements Bean {
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestCustomMethodNames {
|
||||
|
||||
@Test
|
||||
public void builderGetsCustomSetterAndGetterNames() {
|
||||
var obj = CustomMethodNamesBuilder.builder()
|
||||
.setTheValue(1)
|
||||
.setTheList(List.of(2))
|
||||
.setTheBoolean(true);
|
||||
assertEquals(1, obj.getTheValue());
|
||||
assertEquals(List.of(2), obj.getTheList());
|
||||
assertTrue(obj.isTheBoolean());
|
||||
assertEquals(new CustomMethodNames(1, List.of(2), true), obj.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withBuilderGetsCustomSetterAndGetterNames() {
|
||||
var obj = CustomMethodNamesBuilder.from(CustomMethodNamesBuilder.builder()
|
||||
.setTheValue(1)
|
||||
.setTheList(List.of(2))
|
||||
.setTheBoolean(true)
|
||||
.build());
|
||||
assertEquals(1, obj.getTheValue());
|
||||
assertEquals(List.of(2), obj.getTheList());
|
||||
assertTrue(obj.isTheBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordHasPrefixedGetters() {
|
||||
var obj = new CustomMethodNames(1, List.of(2), true);
|
||||
assertEquals(1, obj.getTheValue());
|
||||
assertEquals(List.of(2), obj.getTheList());
|
||||
assertTrue(obj.isTheBoolean());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user