-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: Immutable Collections Extension added
closes #106 BREAKING CHANGE: Extensions API changed
- Loading branch information
1 parent
92e8de4
commit fef91fa
Showing
57 changed files
with
2,183 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
titleAndCommits: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>pl.com.labaj</groupId> | ||
<artifactId>auto-record-project</artifactId> | ||
<!-- <version>2.1.0</version>--> | ||
<version>2.1.1-SNAPSHOT</version> | ||
<relativePath>../../pom.xml</relativePath> | ||
</parent> | ||
|
||
<artifactId>auto-record-immutable-collections</artifactId> | ||
<version>1.0.0-SNAPSHOT</version> | ||
|
||
<properties> | ||
<guava.version>32.1.1-jre</guava.version> | ||
|
||
<license-maven-plugin.header>${project.basedir}/../../.build/lic-header.txt</license-maven-plugin.header> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>pl.com.labaj</groupId> | ||
<artifactId>auto-record</artifactId> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<scope>runtime</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.testing.compile</groupId> | ||
<artifactId>compile-testing</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>pl.com.labaj</groupId> | ||
<artifactId>auto-record</artifactId> | ||
<version>${project.parent.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>${guava.version}</version> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>com.mycila</groupId> | ||
<artifactId>license-maven-plugin</artifactId> | ||
<configuration> | ||
<licenseSets> | ||
<licenseSet> | ||
<header>${license-maven-plugin.header}</header> | ||
</licenseSet> | ||
</licenseSets> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<profiles> | ||
<profile> | ||
<id>verify</id> | ||
|
||
<properties> | ||
<sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/../../target/site/jacoco-aggregate/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths> | ||
</properties> | ||
</profile> | ||
</profiles> | ||
</project> |
220 changes: 220 additions & 0 deletions
220
...s/src/main/java/pl/com/labaj/autorecord/extension/compact/AdditionalMethodsGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
package pl.com.labaj.autorecord.extension.compact; | ||
|
||
/*- | ||
* 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 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 javax.lang.model.element.TypeElement; | ||
import javax.lang.model.util.Types; | ||
import java.util.ArrayDeque; | ||
import java.util.EnumMap; | ||
import java.util.HashSet; | ||
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.collectingAndThen; | ||
import static java.util.stream.Collectors.joining; | ||
import static java.util.stream.Collectors.toMap; | ||
import static java.util.stream.Collectors.toSet; | ||
import static javax.lang.model.element.Modifier.PRIVATE; | ||
import static javax.lang.model.element.Modifier.STATIC; | ||
import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.PROCESSED_TYPES; | ||
import static pl.com.labaj.autorecord.extension.compact.ProcessedType.allProcessedTypes; | ||
|
||
class AdditionalMethodsGenerator { | ||
private static final EnumMap<ProcessedType, MethodGenerator> METHOD_GENERATORS = allProcessedTypes().stream() | ||
.collect(toMap( | ||
identity(), | ||
AdditionalMethodsGenerator::builderFor, | ||
(b1, b2) -> b1, | ||
() -> new EnumMap<>(ProcessedType.class) | ||
)); | ||
|
||
private final Types typeUtils; | ||
|
||
private final StaticImports staticImports; | ||
private final Logger logger; | ||
|
||
AdditionalMethodsGenerator(Types typeUtils, | ||
StaticImports staticImports, | ||
Logger logger) { | ||
this.typeUtils = typeUtils; | ||
this.staticImports = staticImports; | ||
this.logger = logger; | ||
} | ||
|
||
List<MethodSpec> generateAdditionalMethods(List<RecordComponent> componentsToProcess, ProcessedTypesStructure structure) { | ||
var pTypesToGenerate = collectPTypesToGenerate(componentsToProcess, structure); | ||
|
||
return ProcessedType.allProcessedTypes().stream() | ||
.filter(pTypesToGenerate::contains) | ||
.sorted(reverseOrder()) | ||
.map(METHOD_GENERATORS::get) | ||
.map(methodGenerator -> methodGenerator.generateMethod(typeUtils, structure, staticImports, logger)) | ||
.toList(); | ||
} | ||
|
||
private HashSet<ProcessedType> collectPTypesToGenerate(List<RecordComponent> componentsToProcess, ProcessedTypesStructure structure) { | ||
var pTypesToCheck = componentsToProcess.stream() | ||
.map(RecordComponent::pType) | ||
.collect(collectingAndThen(toSet(), ArrayDeque::new)); | ||
|
||
var pTypesToGenerate = new HashSet<ProcessedType>(); | ||
|
||
while (!pTypesToCheck.isEmpty()) { | ||
var pType = pTypesToCheck.removeFirst(); | ||
if (structure.needsAdditionalMethod(pType)) { | ||
pTypesToGenerate.add(pType); | ||
} | ||
|
||
pType.directSubTypes().stream() | ||
.filter(subType -> !pTypesToCheck.contains(subType) && !pTypesToGenerate.contains(subType)) | ||
.forEach(pTypesToCheck::addLast); | ||
} | ||
|
||
return pTypesToGenerate; | ||
} | ||
|
||
private static MethodGenerator builderFor(ProcessedType pType) { | ||
return (typeUtils, structure, staticImports, logger) -> { | ||
var methodBuilder = getMethodBuilder(typeUtils, pType); | ||
|
||
immutableTypesBlock(structure, pType) | ||
.ifPresent(methodBuilder::addCode); | ||
subTypesBlocks(pType, structure) | ||
.forEach(methodBuilder::addCode); | ||
|
||
var returnStatement = isNull(pType.factoryClassName()) | ||
? CodeBlock.of("return $L", pType.argumentName()) | ||
: CodeBlock.of("return $T.$L($L)", pType.factoryClassName(), pType.factoryMethodName(), pType.argumentName()); | ||
methodBuilder.addStatement(returnStatement); | ||
|
||
return methodBuilder.build(); | ||
}; | ||
} | ||
|
||
private static MethodSpec.Builder getMethodBuilder(Types typeUtils, ProcessedType pType) { | ||
var typeVariableNames = pType.genericNames().stream() | ||
.map(TypeVariableName::get) | ||
.toList(); | ||
var typeName = getTypeName(typeUtils, pType, typeVariableNames); | ||
var parameterSpec = ParameterSpec.builder(typeName, pType.argumentName()).build(); | ||
|
||
var builder = MethodSpec.methodBuilder("_" + pType.factoryMethodName()) | ||
.addModifiers(PRIVATE, STATIC) | ||
.returns(typeName) | ||
.addParameter(parameterSpec); | ||
|
||
typeVariableNames.forEach(builder::addTypeVariable); | ||
|
||
return builder; | ||
} | ||
|
||
private static TypeName getTypeName(Types typeUtils, ProcessedType pType, List<TypeVariableName> typeVariableNames) { | ||
var collectionType = PROCESSED_TYPES.get(pType); | ||
|
||
if (typeVariableNames.isEmpty()) { | ||
return TypeName.get(collectionType); | ||
} | ||
|
||
var className = ClassName.get((TypeElement) typeUtils.asElement(collectionType)); | ||
|
||
return ParameterizedTypeName.get(className, typeVariableNames.toArray(TypeVariableName[]::new)); | ||
} | ||
|
||
private static Optional<CodeBlock> immutableTypesBlock(ProcessedTypesStructure structure, ProcessedType pType) { | ||
var immutableTypes = structure.getImmutableTypes(pType); | ||
if (immutableTypes.isEmpty()) { | ||
return Optional.empty(); | ||
} | ||
|
||
var ifFormat = new StringBuilder("if ("); | ||
var i = 0; | ||
for (var iterator = immutableTypes.iterator(); iterator.hasNext(); i++) { | ||
iterator.next(); | ||
String name = pType.argumentName(); | ||
|
||
ifFormat.append(name).append(" instanceof $T"); | ||
if (!pType.genericNames().isEmpty()) { | ||
var genericClause = pType.genericNames().stream() | ||
.collect(joining(",", "<", ">")); | ||
ifFormat.append(genericClause); | ||
} | ||
|
||
if (iterator.hasNext()) { | ||
ifFormat.append(i == 0 ? "\n$>$>|| " : "\n|| "); | ||
} | ||
} | ||
ifFormat.append(")"); | ||
|
||
var size = immutableTypes.size(); | ||
var block = CodeBlock.builder() | ||
.beginControlFlow(ifFormat.toString(), immutableTypes.toArray()) | ||
.addStatement(size > 1 ? "$<$<return $L" : "return $L", pType.argumentName()) | ||
.endControlFlow() | ||
.build(); | ||
|
||
return Optional.of(block); | ||
} | ||
|
||
private static List<CodeBlock> subTypesBlocks(ProcessedType pType, ProcessedTypesStructure structure) { | ||
return pType.directSubTypes().stream() | ||
.filter(structure::needsAdditionalMethod) | ||
.sorted(reverseOrder()) | ||
.map(subPType -> subTypeBlock(subPType, pType, structure)) | ||
.toList(); | ||
} | ||
|
||
private static CodeBlock subTypeBlock(ProcessedType pType, ProcessedType parent, ProcessedTypesStructure structure) { | ||
var argumentName = pType.argumentName(); | ||
var factoryClassName = pType.factoryClassName(); | ||
var factoryMethodName = pType.factoryMethodName(); | ||
|
||
var parentGenericNames = parent.genericNames(); | ||
var genericClause = parentGenericNames.isEmpty() || !pType.checkGenericInInstanceOf() | ||
? "" | ||
: parentGenericNames.stream().collect(joining(",", "<", ">")); | ||
var statement = structure.needsAdditionalMethod(pType) | ||
? CodeBlock.of("return _$L($L)", factoryMethodName, argumentName) | ||
: CodeBlock.of("return $T.$L($L)", factoryClassName, factoryMethodName, argumentName); | ||
|
||
return CodeBlock.builder() | ||
.beginControlFlow("if ($L instanceof $T$L $L)", parent.argumentName(), PROCESSED_TYPES.get(pType), genericClause, argumentName) | ||
.addStatement(statement) | ||
.endControlFlow() | ||
.build(); | ||
} | ||
|
||
@FunctionalInterface | ||
private interface MethodGenerator { | ||
MethodSpec generateMethod(Types typeUtils, | ||
ProcessedTypesStructure structure, | ||
StaticImports staticImports, | ||
Logger logger); | ||
} | ||
} |
Oops, something went wrong.