Compare commits

...

4 Commits

Author SHA1 Message Date
Jordan Zimmerman
6781824f08 [maven-release-plugin] prepare for next development iteration 2023-07-03 09:57:59 +01:00
Jordan Zimmerman
2c34ffd4ca [maven-release-plugin] prepare release record-builder-37 2023-07-03 09:57:53 +01:00
Paweł Łabaj
c6cf23956f Fixes Randgalt/record-builder/#153 (#154) 2023-07-03 09:46:23 +01:00
Jordan Zimmerman
1b22341b58 [maven-release-plugin] prepare for next development iteration 2023-03-29 17:50:15 +01:00
10 changed files with 211 additions and 17 deletions

11
pom.xml
View File

@@ -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>

View 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>

View File

@@ -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,

View 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>

View File

@@ -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();
}
}

View File

@@ -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

View 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>

View File

@@ -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) {
}

View File

@@ -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"));
}
}

View 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>