Compare commits
4 Commits
record-bui
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6781824f08 | ||
|
|
2c34ffd4ca | ||
|
|
c6cf23956f | ||
|
|
1b22341b58 |
11
pom.xml
11
pom.xml
@@ -22,7 +22,7 @@
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>36</version>
|
||||
<version>38-SNAPSHOT</version>
|
||||
|
||||
<modules>
|
||||
<module>record-builder-core</module>
|
||||
@@ -58,6 +58,7 @@
|
||||
|
||||
<javapoet-version>1.12.1</javapoet-version>
|
||||
<junit-jupiter-version>5.5.2</junit-jupiter-version>
|
||||
<assertj-core.version>3.24.2</assertj-core.version>
|
||||
<asm-version>7.2</asm-version>
|
||||
<validation-api-version>2.0.1.Final</validation-api-version>
|
||||
<hibernate-validator-version>6.0.20.Final</hibernate-validator-version>
|
||||
@@ -98,7 +99,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-36</tag>
|
||||
<tag>record-builder-1.16</tag>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
@@ -151,6 +152,12 @@
|
||||
<version>${junit-jupiter-version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>36</version>
|
||||
<version>38-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -183,9 +183,27 @@ public @interface RecordBuilder {
|
||||
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components of these
|
||||
* types are passed through an added shim method that uses the corresponding immutable collection (e.g.
|
||||
* {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
|
||||
*
|
||||
* @see #useUnmodifiableCollections()
|
||||
*/
|
||||
boolean useImmutableCollections() default false;
|
||||
|
||||
/**
|
||||
* Adds special handling for record components of type: {@link java.util.List}, {@link java.util.Set},
|
||||
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components of these
|
||||
* types are passed through an added shim method that uses the corresponding unmodifiable collection (e.g.
|
||||
* {@code Collections.unmodifiableList(o)}) or an empty immutable collection if the component is {@code null}.
|
||||
*
|
||||
* <p>
|
||||
* For backward compatibility, when {@link #useImmutableCollections()} returns {@code true}, this property is
|
||||
* ignored.
|
||||
*
|
||||
* @see #useImmutableCollections()
|
||||
*
|
||||
* @since 37
|
||||
*/
|
||||
boolean useUnmodifiableCollections() default false;
|
||||
|
||||
/**
|
||||
* When enabled, collection types ({@code List}, {@code Set} and {@code Map}) are handled specially. The setters
|
||||
* for these types now create an internal collection and items are added to that collection. Additionally,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>36</version>
|
||||
<version>38-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBu
|
||||
|
||||
class CollectionBuilderUtils {
|
||||
private final boolean useImmutableCollections;
|
||||
private final boolean useUnmodifiableCollections;
|
||||
private final boolean addSingleItemCollectionBuilders;
|
||||
private final boolean addClassRetainedGenerated;
|
||||
private final String listShimName;
|
||||
@@ -50,10 +51,12 @@ class CollectionBuilderUtils {
|
||||
private static final Class<?> mapType = Map.class;
|
||||
private static final Class<?> setType = Set.class;
|
||||
private static final Class<?> collectionType = Collection.class;
|
||||
private static final Class<?> collectionsType = Collections.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 TypeName collectionsTypeName = TypeName.get(collectionsType);
|
||||
|
||||
private static final TypeVariableName tType = TypeVariableName.get("T");
|
||||
private static final TypeVariableName kType = TypeVariableName.get("K");
|
||||
@@ -79,6 +82,7 @@ class CollectionBuilderUtils {
|
||||
|
||||
CollectionBuilderUtils(List<RecordClassType> recordComponents, RecordBuilder.Options metaData) {
|
||||
useImmutableCollections = metaData.useImmutableCollections();
|
||||
useUnmodifiableCollections = !useImmutableCollections && metaData.useUnmodifiableCollections();
|
||||
addSingleItemCollectionBuilders = metaData.addSingleItemCollectionBuilders();
|
||||
addClassRetainedGenerated = metaData.addClassRetainedGenerated();
|
||||
|
||||
@@ -154,8 +158,8 @@ class CollectionBuilderUtils {
|
||||
}
|
||||
|
||||
boolean isImmutableCollection(RecordClassType component) {
|
||||
return useImmutableCollections && (isList(component) || isMap(component) || isSet(component)
|
||||
|| component.rawTypeName().equals(collectionTypeName));
|
||||
return (useImmutableCollections || useUnmodifiableCollections)
|
||||
&& (isList(component) || isMap(component) || isSet(component) || isCollection(component));
|
||||
}
|
||||
|
||||
boolean isList(RecordClassType component) {
|
||||
@@ -170,8 +174,12 @@ class CollectionBuilderUtils {
|
||||
return component.rawTypeName().equals(setTypeName);
|
||||
}
|
||||
|
||||
private boolean isCollection(RecordClassType component) {
|
||||
return component.rawTypeName().equals(collectionTypeName);
|
||||
}
|
||||
|
||||
void addShimCall(CodeBlock.Builder builder, RecordClassType component) {
|
||||
if (useImmutableCollections) {
|
||||
if (useImmutableCollections || useUnmodifiableCollections) {
|
||||
if (isList(component)) {
|
||||
needsListShim = true;
|
||||
needsListMutableMaker = true;
|
||||
@@ -184,7 +192,7 @@ class CollectionBuilderUtils {
|
||||
needsSetShim = true;
|
||||
needsSetMutableMaker = true;
|
||||
builder.add("$L($L)", setShimName, component.name());
|
||||
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||
} else if (isCollection(component)) {
|
||||
needsCollectionShim = true;
|
||||
builder.add("$L($L)", collectionShimName, component.name());
|
||||
} else {
|
||||
@@ -202,7 +210,7 @@ class CollectionBuilderUtils {
|
||||
return mapShimName;
|
||||
} else if (isSet(component)) {
|
||||
return setShimName;
|
||||
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||
} else if (isCollection(component)) {
|
||||
return collectionShimName;
|
||||
} else {
|
||||
throw new IllegalArgumentException(component + " is not a supported collection type");
|
||||
@@ -222,7 +230,7 @@ class CollectionBuilderUtils {
|
||||
}
|
||||
|
||||
void addShims(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
if (!useImmutableCollections && !useUnmodifiableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -242,7 +250,7 @@ class CollectionBuilderUtils {
|
||||
}
|
||||
|
||||
void addMutableMakers(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
if (!useImmutableCollections && !useUnmodifiableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -298,7 +306,8 @@ class CollectionBuilderUtils {
|
||||
|
||||
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);
|
||||
var code = buildShimMethodBody(mainType, parameterizedType);
|
||||
|
||||
TypeName[] wildCardTypeArguments = parameterizedType.typeArguments.stream().map(WildcardTypeName::subtypeOf)
|
||||
.toList().toArray(new TypeName[0]);
|
||||
var extendedParameterizedType = ParameterizedTypeName.get(ClassName.get(abstractType), wildCardTypeArguments);
|
||||
@@ -307,6 +316,29 @@ class CollectionBuilderUtils {
|
||||
.returns(parameterizedType).addParameter(extendedParameterizedType, "o").addStatement(code).build();
|
||||
}
|
||||
|
||||
private CodeBlock buildShimMethodBody(TypeName mainType, ParameterizedTypeName parameterizedType) {
|
||||
if (!useUnmodifiableCollections) {
|
||||
return CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
|
||||
}
|
||||
|
||||
if (mainType.equals(listTypeName)) {
|
||||
return CodeBlock.of("return (o != null) ? $T.<$T>unmodifiableList(($T) o) : $T.<$T>emptyList()",
|
||||
collectionsTypeName, tType, parameterizedType, collectionsTypeName, tType);
|
||||
}
|
||||
|
||||
if (mainType.equals(setTypeName)) {
|
||||
return CodeBlock.of("return (o != null) ? $T.<$T>unmodifiableSet(($T) o) : $T.<$T>emptySet()",
|
||||
collectionsTypeName, tType, parameterizedType, collectionsTypeName, tType);
|
||||
}
|
||||
|
||||
if (mainType.equals(mapTypeName)) {
|
||||
return CodeBlock.of("return (o != null) ? $T.<$T, $T>unmodifiableMap(($T) o) : $T.<$T, $T>emptyMap()",
|
||||
collectionsTypeName, kType, vType, parameterizedType, collectionsTypeName, kType, vType);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Cannot build shim method for" + mainType);
|
||||
}
|
||||
|
||||
private MethodSpec buildMutableMakerMethod(String name, String mutableCollectionType,
|
||||
ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
var nullCase = CodeBlock.of("if (o == null) return new $L<>()", mutableCollectionType);
|
||||
@@ -340,12 +372,25 @@ class CollectionBuilderUtils {
|
||||
}
|
||||
|
||||
private MethodSpec buildCollectionsShimMethod() {
|
||||
var code = CodeBlock.builder().add("if (o instanceof Set) {\n").indent()
|
||||
.addStatement("return $T.copyOf(o)", setTypeName).unindent().addStatement("}")
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listTypeName, listTypeName).build();
|
||||
var code = buildCollectionShimMethodBody();
|
||||
return MethodSpec.methodBuilder(collectionShimName).addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC).addTypeVariable(tType)
|
||||
.returns(parameterizedCollectionType).addParameter(parameterizedCollectionType, "o").addCode(code)
|
||||
.build();
|
||||
}
|
||||
|
||||
private CodeBlock buildCollectionShimMethodBody() {
|
||||
if (!useUnmodifiableCollections) {
|
||||
return CodeBlock.builder().add("if (o instanceof Set) {\n").indent()
|
||||
.addStatement("return $T.copyOf(o)", setTypeName).unindent().addStatement("}")
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listTypeName, listTypeName).build();
|
||||
}
|
||||
|
||||
return CodeBlock.builder().beginControlFlow("if (o instanceof $T)", listType)
|
||||
.addStatement("return $T.<$T>unmodifiableList(($T) o)", collectionsTypeName, tType,
|
||||
parameterizedListType)
|
||||
.endControlFlow().beginControlFlow("if (o instanceof $T)", setType)
|
||||
.addStatement("return $T.<$T>unmodifiableSet(($T) o)", collectionsTypeName, tType, parameterizedSetType)
|
||||
.endControlFlow().addStatement("return $T.<$T>emptyList()", collectionsTypeName, tType).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,9 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
||||
"RecordInterface only valid for interfaces.", element);
|
||||
return;
|
||||
}
|
||||
|
||||
validateMetaData(metaData, element);
|
||||
|
||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData,
|
||||
packageName, fromTemplate);
|
||||
if (!internalProcessor.isValid()) {
|
||||
@@ -184,11 +187,25 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
||||
record);
|
||||
return;
|
||||
}
|
||||
|
||||
validateMetaData(metaData, record);
|
||||
|
||||
var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName);
|
||||
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(),
|
||||
internalProcessor.builderType(), metaData);
|
||||
}
|
||||
|
||||
private void validateMetaData(RecordBuilder.Options metaData, Element record) {
|
||||
var useImmutableCollections = metaData.useImmutableCollections();
|
||||
var useUnmodifiableCollections = metaData.useUnmodifiableCollections();
|
||||
|
||||
if (useImmutableCollections && useUnmodifiableCollections) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING,
|
||||
"Options.useUnmodifiableCollections property is ignored as Options.useImmutableCollections is set to true",
|
||||
record);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType,
|
||||
TypeSpec builderType, RecordBuilder.Options metaData) {
|
||||
// produces the Java file
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>36</version>
|
||||
<version>38-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -64,6 +64,12 @@
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019 The original author or authors
|
||||
*
|
||||
* 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.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(useUnmodifiableCollections = true)
|
||||
record UnmodifiableCollectionsRecord(List<Integer> aList, Set<String> orderedSet, Map<String, Integer> orderedMap,
|
||||
Collection<String> aCollection) {
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2019 The original author or authors
|
||||
*
|
||||
* 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.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class TestUnmodifiableCollectionsBuilder {
|
||||
|
||||
@Test
|
||||
void shouldWrapCollectionsWithUnmodifiableView() {
|
||||
// given
|
||||
var list = new ArrayList<Integer>();
|
||||
list.add(2);
|
||||
list.add(1);
|
||||
list.add(0);
|
||||
|
||||
var orderedSet = new LinkedHashSet<String>();
|
||||
orderedSet.add("C");
|
||||
orderedSet.add("B");
|
||||
orderedSet.add("A");
|
||||
|
||||
var orderedMap = new LinkedHashMap<String, Integer>();
|
||||
orderedMap.put("C", 2);
|
||||
orderedMap.put("B", 1);
|
||||
orderedMap.put("A", 0);
|
||||
|
||||
var collection = new HashSet<String>();
|
||||
collection.add("C");
|
||||
collection.add("B");
|
||||
collection.add("A");
|
||||
|
||||
// when
|
||||
var record = UnmodifiableCollectionsRecordBuilder.builder().aList(list).orderedSet(orderedSet)
|
||||
.orderedMap(orderedMap).aCollection(collection).build();
|
||||
|
||||
// then
|
||||
assertAll(() -> assertThrows(UnsupportedOperationException.class, () -> record.aList().add(9)),
|
||||
() -> assertThat(record.aList()).containsExactly(2, 1, 0),
|
||||
() -> assertThrows(UnsupportedOperationException.class, () -> record.orderedSet().add("newElement")),
|
||||
() -> assertThat(record.orderedSet()).containsExactly("C", "B", "A"),
|
||||
() -> assertThrows(UnsupportedOperationException.class, () -> record.orderedMap().put("newElement", 9)),
|
||||
() -> assertThat(record.orderedMap()).containsExactly(entry("C", 2), entry("B", 1), entry("A", 0)),
|
||||
() -> assertThrows(UnsupportedOperationException.class, () -> record.aCollection().add("newElement")),
|
||||
() -> assertThat(record.aCollection()).containsExactlyInAnyOrder("C", "B", "A"));
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>36</version>
|
||||
<version>38-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user