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 24, 2023
1 parent 5c6df5c commit 9a28deb
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 280 deletions.
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@

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.annotation.Nullable;
import javax.lang.model.type.TypeMirror;
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;
Expand All @@ -37,48 +37,78 @@
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 static final String ENUM_SET = "java.util.EnumSet";
private static final String GUAVA_IMMUTABLE_SET = "com.google.common.collect.ImmutableSet";
static final ClassName GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME
= ClassName.get("com.google.common.collect", "ImmutableSortedSet");

private static final String SORTED_SET = "java.util.SortedSet";
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 Set<String> COLLECTION_SUPER_TYPES = Set.of(

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> knownImmutableTypes;
private final Map<Class<?>, Boolean> userTypesCache = new HashMap<>();
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() {
knownImmutableTypes = new HashSet<>();
knownImmutableTypes.add(ENUM_SET);
knownImmutableTypes.add(GUAVA_IMMUTABLE_SET);
knownImmutableTypeNames = new HashSet<>();
knownImmutableTypeNames.add(ENUM_SET);
knownImmutableTypeNames.add(GUAVA_IMMUTABLE_SET);
}

@Override
public void init(String[] parameters) {
knownImmutableTypes.addAll(Arrays.asList(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(component -> toExtensionRecordComponent(typeUtils, knownImmutableTypes, component))
.map(componentBuilder::toExtensionRecordComponent)
.toList();
if (logger.isDebugEnabled()) {
logger.debug("Record components:\n" + getComponentsDebugInfo(recordComponents));
Expand All @@ -101,39 +131,29 @@ public CodeBlock suffixCompactConstructorContent(Context context, StaticImports
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();

var statementBuilder = new StatementBuilder(typeUtils, collectionTypes, staticImports, logger);

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

additionalMethodMarks = statementBuilder.additionalMethods();

return codeBuilder.build();
}

@Nullable
private CodeBlock statementFor(RecordComponent recordComponent, StaticImports staticImports) {
return statementBuilders().stream()
.map(builder -> builder.buildStatement(recordComponent, staticImports, logger))
.filter(Objects::nonNull)
.findFirst()
.orElseGet(() -> {
//TODO: log debug instead of exception
var message = "Unrecognized %s type of \"%s\" recordComponent".formatted(recordComponent.declaredType(), recordComponent.name());
logger.note(message);
return null;
});
@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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,67 +16,81 @@
* limitations under the License.
*/

import pl.com.labaj.autorecord.context.Logger;

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.Map;
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);
record RecordComponent(String name, boolean isNullable, TypeMirror declaredType, boolean shouldBeProcessed) {

var shouldBeProcessed = shouldBeProcessed(typeUtils, knownImmutableTypes, componentType, declaredType);
static String getComponentsDebugInfo(List<RecordComponent> recordComponents) {
return recordComponents.stream()
.map(RecordComponent::toString)
.collect(joining("\n"));
}

return new RecordComponent(component.name(), isNullable, declaredType, shouldBeProcessed);
@Override
public String toString() {
return name + " " + shouldBeProcessed + " : " + declaredType;
}

private static boolean shouldBeProcessed(Types typeUtils, Set<String> knownImmutableTypes, TypeMirror componentType, String declaredClass) {
if (componentType.getKind() != DECLARED) {
return true;
static class Builder {
private final Types typeUtils;
private final Set<TypeMirror> immutableTypes;
private final Set<TypeMirror> collectionTypes;
private final Logger logger;

Builder(Types typeUtils, Map<String, TypeMirror> immutableTypes, Map<String, TypeMirror> collectionTypes, Logger logger) {
this.typeUtils = typeUtils;
this.immutableTypes = Set.copyOf(immutableTypes.values());
this.collectionTypes = Set.copyOf(collectionTypes.values());
this.logger = logger;
}

if (knownImmutableTypes.contains(declaredClass)) {
return false;
RecordComponent toExtensionRecordComponent(pl.com.labaj.autorecord.context.RecordComponent component) {
var isNullable = component.isAnnotatedWith(Nullable.class);
var componentType = component.type();
var declaredType = typeUtils.erasure(componentType);
var shouldBeProcessed = shouldBeProcessed(componentType);

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

var superTypes = typeUtils.directSupertypes(componentType);
var hasImmutableSuperType = superTypes.stream()
.map(superType -> getQualifiedName((DeclaredType) superType))
.anyMatch(knownImmutableTypes::contains);
private boolean shouldBeProcessed(TypeMirror componentType) {
if (componentType.getKind() != DECLARED) {
return true;
}

if (hasImmutableSuperType) {
return false;
}
var rawComponentType = typeUtils.erasure(componentType);

return COLLECTION_SUPER_TYPES.contains(declaredClass);
}
var isImmutableType = immutableTypes.stream()
.anyMatch(immutableType -> typeUtils.isSameType(rawComponentType, immutableType));
logger.debug(componentType + " is immutable type: " + isImmutableType);

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

static String getComponentsDebugInfo(List<RecordComponent> recordComponents) {
return recordComponents.stream()
.map(RecordComponent::toString)
.collect(joining("\n"));
}
var hasImmutableSuperType = immutableTypes.stream()
.anyMatch(immutableType -> typeUtils.isSubtype(rawComponentType, immutableType));
logger.debug(componentType + " has super immutable type " + hasImmutableSuperType);

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

var isCollection = collectionTypes.stream()
.anyMatch(collectionType -> typeUtils.isSameType(rawComponentType, collectionType));
logger.debug(componentType + " is collection " + isCollection);

return isCollection;
}
}
}
Loading

0 comments on commit 9a28deb

Please sign in to comment.