Skip to content

Commit d977e74

Browse files
committed
creates support for generics in mapped superclasses
1 parent 2231ef9 commit d977e74

File tree

1 file changed

+186
-13
lines changed

1 file changed

+186
-13
lines changed

querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java

Lines changed: 186 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
import javax.lang.model.element.ExecutableElement;
1212
import javax.lang.model.element.Modifier;
1313
import javax.lang.model.element.TypeElement;
14+
import javax.lang.model.element.TypeParameterElement;
1415
import javax.lang.model.element.VariableElement;
1516
import javax.lang.model.type.DeclaredType;
1617
import javax.lang.model.type.TypeKind;
1718
import javax.lang.model.type.TypeMirror;
19+
import javax.lang.model.type.TypeVariable;
1820
import javax.lang.model.util.ElementFilter;
1921
import javax.lang.model.util.Elements;
2022
import javax.lang.model.util.Types;
@@ -28,6 +30,7 @@
2830
import java.io.Reader;
2931
import java.nio.file.NoSuchFileException;
3032
import java.util.ArrayList;
33+
import java.util.HashMap;
3134
import java.util.HashSet;
3235
import java.util.List;
3336
import java.util.Map;
@@ -88,6 +91,11 @@ class ProcessingContext implements Constants {
8891
*/
8992
private String factoryPackage;
9093

94+
/**
95+
* Cache of resolved generic type parameters for mapped superclasses.
96+
*/
97+
private final Map<String, Map<String, TypeMirror>> genericTypeCache = new HashMap<>();
98+
9199
ProcessingContext(ProcessingEnvironment processingEnv) {
92100
this.processingEnv = processingEnv;
93101
this.typeUtils = processingEnv.getTypeUtils();
@@ -122,29 +130,70 @@ TypeElement componentAnnotation() {
122130
*/
123131
List<VariableElement> allFields(Element element) {
124132
List<VariableElement> list = new ArrayList<>();
125-
gatherProperties(list, element);
133+
gatherProperties(list, element, null);
126134
return list;
127135
}
128136

129137
/**
130138
* Recursively gather all the fields (properties) for the given bean element.
131139
*/
132-
private void gatherProperties(List<VariableElement> fields, Element element) {
140+
private void gatherProperties(List<VariableElement> fields, Element element, Map<String, TypeMirror> typeParameterMap) {
133141
TypeElement typeElement = (TypeElement) element;
134142
TypeMirror superclass = typeElement.getSuperclass();
135143
Element mappedSuper = typeUtils.asElement(superclass);
136144
if (isMappedSuperOrInheritance(mappedSuper)) {
137-
gatherProperties(fields, mappedSuper);
145+
// Resolve generic type parameters for the superclass
146+
Map<String, TypeMirror> superTypeParameterMap = resolveGenericTypes(superclass, typeParameterMap);
147+
gatherProperties(fields, mappedSuper, superTypeParameterMap);
138148
}
139149

140150
List<VariableElement> allFields = ElementFilter.fieldsIn(element.getEnclosedElements());
141151
for (VariableElement field : allFields) {
142152
if (!ignoreField(field)) {
143-
fields.add(field);
153+
// Create a wrapper that holds both the field and its resolved type context
154+
if (typeParameterMap != null && !typeParameterMap.isEmpty()) {
155+
fields.add(new ResolvedVariableElement(field, typeParameterMap));
156+
} else {
157+
fields.add(field);
158+
}
144159
}
145160
}
146161
}
147162

163+
/**
164+
* Wrapper class to hold a VariableElement along with its type parameter resolution context.
165+
* This allows us to pass resolved generic type information along with field elements.
166+
*/
167+
private static class ResolvedVariableElement implements VariableElement {
168+
private final VariableElement delegate;
169+
private final Map<String, TypeMirror> typeParameterMap;
170+
171+
ResolvedVariableElement(VariableElement delegate, Map<String, TypeMirror> typeParameterMap) {
172+
this.delegate = delegate;
173+
this.typeParameterMap = new HashMap<>(typeParameterMap);
174+
}
175+
176+
public Map<String, TypeMirror> getTypeParameterMap() {
177+
return typeParameterMap;
178+
}
179+
180+
// Delegate all VariableElement methods to the original element
181+
@Override public TypeMirror asType() { return delegate.asType(); }
182+
@Override public ElementKind getKind() { return delegate.getKind(); }
183+
@Override public Set<Modifier> getModifiers() { return delegate.getModifiers(); }
184+
@Override public javax.lang.model.element.Name getSimpleName() { return delegate.getSimpleName(); }
185+
@Override public Element getEnclosingElement() { return delegate.getEnclosingElement(); }
186+
@Override public List<? extends Element> getEnclosedElements() { return delegate.getEnclosedElements(); }
187+
@Override public List<? extends AnnotationMirror> getAnnotationMirrors() { return delegate.getAnnotationMirrors(); }
188+
@Override public <A extends java.lang.annotation.Annotation> A getAnnotation(Class<A> annotationType) { return delegate.getAnnotation(annotationType); }
189+
@Override public <A extends java.lang.annotation.Annotation> A[] getAnnotationsByType(Class<A> annotationType) { return delegate.getAnnotationsByType(annotationType); }
190+
@Override public Object getConstantValue() { return delegate.getConstantValue(); }
191+
@Override public <R, P> R accept(javax.lang.model.element.ElementVisitor<R, P> v, P p) { return delegate.accept(v, p); }
192+
@Override public String toString() { return delegate.toString(); }
193+
@Override public boolean equals(Object obj) { return delegate.equals(obj); }
194+
@Override public int hashCode() { return delegate.hashCode(); }
195+
}
196+
148197
/**
149198
* Not interested in static, transient or Ebean internal fields.
150199
*/
@@ -247,18 +296,136 @@ private String trimAnnotations(String type) {
247296
return trimAnnotations(remainder);
248297
}
249298

299+
/**
300+
* Resolve generic type parameters for a superclass in the context of a subclass.
301+
* This method maps generic type parameters from the superclass to their actual types
302+
* as specified in the subclass declaration.
303+
*
304+
* @param superclassType The superclass type mirror (may be parameterized)
305+
* @param parentTypeParameterMap Existing type parameter mappings from parent context
306+
* @return A map of type parameter names to their resolved TypeMirror instances
307+
*/
308+
private Map<String, TypeMirror> resolveGenericTypes(TypeMirror superclassType, Map<String, TypeMirror> parentTypeParameterMap) {
309+
Map<String, TypeMirror> typeParameterMap = new HashMap<>();
310+
311+
// If we have parent type parameter mappings, inherit them
312+
if (parentTypeParameterMap != null) {
313+
typeParameterMap.putAll(parentTypeParameterMap);
314+
}
315+
316+
if (superclassType.getKind() != TypeKind.DECLARED) {
317+
return typeParameterMap;
318+
}
319+
320+
DeclaredType declaredSuperclass = (DeclaredType) superclassType;
321+
TypeElement superclassElement = (TypeElement) declaredSuperclass.asElement();
322+
323+
// Get the type parameters from the superclass definition
324+
List<? extends TypeParameterElement> typeParameters = superclassElement.getTypeParameters();
325+
326+
// Get the actual type arguments used in this specific inheritance
327+
List<? extends TypeMirror> typeArguments = declaredSuperclass.getTypeArguments();
328+
329+
// Map each type parameter to its actual type
330+
for (int i = 0; i < typeParameters.size() && i < typeArguments.size(); i++) {
331+
String parameterName = typeParameters.get(i).getSimpleName().toString();
332+
TypeMirror actualType = typeArguments.get(i);
333+
334+
// If the actual type is itself a type variable, try to resolve it from parent context
335+
if (actualType.getKind() == TypeKind.TYPEVAR && parentTypeParameterMap != null) {
336+
TypeVariable typeVar = (TypeVariable) actualType;
337+
String varName = typeVar.asElement().getSimpleName().toString();
338+
TypeMirror resolvedType = parentTypeParameterMap.get(varName);
339+
if (resolvedType != null) {
340+
actualType = resolvedType;
341+
}
342+
}
343+
344+
typeParameterMap.put(parameterName, actualType);
345+
}
346+
347+
// Cache the resolved types for performance
348+
String cacheKey = superclassElement.getQualifiedName().toString();
349+
genericTypeCache.put(cacheKey, new HashMap<>(typeParameterMap));
350+
351+
return typeParameterMap;
352+
}
353+
354+
/**
355+
* Resolve a field's type in the context of generic type parameters.
356+
* If the field type is a type variable, resolve it to the actual type.
357+
*
358+
* @param fieldType The original field type
359+
* @param typeParameterMap The resolved type parameter mappings
360+
* @return The resolved type mirror, or the original type if no resolution needed
361+
*/
362+
private TypeMirror resolveFieldType(TypeMirror fieldType, Map<String, TypeMirror> typeParameterMap) {
363+
if (typeParameterMap == null || typeParameterMap.isEmpty()) {
364+
return fieldType;
365+
}
366+
367+
if (fieldType.getKind() == TypeKind.TYPEVAR) {
368+
TypeVariable typeVar = (TypeVariable) fieldType;
369+
String parameterName = typeVar.asElement().getSimpleName().toString();
370+
TypeMirror resolvedType = typeParameterMap.get(parameterName);
371+
if (resolvedType != null) {
372+
return resolvedType;
373+
}
374+
}
375+
376+
// Handle parameterized types (e.g., List<T> where T needs resolution)
377+
if (fieldType.getKind() == TypeKind.DECLARED) {
378+
DeclaredType declaredType = (DeclaredType) fieldType;
379+
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
380+
381+
if (!typeArguments.isEmpty()) {
382+
List<TypeMirror> resolvedArguments = new ArrayList<>();
383+
boolean hasChanges = false;
384+
385+
for (TypeMirror typeArg : typeArguments) {
386+
TypeMirror resolvedArg = resolveFieldType(typeArg, typeParameterMap);
387+
resolvedArguments.add(resolvedArg);
388+
if (resolvedArg != typeArg) {
389+
hasChanges = true;
390+
}
391+
}
392+
393+
if (hasChanges) {
394+
// Create a new DeclaredType with resolved type arguments
395+
TypeElement typeElement = (TypeElement) declaredType.asElement();
396+
return typeUtils.getDeclaredType(typeElement, resolvedArguments.toArray(new TypeMirror[0]));
397+
}
398+
}
399+
}
400+
401+
return fieldType;
402+
}
403+
250404
PropertyType getPropertyType(VariableElement field) {
405+
// Check if this is a resolved field from a generic mapped superclass
406+
Map<String, TypeMirror> typeParameterMap = null;
407+
if (field instanceof ResolvedVariableElement) {
408+
typeParameterMap = ((ResolvedVariableElement) field).getTypeParameterMap();
409+
}
410+
251411
boolean toMany = dbToMany(field);
252412
if (dbJsonField(field)) {
253413
return propertyTypeMap.getDbJsonType();
254414
}
255415
if (dbArrayField(field)) {
256416
// get generic parameter type
257417
DeclaredType declaredType = (DeclaredType) field.asType();
258-
String fullType = typeDef(declaredType.getTypeArguments().get(0));
418+
TypeMirror arrayElementType = declaredType.getTypeArguments().get(0);
419+
// Resolve the array element type if it's generic
420+
TypeMirror resolvedElementType = resolveFieldType(arrayElementType, typeParameterMap);
421+
String fullType = typeDef(resolvedElementType);
259422
return new PropertyTypeArray(fullType, Split.shortName(fullType));
260423
}
261-
final TypeMirror typeMirror = field.asType();
424+
425+
// Get the field type, potentially resolved if it's generic
426+
final TypeMirror originalTypeMirror = field.asType();
427+
final TypeMirror typeMirror = resolveFieldType(originalTypeMirror, typeParameterMap);
428+
262429
TypeMirror currentType = typeMirror;
263430
while (currentType != null) {
264431
PropertyType type = propertyTypeMap.getType(typeDef(currentType));
@@ -278,7 +445,7 @@ PropertyType getPropertyType(VariableElement field) {
278445

279446
// workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=544288
280447
fieldType = elementUtils.getTypeElement(fieldType.toString());
281-
if (fieldType.getKind() == ElementKind.ENUM) {
448+
if (fieldType != null && fieldType.getKind() == ElementKind.ENUM) {
282449
String fullType = typeDef(typeMirror);
283450
return new PropertyTypeEnum(fullType, Split.shortName(fullType));
284451
}
@@ -301,7 +468,7 @@ PropertyType getPropertyType(VariableElement field) {
301468

302469
final PropertyType result;
303470
if (typeMirror.getKind() == TypeKind.DECLARED) {
304-
result = createManyTypeAssoc(field, (DeclaredType) typeMirror);
471+
result = createManyTypeAssoc(field, (DeclaredType) typeMirror, typeParameterMap);
305472
} else {
306473
result = null;
307474
}
@@ -333,20 +500,26 @@ private boolean typeInstanceOf(final TypeMirror typeMirror, final CharSequence d
333500
.anyMatch(t -> typeInstanceOf(t, desiredInterface));
334501
}
335502

336-
private PropertyType createManyTypeAssoc(VariableElement field, DeclaredType declaredType) {
503+
private PropertyType createManyTypeAssoc(VariableElement field, DeclaredType declaredType, Map<String, TypeMirror> typeParameterMap) {
337504
boolean toMany = dbToMany(field);
338505
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
339506
if (typeArguments.size() == 1) {
340-
Element argElement = typeUtils.asElement(typeArguments.get(0));
507+
TypeMirror argType = typeArguments.get(0);
508+
// Resolve the type argument if it's generic
509+
TypeMirror resolvedArgType = resolveFieldType(argType, typeParameterMap);
510+
Element argElement = typeUtils.asElement(resolvedArgType);
341511
if (isEntityOrEmbedded(argElement)) {
342512
boolean embeddable = isEmbeddable(argElement);
343-
return createPropertyTypeAssoc(embeddable, toMany, typeDef(argElement.asType()));
513+
return createPropertyTypeAssoc(embeddable, toMany, typeDef(resolvedArgType));
344514
}
345515
} else if (typeArguments.size() == 2) {
346-
Element argElement = typeUtils.asElement(typeArguments.get(1));
516+
TypeMirror argType = typeArguments.get(1);
517+
// Resolve the type argument if it's generic
518+
TypeMirror resolvedArgType = resolveFieldType(argType, typeParameterMap);
519+
Element argElement = typeUtils.asElement(resolvedArgType);
347520
if (isEntityOrEmbedded(argElement)) {
348521
boolean embeddable = isEmbeddable(argElement);
349-
return createPropertyTypeAssoc(embeddable, toMany, typeDef(argElement.asType()));
522+
return createPropertyTypeAssoc(embeddable, toMany, typeDef(resolvedArgType));
350523
}
351524
}
352525
return null;

0 commit comments

Comments
 (0)