Skip to content

Commit 5cdac19

Browse files
committed
Support mapper statement return type with type parameters.
1 parent 28c2f15 commit 5cdac19

File tree

9 files changed

+471
-30
lines changed

9 files changed

+471
-30
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 exactReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
249+
if (exactReturnType instanceof Class<?>) {
250+
this.returnType = (Class<?>) exactReturnType;
251+
} else if (exactReturnType instanceof ParameterizedType) {
252+
this.returnType = (Class<?>) ((ParameterizedType) exactReturnType).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: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.apache.ibatis.annotations.Lang;
4646
import org.apache.ibatis.annotations.MapKey;
4747
import org.apache.ibatis.annotations.Options;
48+
import org.apache.ibatis.annotations.Options.FlushCachePolicy;
4849
import org.apache.ibatis.annotations.Result;
4950
import org.apache.ibatis.annotations.ResultMap;
5051
import org.apache.ibatis.annotations.ResultType;
@@ -55,7 +56,6 @@
5556
import org.apache.ibatis.annotations.TypeDiscriminator;
5657
import org.apache.ibatis.annotations.Update;
5758
import org.apache.ibatis.annotations.UpdateProvider;
58-
import org.apache.ibatis.annotations.Options.FlushCachePolicy;
5959
import org.apache.ibatis.binding.BindingException;
6060
import org.apache.ibatis.binding.MapperMethod.ParamMap;
6161
import org.apache.ibatis.builder.BuilderException;
@@ -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 exactReturnType = TypeParameterResolver.resolveReturnType(method, type);
381+
if (exactReturnType instanceof Class) {
382+
returnType = (Class<?>) exactReturnType;
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 (exactReturnType instanceof ParameterizedType) {
391+
ParameterizedType parameterizedType = (ParameterizedType) exactReturnType;
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
17+
package org.apache.ibatis.reflection;
18+
19+
import java.lang.reflect.ParameterizedType;
20+
import java.lang.reflect.Type;
21+
22+
public class ParameterizedTypeImpl implements ParameterizedType {
23+
24+
private Class<?> rawType;
25+
26+
private Type ownerType;
27+
28+
private Type[] actualTypeArguments;
29+
30+
public ParameterizedTypeImpl(Class<?> rawType, Type ownerType, Type[] actualTypeArguments) {
31+
super();
32+
this.rawType = rawType;
33+
this.ownerType = ownerType;
34+
this.actualTypeArguments = actualTypeArguments;
35+
}
36+
37+
@Override
38+
public Type[] getActualTypeArguments() {
39+
return actualTypeArguments;
40+
}
41+
42+
@Override
43+
public Type getOwnerType() {
44+
return ownerType;
45+
}
46+
47+
@Override
48+
public Type getRawType() {
49+
return rawType;
50+
}
51+
52+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
23+
public class TypeParameterResolver {
24+
25+
public static Type resolveReturnType(Method method, Type mapper) {
26+
Type returnType = method.getGenericReturnType();
27+
Class<?> declaringClass = method.getDeclaringClass();
28+
return getReturnType(returnType, mapper, declaringClass);
29+
}
30+
31+
private static Type getReturnType(Type returnType, Type mapper, Class<?> declaringClass) {
32+
Type result = null;
33+
if (returnType instanceof Class) {
34+
result = returnType;
35+
} else if (returnType instanceof TypeVariable) {
36+
if (mapper instanceof Class) {
37+
result = resolveTypeVar((TypeVariable<?>) returnType, mapper, declaringClass);
38+
}
39+
} else if (returnType instanceof ParameterizedType) {
40+
result = scanParameterizedType((ParameterizedType) returnType, mapper, declaringClass);
41+
}
42+
assert result != null;
43+
return result;
44+
}
45+
46+
private static ParameterizedType scanParameterizedType(ParameterizedType parameterizedType, Type mapper, Class<?> declaringClass) {
47+
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
48+
Type[] typeArgs = parameterizedType.getActualTypeArguments();
49+
Type[] args = new Type[typeArgs.length];
50+
for (int i = 0; i < typeArgs.length; i++) {
51+
if (typeArgs[i] instanceof Class) {
52+
args[i] = typeArgs[i];
53+
} else if (typeArgs[i] instanceof TypeVariable) {
54+
args[i] = resolveTypeVar((TypeVariable<?>) typeArgs[i], mapper, declaringClass);
55+
} else if (typeArgs[i] instanceof ParameterizedType) {
56+
args[i] = scanParameterizedType((ParameterizedType) typeArgs[i], mapper, declaringClass);
57+
}
58+
}
59+
return new ParameterizedTypeImpl(rawType, null, args);
60+
}
61+
62+
private static Type resolveTypeVar(TypeVariable<?> typeVar, Type mapper, Class<?> declaringClass) {
63+
Type result = null;
64+
Class<?> mapperClass = null;
65+
if (mapper instanceof Class) {
66+
mapperClass = (Class<?>) mapper;
67+
} else if (mapper instanceof ParameterizedType) {
68+
ParameterizedType parameterizedType = (ParameterizedType) mapper;
69+
mapperClass = (Class<?>) parameterizedType.getRawType();
70+
} else {
71+
throw new IllegalArgumentException("The 2nd arg must be Class or ParameterizedType, but was: " + mapper.getClass());
72+
}
73+
74+
Type[] superInterfaces = mapperClass.getGenericInterfaces();
75+
for (Type superInterface : superInterfaces) {
76+
Class<?> parentAsClass = null;
77+
if (superInterface instanceof ParameterizedType) {
78+
ParameterizedType parentAsType = (ParameterizedType) superInterface;
79+
parentAsClass = (Class<?>) parentAsType.getRawType();
80+
Type[] typeArgs = parentAsType.getActualTypeArguments();
81+
if (declaringClass == parentAsClass) {
82+
TypeVariable<?>[] declaredTypeVars = declaringClass.getTypeParameters();
83+
for (int i = 0; i < declaredTypeVars.length; i++) {
84+
if (declaredTypeVars[i] == typeVar) {
85+
if (typeArgs[i] instanceof TypeVariable) {
86+
TypeVariable<?>[] mapperTypeParams = mapperClass.getTypeParameters();
87+
for (int j = 0; j < mapperTypeParams.length; j++) {
88+
if (mapperTypeParams[j] == typeArgs[i]) {
89+
result = ((ParameterizedType) mapper).getActualTypeArguments()[j];
90+
}
91+
}
92+
} else {
93+
result = typeArgs[i];
94+
}
95+
}
96+
}
97+
} else {
98+
result = resolveTypeVar(typeVar, parentAsType, declaringClass);
99+
}
100+
} else if (superInterface instanceof Class) {
101+
result = resolveTypeVar(typeVar, superInterface, declaringClass);
102+
}
103+
}
104+
assert result != null;
105+
return result;
106+
}
107+
108+
private TypeParameterResolver() {
109+
super();
110+
}
111+
}

0 commit comments

Comments
 (0)