Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,10 @@ private static Type resolveGenericArrayType(GenericArrayType genericArrayType, T
private static ParameterizedType resolveParameterizedType(ParameterizedType parameterizedType, Type srcType,
Class<?> declaringClass) {
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
Type ownerType = parameterizedType.getOwnerType();
Type[] typeArgs = parameterizedType.getActualTypeArguments();
Type[] args = resolveTypes(typeArgs, srcType, declaringClass);
return new ParameterizedTypeImpl(rawType, null, args);
return new ParameterizedTypeImpl(rawType, ownerType, args);
}

private static Type resolveWildcardType(WildcardType wildcardType, Type srcType, Class<?> declaringClass) {
Expand All @@ -158,7 +159,7 @@ private static Type resolveTypeVar(TypeVariable<?> typeVar, Type srcType, Class<
}
} else {
throw new IllegalArgumentException(
"The 2nd arg must be Class or ParameterizedType, but was: " + srcType.getClass());
"The srcType(2nd arg) must be Class or ParameterizedType, but was: " + srcType.getClass());
}

if (clazz == declaringClass) {
Expand Down Expand Up @@ -230,7 +231,9 @@ private static ParameterizedType translateParentTypeVars(ParameterizedType srcTy
newParentArgs[i] = parentTypeArgs[i];
}
}
return noChange ? parentType : new ParameterizedTypeImpl((Class<?>) parentType.getRawType(), null, newParentArgs);
return noChange
? parentType
: new ParameterizedTypeImpl((Class<?>) parentType.getRawType(), parentType.getOwnerType(), newParentArgs);
}

