Compare commits
14 Commits
record-bui
...
record-bui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abfc12bdb0 | ||
|
|
6c0fac0dff | ||
|
|
ae527cd8e5 | ||
|
|
73ba62057a | ||
|
|
87998aba68 | ||
|
|
04a0904d3f | ||
|
|
aa072af8e1 | ||
|
|
b435b5d3fd | ||
|
|
d3c1bb36f3 | ||
|
|
c3719326c9 | ||
|
|
661d0818c0 | ||
|
|
79bc8396f2 | ||
|
|
b2149622e4 | ||
|
|
0718e37f76 |
264
README.md
264
README.md
@@ -89,168 +89,176 @@ _Hat tip to [Benji Weber](https://benjiweber.co.uk/blog/2020/09/19/fun-with-java
|
||||
|
||||
## Builder Class Definition
|
||||
|
||||
(Note: you can see a builder class built using `@RecordBuilderFull` here: [SingleItemsBuilder.java](https://gist.github.com/Randgalt/8aa487a847ea2acdd76d702f7cf17d6a))
|
||||
(Note: you can see a builder class built using `@RecordBuilderFull` here: [FullRecordBuilder.java](https://gist.github.com/Randgalt/8aa487a847ea2acdd76d702f7cf17d6a))
|
||||
|
||||
The full builder class is defined as:
|
||||
|
||||
```java
|
||||
public class NameAndAgeBuilder {
|
||||
private String name;
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
private int age;
|
||||
|
||||
private NameAndAgeBuilder() {
|
||||
}
|
||||
private NameAndAgeBuilder() {
|
||||
}
|
||||
|
||||
private NameAndAgeBuilder(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
private NameAndAgeBuilder(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static constructor/builder. Can be used instead of new NameAndAge(...)
|
||||
*/
|
||||
public static NameAndAge NameAndAge(String name, int age) {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
/**
|
||||
* Static constructor/builder. Can be used instead of new NameAndAge(...)
|
||||
*/
|
||||
public static NameAndAge NameAndAge(String name, int age) {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new builder with all fields set to default Java values
|
||||
*/
|
||||
public static NameAndAgeBuilder builder() {
|
||||
return new NameAndAgeBuilder();
|
||||
}
|
||||
/**
|
||||
* Return a new builder with all fields set to default Java values
|
||||
*/
|
||||
public static NameAndAgeBuilder builder() {
|
||||
return new NameAndAgeBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new builder with all fields set to the values taken from the given record instance
|
||||
*/
|
||||
public static NameAndAgeBuilder builder(NameAndAge from) {
|
||||
return new NameAndAgeBuilder(from.name(), from.age());
|
||||
}
|
||||
/**
|
||||
* Return a new builder with all fields set to the values taken from the given record instance
|
||||
*/
|
||||
public static NameAndAgeBuilder builder(NameAndAge from) {
|
||||
return new NameAndAgeBuilder(from.name(), from.age());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a "with"er for an existing record instance
|
||||
*/
|
||||
public static NameAndAgeBuilder.With from(NameAndAge from) {
|
||||
return new NameAndAgeBuilder.With() {
|
||||
@Override
|
||||
public String name() {
|
||||
return from.name();
|
||||
}
|
||||
/**
|
||||
* Return a "with"er for an existing record instance
|
||||
*/
|
||||
public static NameAndAgeBuilder.With from(NameAndAge from) {
|
||||
return new _FromWith(from);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int age() {
|
||||
return from.age();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Return a stream of the record components as map entries keyed with the component name and the value as the component value
|
||||
*/
|
||||
public static Stream<Map.Entry<String, Object>> stream(NameAndAge record) {
|
||||
return Stream.of(new AbstractMap.SimpleImmutableEntry<>("name", record.name()),
|
||||
new AbstractMap.SimpleImmutableEntry<>("age", record.age()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a stream of the record components as map entries keyed with the component name and the value as the component value
|
||||
*/
|
||||
public static Stream<Map.Entry<String, Object>> stream(NameAndAge record) {
|
||||
return Stream.of(Map.entry("name", record.name()),
|
||||
Map.entry("age", record.age()));
|
||||
}
|
||||
/**
|
||||
* Return a new record instance with all fields set to the current values in this builder
|
||||
*/
|
||||
public NameAndAge build() {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new record instance with all fields set to the current values in this builder
|
||||
*/
|
||||
public NameAndAge build() {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, age);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, age);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (this == o) || ((o instanceof NameAndAgeBuilder r)
|
||||
&& Objects.equals(name, r.name)
|
||||
&& (age == r.age));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (this == o) || ((o instanceof NameAndAgeBuilder r)
|
||||
&& Objects.equals(name, r.name)
|
||||
&& (age == r.age));
|
||||
}
|
||||
/**
|
||||
* Set a new value for the {@code name} record component in the builder
|
||||
*/
|
||||
public NameAndAgeBuilder name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for the {@code name} record component in the builder
|
||||
*/
|
||||
public NameAndAgeBuilder name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current value for the {@code name} record component in the builder
|
||||
*/
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for the {@code age} record component in the builder
|
||||
*/
|
||||
public NameAndAgeBuilder age(int age) {
|
||||
this.age = age;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current value for the {@code age} record component in the builder
|
||||
*/
|
||||
public int age() {
|
||||
return age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add withers to {@code NameAndAge}
|
||||
*/
|
||||
public interface With {
|
||||
/**
|
||||
* Return the current value for the {@code name} record component in the builder
|
||||
*/
|
||||
String name();
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for the {@code age} record component in the builder
|
||||
*/
|
||||
public NameAndAgeBuilder age(int age) {
|
||||
this.age = age;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current value for the {@code age} record component in the builder
|
||||
*/
|
||||
int age();
|
||||
|
||||
/**
|
||||
* Return a new record builder using the current values
|
||||
*/
|
||||
default NameAndAgeBuilder with() {
|
||||
return new NameAndAgeBuilder(name(), age());
|
||||
public int age() {
|
||||
return age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new record built from the builder passed to the given consumer
|
||||
* Add withers to {@code NameAndAge}
|
||||
*/
|
||||
default NameAndAge with(Consumer<NameAndAgeBuilder> consumer) {
|
||||
NameAndAgeBuilder builder = with();
|
||||
consumer.accept(builder);
|
||||
return builder.build();
|
||||
public interface With {
|
||||
/**
|
||||
* Return the current value for the {@code name} record component in the builder
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Return the current value for the {@code age} record component in the builder
|
||||
*/
|
||||
int age();
|
||||
|
||||
/**
|
||||
* Return a new record builder using the current values
|
||||
*/
|
||||
default NameAndAgeBuilder with() {
|
||||
return new NameAndAgeBuilder(name(), age());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new record built from the builder passed to the given consumer
|
||||
*/
|
||||
default NameAndAge with(Consumer<NameAndAgeBuilder> consumer) {
|
||||
NameAndAgeBuilder builder = with();
|
||||
consumer.accept(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
|
||||
*/
|
||||
default NameAndAge withName(String name) {
|
||||
return new NameAndAge(name, age());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code age}
|
||||
*/
|
||||
default NameAndAge withAge(int age) {
|
||||
return new NameAndAge(name(), age);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
|
||||
*/
|
||||
default NameAndAge withName(String name) {
|
||||
return new NameAndAge(name, age());
|
||||
}
|
||||
private static final class _FromWith implements NameAndAgeBuilder.With {
|
||||
private final NameAndAge from;
|
||||
|
||||
/**
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code age}
|
||||
*/
|
||||
default NameAndAge withAge(int age) {
|
||||
return new NameAndAge(name(), age);
|
||||
private _FromWith(NameAndAge from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return from.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int age() {
|
||||
return from.age();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -5,7 +5,7 @@
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>33</version>
|
||||
<version>34</version>
|
||||
|
||||
<modules>
|
||||
<module>record-builder-core</module>
|
||||
@@ -80,7 +80,7 @@
|
||||
<url>https://github.com/randgalt/record-builder</url>
|
||||
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
|
||||
<tag>record-builder-33</tag>
|
||||
<tag>record-builder-34</tag>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>33</version>
|
||||
<version>34</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -252,6 +252,24 @@ public @interface RecordBuilder {
|
||||
* without {@code new}.
|
||||
*/
|
||||
boolean addStaticBuilder() default true;
|
||||
|
||||
/**
|
||||
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||
* uses an internal class to track changes to lists. This is the name of that class.
|
||||
*/
|
||||
String mutableListClassName() default "_MutableList";
|
||||
|
||||
/**
|
||||
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||
* uses an internal class to track changes to sets. This is the name of that class.
|
||||
*/
|
||||
String mutableSetClassName() default "_MutableSet";
|
||||
|
||||
/**
|
||||
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||
* uses an internal class to track changes to maps. This is the name of that class.
|
||||
*/
|
||||
String mutableMapClassName() default "_MutableMap";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>33</version>
|
||||
<version>34</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -22,24 +22,38 @@ import javax.lang.model.element.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||
|
||||
class CollectionBuilderUtils {
|
||||
private final boolean useImmutableCollections;
|
||||
private final boolean addSingleItemCollectionBuilders;
|
||||
private final boolean addClassRetainedGenerated;
|
||||
private final String listShimName;
|
||||
private final String mapShimName;
|
||||
private final String setShimName;
|
||||
private final String collectionShimName;
|
||||
|
||||
private final String listMakerMethodName;
|
||||
private final String mapMakerMethodName;
|
||||
private final String setMakerMethodName;
|
||||
|
||||
private boolean needsListShim;
|
||||
private boolean needsMapShim;
|
||||
private boolean needsSetShim;
|
||||
private boolean needsCollectionShim;
|
||||
|
||||
private static final TypeName listType = TypeName.get(List.class);
|
||||
private static final TypeName mapType = TypeName.get(Map.class);
|
||||
private static final TypeName setType = TypeName.get(Set.class);
|
||||
private static final TypeName collectionType = TypeName.get(Collection.class);
|
||||
private boolean needsListMutableMaker;
|
||||
private boolean needsMapMutableMaker;
|
||||
private boolean needsSetMutableMaker;
|
||||
|
||||
private static final Class<?> listType = List.class;
|
||||
private static final Class<?> mapType = Map.class;
|
||||
private static final Class<?> setType = Set.class;
|
||||
private static final Class<?> collectionType = Collection.class;
|
||||
private static final TypeName listTypeName = TypeName.get(listType);
|
||||
private static final TypeName mapTypeName = TypeName.get(mapType);
|
||||
private static final TypeName setTypeName = TypeName.get(setType);
|
||||
private static final TypeName collectionTypeName = TypeName.get(collectionType);
|
||||
|
||||
private static final TypeVariableName tType = TypeVariableName.get("T");
|
||||
private static final TypeVariableName kType = TypeVariableName.get("K");
|
||||
@@ -49,14 +63,33 @@ class CollectionBuilderUtils {
|
||||
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName.get(ClassName.get(Set.class), tType);
|
||||
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName.get(ClassName.get(Collection.class), tType);
|
||||
|
||||
private static final Class<?> mutableListType = ArrayList.class;
|
||||
private static final Class<?> mutableMapType = HashMap.class;
|
||||
private static final Class<?> mutableSetType = HashSet.class;
|
||||
private static final ClassName mutableListTypeName = ClassName.get(mutableListType);
|
||||
private static final ClassName mutableMapTypeName = ClassName.get(mutableMapType);
|
||||
private static final ClassName mutableSetTypeName = ClassName.get(mutableSetType);
|
||||
private final TypeSpec mutableListSpec;
|
||||
private final TypeSpec mutableSetSpec;
|
||||
private final TypeSpec mutableMapSpec;
|
||||
|
||||
CollectionBuilderUtils(List<RecordClassType> recordComponents, RecordBuilder.Options metaData) {
|
||||
useImmutableCollections = metaData.useImmutableCollections();
|
||||
addSingleItemCollectionBuilders = metaData.addSingleItemCollectionBuilders();
|
||||
addClassRetainedGenerated = metaData.addClassRetainedGenerated();
|
||||
|
||||
listShimName = adjustShimName(recordComponents, "__list", 0);
|
||||
mapShimName = adjustShimName(recordComponents, "__map", 0);
|
||||
setShimName = adjustShimName(recordComponents, "__set", 0);
|
||||
collectionShimName = adjustShimName(recordComponents, "__collection", 0);
|
||||
listShimName = disambiguateGeneratedMethodName(recordComponents, "__list", 0);
|
||||
mapShimName = disambiguateGeneratedMethodName(recordComponents, "__map", 0);
|
||||
setShimName = disambiguateGeneratedMethodName(recordComponents, "__set", 0);
|
||||
collectionShimName = disambiguateGeneratedMethodName(recordComponents, "__collection", 0);
|
||||
|
||||
listMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureListMutable", 0);
|
||||
setMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureSetMutable", 0);
|
||||
mapMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureMapMutable", 0);
|
||||
|
||||
mutableListSpec = buildMutableCollectionSubType(metaData.mutableListClassName(), mutableListTypeName, parameterizedListType, tType);
|
||||
mutableSetSpec = buildMutableCollectionSubType(metaData.mutableSetClassName(), mutableSetTypeName, parameterizedSetType, tType);
|
||||
mutableMapSpec = buildMutableCollectionSubType(metaData.mutableMapClassName(), mutableMapTypeName, parameterizedMapType, kType, vType);
|
||||
}
|
||||
|
||||
enum SingleItemsMetaDataMode {
|
||||
@@ -65,7 +98,8 @@ class CollectionBuilderUtils {
|
||||
EXCLUDE_WILDCARD_TYPES
|
||||
}
|
||||
|
||||
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {}
|
||||
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {
|
||||
}
|
||||
|
||||
Optional<SingleItemsMetaData> singleItemsMetaData(RecordClassType component, SingleItemsMetaDataMode mode) {
|
||||
if (addSingleItemCollectionBuilders && (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)) {
|
||||
@@ -73,15 +107,15 @@ class CollectionBuilderUtils {
|
||||
ClassName wildcardClass = null;
|
||||
int typeArgumentQty = 0;
|
||||
if (isList(component)) {
|
||||
collectionClass = ArrayList.class;
|
||||
collectionClass = mutableListType;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isSet(component)) {
|
||||
collectionClass = HashSet.class;
|
||||
collectionClass = mutableSetType;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isMap(component)) {
|
||||
collectionClass = HashMap.class;
|
||||
collectionClass = mutableMapType;
|
||||
wildcardClass = (ClassName) component.rawTypeName();
|
||||
typeArgumentQty = 2;
|
||||
}
|
||||
@@ -110,33 +144,36 @@ class CollectionBuilderUtils {
|
||||
}
|
||||
|
||||
boolean isImmutableCollection(RecordClassType component) {
|
||||
return useImmutableCollections && (isList(component) || isMap(component) || isSet(component) || component.rawTypeName().equals(collectionType));
|
||||
return useImmutableCollections && (isList(component) || isMap(component) || isSet(component) || component.rawTypeName().equals(collectionTypeName));
|
||||
}
|
||||
|
||||
boolean isList(RecordClassType component) {
|
||||
return component.rawTypeName().equals(listType);
|
||||
return component.rawTypeName().equals(listTypeName);
|
||||
}
|
||||
|
||||
boolean isMap(RecordClassType component) {
|
||||
return component.rawTypeName().equals(mapType);
|
||||
return component.rawTypeName().equals(mapTypeName);
|
||||
}
|
||||
|
||||
boolean isSet(RecordClassType component) {
|
||||
return component.rawTypeName().equals(setType);
|
||||
return component.rawTypeName().equals(setTypeName);
|
||||
}
|
||||
|
||||
void add(CodeBlock.Builder builder, RecordClassType component) {
|
||||
void addShimCall(CodeBlock.Builder builder, RecordClassType component) {
|
||||
if (useImmutableCollections) {
|
||||
if (isList(component)) {
|
||||
needsListShim = true;
|
||||
needsListMutableMaker = true;
|
||||
builder.add("$L($L)", listShimName, component.name());
|
||||
} else if (isMap(component)) {
|
||||
needsMapShim = true;
|
||||
needsMapMutableMaker = true;
|
||||
builder.add("$L($L)", mapShimName, component.name());
|
||||
} else if (isSet(component)) {
|
||||
needsSetShim = true;
|
||||
needsSetMutableMaker = true;
|
||||
builder.add("$L($L)", setShimName, component.name());
|
||||
} else if (component.rawTypeName().equals(collectionType)) {
|
||||
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||
needsCollectionShim = true;
|
||||
builder.add("$L($L)", collectionShimName, component.name());
|
||||
} else {
|
||||
@@ -147,22 +184,67 @@ class CollectionBuilderUtils {
|
||||
}
|
||||
}
|
||||
|
||||
String shimName(RecordClassType component) {
|
||||
if (isList(component)) {
|
||||
return listShimName;
|
||||
} else if (isMap(component)) {
|
||||
return mapShimName;
|
||||
} else if (isSet(component)) {
|
||||
return setShimName;
|
||||
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||
return collectionShimName;
|
||||
} else {
|
||||
throw new IllegalArgumentException(component + " is not a supported collection type");
|
||||
}
|
||||
}
|
||||
|
||||
String mutableMakerName(RecordClassType component) {
|
||||
if (isList(component)) {
|
||||
return listMakerMethodName;
|
||||
} else if (isMap(component)) {
|
||||
return mapMakerMethodName;
|
||||
} else if (isSet(component)) {
|
||||
return setMakerMethodName;
|
||||
} else {
|
||||
throw new IllegalArgumentException(component + " is not a supported collection type");
|
||||
}
|
||||
}
|
||||
|
||||
void addShims(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsListShim) {
|
||||
builder.addMethod(buildMethod(listShimName, listType, parameterizedListType, tType));
|
||||
builder.addMethod(buildShimMethod(listShimName, listTypeName, collectionType, parameterizedListType, tType));
|
||||
}
|
||||
if (needsSetShim) {
|
||||
builder.addMethod(buildMethod(setShimName, setType, parameterizedSetType, tType));
|
||||
builder.addMethod(buildShimMethod(setShimName, setTypeName, collectionType, parameterizedSetType, tType));
|
||||
}
|
||||
if (needsMapShim) {
|
||||
builder.addMethod(buildMethod(mapShimName, mapType, parameterizedMapType, kType, vType));
|
||||
builder.addMethod(buildShimMethod(mapShimName, mapTypeName, mapType, parameterizedMapType, kType, vType));
|
||||
}
|
||||
if (needsCollectionShim) {
|
||||
builder.addMethod(buildCollectionsMethod());
|
||||
builder.addMethod(buildCollectionsShimMethod());
|
||||
}
|
||||
}
|
||||
|
||||
void addMutableMakers(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsListMutableMaker) {
|
||||
builder.addMethod(buildMutableMakerMethod(listMakerMethodName, mutableListSpec.name, parameterizedListType, tType));
|
||||
builder.addType(mutableListSpec);
|
||||
}
|
||||
if (needsSetMutableMaker) {
|
||||
builder.addMethod(buildMutableMakerMethod(setMakerMethodName, mutableSetSpec.name, parameterizedSetType, tType));
|
||||
builder.addType(mutableSetSpec);
|
||||
}
|
||||
if (needsMapMutableMaker) {
|
||||
builder.addMethod(buildMutableMakerMethod(mapMakerMethodName, mutableMapSpec.name, parameterizedMapType, kType, vType));
|
||||
builder.addType(mutableMapSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,34 +269,71 @@ class CollectionBuilderUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
private String adjustShimName(List<RecordClassType> recordComponents, String baseName, int index) {
|
||||
private String disambiguateGeneratedMethodName(List<RecordClassType> recordComponents, String baseName, int index) {
|
||||
var name = (index == 0) ? baseName : (baseName + index);
|
||||
if (recordComponents.stream().anyMatch(component -> component.name().equals(name))) {
|
||||
return adjustShimName(recordComponents, baseName, index + 1);
|
||||
return disambiguateGeneratedMethodName(recordComponents, baseName, index + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private MethodSpec buildMethod(String name, TypeName mainType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
private MethodSpec buildShimMethod(String name, TypeName mainType, Class<?> abstractType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
var code = CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
|
||||
TypeName[] wildCardTypeArguments = parameterizedType.typeArguments.stream().map(WildcardTypeName::subtypeOf).toList().toArray(new TypeName[0]);
|
||||
var extendedParameterizedType = ParameterizedTypeName.get(ClassName.get(abstractType), wildCardTypeArguments);
|
||||
return MethodSpec.methodBuilder(name)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.returns(parameterizedType)
|
||||
.addParameter(extendedParameterizedType, "o")
|
||||
.addStatement(code)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildMutableMakerMethod(String name, String mutableCollectionType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
var nullCase = CodeBlock.of("if (o == null) return new $L<>()", mutableCollectionType);
|
||||
var isMutableCase = CodeBlock.of("if (o instanceof $L) return o", mutableCollectionType);
|
||||
var defaultCase = CodeBlock.of("return new $L<>(o)", mutableCollectionType);
|
||||
return MethodSpec.methodBuilder(name)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.returns(parameterizedType)
|
||||
.addParameter(parameterizedType, "o")
|
||||
.addStatement(code)
|
||||
.addStatement(nullCase)
|
||||
.addStatement(isMutableCase)
|
||||
.addStatement(defaultCase)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCollectionsMethod() {
|
||||
private TypeSpec buildMutableCollectionSubType(String className, ClassName mutableCollectionType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
TypeName[] typeArguments = new TypeName[]{};
|
||||
typeArguments = Arrays.stream(typeVariables).toList().toArray(typeArguments);
|
||||
|
||||
TypeSpec.Builder builder = TypeSpec.classBuilder(className)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.superclass(ParameterizedTypeName.get(mutableCollectionType, typeArguments))
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation).addStatement("super()").build())
|
||||
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation).addParameter(parameterizedType, "o").addStatement("super(o)").build());
|
||||
|
||||
if (addClassRetainedGenerated) {
|
||||
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCollectionsShimMethod() {
|
||||
var code = CodeBlock.builder()
|
||||
.add("if (o instanceof Set) {\n")
|
||||
.indent()
|
||||
.addStatement("return $T.copyOf(o)", setType)
|
||||
.addStatement("return $T.copyOf(o)", setTypeName)
|
||||
.unindent()
|
||||
.addStatement("}")
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listType, listType)
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listTypeName, listTypeName)
|
||||
.build();
|
||||
return MethodSpec.methodBuilder(collectionShimName)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.EXCLUDE_WILDCARD_TYPES;
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.STANDARD_FOR_SETTER;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
@@ -29,11 +33,8 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.*;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
class InternalRecordBuilderProcessor {
|
||||
private final RecordBuilder.Options metaData;
|
||||
@@ -110,6 +111,7 @@ class InternalRecordBuilderProcessor {
|
||||
collectionMetaData.ifPresent(meta -> add1CollectionBuilders(meta, component));
|
||||
});
|
||||
collectionBuilderUtils.addShims(builder);
|
||||
collectionBuilderUtils.addMutableMakers(builder);
|
||||
builderType = builder.build();
|
||||
}
|
||||
|
||||
@@ -331,7 +333,7 @@ class InternalRecordBuilderProcessor {
|
||||
}
|
||||
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||
if (parameterIndex == index) {
|
||||
collectionBuilderUtils.add(codeBlockBuilder, parameterComponent);
|
||||
collectionBuilderUtils.addShimCall(codeBlockBuilder, parameterComponent);
|
||||
} else {
|
||||
codeBlockBuilder.add("$L()", prefixedName(parameterComponent, true));
|
||||
}
|
||||
@@ -415,9 +417,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addAnnotation(generatedRecordBuilderAnnotation);
|
||||
recordComponents.forEach(component -> {
|
||||
constructorBuilder.addParameter(component.typeName(), component.name());
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD);
|
||||
collectionMetaData.ifPresentOrElse(meta -> constructorBuilder.addStatement("this.$L = new $T<>($L)", component.name(), meta.singleItemCollectionClass(), component.name()),
|
||||
() -> constructorBuilder.addStatement("this.$L = $L", component.name(), component.name()));
|
||||
constructorBuilder.addStatement("this.$L = $L", component.name(), component.name());
|
||||
});
|
||||
builder.addMethod(constructorBuilder.build());
|
||||
}
|
||||
@@ -549,7 +549,7 @@ class InternalRecordBuilderProcessor {
|
||||
var recordComponent = recordComponents.get(index);
|
||||
if (collectionBuilderUtils.isImmutableCollection(recordComponent)) {
|
||||
codeBuilder.add("$[$L = ", recordComponent.name());
|
||||
collectionBuilderUtils.add(codeBuilder, recordComponents.get(index));
|
||||
collectionBuilderUtils.addShimCall(codeBuilder, recordComponents.get(index));
|
||||
codeBuilder.add(";\n$]");
|
||||
}
|
||||
});
|
||||
@@ -616,6 +616,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addParameter(recordClassType.typeName(), "from")
|
||||
.addStatement("this.from = from")
|
||||
.addModifiers(Modifier.PRIVATE)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.build();
|
||||
fromWithClassBuilder.addMethod(constructorSpec);
|
||||
|
||||
@@ -626,6 +627,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addStatement("return from.$L()", component.name())
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.build();
|
||||
fromWithClassBuilder.addMethod(methodSpec);
|
||||
});
|
||||
@@ -815,34 +817,34 @@ class InternalRecordBuilderProcessor {
|
||||
For a single map record component, add a methods similar to:
|
||||
|
||||
public T addP(K key, V value) {
|
||||
if (this.p == null) {
|
||||
this.p = new HashMap<>();
|
||||
}
|
||||
this.p = __ensureMapMutable(p);
|
||||
this.p.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
this.p = __ensureMapMutable(p);
|
||||
i.forEach(this.p::put);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
this.p = __ensureMapMutable(p);
|
||||
i.forEach(this.p::put);
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), HashMap.class)
|
||||
.endControlFlow();
|
||||
var codeBlockBuilder = CodeBlock.builder();
|
||||
if (collectionBuilderUtils.isImmutableCollection(component)) {
|
||||
codeBlockBuilder
|
||||
.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.mutableMakerName(component), component.name());
|
||||
} else {
|
||||
codeBlockBuilder
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||
.endControlFlow();
|
||||
}
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
.addJavadoc("Add to the internally allocated {@code HashMap} for {@code $L}\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
@@ -870,25 +872,19 @@ class InternalRecordBuilderProcessor {
|
||||
For a single list or set record component, add methods similar to:
|
||||
|
||||
public T addP(I i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.list = __ensureListMutable(list);
|
||||
this.p.add(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.list = __ensureListMutable(list);
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.list = __ensureListMutable(list);
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
@@ -904,10 +900,17 @@ class InternalRecordBuilderProcessor {
|
||||
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
|
||||
parameter = ParameterizedTypeName.get(parameterClass, WildcardTypeName.subtypeOf(meta.typeArguments().get(0)));
|
||||
}
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||
.endControlFlow()
|
||||
var codeBlockBuilder = CodeBlock.builder();
|
||||
if (collectionBuilderUtils.isImmutableCollection(component)) {
|
||||
codeBlockBuilder
|
||||
.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.mutableMakerName(component), component.name());
|
||||
} else {
|
||||
codeBlockBuilder
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||
.endControlFlow();
|
||||
}
|
||||
codeBlockBuilder
|
||||
.add(addClockBlock.build())
|
||||
.addStatement("return this");
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
@@ -956,8 +959,8 @@ class InternalRecordBuilderProcessor {
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD_FOR_SETTER);
|
||||
var parameterSpecBuilder = collectionMetaData.map(meta -> {
|
||||
CodeBlock.Builder codeSpec = CodeBlock.builder();
|
||||
codeSpec.addStatement("this.$L = ($L != null) ? new $T<>($L) : null", component.name(), component.name(), meta.singleItemCollectionClass(), component.name());
|
||||
methodSpec.addJavadoc("Re-create the internally allocated {@code $L} for {@code $L} by copying the argument\n", meta.singleItemCollectionClass().getSimpleName(), component.name())
|
||||
codeSpec.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.shimName(component), component.name());
|
||||
methodSpec.addJavadoc("Re-create the internally allocated {@code $T} for {@code $L} by copying the argument\n", component.typeName(), component.name())
|
||||
.addCode(codeSpec.build());
|
||||
return ParameterSpec.builder(meta.wildType(), component.name());
|
||||
}).orElseGet(() -> {
|
||||
@@ -991,12 +994,21 @@ class InternalRecordBuilderProcessor {
|
||||
|
||||
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());
|
||||
.addStatement(getOptionalStatement(type), component.name(), type.typeName(), component.name());
|
||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
|
||||
builder.addMethod(methodSpec.build());
|
||||
}
|
||||
|
||||
private String getOptionalStatement(OptionalType type) {
|
||||
|
||||
if(type.isOptional()) {
|
||||
return "this.$L = $T.ofNullable($L)";
|
||||
}
|
||||
|
||||
return "this.$L = $T.of($L)";
|
||||
}
|
||||
|
||||
private List<TypeVariableName> typeVariablesWithReturn() {
|
||||
var variables = new ArrayList<TypeVariableName>();
|
||||
variables.add(rType);
|
||||
|
||||
@@ -19,6 +19,7 @@ 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;
|
||||
|
||||
@@ -58,4 +59,8 @@ public record OptionalType(TypeName typeName, TypeName valueType) {
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean isOptional() {
|
||||
return typeName.equals(optionalType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>33</version>
|
||||
<version>34</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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 io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
addSingleItemCollectionBuilders = true,
|
||||
useImmutableCollections = true,
|
||||
mutableListClassName = "PersonalizedMutableList"
|
||||
)
|
||||
public record CollectionCopying<T>(List<String> list, Set<T> set, Map<Instant, T> map, Collection<T> collection,
|
||||
int count) implements CollectionCopyingBuilder.With<T> {
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -24,4 +26,4 @@ import java.util.OptionalLong;
|
||||
|
||||
@RecordBuilder.Options(emptyDefaultForOptional = true, addConcreteSettersForOptional = true)
|
||||
@RecordBuilder
|
||||
public record RecordWithOptional(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||
public record RecordWithOptional(@NotNull Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestCollections {
|
||||
@Test
|
||||
void testRecordBuilderOptionsCopied() {
|
||||
try {
|
||||
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", List.class));
|
||||
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", Collection.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
Assertions.fail(e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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 org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
public class TestImmutableCollections {
|
||||
@Test
|
||||
public void testImmutableListNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.addList("a")
|
||||
.addList("b")
|
||||
.addList("c")
|
||||
.build();
|
||||
Assertions.assertEquals(item.list(), List.of("a", "b", "c"));
|
||||
|
||||
var oldList = item.list();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldList, copy.list());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldList, otherCopy.list());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmutableSetNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.addSet(Arrays.asList("1", "2", "3"))
|
||||
.build();
|
||||
Assertions.assertEquals(item.set(), Set.of("1", "2", "3"));
|
||||
|
||||
var oldSet = item.set();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldSet, copy.set());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldSet, otherCopy.set());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmutableCollectionNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.collection(List.of("foo", "bar", "baz"))
|
||||
.build();
|
||||
Assertions.assertEquals(item.collection(), List.of("foo", "bar", "baz"));
|
||||
|
||||
var oldCollection = item.collection();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldCollection, copy.collection());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldCollection, otherCopy.collection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmutableMapNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.addMap(Instant.MAX, "future")
|
||||
.addMap(Instant.MIN, "before")
|
||||
.build();
|
||||
Assertions.assertEquals(item.map(), Map.of(Instant.MAX, "future", Instant.MIN, "before"));
|
||||
|
||||
var oldMap = item.map();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldMap, copy.map());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldMap, otherCopy.map());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSourceListNotModified() {
|
||||
var item = new CollectionCopying<>(new ArrayList<>(), null, null, null, 0);
|
||||
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||
.addList("a")
|
||||
.build();
|
||||
|
||||
Assertions.assertEquals(modifiedItem.list(), List.of("a"));
|
||||
Assertions.assertTrue(item.list().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSourceSetNotModified() {
|
||||
var item = new CollectionCopying<>(null, new HashSet<>(), null, null, 0);
|
||||
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||
.addSet("a")
|
||||
.build();
|
||||
|
||||
Assertions.assertEquals(modifiedItem.set(), Set.of("a"));
|
||||
Assertions.assertTrue(item.set().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSourceMapNotModified() {
|
||||
var item = new CollectionCopying<>(null, null, new HashMap<>(), null, 0);
|
||||
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||
.addMap(Instant.MIN, "a")
|
||||
.build();
|
||||
|
||||
Assertions.assertEquals(modifiedItem.map(), Map.of(Instant.MIN, "a"));
|
||||
Assertions.assertTrue(item.map().isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class TestOptional {
|
||||
@Test
|
||||
void testDefaultEmpty() {
|
||||
@@ -65,4 +65,19 @@ class TestOptional {
|
||||
Assertions.assertEquals(OptionalLong.of(424242L), record.l());
|
||||
Assertions.assertEquals(OptionalDouble.of(42.42), record.d());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAcceptNullForOptionalRawSetter() {
|
||||
// given
|
||||
String value = null;
|
||||
|
||||
// when
|
||||
var record = RecordWithOptionalBuilder.builder()
|
||||
.value(value)
|
||||
.build();
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(Optional.empty(), record.value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>33</version>
|
||||
<version>34</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user