diff --git a/src/main/java/org/apache/ibatis/reflection/TypeParameterResolver.java b/src/main/java/org/apache/ibatis/reflection/TypeParameterResolver.java index aba6a7f3e4d..3b7a70652c4 100644 --- a/src/main/java/org/apache/ibatis/reflection/TypeParameterResolver.java +++ b/src/main/java/org/apache/ibatis/reflection/TypeParameterResolver.java @@ -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) { @@ -230,7 +231,8 @@ 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() { @@ -244,7 +246,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; @@ -283,14 +285,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(); } } @@ -329,11 +342,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 @@ -371,16 +384,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(); } } } diff --git a/src/test/java/org/apache/ibatis/reflection/TypeParameterResolverTest.java b/src/test/java/org/apache/ibatis/reflection/TypeParameterResolverTest.java index ca4bb535570..b0a2747f9cb 100644 --- a/src/test/java/org/apache/ibatis/reflection/TypeParameterResolverTest.java +++ b/src/test/java/org/apache/ibatis/reflection/TypeParameterResolverTest.java @@ -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; @@ -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; @@ -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()); @@ -553,4 +554,120 @@ public List m() { } } + @Test + void shouldParameterizedTypesWithOwnerTypeBeEqual() throws Exception { + class Clazz { + @SuppressWarnings("unused") + public Entry 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); + assertTrue(typeJdk.equals(typeMybatis)); + assertTrue(typeMybatis.equals(typeJdk)); + } + + @Test + void shouldWildcardTypeBeEqual() throws Exception { + class WildcardTypeTester { + @SuppressWarnings("unused") + public List 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); + assertTrue(typeJdk.equals(typeMybatis)); + assertTrue(typeMybatis.equals(typeJdk)); + } + + @Test + void shouldGenericArrayTypeBeEqual() throws Exception { + class GenericArrayTypeTester { + @SuppressWarnings("unused") + public List[] 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); + assertTrue(typeJdk.equals(typeMybatis)); + assertTrue(typeMybatis.equals(typeJdk)); + } + + @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.Date>", + type.toString()); + } + + static class Outer { + + class Inner { + } + + public Inner foo() { + return null; + } + + } + + static class InnerTester { + + public Outer.Inner noTypeOuter() { + return null; + } + + public Outer.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$Inner", fooReturnType.toString()); + assertEquals("org.apache.ibatis.reflection.TypeParameterResolverTest$Outer$Inner", + noTypeOuterReturnType.toString()); + assertEquals("org.apache.ibatis.reflection.TypeParameterResolverTest$Outer$Inner", + stringTypeOuterReturnType.toString()); + } + } diff --git a/src/test/java/org/apache/ibatis/reflection/typeparam/Level0Mapper.java b/src/test/java/org/apache/ibatis/reflection/typeparam/Level0Mapper.java index 7f108541f6e..d44e50fe62d 100644 --- a/src/test/java/org/apache/ibatis/reflection/typeparam/Level0Mapper.java +++ b/src/test/java/org/apache/ibatis/reflection/typeparam/Level0Mapper.java @@ -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. @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; +import java.util.Map.Entry; public interface Level0Mapper { @@ -46,6 +47,10 @@ public interface Level0Mapper { Map selectMap(); + Entry selectEntry(); + + Map, L> selectMapEntry(); + N[] selectArray(List[] param); N[][] selectArrayOfArray();