Refactoring of constructors for generated entities

This commit is contained in:
Michal Hajas 2021-12-20 16:04:56 +01:00 committed by Hynek Mlnařík
parent 6d0b551b5e
commit 96b2669a00
11 changed files with 89 additions and 91 deletions

View file

@ -50,6 +50,7 @@ import javax.lang.model.SourceVersion;
import static org.keycloak.models.map.processor.FieldAccessorType.GETTER;
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
import static org.keycloak.models.map.processor.Util.isMapType;
import static org.keycloak.models.map.processor.Util.singularToPlural;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor {
@ -123,11 +124,11 @@ public abstract class AbstractGenerateEntityImplementationsProcessor extends Abs
// Merge plurals with singulars
methodsPerAttribute.keySet().stream()
.filter(key -> methodsPerAttribute.containsKey(key + "s"))
.filter(key -> methodsPerAttribute.containsKey(singularToPlural(key)))
.collect(Collectors.toSet())
.forEach(key -> {
HashSet<ExecutableElement> removed = methodsPerAttribute.remove(key);
methodsPerAttribute.get(key + "s").addAll(removed);
methodsPerAttribute.get(singularToPlural(key)).addAll(removed);
});
return methodsPerAttribute;

View file

@ -26,6 +26,7 @@ import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
import static org.keycloak.models.map.processor.Util.pluralToSingular;
/**
*
@ -52,7 +53,7 @@ enum FieldAccessorType {
COLLECTION_ADD {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "add" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
@ -63,7 +64,7 @@ enum FieldAccessorType {
COLLECTION_DELETE {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
String fieldNameSingular = pluralToSingular(fieldName);
String removeFromCollection = "remove" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(removeFromCollection, method.getSimpleName().toString())
@ -74,7 +75,7 @@ enum FieldAccessorType {
MAP_ADD {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "set" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
@ -86,7 +87,7 @@ enum FieldAccessorType {
MAP_GET {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "get" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())

View file

@ -171,7 +171,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
pw.println(" return " + types.erasure(firstParameterType) + ".class;");
pw.println(" }");
});
FieldAccessorType.getMethod(FieldAccessorType.MAP_ADD, methods, fieldName, types, fieldType).ifPresent(method -> {
TypeMirror firstParameterType = method.getParameters().get(0).asType();
TypeMirror secondParameterType = method.getParameters().get(1).asType();
@ -182,7 +182,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
pw.println(" return " + types.erasure(secondParameterType) + ".class;");
pw.println(" }");
});
for (ExecutableElement ee : methods) {
FieldAccessorType fat = FieldAccessorType.determineType(ee, fieldName, types, fieldType);
printMethodBody(pw, fat, ee, className, fieldType);
@ -265,6 +265,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
.map(ExecutableElement::getReturnType)
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone;
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapImplClassName);
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
@ -283,28 +284,26 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
.map(ExecutableElement.class::cast)
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
.forEach((ExecutableElement ee) -> {
if (hasDeepClone || ! needsDeepClone) {
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " " + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { super(" + ee.getParameters() + "); }"
);
} else if (needsDeepClone) {
// Create constructor and initialize cloner to DUMB_CLONER if necessary
if (usingGeneratedCloner) {
pw.println(" /**");
pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + mapSimpleClassName + "(DeepCloner)} variant instead");
pw.println(" */");
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " "
+ mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { this(DeepCloner.DUMB_CLONER" + (ee.getParameters().isEmpty() ? "" : ", ") + ee.getParameters() + "); }"
);
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " "
+ mapSimpleClassName + "(DeepCloner cloner" + (ee.getParameters().isEmpty() ? "" : ", ") + methodParameters(ee.getParameters()) + ") { super(" + ee.getParameters() + "); this.cloner = cloner; }"
);
}
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " " + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") {"
);
pw.println(" super(" + ee.getParameters() + ");");
if (usingGeneratedCloner) pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
pw.println(" }");
});
pw.println(" "
+ "public "
+ mapSimpleClassName + "(DeepCloner cloner) { super(); " + (!usingGeneratedCloner ? "" : "this.cloner = cloner;") + "}"
);
// equals, hashCode, toString
pw.println(" @Override public boolean equals(Object o) {");
pw.println(" if (o == this) return true; ");
@ -339,7 +338,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
pw.println(" }");
// deepClone
if (! hasDeepClone && needsDeepClone) {
if (usingGeneratedCloner) {
pw.println(" private final DeepCloner cloner;");
pw.println(" public <V> V deepClone(V obj) {");
pw.println(" return cloner.from(obj);");
@ -363,7 +362,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
if (parentMethod.isPresent()) {
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
} else if (fat != FieldAccessorType.UNKNOWN && ! printMethodBody(pw, fat, method, "f" + me.getKey(), fieldType)) {
} else if (fat == FieldAccessorType.UNKNOWN || ! printMethodBody(pw, fat, method, "f" + me.getKey(), fieldType)) {
processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
}
}
@ -446,10 +445,12 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
pw.println(" }");
return true;
case COLLECTION_DELETE:
boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID;
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" if (" + fieldName + " == null) { return; }");
pw.println(" if (" + fieldName + " == null) { return" + (needsReturn ? " false" : "") + "; }");
pw.println(" boolean removed = " + fieldName + ".remove(p0)" + ("java.util.Map".equals(typeElement.getQualifiedName().toString()) ? " != null" : "") + ";");
pw.println(" updated |= removed;");
if (needsReturn) pw.println(" return removed;");
pw.println(" }");
return true;
case MAP_ADD:

