1111import javax .lang .model .element .ExecutableElement ;
1212import javax .lang .model .element .Modifier ;
1313import javax .lang .model .element .TypeElement ;
14+ import javax .lang .model .element .TypeParameterElement ;
1415import javax .lang .model .element .VariableElement ;
1516import javax .lang .model .type .DeclaredType ;
1617import javax .lang .model .type .TypeKind ;
1718import javax .lang .model .type .TypeMirror ;
19+ import javax .lang .model .type .TypeVariable ;
1820import javax .lang .model .util .ElementFilter ;
1921import javax .lang .model .util .Elements ;
2022import javax .lang .model .util .Types ;
2830import java .io .Reader ;
2931import java .nio .file .NoSuchFileException ;
3032import java .util .ArrayList ;
33+ import java .util .HashMap ;
3134import java .util .HashSet ;
3235import java .util .List ;
3336import 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