Skip to content

Commit 57b6c55

Browse files
author
lucve
committed
Issue#69 | Add ResolvedTypeMapper to convert ResolvedType into Type
1 parent 57f3a11 commit 57b6c55

File tree

2 files changed

+476
-0
lines changed

2 files changed

+476
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package com.fasterxml.classmate;
2+
3+
import com.fasterxml.classmate.types.ResolvedArrayType;
4+
import com.fasterxml.classmate.types.ResolvedRecursiveType;
5+
6+
import java.io.Serializable;
7+
import java.lang.reflect.GenericArrayType;
8+
import java.lang.reflect.ParameterizedType;
9+
import java.lang.reflect.Type;
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
import java.util.Objects;
14+
import java.util.stream.Collectors;
15+
16+
17+
/**
18+
* Utility for converting a {@link ResolvedType} into a standard Java {@link Type}
19+
* that can be used with the reflection API.
20+
*
21+
* <p><strong>Limitations:</strong></p>
22+
* <ul>
23+
* <li><b>Type variables and wildcards are not fully supported.</b>
24+
* Since {@link ResolvedType} does not preserve full type variable or wildcard details,
25+
* this mapper resolves them to their <em>upper bound</em> (or {@link Object} if no upper bound is known).</li>
26+
* <li>For example:
27+
* <ul>
28+
* <li>A field declared as {@code E extends List} will map to {@code List} when the type is unbound.</li>
29+
* <li>A wildcard like {@code ? super List} or a type variable like {@code E super List} will map to {@code Object}.</li>
30+
* <li>If a concrete type binding is provided in the {@link TypeBindings},
31+
* for example {@code E extends List} bound to {@code ArrayList},
32+
* this mapper will return {@code ArrayList}.</li>
33+
* </ul>
34+
* </li>
35+
* </ul>
36+
*
37+
* <p>
38+
* For further discussion, see
39+
* <a href="https://github.com/FasterXML/java-classmate/issues/69">issue #69</a>.
40+
* </p>
41+
*/
42+
43+
@SuppressWarnings("serial")
44+
public class ResolvedTypeMapper implements Serializable {
45+
46+
private static final long serialVersionUID = 1L;
47+
48+
/**
49+
* Method for mapping {@link ResolvedType} to java types.
50+
51+
* @param resolvedType The resolved type to map to a java {@link Type}
52+
* @return The type with all generics resolved, OR with the generics replaced with the upper bounds AND with all wildcards resolved to the upper bound.
53+
*/
54+
public Type map(ResolvedType resolvedType) {
55+
if (resolvedType instanceof ResolvedArrayType) {
56+
ResolvedArrayType arrayType = (ResolvedArrayType) resolvedType;
57+
ResolvedType arrayElementType = arrayType.getArrayElementType();
58+
if (arrayElementType == null) {
59+
throw new IllegalStateException("Missing array element type");
60+
}
61+
Type elementType = map(arrayElementType);
62+
//GenericArrayType is only used for parameterized types or TypeVariables, but we don't have TypeVariables
63+
if (elementType instanceof ParameterizedType) {
64+
return new GenericArrayTypeImpl(elementType);
65+
}
66+
return arrayType.getErasedType();
67+
}
68+
// Extract recursive type
69+
if (resolvedType instanceof ResolvedRecursiveType) {
70+
ResolvedRecursiveType recursiveType = (ResolvedRecursiveType) resolvedType;
71+
ResolvedType selfReferencedType = recursiveType.getSelfReferencedType();
72+
if (selfReferencedType == null) {
73+
throw new IllegalStateException("Missing self referenced type");
74+
}
75+
return map(selfReferencedType);
76+
}
77+
// no generics present, so the erased type is equal to the real type
78+
if (resolvedType.getTypeParameters() == null || resolvedType.getTypeParameters().isEmpty()) {
79+
return resolvedType.getErasedType();
80+
}
81+
// Parameters are present, so we need to create a parameterized
82+
// Wildcard types are not supported, since we store the upperbound
83+
return _mapParameterizedType(resolvedType);
84+
}
85+
86+
private ParameterizedTypeImpl _mapParameterizedType(ResolvedType objectType) {
87+
Class<?> erasedType = objectType.getErasedType();
88+
List<Type> list = new ArrayList<>();
89+
for (ResolvedType resolvedType : objectType.getTypeParameters()) {
90+
Type mappedArrayType = map(resolvedType);
91+
list.add(mappedArrayType);
92+
}
93+
return new ParameterizedTypeImpl(erasedType, list.toArray(new Type[0]), erasedType.getEnclosingClass());
94+
}
95+
96+
/**
97+
* Implementation of ParameterizedType for classmate type creation
98+
*/
99+
static final class ParameterizedTypeImpl implements ParameterizedType {
100+
private final Type rawType;
101+
private final Type[] actualTypeArguments;
102+
private final Type ownerType;
103+
104+
private ParameterizedTypeImpl(Type rawType, Type[] actualTypeArguments, Type ownerType) {
105+
this.rawType = rawType;
106+
this.actualTypeArguments = actualTypeArguments;
107+
this.ownerType = ownerType;
108+
}
109+
110+
/**
111+
* {@inheritDoc}
112+
*/
113+
@Override
114+
public Type[] getActualTypeArguments() {
115+
return actualTypeArguments;
116+
}
117+
118+
/**
119+
* {@inheritDoc}
120+
*/
121+
@Override
122+
public Type getRawType() {
123+
return rawType;
124+
}
125+
126+
/**
127+
* {@inheritDoc}
128+
*/
129+
@Override
130+
public Type getOwnerType() {
131+
return ownerType;
132+
}
133+
134+
/**
135+
* {@inheritDoc}
136+
*/
137+
@Override
138+
public boolean equals(Object o) {
139+
if (!(o instanceof ParameterizedType)) {
140+
return false;
141+
}
142+
ParameterizedType that = (ParameterizedType) o;
143+
return Objects.equals(rawType, that.getRawType()) && Objects.deepEquals(actualTypeArguments, that.getActualTypeArguments()) && Objects.equals(ownerType, that.getOwnerType());
144+
}
145+
146+
/**
147+
* {@inheritDoc}
148+
*/
149+
@Override
150+
public int hashCode() {
151+
return Objects.hash(rawType, Arrays.hashCode(actualTypeArguments), ownerType);
152+
}
153+
154+
/**
155+
* {@inheritDoc}
156+
*/
157+
@Override
158+
public String toString() {
159+
return rawType + "<" + Arrays.stream(actualTypeArguments).map(Type::getTypeName).collect(Collectors.joining(", ")) + ">";
160+
}
161+
}
162+
163+
/**
164+
* Implementation of GenericArrayType for classmate type creation
165+
*/
166+
static final class GenericArrayTypeImpl implements GenericArrayType {
167+
private final Type genericComponentType;
168+
169+
private GenericArrayTypeImpl(Type genericComponentType) {
170+
this.genericComponentType = genericComponentType;
171+
}
172+
173+
/**
174+
* {@inheritDoc}
175+
*/
176+
@Override
177+
public Type getGenericComponentType() {
178+
return genericComponentType;
179+
}
180+
181+
/**
182+
* {@inheritDoc}
183+
*/
184+
@Override
185+
public boolean equals(Object o) {
186+
if (!(o instanceof GenericArrayType)) {
187+
return false;
188+
}
189+
GenericArrayType that = (GenericArrayType) o;
190+
return Objects.equals(genericComponentType, that.getGenericComponentType());
191+
}
192+
193+
/**
194+
* {@inheritDoc}
195+
*/
196+
@Override
197+
public int hashCode() {
198+
return Objects.hashCode(genericComponentType);
199+
}
200+
201+
/**
202+
* {@inheritDoc}
203+
*/
204+
@Override
205+
public String toString() {
206+
return genericComponentType.getTypeName() + "[]";
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)