View file

@ -104,6 +104,7 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
.map(ExecutableElement::getReturnType)
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone;
boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
JavaFileObject file = processingEnv.getFiler().createSourceFile(hotRodImplClassName);
@ -132,45 +133,43 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
.map(ExecutableElement.class::cast)
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
.forEach((ExecutableElement ee) -> {
if (hasDeepClone || ! needsDeepClone) {
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " " + hotRodSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") {"
);
pw.println(" super(" + ee.getParameters() + ");");
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
pw.println(" }");
} else if (needsDeepClone) {
// Create constructor and initialize cloner to DUMB_CLONER if necessary
if (usingGeneratedCloner) {
pw.println(" /**");
pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + hotRodSimpleClassName + "(DeepCloner)} variant instead");
pw.println(" */");
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " "
+ hotRodSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { this(DeepCloner.DUMB_CLONER" + (ee.getParameters().isEmpty() ? "" : ", ") + ee.getParameters() + "); }"
);
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " "
+ hotRodSimpleClassName + "(DeepCloner cloner" + (ee.getParameters().isEmpty() ? "" : ", ") + methodParameters(ee.getParameters()) + ") {"
);
pw.println(" super(" + ee.getParameters() + ");");
pw.println(" this.cloner = cloner;");
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
pw.println(" }");
}
pw.println(" "
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ " " + hotRodSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") {"
);
pw.println(" super(" + ee.getParameters() + ");");
if (usingGeneratedCloner) pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
pw.println(" }");
});
// Add constructor for setting HotRodEntity
if (usingGeneratedCloner) {
pw.println(" /**");
pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + hotRodSimpleClassName + "(DeepCloner)} variant instead");
pw.println(" */");
}
pw.println(" " +
"public " + hotRodSimpleClassName + "(" + className + " " + ENTITY_VARIABLE + ") {"
);
pw.println(" this." + ENTITY_VARIABLE + " = " + ENTITY_VARIABLE + ";");
if (! hasDeepClone && needsDeepClone) {
if (usingGeneratedCloner) {
pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
}
pw.println(" }");
pw.println(" public " + hotRodSimpleClassName + "(DeepCloner cloner) {");
pw.println(" super();");
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
if (usingGeneratedCloner) pw.println(" this.cloner = cloner;");
pw.println(" }");
// equals, hashCode, toString
pw.println(" @Override public boolean equals(Object o) {");
pw.println(" if (o == this) return true; ");

View file

@ -99,4 +99,11 @@ public class Util {
.findAny();
}
public static String singularToPlural(String word) {
return word.endsWith("y") ? word.substring(0, word.length() -1) + "ies" : word + "s";
}
public static String pluralToSingular(String word) {
return word.endsWith("ies") ? word.substring(0, word.length() - 3) + "y" : word.endsWith("s") ? word.substring(0, word.length() - 1) : word;
}
}

