Skip to content

Commit

Permalink
To not forget
Browse files Browse the repository at this point in the history
  • Loading branch information
pawellabaj committed Jul 23, 2023
1 parent a9ea65a commit 5c6df5c
Show file tree
Hide file tree
Showing 30 changed files with 530 additions and 266 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ It provides an easy way to avoid writing repetitive boilerplate code. It generat
* [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:
Expand Down
6 changes: 4 additions & 2 deletions extensions/immutable-collections/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<parent>
<groupId>pl.com.labaj</groupId>
<artifactId>auto-record-project</artifactId>
<version>2.1.0</version>
<!-- <version>2.1.1-SNAPSHOT</version>-->
<!-- <version>2.1.0</version>-->
<version>2.1.1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

Expand All @@ -25,9 +25,11 @@
<groupId>pl.com.labaj</groupId>
<artifactId>auto-record</artifactId>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,105 +16,124 @@
* limitations under the License.
*/

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
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 pl.com.labaj.autorecord.processor.AutoRecordProcessorException;

import javax.annotation.Nullable;
import javax.lang.model.util.Types;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static java.util.stream.Collectors.toSet;
import static javax.lang.model.type.TypeKind.DECLARED;
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;
import static pl.com.labaj.autorecord.extension.compact.RecordComponent.toExtensionRecordComponent;
import static pl.com.labaj.autorecord.extension.compact.StatementBuilders.statementBuilders;

public class ImmutableCollectionsExtension implements CompactConstructorExtension {

private String[] classNames;
private Set<Class<?>> userImmutableTypes;
private final Map<Class<?>, Boolean> userTypesCache = new HashMap<>();
private List<Component> components;
private static final String ENUM_SET = "java.util.EnumSet";
private static final String GUAVA_IMMUTABLE_SET = "com.google.common.collect.ImmutableSet";

private static final String SORTED_SET = "java.util.SortedSet";
static final String SET = "java.util.Set";
static final Set<String> COLLECTION_SUPER_TYPES = Set.of(
SORTED_SET,
SET
);

private Types typeUtils;
private Logger logger;
private final Set<String> knownImmutableTypes;
private final Map<Class<?>, Boolean> userTypesCache = new HashMap<>();
private List<RecordComponent> componentsToProcess;

public ImmutableCollectionsExtension() {
knownImmutableTypes = new HashSet<>();
knownImmutableTypes.add(ENUM_SET);
knownImmutableTypes.add(GUAVA_IMMUTABLE_SET);
}

@Override
public void setParameters(String[] parameters) {
classNames = parameters;
public void init(String[] parameters) {
knownImmutableTypes.addAll(Arrays.asList(parameters));
}

@Override
public boolean shouldGenerate(boolean isGeneratedByProcessor, Context context) {
public boolean shouldGenerateCompactConstructor(boolean isGeneratedByProcessor, Context context) {
typeUtils = context.processingEnv().getTypeUtils();
logger = context.logger();
var collectionTypeComponents = context.components()

var recordComponents = context.components()
.stream()
.filter(component -> component.type().getKind() == DECLARED)
.map(Component::from)
.filter(Component::isCollectionType)
.filter(recordComponent -> !recordComponent.type().getKind().isPrimitive())
.filter(recordComponent -> recordComponent.type().getKind() != ARRAY)
.map(component -> toExtensionRecordComponent(typeUtils, knownImmutableTypes, component))
.toList();
logger.debug("Collection type components: " + collectionTypeComponents);
if (logger.isDebugEnabled()) {
logger.debug("Record components:\n" + getComponentsDebugInfo(recordComponents));
}

if (collectionTypeComponents.isEmpty()) {
if (recordComponents.isEmpty()) {
return false;
}

userImmutableTypes = Arrays.stream(classNames)
.map(Utils::loadClass)
.collect(toSet());

components = collectionTypeComponents.stream()
.filter(this::isNotUserType)
componentsToProcess = recordComponents.stream()
.filter(RecordComponent::shouldBeProcessed)
.toList();

return !components.isEmpty();
return !componentsToProcess.isEmpty();
}

@Override
public CodeBlock suffixCompactConstructorContent(Context context, StaticImports staticImports) {
logger.debug("Components to proceed: " + components);
if (true) { //TODO: debugger info
logger.note("Components to process:\n" + getComponentsDebugInfo(componentsToProcess));
}

var elementUtils = context.processingEnv().getElementUtils();

record NameClassName(String name, ClassName className){}

var classNameMap = knownImmutableTypes.stream()
.map(s -> {
var typeElement = elementUtils.getTypeElement(s);
return nonNull(typeElement) ? new NameClassName(s, ClassName.get(typeElement)) : null;
})
.filter(Objects::nonNull)
.collect(toMap(NameClassName::name, NameClassName::className));

var codeBuilder = CodeBlock.builder();

components.stream()
.map(component -> statementFor(component, staticImports))
componentsToProcess.stream()
.map(recordComponent -> statementFor(recordComponent, staticImports))
.filter(Objects::nonNull)
.forEach(codeBuilder::addStatement);

return codeBuilder.build();
}

@Nullable
private CodeBlock statementFor(Component component, StaticImports staticImports) {
private CodeBlock statementFor(RecordComponent recordComponent, StaticImports staticImports) {
return statementBuilders().stream()
.map(builder -> builder.buildStatement(component, staticImports, logger))
.map(builder -> builder.buildStatement(recordComponent, staticImports, logger))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> {
.orElseGet(() -> {
//TODO: log debug instead of exception
var message = "Unrecognized %s type of \"%s\" component".formatted(component.componentClass(), component.name());
return new AutoRecordProcessorException(message);
var message = "Unrecognized %s type of \"%s\" recordComponent".formatted(recordComponent.declaredType(), recordComponent.name());
logger.note(message);
return null;
});
}

private boolean isNotUserType(Component component) {
var componentClass = component.componentClass();

boolean isUserType = userTypesCache.computeIfAbsent(componentClass, this::isUserType);

if (isUserType) {
logger.debug("\"" + component.name() + "\" is " + componentClass + " user type");
}

return !isUserType;
}

private boolean isUserType(Class<?> aClass) {
return userImmutableTypes.stream()
.anyMatch(userType -> userType.isAssignableFrom(aClass));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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 javax.annotation.Nullable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import java.util.List;
import java.util.Set;

import static java.util.stream.Collectors.joining;
import static javax.lang.model.type.TypeKind.DECLARED;
import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.COLLECTION_SUPER_TYPES;

record RecordComponent(String name, boolean isNullable, String declaredType, boolean shouldBeProcessed) {

static RecordComponent toExtensionRecordComponent(Types typeUtils,
Set<String> knownImmutableTypes,
pl.com.labaj.autorecord.context.RecordComponent component) {
var isNullable = component.isAnnotatedWith(Nullable.class);

var componentType = component.type();

var declaredType = getQualifiedName((DeclaredType) componentType);

var shouldBeProcessed = shouldBeProcessed(typeUtils, knownImmutableTypes, componentType, declaredType);

return new RecordComponent(component.name(), isNullable, declaredType, shouldBeProcessed);
}

private static boolean shouldBeProcessed(Types typeUtils, Set<String> knownImmutableTypes, TypeMirror componentType, String declaredClass) {
if (componentType.getKind() != DECLARED) {
return true;
}

if (knownImmutableTypes.contains(declaredClass)) {
return false;
}

var superTypes = typeUtils.directSupertypes(componentType);
var hasImmutableSuperType = superTypes.stream()
.map(superType -> getQualifiedName((DeclaredType) superType))
.anyMatch(knownImmutableTypes::contains);

if (hasImmutableSuperType) {
return false;
}

return COLLECTION_SUPER_TYPES.contains(declaredClass);
}

private static String getQualifiedName(DeclaredType t) {
return ((TypeElement) t.asElement()).getQualifiedName().toString();
}

static String getComponentsDebugInfo(List<RecordComponent> recordComponents) {
return recordComponents.stream()
.map(RecordComponent::toString)
.collect(joining("\n"));
}

@Override
public String toString() {
return name + " " + shouldBeProcessed + " : " + declaredType;
}
}
Loading

0 comments on commit 5c6df5c

Please sign in to comment.