private TypeParameterResolver() {
Expand All @@ -244,7 +247,7 @@ static class ParameterizedTypeImpl implements ParameterizedType {

private final Type[] actualTypeArguments;

public ParameterizedTypeImpl(Class<?> rawType, Type ownerType, Type[] actualTypeArguments) {
ParameterizedTypeImpl(Class<?> rawType, Type ownerType, Type[] actualTypeArguments) {
super();
this.rawType = rawType;
this.ownerType = ownerType;
Expand Down Expand Up @@ -283,14 +286,25 @@ public boolean equals(Object obj) {

@Override
public String toString() {
StringBuilder s = new StringBuilder().append(rawType.getName()).append("<");
for (int i = 0; i < actualTypeArguments.length; i++) {
if (i > 0) {
s.append(", ");
StringBuilder s = new StringBuilder();
if (ownerType != null) {
s.append(ownerType.getTypeName()).append("$");
s.append(rawType.getSimpleName());
} else {
s.append(rawType.getName());
}
int argLength = actualTypeArguments.length;
if (argLength > 0) {
s.append("<");
for (int i = 0; i < argLength; i++) {
if (i > 0) {
s.append(", ");
}
s.append(actualTypeArguments[i].getTypeName());
}
s.append(actualTypeArguments[i].getTypeName());
s.append(">");
}
return s.append(">").toString();
return s.toString();
}
}

Expand Down Expand Up @@ -329,11 +343,11 @@ public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof WildcardTypeImpl)) {
if (!(obj instanceof WildcardType)) {
return false;
}
WildcardTypeImpl other = (WildcardTypeImpl) obj;
return Arrays.equals(lowerBounds, other.lowerBounds) && Arrays.equals(upperBounds, other.upperBounds);
WildcardType other = (WildcardType) obj;
return Arrays.equals(lowerBounds, other.getLowerBounds()) && Arrays.equals(upperBounds, other.getUpperBounds());
}

@Override
Expand Down Expand Up @@ -371,16 +385,16 @@ public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GenericArrayTypeImpl)) {
if (!(obj instanceof GenericArrayType)) {
return false;
}
GenericArrayTypeImpl other = (GenericArrayTypeImpl) obj;
return Objects.equals(genericComponentType, other.genericComponentType);
GenericArrayType other = (GenericArrayType) obj;
return Objects.equals(genericComponentType, other.getGenericComponentType());
}

@Override
public String toString() {
return new StringBuilder().append(genericComponentType.toString()).append("[]").toString();
return new StringBuilder().append(genericComponentType.getTypeName()).append("[]").toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
Expand All @@ -27,6 +28,7 @@
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Expand Down Expand Up @@ -230,7 +232,6 @@ void returnLv2ArrayOfArray() throws Exception {
Type result = TypeParameterResolver.resolveReturnType(method, clazz);
assertTrue(result instanceof Class);
Class<?> resultClass = (Class<?>) result;
assertTrue(result instanceof Class);
assertTrue(resultClass.isArray());
assertTrue(resultClass.getComponentType().isArray());
assertEquals(String.class, resultClass.getComponentType().getComponentType());
Expand Down Expand Up @@ -553,4 +554,115 @@ public List<Integer> m() {
}
}

@Test
void shouldParameterizedTypesWithOwnerTypeBeEqual() throws Exception {
class Clazz {
@SuppressWarnings("unused")
public Entry<String, Integer> entry() {
return null;
}
}

Type typeJdk = Clazz.class.getMethod("entry").getGenericReturnType();

Class<?> clazz = Level2Mapper.class;
Method method = clazz.getMethod("selectEntry");
Type typeMybatis = TypeParameterResolver.resolveReturnType(method, clazz);

assertTrue(typeJdk instanceof ParameterizedType && !(typeJdk instanceof TypeParameterResolver.ParameterizedTypeImpl));
assertTrue(typeMybatis instanceof TypeParameterResolver.ParameterizedTypeImpl);
assertEquals(typeMybatis.equals(typeJdk), typeJdk.equals(typeMybatis));
}

@Test
void shouldWildcardTypeBeEqual() throws Exception {
class WildcardTypeTester {
public List<? extends Serializable> foo() {
return null;
}
}

Class<?> clazz = WildcardTypeTester.class;
Method foo = clazz.getMethod("foo");
Type typeMybatis = TypeParameterResolver.resolveReturnType(foo, clazz);
Type typeJdk = foo.getGenericReturnType();

Type wildcardMybatis = ((ParameterizedType) typeMybatis).getActualTypeArguments()[0];
Type wildcardJdk = ((ParameterizedType) typeJdk).getActualTypeArguments()[0];

assertTrue(wildcardJdk instanceof WildcardType && !(wildcardJdk instanceof TypeParameterResolver.WildcardTypeImpl));
assertTrue(wildcardMybatis instanceof TypeParameterResolver.WildcardTypeImpl);
assertEquals(wildcardMybatis.equals(wildcardJdk), wildcardJdk.equals(wildcardMybatis));
}

@Test
void shouldGenericArrayTypeBeEqual() throws Exception {
class GenericArrayTypeTester {
public List<String>[] foo() {
return null;
}
}

Class<?> clazz = GenericArrayTypeTester.class;
Method foo = clazz.getMethod("foo");
Type typeMybatis = TypeParameterResolver.resolveReturnType(foo, clazz);
Type typeJdk = foo.getGenericReturnType();

assertTrue(typeJdk instanceof GenericArrayType && !(typeJdk instanceof TypeParameterResolver.GenericArrayTypeImpl));
assertTrue(typeMybatis instanceof TypeParameterResolver.GenericArrayTypeImpl);
assertEquals(typeMybatis.equals(typeJdk), typeJdk.equals(typeMybatis));
}

@Test
void shouldNestedParamTypeToStringOmitCommonFqn() throws Exception {
Class<?> clazz = Level2Mapper.class;
Method method = clazz.getMethod("selectMapEntry");
Type type = TypeParameterResolver.resolveReturnType(method, clazz);
assertEquals("java.util.Map<java.util.Map$Entry<java.lang.String, java.lang.Integer>, java.util.Date>",
type.toString());
}

static class Outer<T> {

class Inner {

Check notice

Code scanning / CodeQL

Inner class could be static Note test

Inner should be made static, since the enclosing instance is not used.
}

public Inner foo() {
return null;
}

}

static class InnerTester {

public Outer<?>.Inner noTypeOuter() {
return null;
}

public Outer<String>.Inner stringTypeOuter() {
return null;
}

}

@Test
void shouldToStringHandleInnerClass() throws Exception {
Class<?> outerClass = Outer.class;
Class<?> innerTesterClass = InnerTester.class;
Method foo = outerClass.getMethod("foo");
Method noTypeOuter = innerTesterClass.getMethod("noTypeOuter");
Method stringTypeOuter = innerTesterClass.getMethod("stringTypeOuter");

Type fooReturnType = TypeParameterResolver.resolveReturnType(foo, outerClass);
Type noTypeOuterReturnType = TypeParameterResolver.resolveReturnType(noTypeOuter, innerTesterClass);
Type stringTypeOuterReturnType = TypeParameterResolver.resolveReturnType(stringTypeOuter, innerTesterClass);

assertEquals("org.apache.ibatis.reflection.TypeParameterResolverTest$Outer<T>$Inner",
fooReturnType.toString());
assertEquals("org.apache.ibatis.reflection.TypeParameterResolverTest$Outer<?>$Inner",
noTypeOuterReturnType.toString());
assertEquals("org.apache.ibatis.reflection.TypeParameterResolverTest$Outer<java.lang.String>$Inner",
stringTypeOuterReturnType.toString());
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2022 the original author or authors.
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public interface Level0Mapper<L, M, N> {

Expand Down Expand Up @@ -46,6 +47,10 @@ public interface Level0Mapper<L, M, N> {

Map<N, M> selectMap();

Entry<N, M> selectEntry();

Map<Entry<N, M>, L> selectMapEntry();

N[] selectArray(List<N>[] param);

N[][] selectArrayOfArray();
Expand Down