View file

@ -49,9 +49,9 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
private static final Logger LOG = Logger.getLogger(HotRodMapStorageProviderFactory.class);
private final static DeepCloner CLONER = new DeepCloner.Builder()
.constructorDC(MapClientEntity.class, HotRodClientEntityDelegate::new)
.constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
.constructorDC(MapGroupEntity.class, HotRodGroupEntityDelegate::new)
.constructor(MapGroupEntity.class, HotRodGroupEntityDelegate::new)
.build();
public static final Map<Class<?>, HotRodEntityDescriptor<?, ?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();

View file

@ -42,6 +42,8 @@ import org.hibernate.annotations.TypeDefs;
import org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import static org.keycloak.models.map.storage.jpa.client.JpaClientMapStorage.SUPPORTED_VERSION;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
@Entity
@ -87,6 +89,10 @@ public class JpaClientEntity extends AbstractClientEntity implements Serializabl
this.metadata = new JpaClientMetadata();
}
public JpaClientEntity(DeepCloner cloner) {
this.metadata = new JpaClientMetadata(cloner);
}
/**
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
* It is used to select client without metadata(json) field.

View file

@ -18,9 +18,18 @@ package org.keycloak.models.map.storage.jpa.client.entity;
import java.io.Serializable;
import org.keycloak.models.map.client.MapClientEntityImpl;
import org.keycloak.models.map.common.DeepCloner;
public class JpaClientMetadata extends MapClientEntityImpl implements Serializable {
public JpaClientMetadata(DeepCloner cloner) {
super(cloner);
}
public JpaClientMetadata() {
super();
}
private Integer entityVersion;
public Integer getEntityVersion() {

View file

@ -111,7 +111,6 @@ public class DeepCloner {
* Builder for the {@code DeepCloner} helper class.
*/
public static class Builder {
private final Map<Class<?>, Supplier<?>> parameterlessConstructors = new HashMap<>();
private final Map<Class<?>, Function<DeepCloner, ?>> constructors = new HashMap<>();
private final Map<Class<?>, Cloner<?>> clonersWithId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.CLONERS_WITH_ID);
private final Map<Class<?>, Cloner<?>> clonersWithoutId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.CLONERS_WITHOUT_ID);
@ -124,7 +123,7 @@ public class DeepCloner {
* @return
*/
public DeepCloner build() {
return new DeepCloner(parameterlessConstructors, constructors, delegateCreators, entityFieldDelegateCreators, clonersWithId, clonersWithoutId, genericCloner);
return new DeepCloner(constructors, delegateCreators, entityFieldDelegateCreators, clonersWithId, clonersWithoutId, genericCloner);
}
private <V> void forThisClassAndAllMarkedParentsAndInterfaces(Class<V> rootClazz, Consumer<Class<?>> action) {
@ -149,22 +148,6 @@ public class DeepCloner {
}
}
/**
* Adds a method, often a constructor, that instantiates a record of type {@code V}.
*
* @param <V> Class or interface that would be instantiated by the given methods
* @param clazz Class or interface that would be instantiated by the given methods
* @param constructorNoParameters Parameterless function that creates a new instance of class {@code V}.
* If {@code null}, parameterless constructor is not available.
* @return This builder.
*/
public <V> Builder constructor(Class<V> clazz, Supplier<? extends V> constructorNoParameters) {
if (constructorNoParameters != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.parameterlessConstructors.put(cl, constructorNoParameters));
}
return this;
}
/**
* Adds a method, often a constructor, that instantiates a record of type {@code V}.
*
@ -174,7 +157,7 @@ public class DeepCloner {
* If {@code null}, such a single-parameter constructor is not available.
* @return This builder.
*/
public <V> Builder constructorDC(Class<V> clazz, Function<DeepCloner, ? extends V> constructor) {
public <V> Builder constructor(Class<V> clazz, Function<DeepCloner, ? extends V> constructor) {
if (constructor != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.constructors.put(cl, constructor));
}
@ -267,7 +250,6 @@ public class DeepCloner {
private static final Logger LOG = Logger.getLogger(DeepCloner.class);
private final Map<Class<?>, Supplier<?>> parameterlessConstructors;
private final Map<Class<?>, Function<DeepCloner, ?>> constructors;
private final Map<Class<?>, Cloner<?>> clonersWithId;
private final Map<Class<?>, Cloner<?>> clonersWithoutId;
@ -276,14 +258,12 @@ public class DeepCloner {
private final Cloner<?> genericCloner;
private final Map<Class<?>, Object> emptyInstances = new HashMap<>(AutogeneratedCloners.EMPTY_INSTANCES);
private DeepCloner(Map<Class<?>, Supplier<?>> parameterlessConstructors,
Map<Class<?>, Function<DeepCloner, ?>> constructors,
private DeepCloner(Map<Class<?>, Function<DeepCloner, ?>> constructors,
Map<Class<?>, DelegateCreator<?>> delegateCreators,
Map<Class<?>, EntityFieldDelegateCreator<?>> entityFieldDelegateCreators,
Map<Class<?>, Cloner<?>> clonersWithId,
Map<Class<?>, Cloner<?>> clonersWithoutId,
Cloner<?> genericCloner) {
this.parameterlessConstructors = parameterlessConstructors;
this.constructors = constructors;
this.clonersWithId = clonersWithId;
this.clonersWithoutId = clonersWithoutId;
@ -377,16 +357,10 @@ public class DeepCloner {
@SuppressWarnings("unchecked")
Function<DeepCloner, V> c = (Function<DeepCloner, V>) getFromClassRespectingHierarchy(this.constructors, clazz);
if (c == null) {
@SuppressWarnings("unchecked")
Supplier<V> s = (Supplier<V>) getFromClassRespectingHierarchy(this.parameterlessConstructors, clazz);
if (s == null) {
try {
res = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
res = null;
}
} else {
res = s.get();
try {
res = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
res = null;
}
} else {
res = c.apply(this);

View file

@ -80,10 +80,10 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
private final static DeepCloner CLONER = new DeepCloner.Builder()
.genericCloner(Serialization::from)
.constructorDC(MapClientEntityImpl.class, MapClientEntityImpl::new)
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
.constructorDC(MapGroupEntityImpl.class, MapGroupEntityImpl::new)
.constructorDC(MapRoleEntityImpl.class, MapRoleEntityImpl::new)
.constructor(MapClientEntityImpl.class, MapClientEntityImpl::new)
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
.constructor(MapGroupEntityImpl.class, MapGroupEntityImpl::new)
.constructor(MapRoleEntityImpl.class, MapRoleEntityImpl::new)
.build();
private static final Map<String, StringKeyConvertor> KEY_CONVERTORS = new HashMap<>();

View file

@ -38,7 +38,7 @@ import static org.keycloak.models.map.common.DeepCloner.DUMB_CLONER;
public class MapClientEntityClonerTest {
private final static DeepCloner CLONER = new DeepCloner.Builder()
.constructorDC(MapClientEntityImpl.class, MapClientEntityImpl::new)
.constructor(MapClientEntityImpl.class, MapClientEntityImpl::new)
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
.build();