diff --git a/.github/semantic.yml b/.github/semantic.yml
new file mode 100644
index 0000000..052d4d5
--- /dev/null
+++ b/.github/semantic.yml
@@ -0,0 +1 @@
+titleAndCommits: true
\ No newline at end of file
diff --git a/README.md b/README.md
index ab5e3c8..ecc79cf 100644
--- a/README.md
+++ b/README.md
@@ -2,27 +2,31 @@
Java record newContext generator
-[![Maven Central version](https://img.shields.io/maven-central/v/pl.com.labaj/auto-record)](https://mvnrepository.com/artifact/pl.com.labaj/auto-record)
-[![javadoc](https://javadoc.io/badge2/pl.com.labaj/auto-record/javadoc.svg)](https://javadoc.io/doc/pl.com.labaj/auto-record)
+[![Maven Central version](https://img.shields.io/maven-central/v/pl.com.labaj.autorecord/auto-record)](https://mvnrepository.com/artifact/pl.com.labaj.autorecord/auto-record)
+[![javadoc](https://javadoc.io/badge2/pl.com.labaj.autorecord/auto-record-project/javadoc.svg)](https://javadoc.io/doc/pl.com.labaj.autorecord/auto-record-project)
[![CI Verify Status](https://github.com/pawellabaj/auto-record/actions/workflows/verify.yml/badge.svg?branch=main)](https://github.com/pawellabaj/auto-record/actions/workflows/verify.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=pl.com.labaj%3Aauto-record-project&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=pl.com.labaj%3Aauto-record-project)
[![Sonatype Lift Status](https://lift.sonatype.com/api/badge/github.com/pawellabaj/auto-record)](https://lift.sonatype.com/results/github.com/pawellabaj/auto-record)
-[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central#pl.com.labaj:auto-record)
+[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central#pl.com.labaj.autorecord:auto-record)
+[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7700/badge)](https://bestpractices.coreinfrastructure.org/projects/7700)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](.github/CODE_OF_CONDUCT.md)
## What is AutoRecord
-AutoRecord is a code generator that helps you easily generate Java records.
+AutoRecord is a code generator that helps you easily generate Java records.
It provides an easy way to avoid writing repetitive boilerplate code. It generates the code with features such as:
+
* [nullability](https://github.com/pawellabaj/auto-record/wiki/Nullability) checking
-* [builders](https://github.com/pawellabaj/auto-record/wiki/Record-Builder) - incorporating [Randgalt/record-builder](https://github.com/Randgalt/record-builder) library
+* [builders](https://github.com/pawellabaj/auto-record/wiki/Record-Builder) -
+ incorporating [Randgalt/record-builder](https://github.com/Randgalt/record-builder) library
* [memoization](https://github.com/pawellabaj/auto-record/wiki/Memoization)
* [ignoring fields](https://github.com/pawellabaj/auto-record/wiki/Ignored-components) in `hashCode()` and `equals()` methods
-* generated _common_ methods if the record has an [array component](https://github.com/pawellabaj/auto-record/wiki/Array-components)
+* generated _common_ methods if the record has an [array recordComponent](https://github.com/pawellabaj/auto-record/wiki/Array-components)
* exclusion from [JaCoCo test coverage](https://github.com/pawellabaj/auto-record/wiki/JaCoCo-exclusion) analysis
AutoRecord allows users to customize record generation process by:
+
* specifying [options](https://github.com/pawellabaj/auto-record/wiki/Single-record-options)
* using [custom annotation](https://github.com/pawellabaj/auto-record/wiki/Custom-annotations) templates
* implementing [custom extensions](https://github.com/pawellabaj/auto-record/wiki/Extensions)
@@ -30,12 +34,14 @@ AutoRecord allows users to customize record generation process by:
## Why AutoRecord was created
Google [AutoValue](https://github.com/google/auto) has long been used as a way to work with _Value Classes_ in an easy way.
-However, when Java [records](https://docs.oracle.com/en/java/javase/17/language/records.html) were introduced, they lacked some features that AutoValue had, such as nullability checking, builders, and memoization.
+However, when Java [records](https://docs.oracle.com/en/java/javase/17/language/records.html) were introduced, they lacked some features that AutoValue had,
+such as nullability checking, builders, and memoization.
This is why AutoRecord was created.
## How to use AutoRecord
-To use AutoRecord, simply annotate your interface with [@AutoRecord](https://github.com/pawellabaj/auto-record/blob/main/modules/auto-record/src/main/java/pl/com/labaj/autorecord/AutoRecord.java) annotation:
+To use AutoRecord, simply annotate your interface
+with [@AutoRecord](https://github.com/pawellabaj/auto-record/blob/main/modules/auto-record/src/main/java/pl/com/labaj/autorecord/AutoRecord.java) annotation:
```java
import pl.com.labaj.autorecord.AutoRecord;
@@ -43,6 +49,7 @@ import pl.com.labaj.autorecord.AutoRecord;
@AutoRecord
interface Person {
String name();
+
int age();
}
```
@@ -51,7 +58,9 @@ AutoRecord will then generate a Java record class that implements your interface
```java
import pl.com.labaj.autorecord.GeneratedWithAutoRecord;
+
import javax.annotation.processing.Generated;
+
import static java.util.Objects.requireNonNull;
@Generated("pl.com.labaj.autorecord.AutoRecord")
@@ -71,6 +80,17 @@ record PersonRecord(String name, int age) implements Person {
For more information on how to use AutoRecord and all its features, please visit the project's [Wiki](https://github.com/pawellabaj/auto-record/wiki).
+## Projects maintained in this repository
+
+| Project | Description |
+|:---------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------|
+| [Auto Record API](https://github.com/pawellabaj/auto-record/tree/main/modules/auto-record-api) | Annotations to mark interfaces to be processed |
+| [Auto Record](https://github.com/pawellabaj/auto-record/tree/main/modules/auto-record) | Annotation processor to generate records |
+| [Auto Record Utilities](https://github.com/pawellabaj/auto-record/tree/main/modules/auto-record-utils) | Utility classes used by generated records |
+| [ARICE API](https://github.com/pawellabaj/auto-record/tree/main/arice/api) | Annotations to mark interface methods, used by ARICE |
+| [ARICE (Auto Record Immutable Collections Extension)](https://github.com/pawellabaj/auto-record/tree/main/arice/extension) | The extension for customizing a record generation process with support for immutable collections |
+| [ARICE Utilities](https://github.com/pawellabaj/auto-record/tree/main/arice/utils) | Utility classes used by ARICE |
+
## Getting started
### Maven
@@ -78,10 +98,12 @@ For more information on how to use AutoRecord and all its features, please visit
Add the following dependency to your `pom.xml` file:
```xml
+
- pl.com.labaj
+ pl.com.labaj.autorecord
auto-record
${auto-record.version}
+ provided
```
@@ -91,7 +113,7 @@ Declare the following dependency in your `build.gradle` script:
```groovy
dependencies {
- annotationProcessor 'pl.com.labaj:auto-record:${autoRecordVersion}'
+ annotationProcessor 'pl.com.labaj.autorecord:auto-record:${autoRecordVersion}'
}
```
@@ -100,7 +122,9 @@ dependencies {
Depending on your IDE you are likely to need to enable Annotation Processing in your IDE settings.
## Contributing
-We welcome contributions from all developers! If you would like to contribute to AutoRecord, please refer to the [Contributing guide](.github/CONTRIBUTING.md) for more information on how to get started.
+
+We welcome contributions from all developers! If you would like to contribute to AutoRecord, please refer to the [Contributing guide](.github/CONTRIBUTING.md)
+for more information on how to get started.
## License
diff --git a/arice/api/README.md b/arice/api/README.md
new file mode 100644
index 0000000..8e6b72e
--- /dev/null
+++ b/arice/api/README.md
@@ -0,0 +1,34 @@
+# ARICE API
+
+Annotations to mark methods in an interface, used during annotation processing with ARICE usage.
+
+Please, see [WIKI](https://github.com/pawellabaj/auto-record/wiki/ARICE) for information.
+
+## Usage
+
+If you use annotation processing in your project, the library will be provided as a dependency of [ARICE](https://github.com/pawellabaj/auto-record/tree/main/arice/extension).
+
+If you just want to mark your sources (ieg. annotations are being processed in other project or build step),
+use `arice-api` directly.
+
+### Maven
+
+Add the following dependency to your `pom.xml` file:
+
+```xml
+
+
+ pl.com.labaj.autorecord
+ arice-api
+ ${arice.version}
+
+```
+
+### Gradle
+
+Declare the following dependency in your `build.gradle` script:
+
+```groovy
+dependencies {
+ annotationProcessor 'pl.com.labaj.autorecord:arice-api:${ariceVersion}'
+}
\ No newline at end of file
diff --git a/arice/api/pom.xml b/arice/api/pom.xml
new file mode 100644
index 0000000..e9c2a12
--- /dev/null
+++ b/arice/api/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+ pl.com.labaj.autorecord
+ arice-project
+ 1.0.0-SNAPSHOT
+ ../project/pom.xml
+
+
+ ARICE API
+
+ arice-api
+
\ No newline at end of file
diff --git a/arice/api/src/main/java/pl/com/labaj/autorecord/extension/arice/Mutable.java b/arice/api/src/main/java/pl/com/labaj/autorecord/extension/arice/Mutable.java
new file mode 100644
index 0000000..1668048
--- /dev/null
+++ b/arice/api/src/main/java/pl/com/labaj/autorecord/extension/arice/Mutable.java
@@ -0,0 +1,41 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import org.apiguardian.api.API;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static org.apiguardian.api.API.Status.STABLE;
+
+/**
+ * Annotates methods in @AutoRecord annotated interface for which the corresponding components in generated record are not relevant
+ * for copying to immutable collection.
+ *
+ * @see ARICE Wiki
+ */
+@Retention(SOURCE)
+@Target({METHOD, TYPE_PARAMETER, PARAMETER})
+@Inherited
+@API(status = STABLE)
+public @interface Mutable {}
diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/package-info.java b/arice/api/src/main/java/pl/com/labaj/autorecord/extension/arice/package-info.java
similarity index 62%
rename from modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/package-info.java
rename to arice/api/src/main/java/pl/com/labaj/autorecord/extension/arice/package-info.java
index 1a4c9ba..49e9159 100644
--- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/package-info.java
+++ b/arice/api/src/main/java/pl/com/labaj/autorecord/extension/arice/package-info.java
@@ -14,19 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
/**
- * Provides annotations and interfaces needed to process extensions.
- *
- *
{@link pl.com.labaj.autorecord.extension.AutoRecordExtension} interface has to be implemented by all extensions.
+ * Provides annotations used to annotate interfaces or methods that are used by ImmutableCollectionsExtension.
*
- * @see Extensions Wiki
- * @since 2.1.0
+ * @see Wiki
*/
-
-@API(status = STABLE, since = "2.1.0")
-package pl.com.labaj.autorecord.extension;
-
-import org.apiguardian.api.API;
-
-import static org.apiguardian.api.API.Status.STABLE;
\ No newline at end of file
+package pl.com.labaj.autorecord.extension.arice;
\ No newline at end of file
diff --git a/arice/extension/.skip-annotation-processing b/arice/extension/.skip-annotation-processing
new file mode 100644
index 0000000..e69de29
diff --git a/arice/extension/README.md b/arice/extension/README.md
new file mode 100644
index 0000000..f14f909
--- /dev/null
+++ b/arice/extension/README.md
@@ -0,0 +1,43 @@
+# ARICE (Auto Record Immutable Collections Extension)
+
+An implementations of the link AutoRecord extension for customizing a record generation process with support for immutable collections.
+
+## Documentation
+
+For more information on how to use ARICE and all its features, please visit the project's [Wiki](https://github.com/pawellabaj/auto-record/wiki/ARICE).
+
+## Getting started
+
+### Maven
+
+Add the following dependency to your `pom.xml` file:
+
+```xml
+
+ pl.com.labaj.autorecord
+ auto-record
+ ${auto-record.version}
+ provided
+
+
+ pl.com.labaj.autorecord
+ arice
+ ${arice.version}
+ provided
+
+```
+
+### Gradle
+
+Declare the following dependency in your `build.gradle` script:
+
+```groovy
+dependencies {
+ annotationProcessor 'pl.com.labaj.autorecord:auto-record:${autoRecordVersion}',
+ implementation 'pl.com.labaj.autorecord:arice:${ariceVersion}'
+}
+```
+
+### IDE
+
+Depending on your IDE you are likely to need to enable Annotation Processing in your IDE settings.
diff --git a/arice/extension/pom.xml b/arice/extension/pom.xml
new file mode 100644
index 0000000..3656d24
--- /dev/null
+++ b/arice/extension/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+ pl.com.labaj.autorecord
+ arice-project
+ 1.0.0-SNAPSHOT
+ ../project/pom.xml
+
+
+ ARICE
+ Auto Record Immutable Collections Extension
+ arice-extension
+
+
+ ${project.basedir}/../../.build/lic-header.txt
+
+
+
+
+ pl.com.labaj.autorecord
+ auto-record
+
+
+ pl.com.labaj.autorecord
+ arice-api
+
+
+ pl.com.labaj.autorecord
+ arice-utils
+
+
+
\ No newline at end of file
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ARICEUtilities.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ARICEUtilities.java
new file mode 100644
index 0000000..81a8550
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ARICEUtilities.java
@@ -0,0 +1,54 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import org.apiguardian.api.API;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static org.apiguardian.api.API.Status.MAINTAINED;
+
+/**
+ * This annotation is used to mark the generated record, so the ARICE (AutoRecord Immutable Collections Extension)
+ * knows what utility class it should generate.
+ *
+ * @since 3.0.0
+ */
+@Retention(SOURCE)
+@Target(TYPE)
+@Inherited
+@API(status = MAINTAINED)
+public @interface ARICEUtilities {
+ /**
+ * Specifies the fully qualified name of the utility class that provides additional methods and functionality
+ * to support the ARICE extension.
+ *
+ * @return the fully qualified name of the utility class.
+ */
+ String className();
+
+ /**
+ * Specifies an array of fully qualified names of immutable types. These types will be used in conjunction with the ARICE extension
+ *
+ * @return an array of fully qualified names of immutable types.
+ */
+ String[] immutableTypes() default {};
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ARICEUtilitiesProcessor.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ARICEUtilitiesProcessor.java
new file mode 100644
index 0000000..1cb9872
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ARICEUtilitiesProcessor.java
@@ -0,0 +1,116 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import org.apiguardian.api.API;
+import pl.com.labaj.autorecord.processor.context.MessagerLogger;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.apiguardian.api.API.Status.MAINTAINED;
+
+/**
+ * {@inheritDoc}
+ */
+@API(status = MAINTAINED)
+@SupportedAnnotationTypes("pl.com.labaj.autorecord.extension.arice.ARICEUtilities")
+public class ARICEUtilitiesProcessor extends AbstractProcessor {
+ private static final ExtensionContext extContext = new ExtensionContext();
+
+ private Messager messager;
+ private MethodsClassGenerator methodsClassGenerator;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+
+ extContext.init(processingEnv);
+
+ messager = processingEnv.getMessager();
+ var filer = processingEnv.getFiler();
+
+ methodsClassGenerator = new MethodsClassGenerator(extContext, filer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ annotations.forEach(annotation -> processAnnotation(roundEnv, annotation));
+
+ return false;
+ }
+
+ private void processAnnotation(RoundEnvironment roundEnv, TypeElement annotation) {
+ var logger = new MessagerLogger(messager, annotation);
+
+ roundEnv.getElementsAnnotatedWith(annotation).stream()
+ .map(element -> element.getAnnotation(ARICEUtilities.class))
+ .map(NameTypes::toNameTypes)
+ .distinct()
+ .forEach(nameTypes -> methodsClassGenerator.generate(nameTypes.className, nameTypes.immutableTypes, logger));
+ }
+
+ record NameTypes(String className, String[] immutableTypes) {
+ private static NameTypes toNameTypes(ARICEUtilities a) {
+ return new NameTypes(a.className(), a.immutableTypes());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NameTypes nameTypes)) return false;
+ return className.equals(nameTypes.className) && Arrays.equals(immutableTypes, nameTypes.immutableTypes);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(className);
+ result = 31 * result + Arrays.hashCode(immutableTypes);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "NameTypes{" +
+ "className='" + className + '\'' +
+ ", immutableTypes=" + Arrays.toString(immutableTypes) +
+ '}';
+ }
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/CopyMethodsGenerator.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/CopyMethodsGenerator.java
new file mode 100644
index 0000000..841577f
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/CopyMethodsGenerator.java
@@ -0,0 +1,201 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeVariableName;
+import pl.com.labaj.autorecord.context.Logger;
+import pl.com.labaj.autorecord.context.StaticImports;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Optional;
+
+import static java.util.Comparator.reverseOrder;
+import static java.util.Objects.isNull;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toMap;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+import static org.apache.commons.lang3.StringUtils.substringBefore;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.allProcessedTypes;
+
+class CopyMethodsGenerator {
+ private static final EnumMap METHOD_GENERATORS = allProcessedTypes().stream()
+ .collect(toMap(
+ identity(),
+ CopyMethodsGenerator::builderFor,
+ (b1, b2) -> b1,
+ () -> new EnumMap<>(InterfaceType.class)
+ ));
+
+ private final ExtensionContext extContext;
+
+ private final StaticImports staticImports;
+ private final Logger logger;
+
+ CopyMethodsGenerator(ExtensionContext extContext, StaticImports staticImports, Logger logger) {
+ this.extContext = extContext;
+ this.staticImports = staticImports;
+ this.logger = logger;
+ }
+
+ List generateMethods(TypesStructure structure) {
+ return allProcessedTypes().stream()
+ .sorted(reverseOrder())
+ .map(METHOD_GENERATORS::get)
+ .map(methodGenerator -> methodGenerator.generateMethod(extContext, structure, staticImports, logger))
+ .toList();
+ }
+
+ private static MethodGenerator builderFor(InterfaceType iType) {
+ return (extContext, structure, staticImports, logger) -> {
+ var methodBuilder = getMethodBuilder(extContext, iType, logger);
+
+ immutableTypesBlock(structure, iType)
+ .ifPresent(methodBuilder::addCode);
+ subTypesBlocks(extContext, iType, structure, logger)
+ .forEach(methodBuilder::addCode);
+
+ var returnStatement = isNull(iType.factoryClassName())
+ ? CodeBlock.of("return $L", iType.argumentName())
+ : CodeBlock.of("return $T.$L($L)", iType.factoryClassName(), iType.factoryMethodName(), iType.argumentName());
+ methodBuilder.addStatement(returnStatement);
+
+ return methodBuilder.build();
+ };
+ }
+
+ private static MethodSpec.Builder getMethodBuilder(ExtensionContext extContext, InterfaceType iType, Logger logger) {
+ var builder = MethodSpec.methodBuilder("immutable")
+ .addModifiers(PUBLIC, STATIC);
+
+ if (iType.genericNames().isEmpty()) {
+ var annotation = AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "$S", "unchecked")
+ .build();
+ builder.addAnnotation(annotation);
+ }
+
+ getTypeName(extContext, iType, builder, true);
+ var trimmedTypeName = getTypeName(extContext, iType, builder, false);
+
+ var parameterSpec = ParameterSpec.builder(trimmedTypeName, iType.argumentName()).build();
+
+ builder.returns(trimmedTypeName)
+ .addParameter(parameterSpec);
+
+ return builder;
+ }
+
+ private static TypeName getTypeName(ExtensionContext extContext, InterfaceType iType, MethodSpec.Builder methodBuilder, boolean full) {
+
+ var mirrorType = extContext.getInterfaceMirrorType(iType);
+
+ if (iType.genericNames().isEmpty()) {
+ return TypeName.get(mirrorType);
+ }
+
+ var className = (ClassName) ClassName.get(mirrorType);
+ var typeVariableNames = iType.genericNames().stream()
+ .map(name -> full ? name : substringBefore(name, " "))
+ .map(TypeVariableName::get)
+ .toList();
+
+ if (full) {
+ typeVariableNames.forEach(methodBuilder::addTypeVariable);
+ }
+ return ParameterizedTypeName.get(className, typeVariableNames.toArray(TypeVariableName[]::new));
+ }
+
+ private static Optional immutableTypesBlock(TypesStructure structure, InterfaceType iType) {
+ var immutableTypeNames = structure.getClassNames(iType);
+ if (immutableTypeNames.isEmpty()) {
+ return Optional.empty();
+ }
+
+ var ifFormat = new StringBuilder("if (");
+ var i = 0;
+ for (var iterator = immutableTypeNames.iterator(); iterator.hasNext(); i++) {
+ iterator.next();
+ String name = iType.argumentName();
+
+ ifFormat.append(name).append(" instanceof $T");
+ if (!iType.genericNames().isEmpty()) {
+ var genericClause = iType.genericNames().stream()
+ .map(n -> substringBefore(n, " "))
+ .collect(joining(", ", "<", ">"));
+ ifFormat.append(genericClause);
+ }
+
+ if (iterator.hasNext()) {
+ ifFormat.append(i == 0 ? "\n$>$>|| " : "\n|| ");
+ }
+ }
+ ifFormat.append(")");
+
+ var size = immutableTypeNames.size();
+ var block = CodeBlock.builder()
+ .beginControlFlow(ifFormat.toString(), immutableTypeNames.toArray())
+ .addStatement(size > 1 ? "$<$ subTypesBlocks(ExtensionContext extContext, InterfaceType iType, TypesStructure structure, Logger logger) {
+ return iType.directSubTypes().stream()
+ .filter(structure::needsAdditionalMethod)
+ .sorted(reverseOrder())
+ .map(subPType -> subTypeBlock(extContext, subPType, iType, structure))
+ .toList();
+ }
+
+ private static CodeBlock subTypeBlock(ExtensionContext extContext, InterfaceType iType, InterfaceType parent, TypesStructure structure) {
+ var argumentName = iType.argumentName();
+
+ var parentGenericNames = parent.genericNames();
+ var genericClause = parentGenericNames.isEmpty() || !iType.checkGenericInInstanceOf()
+ ? ""
+ : parentGenericNames.stream().collect(joining(",", "<", ">"));
+ var statement = structure.needsAdditionalMethod(iType)
+ ? CodeBlock.of("return $L($L)", "immutable", argumentName)
+ : CodeBlock.of("return $T.$L($L)", iType.factoryClassName(), iType.factoryMethodName(), argumentName);
+
+ return CodeBlock.builder()
+ .beginControlFlow("if ($L instanceof $T$L $L)", parent.argumentName(), extContext.getInterfaceMirrorType(iType), genericClause, argumentName)
+ .addStatement(statement)
+ .endControlFlow()
+ .build();
+ }
+
+ @FunctionalInterface
+ private interface MethodGenerator {
+ MethodSpec generateMethod(ExtensionContext extContext,
+ TypesStructure structure,
+ StaticImports staticImports,
+ Logger logger);
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ExtensionContext.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ExtensionContext.java
new file mode 100644
index 0000000..ca276bd
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ExtensionContext.java
@@ -0,0 +1,140 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import pl.com.labaj.autorecord.context.Logger;
+import pl.com.labaj.autorecord.processor.AutoRecordProcessorException;
+
+import javax.annotation.Nullable;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static java.util.Objects.isNull;
+import static java.util.Objects.nonNull;
+import static java.util.stream.Collectors.toSet;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.allProcessedTypes;
+
+class ExtensionContext {
+ private Types existingTypeUtils;
+ private Elements elementUtils;
+ private Types typeUtils;
+
+ private final Map immutableTypes = new HashMap<>();
+ private final EnumMap interfaceTypes = new EnumMap<>(InterfaceType.class);
+
+ void init(ProcessingEnvironment processingEnv) {
+ resetIfNeeded(processingEnv);
+ allProcessedTypes().forEach(name -> interfaceTypes.computeIfAbsent(name, this::loadType));
+ }
+
+ Set getTypes(Set immutableNames) {
+ return immutableNames.stream()
+ .map(name -> immutableTypes.computeIfAbsent(name, this::loadType))
+ .filter(Objects::nonNull)
+ .collect(toSet());
+ }
+
+ TypeMirror erasure(TypeMirror type) {
+ return typeUtils.erasure(type);
+ }
+
+ boolean isSubtype(TypeMirror type, InterfaceType iType, Logger logger) {
+ var pTypeMirror = interfaceTypes.get(iType);
+ if (nonNull(pTypeMirror)) {
+ return typeUtils.isSubtype(type, pTypeMirror);
+ }
+
+ logger.warning("isSubtype fallback for :" + iType);
+
+ return typeUtils.directSupertypes(type).stream()
+ .map(Object::toString)
+ .anyMatch(s -> s.equals(iType.className()));
+ }
+
+ boolean isSubtype(TypeMirror type1, TypeMirror type2) {
+ return typeUtils.isSubtype(type1, type2);
+ }
+
+ boolean isSameType(TypeMirror type, InterfaceType iType, Logger logger) {
+ var pTypeMirror = interfaceTypes.get(iType);
+ if (nonNull(pTypeMirror)) {
+ return typeUtils.isSameType(type, pTypeMirror);
+ }
+
+ logger.warning("isSameType fallback for :" + iType);
+
+ return type.toString().equals(iType.className());
+ }
+
+ boolean isSameType(TypeMirror type1, TypeMirror type2) {
+ return typeUtils.isSameType(type1, type2);
+ }
+
+ @Nullable
+ TypeMirror getImmutableType(String name, Logger logger) {
+ var typeMirror = immutableTypes.computeIfAbsent(name, this::loadType);
+
+ if (isNull(typeMirror)) {
+ logger.debug("Cannot get type for " + name);
+ }
+
+ return typeMirror;
+ }
+
+ TypeMirror getInterfaceMirrorType(InterfaceType iType) {
+ var typeMirror = interfaceTypes.computeIfAbsent(iType, this::loadType);
+
+ if (isNull(typeMirror)) {
+ throw new AutoRecordProcessorException("Cannot get type for " + iType+ ". Please check if it's in classpath");
+ }
+
+ return typeMirror;
+ }
+
+ private void resetIfNeeded(ProcessingEnvironment processingEnv) {
+
+ if (isNull(existingTypeUtils) || existingTypeUtils != processingEnv.getTypeUtils()) {
+ elementUtils = processingEnv.getElementUtils();
+ typeUtils = processingEnv.getTypeUtils();
+ existingTypeUtils = typeUtils;
+
+ immutableTypes.clear();
+ interfaceTypes.clear();
+ }
+ }
+
+ private TypeMirror loadType(InterfaceType iType) {
+ return loadType(iType.className());
+ }
+
+ private TypeMirror loadType(String className) {
+ var typeElement = elementUtils.getTypeElement(className);
+ if (isNull(typeElement)) {
+ return null;
+ }
+
+ var type = typeElement.asType();
+ return typeUtils.erasure(type);
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ImmutableCollectionsExtension.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ImmutableCollectionsExtension.java
new file mode 100644
index 0000000..df230b5
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/ImmutableCollectionsExtension.java
@@ -0,0 +1,189 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.CodeBlock;
+import io.soabase.recordbuilder.core.RecordBuilder;
+import org.apiguardian.api.API;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.context.Context;
+import pl.com.labaj.autorecord.context.StaticImports;
+import pl.com.labaj.autorecord.extension.CompactConstructorExtension;
+
+import javax.annotation.processing.Generated;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeMirror;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static java.lang.Character.isLowerCase;
+import static java.lang.Integer.toUnsignedString;
+import static java.lang.Math.abs;
+import static java.util.stream.Collectors.joining;
+import static javax.lang.model.type.TypeKind.ARRAY;
+import static org.apiguardian.api.API.Status.STABLE;
+import static pl.com.labaj.autorecord.extension.arice.Names.ARICE_PACKAGE;
+import static pl.com.labaj.autorecord.extension.arice.Names.allImmutableNames;
+import static pl.com.labaj.autorecord.extension.arice.Names.notPredefinedNames;
+import static pl.com.labaj.autorecord.extension.arice.RecordComponent.debugInfo;
+
+/**
+ * An implementations of the {@link AutoRecord} {@link CompactConstructorExtension} for customizing a record generation process
+ * with support for immutable collections.
+ *
+ * Components that are collections or maps a being copied to their corresponding immutable types.
+ *
+ * Note: It is recommended to disable {@link RecordBuilder.Options#useImmutableCollections() useImmutableCollections}
+ * and {@link RecordBuilder.Options#useUnmodifiableCollections() useUnmodifiableCollections} features for generated builders.
+ */
+@API(status = STABLE)
+public class ImmutableCollectionsExtension implements CompactConstructorExtension {
+ private static final AnnotationSpec GENERATED_WITH_EXTENSION_ANNOTATION = AnnotationSpec.builder(Generated.class)
+ .addMember("value", "$S", AutoRecord.class.getName())
+ .addMember("comments", "$S", ImmutableCollectionsExtension.class.getName())
+ .build();
+
+ private static final String METHODS_CLASS_NAME = ARICE_PACKAGE + ".ARICE";
+
+ private static final ExtensionContext extContext = new ExtensionContext();
+
+ private Set immutableTypeNames;
+ private Set immutableTypes;
+ private Set immutableNames;
+ private List componentsToProcess;
+ private String methodsClassName;
+
+ /**
+ * Initializes the extension with the given processing environment and parameters.
+ *
+ * @param processingEnv The {@link ProcessingEnvironment} providing access to facilities provided by the annotation processing tool.
+ * @param parameters An array of type names that are immutable.
+ */
+ @Override
+ public void init(ProcessingEnvironment processingEnv, String[] parameters) {
+ extContext.init(processingEnv);
+
+ immutableTypeNames = notPredefinedNames(parameters);
+ immutableNames = allImmutableNames(parameters);
+ immutableTypes = extContext.getTypes(immutableNames);
+ methodsClassName = methodsClassName();
+ }
+
+ /**
+ * Determines whether the extension should generate the compact constructor content.
+ *
+ * It filters out components that:
+ *
+ * - are annotated with {@link Mutable} annotation
+ * - are primitives
+ * - are arrays
+ * - has or extends an immutable type
+ *
+ * If something has left, the content will be generated.
+ *
+ * @param isGeneratedByProcessor A flag indicating if the compact constructor is generated by the
+ * {@link AutoRecord} Processor. If the flag is {@code false},
+ * it means that compact constructor won't be generated without extensions.
+ * @param context The {@link Context} object containing the annotation processing information.
+ * @return {@code true} if the compact constructor should be generated by the extension, {@code false} otherwise.
+ */
+ @Override
+ public boolean shouldGenerateCompactConstructor(boolean isGeneratedByProcessor, Context context) {
+ var logger = context.logger();
+
+ var componentBuilder = new RecordComponent.Builder(extContext, immutableTypes, logger);
+ var declaredComponents = context.components()
+ .stream()
+ .filter(recordComponent -> !recordComponent.isAnnotatedWith(Mutable.class))
+ .filter(recordComponent -> !recordComponent.type().getKind().isPrimitive())
+ .filter(recordComponent -> recordComponent.type().getKind() != ARRAY)
+ .toList();
+ if (declaredComponents.isEmpty()) {
+ return false;
+ }
+
+ componentsToProcess = declaredComponents.stream()
+ .map(componentBuilder::toExtensionRecordComponent)
+ .filter(Objects::nonNull)
+ .toList();
+
+ return !componentsToProcess.isEmpty();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CodeBlock suffixCompactConstructorContent(Context context, StaticImports staticImports) {
+ var logger = context.logger();
+
+ var structreBuilder = new TypesStructure.Builder(extContext, immutableNames);
+ var structure = structreBuilder.buildStructure(logger);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Components to process:\n" + debugInfo(componentsToProcess));
+ logger.note("Types structure:\n" + structure.debugInfo());
+ }
+
+ var codeBuilder = CodeBlock.builder();
+
+ var statementGenerator = new StatementGenerator(extContext, structure, methodsClassName, staticImports, logger);
+
+ componentsToProcess.stream()
+ .map(statementGenerator::generateStatement)
+ .forEach(codeBuilder::addStatement);
+
+ return codeBuilder.build();
+ }
+
+ /**
+ * Returns, among others, {@link ARICEUtilities} annotation that specifies:
+ *
+ * @param context The {@link Context} object containing the annotation processing information.
+ * @param staticImports The {@link StaticImports} object containing {@code static import} statements that will be added into the generated record.
+ * @return A list of {@link AnnotationSpec} representing annotations that need to be added to the generated record.
+ */
+ @Override
+ public List annotationsToSupportCompactConstructor(Context context, StaticImports staticImports) {
+ var builder = AnnotationSpec.builder(ARICEUtilities.class)
+ .addMember("className", "$S", methodsClassName);
+
+ immutableTypeNames.forEach(parameter -> builder.addMember("immutableTypes", "$S", parameter));
+
+ return List.of(GENERATED_WITH_EXTENSION_ANNOTATION, builder.build());
+ }
+
+ private String methodsClassName() {
+ return METHODS_CLASS_NAME + (immutableTypeNames.isEmpty() ? "" : "_" + stringHashCode(immutableTypeNames));
+ }
+
+ private String stringHashCode(Set parameters) {
+ var hashCode = parameters.stream()
+ .sorted()
+ .mapToInt(String::hashCode)
+ .reduce(1, (h1, h2) -> 31 * h1 + h2);
+ var alphaString = toUnsignedString(abs(hashCode), 26);
+
+ return alphaString.chars()
+ .map(i -> isLowerCase(i) ? i - 22 : i + 17)
+ .map(i -> 65 + (i + 17) % 26)
+ .mapToObj(Character::toString)
+ .collect(joining());
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/InterfaceType.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/InterfaceType.java
new file mode 100644
index 0000000..660c2b6
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/InterfaceType.java
@@ -0,0 +1,169 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.squareup.javapoet.ClassName;
+
+import javax.annotation.Nullable;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.commons.lang3.StringUtils.capitalize;
+import static pl.com.labaj.autorecord.extension.arice.Names.ARICE_IMMUTABLE_COLLECTION_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.ARICE_IMMUTABLE_DEQUE_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_LIST_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_LIST_MULTIMAP_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_MAP_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_MULTIMAP_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_MULTISET_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_RANGE_MAP_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_RANGE_SET_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_SET_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_SET_MULTIMAP_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_SORTED_MAP_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_SORTED_MULTISET_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME;
+import static pl.com.labaj.autorecord.extension.arice.Names.GUAVA_IMMUTABLE_TABLE_CLASS_NAME;
+
+@SuppressWarnings("java:S1192")
+enum InterfaceType {
+ TABLE("com.google.common.collect.Table", Set.of(), GUAVA_IMMUTABLE_TABLE_CLASS_NAME, "copyOf") {
+ @Override
+ List genericNames() {return List.of("R", "C", "V");}
+ },
+ RANGE_MAP("com.google.common.collect.RangeMap", Set.of(), GUAVA_IMMUTABLE_RANGE_MAP_CLASS_NAME, "copyOf") {
+ @Override
+ List genericNames() {return List.of("K extends Comparable", "V");}
+ },
+ RANGE_SET("com.google.common.collect.RangeSet", Set.of(), GUAVA_IMMUTABLE_RANGE_SET_CLASS_NAME, "copyOf") {
+ @Override
+ List genericNames() {return List.of("E extends Comparable");}
+ },
+ LIST_MULTIMAP("com.google.common.collect.ListMultimap", Set.of(), GUAVA_IMMUTABLE_LIST_MULTIMAP_CLASS_NAME, "copyOf") {
+ @Override
+ List genericNames() {return List.of("K", "V");}
+ },
+ SET_MULTIMAP("com.google.common.collect.SetMultimap", Set.of(), GUAVA_IMMUTABLE_SET_MULTIMAP_CLASS_NAME, "copyOf") {
+ @Override
+ List genericNames() {return List.of("K", "V");}
+ },
+ MULTIMAP("com.google.common.collect.Multimap", Set.of(SET_MULTIMAP, LIST_MULTIMAP), GUAVA_IMMUTABLE_MULTIMAP_CLASS_NAME, "copyOf") {
+ @Override
+ List genericNames() {return List.of("K", "V");}
+ },
+ SORTED_MULTISET("com.google.common.collect.SortedMultiset", Set.of(), GUAVA_IMMUTABLE_SORTED_MULTISET_CLASS_NAME, "copyOfSorted"),
+ MULTISET("com.google.common.collect.Multiset", Set.of(SORTED_MULTISET), GUAVA_IMMUTABLE_MULTISET_CLASS_NAME, "copyOf"),
+ NAVIGABLE_MAP("java.util.NavigableMap", Set.of(), GUAVA_IMMUTABLE_SORTED_MAP_CLASS_NAME, "copyOfSorted") {
+ @Override
+ List genericNames() {return List.of("K", "V");}
+ },
+ SORTED_MAP("java.util.SortedMap", Set.of(NAVIGABLE_MAP), GUAVA_IMMUTABLE_SORTED_MAP_CLASS_NAME, "copyOfSorted") {
+ @Override
+ List genericNames() {return List.of("K", "V");}
+ },
+ MAP("java.util.Map", Set.of(SORTED_MAP), GUAVA_IMMUTABLE_MAP_CLASS_NAME, "copyOf") {
+ @Override
+ List genericNames() {return List.of("K", "V");}
+ },
+ DEQUE("java.util.Deque", Set.of(), ARICE_IMMUTABLE_DEQUE_CLASS_NAME, "copyOfQueue"),
+ QUEUE("java.util.Queue", Set.of(DEQUE), ARICE_IMMUTABLE_DEQUE_CLASS_NAME, "copyOfQueue"),
+ LIST("java.util.List", Set.of(), GUAVA_IMMUTABLE_LIST_CLASS_NAME, "copyOf"),
+ NAVIGABLE_SET("java.util.NavigableSet", Set.of(), GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME, "copyOfSorted"),
+ SORTED_SET("java.util.SortedSet", Set.of(NAVIGABLE_SET), GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME, "copyOfSorted"),
+ SET("java.util.Set", Set.of(SORTED_SET), GUAVA_IMMUTABLE_SET_CLASS_NAME, "copyOf"),
+ COLLECTION("java.util.Collection", Set.of(SET, LIST, QUEUE, MULTISET), ARICE_IMMUTABLE_COLLECTION_CLASS_NAME, "copyOfCollection"),
+ OBJECT("java.lang.Object", Set.of(COLLECTION, MAP, MULTIMAP, RANGE_SET, RANGE_MAP, TABLE), null, null) {
+ @Override
+ List genericNames() {return List.of();}
+ };
+
+ private static final Set ALL_TYPES = EnumSet.allOf(InterfaceType.class);
+ private static final Map NAME_TO_TYPES = ALL_TYPES.stream()
+ .collect(toMap(InterfaceType::className, identity()));
+ private final String className;
+
+ private final Set directSubTypes;
+ private final ClassName factoryClassName;
+ private final String factoryMethodName;
+ private final String argumentName;
+
+ InterfaceType(String className, Set directSubTypes, ClassName factoryClassName, String factoryMethodName) {
+ this.className = className;
+ this.directSubTypes = directSubTypes;
+ this.factoryClassName = factoryClassName;
+ this.factoryMethodName = factoryMethodName;
+
+ argumentName = nameToCamelCase();
+ }
+
+ static Set allProcessedTypes() {
+ return ALL_TYPES;
+ }
+
+ @Nullable
+ static InterfaceType interfaceTypeWith(String className) {
+ return NAME_TO_TYPES.get(className);
+ }
+
+ String className() {
+ return className;
+ }
+
+ String argumentName() {
+ return argumentName;
+ }
+
+ Set directSubTypes() {
+ return directSubTypes;
+ }
+
+ boolean checkGenericInInstanceOf() {
+ return true;
+ }
+
+ ClassName factoryClassName() {
+ return factoryClassName;
+ }
+
+ String factoryMethodName() {
+ return factoryMethodName;
+ }
+
+ List genericNames() {
+ return List.of("E");
+ }
+
+ @Override
+ public String toString() {
+ return className;
+ }
+
+ private String nameToCamelCase() {
+ var parts = name().split("_");
+ var camelCase = new StringBuilder(parts[0].toLowerCase());
+
+ for (int i = 1; i < parts.length; i++) {
+ camelCase.append(capitalize(parts[i].toLowerCase()));
+ }
+
+ return camelCase.toString();
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/MethodsClassGenerator.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/MethodsClassGenerator.java
new file mode 100644
index 0000000..0d8a3bb
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/MethodsClassGenerator.java
@@ -0,0 +1,137 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import org.apache.commons.lang3.StringUtils;
+import pl.com.labaj.autorecord.GeneratedWithAutoRecord;
+import pl.com.labaj.autorecord.context.Logger;
+import pl.com.labaj.autorecord.processor.AutoRecordProcessor;
+import pl.com.labaj.autorecord.processor.AutoRecordProcessorException;
+import pl.com.labaj.autorecord.processor.context.MessagerLogger;
+import pl.com.labaj.autorecord.processor.context.StaticImportsCollectors;
+
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Generated;
+import java.util.Arrays;
+import java.util.List;
+
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static org.apache.commons.lang3.StringUtils.rightPad;
+import static pl.com.labaj.autorecord.extension.arice.Names.ARICE_PACKAGE;
+import static pl.com.labaj.autorecord.extension.arice.Names.allImmutableNames;
+
+class MethodsClassGenerator {
+ private static final AnnotationSpec GENERATED_WITH_EXTENSION_ANNOTATION = AnnotationSpec.builder(Generated.class)
+ .addMember("value", "$S", AutoRecordProcessor.class.getName())
+ .addMember("value", "$S", ARICEUtilitiesProcessor.class.getName())
+ .addMember("comments", "$S", ImmutableCollectionsExtension.class.getName())
+ .build();
+ private static final AnnotationSpec GENERATED_WITH_AUTO_RECORD_ANNOTATION = AnnotationSpec.builder(GeneratedWithAutoRecord.class).build();
+ private static final List ANNOTATIONS = List.of(
+ GENERATED_WITH_EXTENSION_ANNOTATION,
+ GENERATED_WITH_AUTO_RECORD_ANNOTATION
+ );
+ private final ExtensionContext extContext;
+ private final Filer filer;
+
+ public MethodsClassGenerator(ExtensionContext extContext, Filer filer) {
+ this.extContext = extContext;
+ this.filer = filer;
+ }
+
+ public void generate(String className, String[] immutableTypes, MessagerLogger logger) {
+ logStartEnd("[START] ", className, logger);
+
+ try {
+ var classSimpleName = StringUtils.removeStart(className, ARICE_PACKAGE + ".");
+ var classBuilder = TypeSpec.classBuilder(classSimpleName);
+ var staticImports = new StaticImportsCollectors();
+
+ classBuilder.addAnnotations(ANNOTATIONS)
+ .addModifiers(PUBLIC, FINAL)
+ .addJavadoc(generateJavadoc(immutableTypes));
+
+ var structure = fillStructure(immutableTypes, logger);
+ var additionalMethodsGenerator = new CopyMethodsGenerator(extContext, staticImports, logger);
+ additionalMethodsGenerator.generateMethods(structure)
+ .forEach(classBuilder::addMethod);
+
+ var constructor = MethodSpec.constructorBuilder()
+ .addModifiers(PRIVATE)
+ .build();
+ classBuilder.addMethod(constructor);
+
+ var javaFile = buildJavaFile(staticImports, classBuilder.build());
+ javaFile.writeTo(filer);
+
+ logStartEnd("[ END ] ", className, logger);
+ } catch (Exception e) {
+ throw new AutoRecordProcessorException("Cannot generate file for " + className, e);
+ }
+ }
+
+ private TypesStructure fillStructure(String[] names, Logger logger) {
+ var structreBuilder = new TypesStructure.Builder(extContext, allImmutableNames(names));
+ var structure = structreBuilder.buildStructure(logger);
+
+ if (logger.isDebugEnabled()) {
+ logger.note("Types structure:\n" + structure.debugInfo());
+ }
+
+ return structure;
+ }
+
+ private CodeBlock generateJavadoc(String[] immutableTypes) {
+ var builder = CodeBlock.builder()
+ .add("Class providing methods to copy collections to their corresponding immutable versions");
+
+ if (immutableTypes.length > 0) {
+ builder.add("\n")
+ .add("\n")
+ .add("User defined immutable types:\n")
+ .add("
\n");
+
+ Arrays.stream(immutableTypes)
+ .map(type -> "\t- {@link " + type + "}
\n")
+ .forEach(builder::add);
+
+ builder.add("
");
+ }
+ return builder.build();
+ }
+
+ private JavaFile buildJavaFile(StaticImportsCollectors staticImports, TypeSpec classSpec) {
+ var javaFileBuilder = JavaFile.builder(ARICE_PACKAGE, classSpec);
+ staticImports.forEach(javaFileBuilder::addStaticImport);
+
+ return javaFileBuilder.build();
+ }
+
+ private void logStartEnd(String prefix, String className, Logger logger) {
+ if (logger.isDebugEnabled()) {
+ var message = rightPad(prefix + className + " ", 100, "-");
+ logger.note(message);
+ }
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/Names.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/Names.java
new file mode 100644
index 0000000..65a6719
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/Names.java
@@ -0,0 +1,97 @@
+
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.squareup.javapoet.ClassName;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.toSet;
+
+final class Names {
+ static final String ARICE_PACKAGE = "pl.com.labaj.autorecord.extension.arice";
+ static final String ARICE_COLLECTIONS_PACKAGE = "pl.com.labaj.autorecord.collections";
+
+ private static final String GUAVA_PACKAGE = "com.google.common.collect";
+ private static final String IMMUTABLE_SET = "ImmutableSet";
+ private static final String IMMUTABLE_SORTED_SET = "ImmutableSortedSet";
+ private static final String IMMUTABLE_LIST = "ImmutableList";
+ private static final String IMMUTABLE_MAP = "ImmutableMap";
+ private static final String IMMUTABLE_SORTED_MAP = "ImmutableSortedMap";
+ private static final String IMMUTABLE_COLLECTION = "ImmutableCollection";
+ private static final String IMMUTABLE_DEQUE = "ImmutableDeque";
+ private static final String IMMUTABLE_MULTISET = "ImmutableMultiset";
+ private static final String IMMUTABLE_SORTED_MULTISET = "ImmutableSortedMultiset";
+ private static final String IMMUTABLE_MULTIMAP = "ImmutableMultimap";
+ private static final String IMMUTABLE_SET_MULTIMAP = "ImmutableSetMultimap";
+ private static final String IMMUTABLE_LIST_MULTIMAP = "ImmutableListMultimap";
+ private static final String IMMUTABLE_RANGE_SET = "ImmutableRangeSet";
+ private static final String IMMUTABLE_RANGE_MAP = "ImmutableRangeMap";
+ private static final String IMMUTABLE_TABLE = "ImmutableTable";
+
+ static final ClassName GUAVA_IMMUTABLE_SET_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_SET);
+ static final ClassName GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_SORTED_SET);
+ static final ClassName GUAVA_IMMUTABLE_LIST_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_LIST);
+ static final ClassName GUAVA_IMMUTABLE_MAP_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_MAP);
+ static final ClassName GUAVA_IMMUTABLE_SORTED_MAP_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_SORTED_MAP);
+ static final ClassName ARICE_IMMUTABLE_COLLECTION_CLASS_NAME = ClassName.get(ARICE_COLLECTIONS_PACKAGE, IMMUTABLE_COLLECTION);
+ static final ClassName ARICE_IMMUTABLE_DEQUE_CLASS_NAME = ClassName.get(ARICE_COLLECTIONS_PACKAGE, IMMUTABLE_DEQUE);
+ static final ClassName GUAVA_IMMUTABLE_MULTISET_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_MULTISET);
+ static final ClassName GUAVA_IMMUTABLE_SORTED_MULTISET_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_SORTED_MULTISET);
+ static final ClassName GUAVA_IMMUTABLE_MULTIMAP_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_MULTIMAP);
+ static final ClassName GUAVA_IMMUTABLE_SET_MULTIMAP_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_SET_MULTIMAP);
+ static final ClassName GUAVA_IMMUTABLE_LIST_MULTIMAP_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_LIST_MULTIMAP);
+ static final ClassName GUAVA_IMMUTABLE_RANGE_SET_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_RANGE_SET);
+ static final ClassName GUAVA_IMMUTABLE_RANGE_MAP_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_RANGE_MAP);
+ static final ClassName GUAVA_IMMUTABLE_TABLE_CLASS_NAME = ClassName.get(GUAVA_PACKAGE, IMMUTABLE_TABLE);
+
+ private static final Set PREDEFINED_IMMUTABLE_NAMES = Set.of(
+ GUAVA_PACKAGE + "." + IMMUTABLE_SET,
+ GUAVA_PACKAGE + "." + IMMUTABLE_SORTED_SET,
+ GUAVA_PACKAGE + "." + IMMUTABLE_LIST,
+ GUAVA_PACKAGE + "." + IMMUTABLE_MAP,
+ GUAVA_PACKAGE + "." + IMMUTABLE_SORTED_MAP,
+ ARICE_COLLECTIONS_PACKAGE + "." + IMMUTABLE_COLLECTION,
+ ARICE_COLLECTIONS_PACKAGE + "." + IMMUTABLE_DEQUE,
+ GUAVA_PACKAGE + "." + IMMUTABLE_MULTISET,
+ GUAVA_PACKAGE + "." + IMMUTABLE_SORTED_MULTISET,
+ GUAVA_PACKAGE + "." + IMMUTABLE_MULTIMAP,
+ GUAVA_PACKAGE + "." + IMMUTABLE_SET_MULTIMAP,
+ GUAVA_PACKAGE + "." + IMMUTABLE_LIST_MULTIMAP,
+ GUAVA_PACKAGE + "." + IMMUTABLE_RANGE_SET,
+ GUAVA_PACKAGE + "." + IMMUTABLE_RANGE_MAP,
+ GUAVA_PACKAGE + "." + IMMUTABLE_TABLE
+ );
+
+ private Names() {}
+
+ static Set notPredefinedNames(String[] parameters) {
+ return Arrays.stream(parameters)
+ .filter(name -> !PREDEFINED_IMMUTABLE_NAMES.contains(name))
+ .collect(toSet());
+ }
+
+ static Set allImmutableNames(String[] parameters) {
+ return Stream.concat(
+ PREDEFINED_IMMUTABLE_NAMES.stream(),
+ Arrays.stream(parameters)
+ ).collect(toSet());
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/RecordComponent.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/RecordComponent.java
new file mode 100644
index 0000000..76f8b3a
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/RecordComponent.java
@@ -0,0 +1,109 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import pl.com.labaj.autorecord.context.Logger;
+
+import javax.annotation.Nullable;
+import javax.lang.model.type.TypeMirror;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.lang.Boolean.FALSE;
+import static java.util.Objects.isNull;
+import static java.util.stream.Collectors.joining;
+import static javax.lang.model.type.TypeKind.ERROR;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.OBJECT;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.allProcessedTypes;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.interfaceTypeWith;
+
+record RecordComponent(String name, boolean isNullable, TypeMirror declaredType, InterfaceType pType) {
+
+ static String debugInfo(List recordComponents) {
+ return recordComponents.stream()
+ .map(RecordComponent::toString)
+ .collect(joining("\n"));
+ }
+
+ @Override
+ public String toString() {
+ return name + " : " + declaredType;
+ }
+
+ static class Builder {
+ private final ExtensionContext extContext;
+ private final Set immutableTypes;
+ private final Logger logger;
+ private final Map toBeProcessedCache = new HashMap<>();
+
+ Builder(ExtensionContext extContext, Set immutableTypes, Logger logger) {
+ this.extContext = extContext;
+ this.immutableTypes = immutableTypes;
+ this.logger = logger;
+ }
+
+ @Nullable
+ RecordComponent toExtensionRecordComponent(pl.com.labaj.autorecord.context.RecordComponent component) {
+ var isNullable = component.isAnnotatedWith(Nullable.class);
+ var componentType = component.type();
+ var declaredType = extContext.erasure(componentType);
+ var shouldBeProcessed = toBeProcessedCache.computeIfAbsent(declaredType.toString(), name -> shouldBeProcessed(declaredType));
+
+ if (FALSE.equals(shouldBeProcessed)) {
+ return null;
+ }
+
+ var pType = interfaceTypeWith(declaredType.toString());
+ if (isNull(pType) || declaredType.getKind() == ERROR) {
+ logger.warning("Unrecognized type for processing " + declaredType);
+ pType = OBJECT;
+ }
+
+ return new RecordComponent(component.name(), isNullable, declaredType, pType);
+ }
+
+ private boolean shouldBeProcessed(TypeMirror componentType) {
+ if (componentType.getKind() == ERROR) {
+ return true;
+ }
+
+ var isImmutableType = immutableTypes.stream()
+ .anyMatch(immutableType -> extContext.isSameType(componentType, immutableType));
+ if (isImmutableType) {
+ logger.debug(componentType + " is immutable");
+ return false;
+ }
+
+ var hasImmutableSuperType = immutableTypes.stream()
+ .anyMatch(immutableType -> extContext.isSubtype(componentType, immutableType));
+ if (hasImmutableSuperType) {
+ logger.debug(componentType + " has immutable super type");
+ return false;
+ }
+
+ var isCollection = allProcessedTypes().stream()
+ .anyMatch(pType -> extContext.isSameType(componentType, pType, logger));
+ if (isCollection) {
+ logger.debug(componentType + " has processed type");
+ }
+
+ return isCollection;
+ }
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/StatementGenerator.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/StatementGenerator.java
new file mode 100644
index 0000000..ac88d6f
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/StatementGenerator.java
@@ -0,0 +1,93 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import pl.com.labaj.autorecord.context.Logger;
+import pl.com.labaj.autorecord.context.StaticImports;
+
+import javax.annotation.Nullable;
+import java.util.EnumMap;
+import java.util.Objects;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.commons.lang3.StringUtils.substringAfterLast;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.allProcessedTypes;
+import static pl.com.labaj.autorecord.extension.arice.Names.ARICE_PACKAGE;
+
+final class StatementGenerator {
+
+ private static final EnumMap RECORD_STATEMENT_GENERATORS = allProcessedTypes().stream()
+ .collect(toMap(
+ identity(),
+ StatementGenerator::builderFor,
+ (b1, b2) -> b1,
+ () -> new EnumMap<>(InterfaceType.class)
+ ));
+
+ private final ExtensionContext extContext;
+ private final TypesStructure structure;
+ private final String methodsClassName;
+ private final StaticImports staticImports;
+ private final Logger logger;
+
+ StatementGenerator(ExtensionContext extContext, TypesStructure structure, String methodsClassName, StaticImports staticImports, Logger logger) {
+ this.extContext = extContext;
+ this.structure = structure;
+ this.methodsClassName = methodsClassName;
+ this.staticImports = staticImports;
+ this.logger = logger;
+ }
+
+ CodeBlock generateStatement(RecordComponent recordComponent) {
+ var pType = recordComponent.pType();
+
+ return RECORD_STATEMENT_GENERATORS.get(pType)
+ .generateStatement(recordComponent, extContext, structure, methodsClassName, staticImports, logger);
+ }
+
+ private static RecordStatementGenerator builderFor(InterfaceType iType) {
+ return (component, extensionContext, structure, methodsClassName, staticImports, logger) -> {
+ var nullable = component.isNullable();
+ if (nullable) {
+ staticImports.add(Objects.class, "isNull");
+ }
+
+ var format = nullable ? "$1L = isNull($1L) ? null : $2T.$3L($1L)" : "$1L = $2T.$3L($1L)";
+
+ if (structure.needsAdditionalMethod(iType)) {
+ var className = ClassName.get(ARICE_PACKAGE, substringAfterLast(methodsClassName, "."));
+ return CodeBlock.of(format, component.name(), className, "immutable");
+ }
+
+ return CodeBlock.of(format, component.name(), iType.factoryClassName(), iType.factoryMethodName());
+ };
+ }
+
+ @FunctionalInterface
+ private interface RecordStatementGenerator {
+ @Nullable
+ CodeBlock generateStatement(RecordComponent recordComponent,
+ ExtensionContext extensionContext,
+ TypesStructure structure,
+ String methodsClassName,
+ StaticImports staticImports,
+ Logger logger);
+ }
+}
diff --git a/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/TypesStructure.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/TypesStructure.java
new file mode 100644
index 0000000..d6543e8
--- /dev/null
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/TypesStructure.java
@@ -0,0 +1,173 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.squareup.javapoet.ClassName;
+import pl.com.labaj.autorecord.context.Logger;
+
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+import static java.util.Comparator.comparing;
+import static java.util.Objects.isNull;
+import static java.util.Objects.nonNull;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.commons.lang3.StringUtils.substringAfterLast;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.OBJECT;
+import static pl.com.labaj.autorecord.extension.arice.InterfaceType.allProcessedTypes;
+
+final class TypesStructure {
+ private final EnumMap> classNames;
+ private final Map additionalMethodNeeded;
+
+ private TypesStructure(EnumMap> classNames, Map additionalMethodNeeded) {
+ this.classNames = classNames;
+ this.additionalMethodNeeded = additionalMethodNeeded;
+ }
+
+ String debugInfo() {
+ return classNames.entrySet().stream()
+ .map(item -> {
+ var types = item.getValue()
+ .stream()
+ .map(ClassName::simpleName)
+ .collect(joining(", ", "(", ")"));
+ var className = substringAfterLast(item.getKey().className(), ".");
+ var needsAdditionalMethod = additionalMethodNeeded.get(item.getKey());
+
+ return className + " : " + needsAdditionalMethod + " -> " + types;
+ })
+ .collect(joining("\n"));
+ }
+
+ boolean needsAdditionalMethod(InterfaceType iType) {
+ return additionalMethodNeeded.containsKey(iType) && additionalMethodNeeded.get(iType);
+ }
+
+ Set getClassNames(InterfaceType iType) {
+ return classNames.get(iType);
+ }
+
+ static class Builder {
+ private final ExtensionContext extContext;
+ private final Set immutableNames;
+
+ Builder(ExtensionContext extContext, Set immutableNames) {
+ this.extContext = extContext;
+ this.immutableNames = immutableNames;
+ }
+
+ TypesStructure buildStructure(Logger logger) {
+ var classNames = allProcessedTypes().stream()
+ .collect(toMap(
+ identity(),
+ pType -> collectClassNames(pType, logger),
+ (s1, s2) -> {
+ s1.addAll(s2);
+ return s1;
+ },
+ () -> new EnumMap<>(InterfaceType.class)
+ ));
+
+ optimizeStructure(OBJECT, classNames);
+
+ var additionalMethodNeeded = new EnumMap(InterfaceType.class);
+ collectNeedsInfo(null, OBJECT, classNames, additionalMethodNeeded);
+
+ return new TypesStructure(classNames, additionalMethodNeeded);
+ }
+
+ private Set collectClassNames(InterfaceType iType, Logger logger) {
+ var classNames = immutableNames
+ .stream()
+ .map(immutableName -> extContext.getImmutableType(immutableName, logger))
+ .filter(Objects::nonNull)
+ .filter(type -> extContext.isSubtype(type, iType, logger))
+ .map(ClassName::get)
+ .map(ClassName.class::cast)
+ .collect(toCollection(() -> new TreeSet<>(comparing(ClassName::canonicalName))));
+
+ if (iType == OBJECT) {
+ immutableNames.stream()
+ .filter(immutableName -> isNull(extContext.getImmutableType(immutableName, logger)))
+ .map(ClassName::bestGuess)
+ .forEach(className -> {
+ logger.debug("Cannot get TypeMirror for " + className + " so added to Object");
+ classNames.add(className);
+ });
+ }
+
+ return classNames;
+ }
+
+ private Set optimizeStructure(InterfaceType iType, EnumMap> classNames) {
+ var typeClassNames = classNames.get(iType);
+
+ if (iType.directSubTypes().isEmpty()) {
+ return typeClassNames;
+ }
+
+ var toRemove = iType.directSubTypes().stream()
+ .map(subType -> optimizeStructure(subType, classNames))
+ .flatMap(Collection::stream)
+ .collect(toSet());
+
+ classNames.get(iType).removeAll(toRemove);
+
+ return Stream.concat(toRemove.stream(), typeClassNames.stream()).collect(toSet());
+ }
+
+ private boolean collectNeedsInfo(InterfaceType parent, InterfaceType iType,
+ EnumMap> classNames,
+ EnumMap additionalMethodNeeded) {
+
+ if (iType.directSubTypes().isEmpty()) {
+ var result = !classNames.get(iType).isEmpty();
+ additionalMethodNeeded.put(iType, result);
+ return result;
+ }
+
+ var needs = new AtomicBoolean();
+ var subNeededInfo = iType.directSubTypes().stream()
+ .collect(toMap(
+ identity(),
+ subType -> collectNeedsInfo(iType, subType, classNames, additionalMethodNeeded)
+ ));
+
+ var someSubNeeds = subNeededInfo.values().stream()
+ .reduce(false, (b1, b2) -> b1 || b2);
+ needs.set(someSubNeeds);
+
+ if (!needs.get()) {
+ needs.set(!nonNull(parent));
+ }
+
+ additionalMethodNeeded.put(iType, needs.get());
+ return needs.get();
+ }
+ }
+}
diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/package-info.java b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/package-info.java
similarity index 70%
rename from modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/package-info.java
rename to arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/package-info.java
index ae7273f..23793c2 100644
--- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/package-info.java
+++ b/arice/extension/src/main/java/pl/com/labaj/autorecord/extension/arice/package-info.java
@@ -1,5 +1,3 @@
-@API(status = INTERNAL)
-package pl.com.labaj.autorecord.processor.context;
/*-
* Copyright © 2023 Auto Record
@@ -16,7 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-import org.apiguardian.api.API;
-
-import static org.apiguardian.api.API.Status.INTERNAL;
\ No newline at end of file
+/**
+ * Provides classes used by ARICE (Auto Record Immutable Collections Extension).
+ *
+ *
+ * {@link pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension} is the main class.
+ */
+package pl.com.labaj.autorecord.extension.arice;
\ No newline at end of file
diff --git a/arice/extension/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/arice/extension/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..ca7ea81
--- /dev/null
+++ b/arice/extension/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+pl.com.labaj.autorecord.extension.arice.ARICEUtilitiesProcessor
\ No newline at end of file
diff --git a/arice/project/pom.xml b/arice/project/pom.xml
new file mode 100644
index 0000000..2a5d14e
--- /dev/null
+++ b/arice/project/pom.xml
@@ -0,0 +1,79 @@
+
+
+ 4.0.0
+
+ pl.com.labaj.autorecord
+ auto-record-project
+ 2.1.1-SNAPSHOT
+ ../../pom.xml
+
+
+ arice-project
+ 1.0.0-SNAPSHOT
+ pom
+
+
+ ${project.basedir}/../../.build/lic-header.txt
+
+
+
+ ../api
+ ../utils
+ ../extension
+ ../tests
+
+
+
+
+
+ pl.com.labaj.autorecord
+ auto-record
+ 2.1.1-SNAPSHOT
+
+
+ pl.com.labaj.autorecord
+ arice-api
+ 1.0.0-SNAPSHOT
+
+
+ pl.com.labaj.autorecord
+ arice-utils
+ 1.0.0-SNAPSHOT
+
+
+ pl.com.labaj.autorecord
+ arice-extension
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+
+ verify
+
+
+ ${project.basedir}/../../target/site/jacoco-aggregate/jacoco.xml
+
+
+
+ release
+
+
+
+
+ org.honton.chas
+ git-tag-maven-plugin
+
+ false
+ v-arice${project.version}
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/arice/tests/pom.xml b/arice/tests/pom.xml
new file mode 100644
index 0000000..34e6f00
--- /dev/null
+++ b/arice/tests/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+
+ pl.com.labaj.autorecord
+ arice-project
+ 1.0.0-SNAPSHOT
+ ../project/pom.xml
+
+
+ arice-tests
+
+
+ true
+
+
+
+
+ pl.com.labaj.autorecord
+ auto-record
+
+
+ pl.com.labaj.autorecord
+ arice-extension
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ com.google.testing.compile
+ compile-testing
+ test
+
+
+
+
+
+ verify
+
+
+ ${project.basedir}/../../target/site/jacoco-aggregate/jacoco.xml
+
+
+
+ release
+
+
+ true
+ none
+
+
+
+
\ No newline at end of file
diff --git a/arice/tests/src/test/java/pl/com/labaj/autorecord/extension/arice/ImmutabilityTest.java b/arice/tests/src/test/java/pl/com/labaj/autorecord/extension/arice/ImmutabilityTest.java
new file mode 100644
index 0000000..f73bad9
--- /dev/null
+++ b/arice/tests/src/test/java/pl/com/labaj/autorecord/extension/arice/ImmutabilityTest.java
@@ -0,0 +1,136 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import pl.com.labaj.autorecord.AutoRecord;
+
+import java.util.AbstractCollection;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static pl.com.labaj.autorecord.extension.arice.ImmutabilityTest.TestArgument.data;
+
+class ImmutabilityTest {
+ @AutoRecord
+ @AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+ static
+ interface Simple {
+ Object value();
+ @Mutable Object mutableValue();
+ }
+
+
+ @SuppressWarnings("unchecked")
+ static Stream testData() {
+ var aValue = "A";
+ return Stream.of(
+ data(Collection.class, TestCollection::new, collection -> collection.add(aValue)),
+ data(Set.class, HashSet::new, set -> set.add(aValue)),
+ data(SortedSet.class, TreeSet::new, set -> set.add(aValue)),
+ data(NavigableSet.class, TreeSet::new, set -> set.add(aValue)),
+ data(List.class, ArrayList::new, list -> list.add(aValue)),
+ data(Queue.class, ArrayDeque::new, queue -> queue.add(aValue)),
+ data(Deque.class, ArrayDeque::new, queue -> queue.add(aValue)),
+ data(Map.class, HashMap::new, map -> map.put(aValue, aValue))
+ );
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testData")
+ void shouldWrapCollectionWithImmutable(Class aClass, Supplier mutableValueSupplier, Consumer methodToCall) {
+ //given
+ var mutableValue = mutableValueSupplier.get();
+
+ //when
+ var simpleRecord = new ImmutabilityTest_SimpleRecord(mutableValue, mutableValue);
+ var immutableValue = simpleRecord.value();
+ var mutableValueFromRecord = simpleRecord.mutableValue();
+
+ System.out.println(mutableValue.getClass() + " -> " + immutableValue.getClass());
+
+ //then
+ assertAll(
+ () -> assertThat(immutableValue).isInstanceOf(aClass),
+ () -> assertDoesNotThrow(() -> methodToCall.accept(mutableValue)),
+ () -> assertThrows(UnsupportedOperationException.class, () -> methodToCall.accept(immutableValue)),
+ () -> assertThat(mutableValueFromRecord).isSameInstanceAs(mutableValue),
+ () -> assertDoesNotThrow(() -> methodToCall.accept(mutableValueFromRecord))
+ );
+ }
+ record TestArgument(Class aClass, Supplier extends E> mutableValueSupplier, Consumer methodToCall) implements Arguments {
+
+
+ static TestArgument data(Class aClass, Supplier extends E> mutableValueSupplier, Consumer methodToCall) {
+ return new TestArgument<>(aClass, mutableValueSupplier, methodToCall);
+ }
+ @Override
+ public Object[] get() {
+ return new Object[] {aClass, mutableValueSupplier, methodToCall};
+ }
+
+ }
+ private static class TestCollection extends AbstractCollection implements Collection {
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ @Override
+ public boolean hasNext() {
+ return false;
+ }
+
+ @Override
+ public E next() {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ }
+}
diff --git a/arice/tests/src/test/java/pl/com/labaj/autorecord/extension/arice/ImmutableCollectionsExtensionTest.java b/arice/tests/src/test/java/pl/com/labaj/autorecord/extension/arice/ImmutableCollectionsExtensionTest.java
new file mode 100644
index 0000000..0ff4669
--- /dev/null
+++ b/arice/tests/src/test/java/pl/com/labaj/autorecord/extension/arice/ImmutableCollectionsExtensionTest.java
@@ -0,0 +1,167 @@
+package pl.com.labaj.autorecord.extension.arice;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableSet;
+import com.google.testing.compile.Compiler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.processor.AutoRecordProcessor;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+import static com.google.testing.compile.JavaFileObjects.forResource;
+import static java.util.Objects.isNull;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.commons.lang3.StringUtils.removeStart;
+import static org.apache.commons.lang3.StringUtils.substringBefore;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class ImmutableCollectionsExtensionTest {
+ private static final String[] NAMES = {
+ "ItemWithObject",
+ "ItemWithLists",
+ "ItemWithListsCustomTypes",
+ "ItemWithMutableLists",
+ "ItemWithSets",
+ "ItemWithSetsCustomTypes",
+ "ItemWithMutableSets",
+ "ItemWithMaps",
+ "ItemWithMapsCustomTypes",
+ "ItemWithQueues",
+ "ItemWithQueuesCustomTypes",
+ "ItemWithMutableQueues",
+ "ItemWithGuavaCollections"
+ };
+ private static final String GENERATED_PATH = "pl/com/labaj/autorecord/testcase/";
+
+ static Stream names() {
+ return Arrays.stream(NAMES);
+ }
+
+ private Compiler compiler;
+
+ @BeforeEach
+ void setUp() {
+ compiler = javac()
+ .withOptions("-proc:only", "-Xprefer:source");
+ }
+
+ @ParameterizedTest(name = "{0}.java")
+ @MethodSource("names")
+ void shouldGenerateSingleRecord(String name) {
+ //given
+ var inputs = List.of(
+ forResource("in/" + name + ".java"),
+ forResource("in/UserCollections.java")
+ );
+
+ var expectedOutput = forResource("out/" + name + "Record.java");
+
+ //when
+ var compilation = compiler
+ .withProcessors(new AutoRecordProcessor(), new ARICEUtilitiesProcessor())
+ .compile(inputs);
+
+ //then
+ assertAll(
+ () -> assertThat(compilation).generatedSourceFile(GENERATED_PATH + name + "Record")
+ .hasSourceEquivalentTo(expectedOutput),
+ () -> assertThat(compilation).succeeded()
+ );
+ }
+
+ @Test
+ void shouldGenerateCorrectAllRecordsTogether() {
+ //given
+ var inputs = Arrays.stream(NAMES)
+ .map(name -> forResource("in/" + name + ".java"))
+ .collect(toCollection(ArrayList::new));
+
+ inputs.add(forResource("in/UserCollections.java"));
+
+ var expectedOutputs = Arrays.stream(NAMES)
+ .collect(toMap(
+ identity(),
+ name -> forResource("out/" + name + "Record.java")
+ ));
+ var expectedArice = forResource("out/ARICE.java");
+
+ //when
+ var compilation = compiler
+ .withProcessors(new AutoRecordProcessor(), new ARICEUtilitiesProcessor())
+ .compile(inputs);
+
+ //then
+ var assertions = Arrays.stream(NAMES)
+ .map(name -> (Executable) () -> assertThat(compilation).generatedSourceFile(GENERATED_PATH + name + "Record")
+ .hasSourceEquivalentTo(expectedOutputs.get(name)))
+ .collect(toCollection(ArrayList::new));
+
+ assertions.add(() -> assertThat(compilation).succeeded());
+ assertions.add(() -> assertThat(compilation).generatedSourceFile("pl/com/labaj/autorecord/extension/arice/ARICE")
+ .hasSourceEquivalentTo(expectedArice));
+
+ assertAll(assertions);
+ }
+
+ @SuppressWarnings("DataFlowIssue")
+ private static List prepareClasspath() {
+ var autoRecordJar = findClasspathFile(AutoRecord.class);
+ var guavaJar = findClasspathFile(ImmutableSet.class);
+ var nullableJar = findClasspathFile(Nullable.class);
+ var targetClassesFolder = findClasspathFile(ImmutableCollectionsExtension.class);
+
+ return List.of(
+ autoRecordJar,
+ guavaJar,
+ nullableJar,
+ targetClassesFolder
+ );
+ }
+
+ private static File findClasspathFile(Class> aClass) {
+ var url = aClass.getResource(aClass.getSimpleName() + ".class");
+ var fileInFolderWithoutExtension = aClass.getName().replace('.', '/');
+
+ if (isNull(url)) {
+ fail("Cannot get URL for " + aClass.getName());
+ return null;
+ }
+ var path = url.getPath();
+ path = removeStart(path, "jar:");
+ path = removeStart(path, "file:");
+ path = substringBefore(path, fileInFolderWithoutExtension);
+ path = substringBefore(path, "!");
+
+ return new File(path);
+ }
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithGuavaCollections.java b/arice/tests/src/test/resources/in/ItemWithGuavaCollections.java
new file mode 100644
index 0000000..c3511c6
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithGuavaCollections.java
@@ -0,0 +1,50 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.RangeMap;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.SortedMultiset;
+import com.google.common.collect.SortedSetMultimap;
+import com.google.common.collect.Table;
+import pl.com.labaj.autorecord.AutoRecord;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithGuavaCollections> {
+ Multiset multiset();
+
+ SortedMultiset sortedMultiset();
+
+ Multimap multimap();
+
+ SetMultimap setMultiMap();
+
+ SortedSetMultimap sortedSetMultimap();
+
+ ListMultimap listMultiMap();
+
+ RangeSet rangeSet();
+
+ RangeMap rangeMap();
+
+ Table table();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithLists.java b/arice/tests/src/test/resources/in/ItemWithLists.java
new file mode 100644
index 0000000..052a4b4
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithLists.java
@@ -0,0 +1,43 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableList;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithLists {
+ List list();
+ LinkedList linkedList();
+ ArrayList arrayList();
+ UserCollections.UserList userList();
+ UserCollections.UserListImpl userListImpl();
+ ImmutableList immutableList();
+ @Nullable List nullableList();
+ @Nullable LinkedList nullableLinkedList();
+ @Nullable ArrayList nullableArrayList();
+ @Nullable UserCollections.UserList nullableUserList();
+ @Nullable UserCollections.UserListImpl nullableUserListImpl();
+ @Nullable ImmutableList nullableImmutableList();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithListsCustomTypes.java b/arice/tests/src/test/resources/in/ItemWithListsCustomTypes.java
new file mode 100644
index 0000000..2fad131
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithListsCustomTypes.java
@@ -0,0 +1,47 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableList;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+@AutoRecord
+@AutoRecord.Extension(
+ extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension",
+ parameters = {
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserList"
+ })
+interface ItemWithListsCustomTypes {
+ List list();
+ LinkedList linkedList();
+ ArrayList arrayList();
+ UserCollections.UserList userList();
+ UserCollections.UserListImpl userListImpl();
+ ImmutableList immutableList();
+ @Nullable List nullableList();
+ @Nullable LinkedList nullableLinkedList();
+ @Nullable ArrayList nullableArrayList();
+ @Nullable UserCollections.UserList nullableUserList();
+ @Nullable UserCollections.UserListImpl nullableUserListImpl();
+ @Nullable ImmutableList nullableImmutableList();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithMaps.java b/arice/tests/src/test/resources/in/ItemWithMaps.java
new file mode 100644
index 0000000..88faab7
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithMaps.java
@@ -0,0 +1,45 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableMap;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithMaps {
+ Map set();
+ HashMap hashMap();
+ TreeMap treeMap();
+ SortedMap sortedMap();
+ NavigableMap navigableMap();
+ ImmutableMap immutableMap();
+ @Nullable MapnullableSet();
+ @Nullable HashMapnullableHashMap();
+ @Nullable TreeMapnullableTreeMap();
+ @Nullable SortedMapnullableSortedMap();
+ @Nullable NavigableMap nullableNavigableMap();
+ @Nullable ImmutableMap nullableImmutableMap();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithMapsCustomTypes.java b/arice/tests/src/test/resources/in/ItemWithMapsCustomTypes.java
new file mode 100644
index 0000000..2680c00
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithMapsCustomTypes.java
@@ -0,0 +1,63 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableMap;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+@AutoRecord
+@AutoRecord.Extension(
+ extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension",
+ parameters = {
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserMap",
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserSortedMap",
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserNavigableMap"
+ })
+interface ItemWithMapsCustomTypes {
+ Map set();
+ HashMap hashMap();
+ TreeMap treeMap();
+ SortedMap sortedMap();
+ NavigableMap navigableMap();
+ UserCollections.UserMap userMap();
+ UserCollections.UserMapImpl userMapImpl();
+ UserCollections.UserSortedMap userSortedMap();
+ UserCollections.UserSortedMapImpl userSortedMapImpl();
+ UserCollections.UserNavigableMap userNavigableMap();
+ UserCollections.UserNavigableMapImpl userNavigableMapImpl();
+ ImmutableMap immutableMap();
+ @Nullable MapnullableSet();
+ @Nullable HashMapnullableHashMap();
+ @Nullable TreeMapnullableTreeMap();
+ @Nullable SortedMapnullableSortedMap();
+ @Nullable NavigableMap nullableNavigableMap();
+ @Nullable UserCollections.UserMap nullableUserMap();
+ @Nullable UserCollections.UserMapImpl nullableUserMapImpl();
+ @Nullable UserCollections.UserSortedMap nullableUserSortedMap();
+ @Nullable UserCollections.UserSortedMapImpl nullableUserSortedMapImpl();
+ @Nullable UserCollections.UserNavigableMap nullableUserNavigableMap();
+ @Nullable UserCollections.UserNavigableMapImpl nullableUserNavigableMapImpl();
+ @Nullable ImmutableMap nullableImmutableMap();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithMutableLists.java b/arice/tests/src/test/resources/in/ItemWithMutableLists.java
new file mode 100644
index 0000000..03f1ba3
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithMutableLists.java
@@ -0,0 +1,44 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableList;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.extension.arice.Mutable;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithMutableLists {
+ List list();
+ LinkedList linkedList();
+ ArrayList arrayList();
+ UserCollections.UserList userList();
+ UserCollections.UserListImpl userListImpl();
+ ImmutableList immutableList();
+ @Mutable
+ List mutableList();
+ @Mutable LinkedList mutableLinkedList();
+ @Mutable ArrayList mutableArrayList();
+ @Mutable UserCollections.UserList mutableUserList();
+ @Mutable UserCollections.UserListImpl mutableUserListImpl();
+ @Mutable ImmutableList mutableImmutableList();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithMutableQueues.java b/arice/tests/src/test/resources/in/ItemWithMutableQueues.java
new file mode 100644
index 0000000..6a63a6d
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithMutableQueues.java
@@ -0,0 +1,39 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.collections.ImmutableDeque;
+import pl.com.labaj.autorecord.extension.arice.Mutable;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Queue;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithMutableQueues {
+ Queue queue();
+ Deque deque();
+ ArrayDeque arrayDeque();
+ ImmutableDeque immutableDeque();
+ @Mutable Queue mutableQueue();
+ @Mutable Deque mutableDeque();
+ @Mutable
+ ArrayDeque mutableArrayDeque();
+ @Mutable ImmutableDeque mutableImmutableDeque();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithMutableSets.java b/arice/tests/src/test/resources/in/ItemWithMutableSets.java
new file mode 100644
index 0000000..3f13c1a
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithMutableSets.java
@@ -0,0 +1,63 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableSet;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.extension.arice.Mutable;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithMutableSets> {
+ Set set();
+ HashSet hashSet();
+ LinkedHashSet linkedHashSet();
+ SortedSet sortedSet();
+ UserCollections.SortedSetImpl sortedSetImpl();
+ NavigableSet navigableSet();
+ TreeSet treeSet();
+ UserCollections.UserSet userSet();
+ UserCollections.UserSetImpl userSetImpl();
+ UserCollections.UserSortedSet userSortedSet();
+ UserCollections.UserSortedSetImpl userSortedSetImpl();
+ UserCollections.UserNavigableSet userNavigableSet();
+ UserCollections.UserNavigableSetImpl userNavigableSetImpl();
+ ImmutableSet immutableSet();
+ @Mutable Set mutableSet();
+ @Mutable HashSet mutableHashSet();
+ @Mutable LinkedHashSet mutableLinkedHashSet();
+ @Mutable SortedSet mutableSortedSet();
+ @Mutable UserCollections.SortedSetImpl mutableSortedSetImpl();
+ @Mutable NavigableSet mutableNavigableSet();
+ @Mutable TreeSet mutableTreeSet();
+ @Mutable UserCollections.UserSet mutableUserSet();
+ @Mutable UserCollections.UserSetImpl mutableUserSetImpl();
+ @Mutable UserCollections.UserSortedSet mutableUserSortedSet();
+ @Mutable UserCollections.UserSortedSetImpl mutableUserSortedSetImpl();
+ @Mutable UserCollections.UserNavigableSet mutableUserNavigableSet();
+ @Mutable UserCollections.UserNavigableSetImpl mutableUserNavigableSetImpl();
+ @Mutable
+ ImmutableSet mutableImmutableSet();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithObject.java b/arice/tests/src/test/resources/in/ItemWithObject.java
new file mode 100644
index 0000000..9e46a88
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithObject.java
@@ -0,0 +1,28 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import pl.com.labaj.autorecord.AutoRecord;
+
+import javax.annotation.Nullable;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithObject {
+ Object object();
+ @Nullable Object nullableObject();
+}
\ No newline at end of file
diff --git a/arice/tests/src/test/resources/in/ItemWithQueues.java b/arice/tests/src/test/resources/in/ItemWithQueues.java
new file mode 100644
index 0000000..b359762
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithQueues.java
@@ -0,0 +1,38 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.collections.ImmutableDeque;
+
+import javax.annotation.Nullable;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Queue;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithQueues {
+ Queue queue();
+ Deque deque();
+ ArrayDeque arrayDeque();
+ ImmutableDeque immutableDeque();
+ @Nullable QueuenullableQueue();
+ @Nullable Deque nullableDeque();
+ @Nullable ArrayDeque nullableArrayDeque();
+ @Nullable ImmutableDeque nullableImmutableDeque();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithQueuesCustomTypes.java b/arice/tests/src/test/resources/in/ItemWithQueuesCustomTypes.java
new file mode 100644
index 0000000..d8f0d88
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithQueuesCustomTypes.java
@@ -0,0 +1,53 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.collections.ImmutableDeque;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import javax.annotation.Nullable;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Queue;
+
+@AutoRecord
+@AutoRecord.Extension(
+ extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension",
+ parameters = {
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserQueue",
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserDeque"
+ }
+)
+interface ItemWithQueuesCustomTypes {
+ Queue queue();
+ Deque deque();
+ ArrayDeque arrayDeque();
+ UserCollections.UserQueue userQueue();
+ UserCollections.UserDeque userDeque();
+ UserCollections.UserQueueImpl userQueueImpl();
+ UserCollections.UserDequeImpl userDequeImpl();
+ ImmutableDeque immutableDeque();
+ @Nullable QueuenullableQueue();
+ @Nullable Deque nullableDeque();
+ @Nullable ArrayDeque nullableArrayDeque();
+ @Nullable UserCollections.UserQueue nullableUserQueue();
+ @Nullable UserCollections.UserDeque nullableUserDeque();
+ @Nullable UserCollections.UserQueueImpl nullableUserQueueImpl();
+ @Nullable UserCollections.UserDequeImpl nullableUserDequeImpl();
+ @Nullable ImmutableDeque nullableImmutableDeque();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithSets.java b/arice/tests/src/test/resources/in/ItemWithSets.java
new file mode 100644
index 0000000..b121cab
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithSets.java
@@ -0,0 +1,62 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableSet;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import javax.annotation.Nullable;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+@AutoRecord
+@AutoRecord.Extension(extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension")
+interface ItemWithSets> {
+ Set set();
+ HashSet hashSet();
+ LinkedHashSet linkedHashSet();
+ SortedSet sortedSet();
+ UserCollections.SortedSetImpl sortedSetImpl();
+ NavigableSet navigableSet();
+ TreeSet treeSet();
+ UserCollections.UserSet userSet();
+ UserCollections.UserSetImpl userSetImpl();
+ UserCollections.UserSortedSet userSortedSet();
+ UserCollections.UserSortedSetImpl userSortedSetImpl();
+ UserCollections.UserNavigableSet userNavigableSet();
+ UserCollections.UserNavigableSetImpl userNavigableSetImpl();
+ ImmutableSet immutableSet();
+ @Nullable Set nullableSet();
+ @Nullable HashSet nullableHashSet();
+ @Nullable LinkedHashSet nullableLinkedHashSet();
+ @Nullable SortedSet nullableSortedSet();
+ @Nullable UserCollections.SortedSetImpl nullableSortedSetImpl();
+ @Nullable NavigableSet nullableNavigableSet();
+ @Nullable TreeSet nullableTreeSet();
+ @Nullable UserCollections.UserSet nullableUserSet();
+ @Nullable UserCollections.UserSetImpl nullableUserSetImpl();
+ @Nullable UserCollections.UserSortedSet nullableUserSortedSet();
+ @Nullable UserCollections.UserSortedSetImpl nullableUserSortedSetImpl();
+ @Nullable UserCollections.UserNavigableSet nullableUserNavigableSet();
+ @Nullable UserCollections.UserNavigableSetImpl nullableUserNavigableSetImpl();
+ @Nullable ImmutableSet nullableImmutableSet();
+}
diff --git a/arice/tests/src/test/resources/in/ItemWithSetsCustomTypes.java b/arice/tests/src/test/resources/in/ItemWithSetsCustomTypes.java
new file mode 100644
index 0000000..92a8202
--- /dev/null
+++ b/arice/tests/src/test/resources/in/ItemWithSetsCustomTypes.java
@@ -0,0 +1,69 @@
+package pl.com.labaj.autorecord.testcase;
+
+/*-
+ * Copyright © 2023 Auto Record
+ *
+ * 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.
+ */
+
+import com.google.common.collect.ImmutableSet;
+import pl.com.labaj.autorecord.AutoRecord;
+import pl.com.labaj.autorecord.testcase.user.UserCollections;
+
+import javax.annotation.Nullable;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+@AutoRecord
+@AutoRecord.Extension(
+ extensionClass = "pl.com.labaj.autorecord.extension.arice.ImmutableCollectionsExtension",
+ parameters = {
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserSet",
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserSortedSet",
+ "pl.com.labaj.autorecord.testcase.user.UserCollections.UserNavigableSet"
+ })
+interface ItemWithSetsCustomTypes