-
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 cb1fdae
Showing
43 changed files
with
1,558 additions
and
37 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> |
91 changes: 91 additions & 0 deletions
91
...ions/src/main/java/pl/com/labaj/autorecord/extension/compact/AdditionalMethodBuilder.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,91 @@ | ||
package pl.com.labaj.autorecord.extension.compact; | ||
|
||
import com.squareup.javapoet.ClassName; | ||
import com.squareup.javapoet.MethodSpec; | ||
import com.squareup.javapoet.ParameterSpec; | ||
import com.squareup.javapoet.ParameterizedTypeName; | ||
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.type.TypeMirror; | ||
import javax.lang.model.util.Types; | ||
import java.util.Map; | ||
|
||
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.ENUM_SET; | ||
import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME; | ||
import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.SET; | ||
import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.SORTED_SET; | ||
|
||
class AdditionalMethodBuilder { | ||
private static final Map<String, MethodBuilder> METHOD_BUILDERS = Map.of( | ||
SET, builderForSet() | ||
); | ||
|
||
private final Types typeUtils; | ||
|
||
private final Map<String, TypeMirror> immutableTypes; | ||
private final Map<String, TypeMirror> collectionTypes; | ||
private final StaticImports staticImports; | ||
private final Logger logger; | ||
|
||
AdditionalMethodBuilder(Types typeUtils, | ||
Map<String, TypeMirror> immutableTypes, | ||
Map<String, TypeMirror> collectionTypes, | ||
StaticImports staticImports, | ||
Logger logger) { | ||
this.typeUtils = typeUtils; | ||
this.immutableTypes = immutableTypes; | ||
this.collectionTypes = collectionTypes; | ||
this.staticImports = staticImports; | ||
this.logger = logger; | ||
} | ||
|
||
MethodSpec buildMethodFor(String mark) { | ||
return METHOD_BUILDERS.get(mark).buildMethod(typeUtils, immutableTypes, collectionTypes, staticImports, logger); | ||
} | ||
|
||
private static MethodBuilder builderForSet() { | ||
return (typeUtils, immutableTypes, collectionTypes, staticImports, logger) -> { | ||
var typeVariableName = TypeVariableName.get("E"); | ||
var setType = collectionTypes.get(SET); | ||
var className = ClassName.get((TypeElement) typeUtils.asElement(setType)); | ||
var setTypeName = ParameterizedTypeName.get(className, typeVariableName); | ||
var parameterSpec = ParameterSpec.builder(setTypeName, "set").build(); | ||
var methodBuilder = MethodSpec.methodBuilder("_immutableSet") | ||
.addModifiers(PRIVATE, STATIC) | ||
.addTypeVariable(typeVariableName) | ||
.returns(setTypeName) | ||
.addParameter(parameterSpec); | ||
|
||
var enumSetType = immutableTypes.get(ENUM_SET); | ||
immutableTypes.values().stream() | ||
.filter(immutableType -> typeUtils.isSubtype(immutableType, setType)) | ||
.forEach(immutableType -> { | ||
var isEnumSet = typeUtils.isSameType(immutableType, enumSetType); | ||
methodBuilder.beginControlFlow("if (set instanceof $T<$L>)", immutableType, isEnumSet ? '?' : 'E') | ||
.addStatement("return set") | ||
.endControlFlow(); | ||
}); | ||
|
||
methodBuilder.beginControlFlow("if (set instanceof $T<E> sortedSet)", collectionTypes.get(SORTED_SET)) | ||
.addStatement("return $T.copyOfSorted(sortedSet)", GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME) | ||
.endControlFlow() | ||
.addStatement("return set"); | ||
|
||
return methodBuilder.build(); | ||
}; | ||
} | ||
|
||
@FunctionalInterface | ||
private static interface MethodBuilder { | ||
MethodSpec buildMethod(Types typeUtils, | ||
Map<String, TypeMirror> immutableTypes, | ||
Map<String, TypeMirror> collectionTypes, | ||
StaticImports staticImports, | ||
Logger logger); | ||
} | ||
} |
159 changes: 159 additions & 0 deletions
159
...rc/main/java/pl/com/labaj/autorecord/extension/compact/ImmutableCollectionsExtension.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,159 @@ | ||
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 pl.com.labaj.autorecord.context.Context; | ||
import pl.com.labaj.autorecord.context.Logger; | ||
import pl.com.labaj.autorecord.context.StaticImports; | ||
import pl.com.labaj.autorecord.extension.CompactConstructorExtension; | ||
|
||
import javax.lang.model.type.TypeMirror; | ||
import javax.lang.model.util.Types; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
import static java.util.Objects.nonNull; | ||
import static java.util.stream.Collectors.toMap; | ||
import static javax.lang.model.type.TypeKind.ARRAY; | ||
import static pl.com.labaj.autorecord.extension.compact.RecordComponent.getComponentsDebugInfo; | ||
|
||
public class ImmutableCollectionsExtension implements CompactConstructorExtension { | ||
|
||
static final ClassName GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME | ||
= ClassName.get("com.google.common.collect", "ImmutableSortedSet"); | ||
|
||
static final String NAVIGABLE_SET = "java.util.NavigableSet"; | ||
static final String SORTED_SET = "java.util.SortedSet"; | ||
static final String SET = "java.util.Set"; | ||
|
||
static final String ENUM_SET = "java.util.EnumSet"; | ||
private static final String GUAVA_IMMUTABLE_SET = "com.google.common.collect.ImmutableSet"; | ||
private static final Set<String> COLLECTION_NAMES = Set.of( | ||
NAVIGABLE_SET, | ||
SORTED_SET, | ||
SET | ||
); | ||
|
||
private Types typeUtils; | ||
private Logger logger; | ||
private final Set<String> knownImmutableTypeNames; | ||
private Map<String, TypeMirror> collectionTypes; | ||
private List<RecordComponent> componentsToProcess; | ||
private Set<String> additionalMethodMarks; | ||
private Map<String, TypeMirror> immutableTypes; | ||
|
||
public ImmutableCollectionsExtension() { | ||
knownImmutableTypeNames = new HashSet<>(); | ||
knownImmutableTypeNames.add(ENUM_SET); | ||
knownImmutableTypeNames.add(GUAVA_IMMUTABLE_SET); | ||
} | ||
|
||
@Override | ||
public void init(String[] parameters) { | ||
knownImmutableTypeNames.addAll(Arrays.asList(parameters)); | ||
} | ||
|
||
@Override | ||
public boolean shouldGenerateCompactConstructor(boolean isGeneratedByProcessor, Context context) { | ||
typeUtils = context.processingEnv().getTypeUtils(); | ||
logger = context.logger(); | ||
|
||
var elementUtils = context.processingEnv().getElementUtils(); | ||
|
||
record NameType(String name, TypeMirror type) {} | ||
immutableTypes = knownImmutableTypeNames.stream() | ||
.map(name -> { | ||
var typeElement = elementUtils.getTypeElement(name); | ||
return nonNull(typeElement) ? new NameType(name, typeUtils.erasure(typeElement.asType())) : null; | ||
}) | ||
.filter(Objects::nonNull) | ||
.collect(toMap( | ||
NameType::name, | ||
NameType::type | ||
)); | ||
collectionTypes = COLLECTION_NAMES.stream() | ||
.map(name -> { | ||
var typeElement = elementUtils.getTypeElement(name); | ||
return nonNull(typeElement) ? new NameType(name, typeUtils.erasure(typeElement.asType())) : null; | ||
}) | ||
.filter(Objects::nonNull) | ||
.collect(toMap( | ||
NameType::name, | ||
NameType::type | ||
)); | ||
|
||
var componentBuilder = new RecordComponent.Builder(typeUtils, immutableTypes, collectionTypes, logger); | ||
var recordComponents = context.components() | ||
.stream() | ||
.filter(recordComponent -> !recordComponent.type().getKind().isPrimitive()) | ||
.filter(recordComponent -> recordComponent.type().getKind() != ARRAY) | ||
.map(componentBuilder::toExtensionRecordComponent) | ||
.toList(); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug("Record components:\n" + getComponentsDebugInfo(recordComponents)); | ||
} | ||
|
||
if (recordComponents.isEmpty()) { | ||
return false; | ||
} | ||
|
||
componentsToProcess = recordComponents.stream() | ||
.filter(RecordComponent::shouldBeProcessed) | ||
.toList(); | ||
|
||
return !componentsToProcess.isEmpty(); | ||
} | ||
|
||
@Override | ||
public CodeBlock suffixCompactConstructorContent(Context context, StaticImports staticImports) { | ||
if (true) { //TODO: debugger info | ||
logger.note("Components to process:\n" + getComponentsDebugInfo(componentsToProcess)); | ||
} | ||
|
||
var codeBuilder = CodeBlock.builder(); | ||
|
||
var statementBuilder = new StatementBuilder(typeUtils, collectionTypes, staticImports, logger); | ||
|
||
componentsToProcess.stream() | ||
.map(statementBuilder::statementFor) | ||
.filter(Objects::nonNull) | ||
.forEach(codeBuilder::addStatement); | ||
|
||
additionalMethodMarks = statementBuilder.additionalMethods(); | ||
|
||
return codeBuilder.build(); | ||
} | ||
|
||
@Override | ||
public List<MethodSpec> additionalMethodsToSupportCompactConstructor(Context context, StaticImports staticImports) { | ||
if (additionalMethodMarks.isEmpty()) { | ||
return List.of(); | ||
} | ||
|
||
var additionalMethodsBuilder = new AdditionalMethodBuilder(typeUtils, immutableTypes, collectionTypes, staticImports, logger); | ||
return additionalMethodMarks.stream() | ||
.map(additionalMethodsBuilder::buildMethodFor) | ||
.toList(); | ||
} | ||
} |
Oops, something went wrong.