diff --git a/src/main/java/spoon/reflect/factory/TypeFactory.java b/src/main/java/spoon/reflect/factory/TypeFactory.java index e69fbcb03b8..79b506bccca 100644 --- a/src/main/java/spoon/reflect/factory/TypeFactory.java +++ b/src/main/java/spoon/reflect/factory/TypeFactory.java @@ -316,15 +316,29 @@ public CtTypeReference createReference(Class type) { * Create a reference to a simple type */ public CtTypeReference createReference(CtType type) { + return createReference(type, false); + } + + /** + * @param includingFormalTypeParameter if true then references to formal type parameters + * are added as actual type arguments of returned {@link CtTypeReference} + */ + public CtTypeReference createReference(CtType type, boolean includingFormalTypeParameter) { CtTypeReference ref = factory.Core().createTypeReference(); if (type.getDeclaringType() != null) { - ref.setDeclaringType(createReference(type.getDeclaringType())); + ref.setDeclaringType(createReference(type.getDeclaringType(), includingFormalTypeParameter)); } else if (type.getPackage() != null) { ref.setPackage(factory.Package().createReference(type.getPackage())); } ref.setSimpleName(type.getSimpleName()); + + if (includingFormalTypeParameter) { + for (CtTypeParameter formalTypeParam : type.getFormalCtTypeParameters()) { + ref.addActualTypeArgument(formalTypeParam.getReference()); + } + } return ref; } @@ -342,7 +356,6 @@ public CtTypeParameterReference createReference(CtTypeParameter type) { ref.addAnnotation(ctAnnotation.clone()); } ref.setSimpleName(type.getSimpleName()); - //TypeParameter reference without parent is unusable. It lost information about it's declarer ref.setParent(type); return ref; } diff --git a/src/main/java/spoon/reflect/reference/CtTypeParameterReference.java b/src/main/java/spoon/reflect/reference/CtTypeParameterReference.java index a362abead54..7f645b0396a 100644 --- a/src/main/java/spoon/reflect/reference/CtTypeParameterReference.java +++ b/src/main/java/spoon/reflect/reference/CtTypeParameterReference.java @@ -72,7 +72,11 @@ public interface CtTypeParameterReference extends CtTypeReference { */ T setBoundingType(CtTypeReference superType); - // overriding the return type + /** + * Returns the {@link CtTypeParameter}, a {@link CtTypeParameter}, that declares the type parameter + * referenced or null if the reference is not in a context where such type parameter is declared. + * See also {@link #getTypeParameterDeclaration()} which has a different semantic. + */ @Override @DerivedProperty CtTypeParameter getDeclaration(); diff --git a/src/main/java/spoon/reflect/reference/CtTypeReference.java b/src/main/java/spoon/reflect/reference/CtTypeReference.java index f96a01fffd2..6fc9d3d33eb 100644 --- a/src/main/java/spoon/reflect/reference/CtTypeReference.java +++ b/src/main/java/spoon/reflect/reference/CtTypeReference.java @@ -20,6 +20,7 @@ import spoon.reflect.declaration.CtShadowable; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeInformation; +import spoon.reflect.declaration.CtTypeParameter; import spoon.support.DerivedProperty; import spoon.support.SpoonClassNotFoundException; @@ -181,4 +182,25 @@ public interface CtTypeReference extends CtReference, CtActualTypeContainer, */ @DerivedProperty CtTypeReference getAccessType(); + + /** + * If this type reference is used as a type argument (see {@link #getActualTypeArguments()}), returns the type parameter declaration in the target type, returns null otherwise. + * + * In the following example, getTypeParameterDeclaration of "String" returns the type parameter definition "X". + *
+	 * class Dog<X>{}
+	 * Dog<String>var = ...;
+	 * 
+ ** + * In this other example, getTypeParameterDeclaration of T in Dog<T> returns the type parameter definition "X" (while {@link #getDeclaration()} returns the "T" of Cat). + *
+	 * class Dog<X>{}
+	 * class Cat<T> {
+	 * Dog<T> dog;
+	 * }
+	 * 
+ */ + @DerivedProperty + CtTypeParameter getTypeParameterDeclaration(); + } diff --git a/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java b/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java index 50e9b7de466..8eb41eca8a1 100644 --- a/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java +++ b/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java @@ -171,20 +171,27 @@ public CtTypeParameter getDeclaration() { if (!isParentInitialized()) { return null; } - return getRecursiveDeclaration(this); - } - private CtTypeParameter getRecursiveDeclaration(CtElement element) { - final CtFormalTypeDeclarer formalTypeDeclarer = element.getParent(CtFormalTypeDeclarer.class); - if (formalTypeDeclarer == null) { - return null; + // case #1: we're a type of a method parameter, a local variable, ... + // the strategy is to look in the parents + // collecting all formal type declarers of the hierarchy + CtElement e = this; + while ((e = e.getParent(CtFormalTypeDeclarer.class)) != null) { + CtTypeParameter result = findTypeParamDeclaration((CtFormalTypeDeclarer) e, this.getSimpleName()); + if (result != null) { + return result; + } } - for (CtTypeParameter typeParameter : formalTypeDeclarer.getFormalCtTypeParameters()) { - if (simplename.equals(typeParameter.getSimpleName())) { - return typeParameter; + return null; + } + + private CtTypeParameter findTypeParamDeclaration(CtFormalTypeDeclarer type, String refName) { + for (CtTypeParameter typeParam : type.getFormalCtTypeParameters()) { + if (typeParam.getSimpleName().equals(refName)) { + return typeParam; } } - return getRecursiveDeclaration(formalTypeDeclarer); + return null; } @Override diff --git a/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java b/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java index 4d39fac4446..c8acfe3e6ef 100644 --- a/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java +++ b/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java @@ -19,9 +19,15 @@ import spoon.Launcher; import spoon.SpoonException; import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtConstructor; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtFormalTypeDeclarer; +import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtShadowable; import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeParameter; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.reference.CtActualTypeContainer; import spoon.reflect.reference.CtArrayTypeReference; @@ -801,4 +807,30 @@ public E setShadow(boolean isShadow) { public CtTypeReference clone() { return (CtTypeReference) super.clone(); } + + @Override + public CtTypeParameter getTypeParameterDeclaration() { + + CtElement parent = this.getParent(); + + // case 1: this is an actual type argument of a type reference eg List + if (parent instanceof CtTypeReference) { + CtType t = ((CtTypeReference) parent).getTypeDeclaration(); + return findTypeParamDeclarationByPosition(t, ((CtTypeReference) parent).getActualTypeArguments().indexOf(this)); + } + + // case 2: this is an actual type argument of a method/constructor reference + if (parent instanceof CtExecutableReference) { + CtExecutable exec = ((CtExecutableReference) parent).getExecutableDeclaration(); + if (exec instanceof CtMethod || exec instanceof CtConstructor) { + return findTypeParamDeclarationByPosition((CtFormalTypeDeclarer) exec, ((CtTypeReference) parent).getActualTypeArguments().indexOf(this)); + } + } + + return null; + } + + private CtTypeParameter findTypeParamDeclarationByPosition(CtFormalTypeDeclarer type, int position) { + return type.getFormalCtTypeParameters().get(position); + } } diff --git a/src/test/java/spoon/test/generics/GenericsTest.java b/src/test/java/spoon/test/generics/GenericsTest.java index d2a33a27cef..64dd10ec148 100644 --- a/src/test/java/spoon/test/generics/GenericsTest.java +++ b/src/test/java/spoon/test/generics/GenericsTest.java @@ -166,11 +166,34 @@ public void testTypeParameterReference() throws Exception { @Test public void testTypeParameterDeclarer() throws Exception { - // contract: one can navigate to the declarer of a type parameter + // contract: one can lookup the declarer of a type parameter if it is in appropriate context (the declararer is in the parent hierarchy) CtClass classThatDefinesANewTypeArgument = build("spoon.test.generics", "ClassThatDefinesANewTypeArgument"); CtTypeParameter typeParam = classThatDefinesANewTypeArgument.getFormalCtTypeParameters().get(0); + assertEquals("T", classThatDefinesANewTypeArgument.getFormalCtTypeParameters().get(0).getSimpleName()); assertSame(classThatDefinesANewTypeArgument, typeParam.getTypeParameterDeclarer()); - assertSame(typeParam, typeParam.getReference().getDeclaration()); + CtTypeParameterReference typeParamReference = typeParam.getReference(); + assertSame(typeParam, typeParamReference.getDeclaration()); + + // creating an appropriate context + CtMethod m = classThatDefinesANewTypeArgument.getFactory().createMethod(); + m.setParent(classThatDefinesANewTypeArgument); + // setting the return type of the method + m.setType(typeParamReference); + classThatDefinesANewTypeArgument.addMethod(m); + + // the final assertions + assertSame(typeParam, typeParamReference.getDeclaration()); + + assertSame(classThatDefinesANewTypeArgument, typeParamReference.getDeclaration().getParent()); + + // now testing that the getDeclaration of a type parameter is actually a dynamic lookup + CtClass c2 = classThatDefinesANewTypeArgument.clone(); + c2.addMethod(m); + assertSame(c2, typeParamReference.getDeclaration().getParent()); + // even if we rename it + typeParamReference.setSimpleName("R"); // renaming the reference + c2.getFormalCtTypeParameters().get(0).setSimpleName("R"); // renaming the declaration + assertSame(c2, typeParamReference.getDeclaration().getParent()); } @Test @@ -558,4 +581,59 @@ public void testIsGenericsMethod() throws Exception { assertFalse(aTacos.isGenerics()); assertFalse(ctTypeReference.isGenerics()); } + @Test + public void testTypeParameterReferenceAsActualTypeArgument() throws Exception { + CtType aTacos = buildNoClasspath(ClassThatDefinesANewTypeArgument.class).Type().get(ClassThatDefinesANewTypeArgument.class); + + CtTypeReference typeRef = aTacos.getReference(); + + assertSame(aTacos, typeRef.getDeclaration()); + + CtTypeParameter typeParam = aTacos.getFormalCtTypeParameters().get(0); + CtTypeParameterReference typeParamRef = typeParam.getReference(); + assertSame(typeParam, typeParamRef.getDeclaration()); + + assertEquals("spoon.test.generics.ClassThatDefinesANewTypeArgument", typeRef.toString()); + + // creating a reference to "ClassThatDefinesANewTypeArgument" + //this assignment changes parent of typeParamRef to TYPEREF + typeRef.addActualTypeArgument(typeParamRef); + + assertEquals("spoon.test.generics.ClassThatDefinesANewTypeArgument", typeRef.toString()); + + // this does not change the declaration + assertSame(aTacos, typeRef.getDeclaration()); + //stored typeParamRef is same like the added one, no clone - OK + assertSame(typeParamRef, typeRef.getActualTypeArguments().get(0)); + //typeParamRef has got new parent + assertSame(typeRef, typeParamRef.getParent()); + + // null because without context + assertEquals(null, typeParamRef.getDeclaration()); + assertEquals(typeParam, typeParamRef.getTypeParameterDeclaration()); + typeParamRef.setSimpleName("Y"); + assertEquals(typeParam, typeParamRef.getTypeParameterDeclaration()); + } + @Test + public void testGenericTypeReference() throws Exception { + + // contract: the parameter includingFormalTypeParameter of createReference enables one to also create actual type arguments + + CtType aTacos = buildNoClasspath(Tacos.class).Type().get(Tacos.class); + //this returns a type reference with uninitialized actual type arguments. +// CtTypeReference genericTypeRef = aTacos.getReference(); + CtTypeReference genericTypeRef = aTacos.getFactory().Type().createReference(aTacos, true); + + assertTrue(genericTypeRef.getActualTypeArguments().size()>0); + assertEquals(aTacos.getFormalCtTypeParameters().size(), genericTypeRef.getActualTypeArguments().size()); + for(int i=0; i