Skip to content

Commit 3d8dbe1

Browse files
committed
fixes #321 The return type of a mapper method is not correctly resolved when it contains type parameters.
2 parents 361381f + a28461c commit 3d8dbe1

File tree

17 files changed

+1039
-29
lines changed

17 files changed

+1039
-29
lines changed

src/main/java/org/apache/ibatis/binding/MapperMethod.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@
2222
import org.apache.ibatis.mapping.MappedStatement;
2323
import org.apache.ibatis.mapping.SqlCommandType;
2424
import org.apache.ibatis.reflection.MetaObject;
25+
import org.apache.ibatis.reflection.TypeParameterResolver;
2526
import org.apache.ibatis.session.Configuration;
2627
import org.apache.ibatis.session.ResultHandler;
2728
import org.apache.ibatis.session.RowBounds;
2829
import org.apache.ibatis.session.SqlSession;
2930

3031
import java.lang.reflect.Array;
3132
import java.lang.reflect.Method;
33+
import java.lang.reflect.ParameterizedType;
34+
import java.lang.reflect.Type;
3235
import java.util.*;
3336

3437
/**
@@ -43,7 +46,7 @@ public class MapperMethod {
4346

4447
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
4548
this.command = new SqlCommand(config, mapperInterface, method);
46-
this.method = new MethodSignature(config, method);
49+
this.method = new MethodSignature(config, mapperInterface, method);
4750
}
4851

4952
public Object execute(SqlSession sqlSession, Object[] args) {
@@ -241,8 +244,15 @@ public static class MethodSignature {
241244
private final SortedMap<Integer, String> params;
242245
private final boolean hasNamedParameters;
243246

244-
public MethodSignature(Configuration configuration, Method method) {
245-
this.returnType = method.getReturnType();
247+
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
248+
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
249+
if (resolvedReturnType instanceof Class<?>) {
250+
this.returnType = (Class<?>) resolvedReturnType;
251+
} else if (resolvedReturnType instanceof ParameterizedType) {
252+
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
253+
} else {
254+
this.returnType = method.getReturnType();
255+
}
246256
this.returnsVoid = void.class.equals(this.returnType);
247257
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
248258
this.returnsCursor = Cursor.class.equals(this.returnType);

src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.apache.ibatis.mapping.SqlCommandType;
7777
import org.apache.ibatis.mapping.SqlSource;
7878
import org.apache.ibatis.mapping.StatementType;
79+
import org.apache.ibatis.reflection.TypeParameterResolver;
7980
import org.apache.ibatis.scripting.LanguageDriver;
8081
import org.apache.ibatis.session.Configuration;
8182
import org.apache.ibatis.session.ResultHandler;
@@ -376,19 +377,24 @@ private Class<?> getParameterType(Method method) {
376377

377378
private Class<?> getReturnType(Method method) {
378379
Class<?> returnType = method.getReturnType();
379-
// gcode issue #508
380-
if (void.class.equals(returnType)) {
381-
ResultType rt = method.getAnnotation(ResultType.class);
382-
if (rt != null) {
383-
returnType = rt.value();
384-
}
385-
} else if (Collection.class.isAssignableFrom(returnType)) {
386-
Type returnTypeParameter = method.getGenericReturnType();
387-
if (returnTypeParameter instanceof ParameterizedType) {
388-
Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
380+
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
381+
if (resolvedReturnType instanceof Class) {
382+
returnType = (Class<?>) resolvedReturnType;
383+
// gcode issue #508
384+
if (void.class.equals(returnType)) {
385+
ResultType rt = method.getAnnotation(ResultType.class);
386+
if (rt != null) {
387+
returnType = rt.value();
388+
}
389+
}
390+
} else if (resolvedReturnType instanceof ParameterizedType) {
391+
ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
392+
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
393+
if (Collection.class.isAssignableFrom(rawType)) {
394+
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
389395
if (actualTypeArguments != null && actualTypeArguments.length == 1) {
390-
returnTypeParameter = actualTypeArguments[0];
391-
if (returnTypeParameter instanceof Class) {
396+
Type returnTypeParameter = actualTypeArguments[0];
397+
if (returnTypeParameter instanceof Class<?>) {
392398
returnType = (Class<?>) returnTypeParameter;
393399
} else if (returnTypeParameter instanceof ParameterizedType) {
394400
// (gcode issue #443) actual type can be a also a parameterized type
@@ -399,21 +405,18 @@ private Class<?> getReturnType(Method method) {
399405
returnType = Array.newInstance(componentType, 0).getClass();
400406
}
401407
}
402-
}
403-
} else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(returnType)) {
404-
// (gcode issue 504) Do not look into Maps if there is not MapKey annotation
405-
Type returnTypeParameter = method.getGenericReturnType();
406-
if (returnTypeParameter instanceof ParameterizedType) {
407-
Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
408-
if (actualTypeArguments != null && actualTypeArguments.length == 2) {
409-
returnTypeParameter = actualTypeArguments[1];
410-
if (returnTypeParameter instanceof Class) {
411-
returnType = (Class<?>) returnTypeParameter;
412-
} else if (returnTypeParameter instanceof ParameterizedType) {
413-
// (gcode issue 443) actual type can be a also a parameterized type
414-
returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
408+
} else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
409+
// (gcode issue 504) Do not look into Maps if there is not MapKey annotation
410+
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
411+
if (actualTypeArguments != null && actualTypeArguments.length == 2) {
412+
Type returnTypeParameter = actualTypeArguments[1];
413+
if (returnTypeParameter instanceof Class<?>) {
414+
returnType = (Class<?>) returnTypeParameter;
415+
} else if (returnTypeParameter instanceof ParameterizedType) {
416+
// (gcode issue 443) actual type can be a also a parameterized type
417+
returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
418+
}
415419
}
416-
}
417420
}
418421
}
419422

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* Copyright 2009-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.apache.ibatis.reflection;
17+
18+
import java.lang.reflect.Method;
19+
import java.lang.reflect.ParameterizedType;
20+
import java.lang.reflect.Type;
21+
import java.lang.reflect.TypeVariable;
22+
import java.lang.reflect.WildcardType;
23+
24+
/**
25+
* @author Iwao AVE!
26+
*/
27+
public class TypeParameterResolver {
28+
29+
/**
30+
* @return The return type of the method as {@link Type}. If it has type parameters in the declaration,<br>
31+
* they will be resolved to the actual runtime {@link Type}.
32+
*/
33+
public static Type resolveReturnType(Method method, Type mapper) {
34+
Type returnType = method.getGenericReturnType();
35+
Class<?> declaringClass = method.getDeclaringClass();
36+
Type result = null;
37+
if (returnType instanceof TypeVariable) {
38+
result = resolveTypeVar((TypeVariable<?>) returnType, mapper, declaringClass);
39+
} else if (returnType instanceof ParameterizedType) {
40+
result = resolveParameterizedType((ParameterizedType) returnType, mapper, declaringClass);
41+
} else {
42+
result = returnType;
43+
}
44+
return result;
45+
}
46+
47+
private static ParameterizedType resolveParameterizedType(ParameterizedType parameterizedType, Type mapper, Class<?> declaringClass) {
48+
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
49+
Type[] typeArgs = parameterizedType.getActualTypeArguments();
50+
Type[] args = new Type[typeArgs.length];
51+
for (int i = 0; i < typeArgs.length; i++) {
52+
if (typeArgs[i] instanceof TypeVariable) {
53+
args[i] = resolveTypeVar((TypeVariable<?>) typeArgs[i], mapper, declaringClass);
54+
} else if (typeArgs[i] instanceof ParameterizedType) {
55+
args[i] = resolveParameterizedType((ParameterizedType) typeArgs[i], mapper, declaringClass);
56+
} else if (typeArgs[i] instanceof WildcardType) {
57+
args[i] = resolveWildcardType((WildcardType) typeArgs[i], mapper, declaringClass);
58+
} else {
59+
args[i] = typeArgs[i];
60+
}
61+
}
62+
return new ParameterizedTypeImpl(rawType, null, args);
63+
}
64+
65+
private static Type resolveWildcardType(WildcardType wildcardType, Type mapper, Class<?> declaringClass) {
66+
Type[] lowerBounds = resolveWildcardTypeBounds(wildcardType.getLowerBounds(), mapper, declaringClass);
67+
Type[] upperBounds = resolveWildcardTypeBounds(wildcardType.getUpperBounds(), mapper, declaringClass);
68+
return new WildcardTypeImpl(lowerBounds, upperBounds);
69+
}
70+
71+
private static Type[] resolveWildcardTypeBounds(Type[] bounds, Type mapper, Class<?> declaringClass) {
72+
Type[] result = new Type[bounds.length];
73+
for (int i = 0; i < bounds.length; i++) {
74+
if (bounds[i] instanceof TypeVariable) {
75+
result[i] = resolveTypeVar((TypeVariable<?>) bounds[i], mapper, declaringClass);
76+
} else if (bounds[i] instanceof ParameterizedType) {
77+
result[i] = resolveParameterizedType((ParameterizedType) bounds[i], mapper, declaringClass);
78+
} else if (bounds[i] instanceof WildcardType) {
79+
result[i] = resolveWildcardType((WildcardType) bounds[i], mapper, declaringClass);
80+
} else {
81+
result[i] = bounds[i];
82+
}
83+
}
84+
return result;
85+
}
86+
87+
private static Type resolveTypeVar(TypeVariable<?> typeVar, Type type, Class<?> declaringClass) {
88+
Type result = null;
89+
Class<?> clazz = null;
90+
if (type instanceof Class) {
91+
clazz = (Class<?>) type;
92+
} else if (type instanceof ParameterizedType) {
93+
ParameterizedType parameterizedType = (ParameterizedType) type;
94+
clazz = (Class<?>) parameterizedType.getRawType();
95+
} else {
96+
throw new IllegalArgumentException("The 2nd arg must be Class or ParameterizedType, but was: " + type.getClass());
97+
}
98+
99+
if (clazz == declaringClass) {
100+
return typeVar;
101+
}
102+
103+
Type[] superInterfaces = clazz.getGenericInterfaces();
104+
for (Type superInterface : superInterfaces) {
105+
if (superInterface instanceof ParameterizedType) {
106+
ParameterizedType parentAsType = (ParameterizedType) superInterface;
107+
Class<?> parentAsClass = (Class<?>) parentAsType.getRawType();
108+
if (declaringClass == parentAsClass) {
109+
Type[] typeArgs = parentAsType.getActualTypeArguments();
110+
TypeVariable<?>[] declaredTypeVars = declaringClass.getTypeParameters();
111+
for (int i = 0; i < declaredTypeVars.length; i++) {
112+
if (declaredTypeVars[i] == typeVar) {
113+
if (typeArgs[i] instanceof TypeVariable) {
114+
TypeVariable<?>[] typeParams = clazz.getTypeParameters();
115+
for (int j = 0; j < typeParams.length; j++) {
116+
if (typeParams[j] == typeArgs[i]) {
117+
result = ((ParameterizedType) type).getActualTypeArguments()[j];
118+
break;
119+
}
120+
}
121+
} else {
122+
result = typeArgs[i];
123+
}
124+
}
125+
}
126+
} else if (declaringClass.isAssignableFrom(parentAsClass)) {
127+
result = resolveTypeVar(typeVar, parentAsType, declaringClass);
128+
}
129+
} else if (superInterface instanceof Class) {
130+
if (declaringClass.isAssignableFrom((Class<?>) superInterface)) {
131+
result = resolveTypeVar(typeVar, superInterface, declaringClass);
132+
}
133+
}
134+
}
135+
if (result == null) {
136+
return typeVar;
137+
}
138+
return result;
139+
}
140+
141+
private TypeParameterResolver() {
142+
super();
143+
}
144+
145+
static class ParameterizedTypeImpl implements ParameterizedType {
146+
private Class<?> rawType;
147+
148+
private Type ownerType;
149+
150+
private Type[] actualTypeArguments;
151+
152+
public ParameterizedTypeImpl(Class<?> rawType, Type ownerType, Type[] actualTypeArguments) {
153+
super();
154+
this.rawType = rawType;
155+
this.ownerType = ownerType;
156+
this.actualTypeArguments = actualTypeArguments;
157+
}
158+
159+
@Override
160+
public Type[] getActualTypeArguments() {
161+
return actualTypeArguments;
162+
}
163+
164+
@Override
165+
public Type getOwnerType() {
166+
return ownerType;
167+
}
168+
169+
@Override
170+
public Type getRawType() {
171+
return rawType;
172+
}
173+
}
174+
175+
static class WildcardTypeImpl implements WildcardType {
176+
private Type[] lowerBounds;
177+
178+
private Type[] upperBounds;
179+
180+
private WildcardTypeImpl(Type[] lowerBounds, Type[] upperBounds) {
181+
super();
182+
this.lowerBounds = lowerBounds;
183+
this.upperBounds = upperBounds;
184+
}
185+
186+
@Override
187+
public Type[] getLowerBounds() {
188+
return lowerBounds;
189+
}
190+
191+
@Override
192+
public Type[] getUpperBounds() {
193+
return upperBounds;
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)