/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.resolve;
import com.google.common.collect.Lists;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.sonar.java.ast.parser.JavaParser;
import org.sonar.java.resolve.JavaSymbol.MethodJavaSymbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.SyntaxToken;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
public class GenericsTest {
@Test
public void test_wildcard_instances() {
assertTypesAreTheSame("A<?>", "A<?>");
assertTypesAreTheSame("A<? extends String>", "A<? extends String>");
assertTypesAreTheSame("A<? super Number>", "A<? super Number>");
assertTypesAreTheSame("A<? super UnknownType>", "A<? super UnknownType>");
assertTypesAreTheSame("A<? extends UnknownType>", "A<? extends UnknownType>");
assertTypesAreNotTheSame("A<?>", "A<? extends Object>");
assertTypesAreNotTheSame("A<?>", "A<? super String>");
assertTypesAreNotTheSame("A<?>", "A<? extends String>");
assertTypesAreNotTheSame("A<? extends String>", "A<? super String>");
}
private static void assertTypesAreTheSame(String type1, String type2) {
List<Type> elementTypes = declaredTypesUsingHierarchy(type1, type2);
Type t1 = elementTypes.get(0);
Type t2 = elementTypes.get(1);
SubtypeAssert.assertThat(t1).isSameAs(t2);
}
private static void assertTypesAreNotTheSame(String type1, String type2) {
List<Type> elementTypes = declaredTypesUsingHierarchy(type1, type2);
Type t1 = elementTypes.get(0);
Type t2 = elementTypes.get(1);
SubtypeAssert.assertThat(t1).isNotSameAs(t2);
}
@Test
public void testUnboundedWildCards() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"A<?>",
"B<?>",
"B<Animal> ",
"B<Cat>",
"Animal",
"Cat");
Type wcAType = elementTypes.get(0);
Type wcBType = elementTypes.get(1);
Type animalBType = elementTypes.get(2);
Type catBType = elementTypes.get(3);
Type animalType = elementTypes.get(4);
Type catType = elementTypes.get(5);
SubtypeAssert.assertThat(catType).isSubtypeOf(animalType);
SubtypeAssert.assertThat(animalBType).isSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcBType).isNotSubtypeOf(animalBType);
SubtypeAssert.assertThat(catBType).isSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcBType).isNotSubtypeOf(catBType);
SubtypeAssert.assertThat(animalBType).isNotSubtypeOf(catBType);
SubtypeAssert.assertThat(catBType).isNotSubtypeOf(animalBType);
SubtypeAssert.assertThat(animalBType).isNotSubtypeOf(animalType);
SubtypeAssert.assertThat(animalType).isNotSubtypeOf(animalBType);
SubtypeAssert.assertThat(wcAType).isNotSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcBType).isSubtypeOf(wcAType);
SubtypeAssert.assertThat(wcBType).isSubtypeOf(wcAType);
}
@Test
public void testLowerBoundedWildCards() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"B<?>",
"B<? super Object>",
"A<? super Object>",
"A<? super Animal>",
"A<? super Cat>",
"A<Cat>",
"A<Cat>");
Type wcBType = elementTypes.get(0);
Type wcSuperObjectBType = elementTypes.get(1);
Type wcSuperObjectAType = elementTypes.get(2);
Type wcSuperAnimalAType = elementTypes.get(3);
Type wcSuperCatAType = elementTypes.get(4);
Type catAType = elementTypes.get(5);
Type catAType2 = elementTypes.get(6);
SubtypeAssert.assertThat(wcBType).isNotSubtypeOf(wcSuperObjectAType);
SubtypeAssert.assertThat(wcSuperObjectAType).isNotSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcBType).isNotSubtypeOf(wcSuperObjectBType);
SubtypeAssert.assertThat(wcSuperObjectBType).isSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcSuperObjectAType).isNotSubtypeOf(wcSuperObjectBType);
SubtypeAssert.assertThat(wcSuperObjectBType).isSubtypeOf(wcSuperObjectAType);
SubtypeAssert.assertThat(wcSuperObjectBType).isSubtypeOf(wcSuperAnimalAType);
SubtypeAssert.assertThat(wcSuperAnimalAType).isNotSubtypeOf(wcSuperObjectBType);
SubtypeAssert.assertThat(wcSuperAnimalAType).isSubtypeOf(wcSuperCatAType);
SubtypeAssert.assertThat(wcSuperCatAType).isNotSubtypeOf(wcSuperAnimalAType);
SubtypeAssert.assertThat(catAType).isSubtypeOf(wcSuperCatAType);
SubtypeAssert.assertThat(wcSuperCatAType).isNotSubtypeOf(catAType);
SubtypeAssert.assertThat(catAType).isSubtypeOf(catAType2);
SubtypeAssert.assertThat(catAType2).isSubtypeOf(catAType);
}
@Test
public void testUpperBoundedWildCards() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"B<?>",
"B<? extends Animal>",
"B<? extends Cat>",
"B<? extends Object>",
"B<Cat>",
"A<? extends Object>");
Type wcBType = elementTypes.get(0);
Type wcExtendsAnimalBType = elementTypes.get(1);
Type wcExtendsCatBType = elementTypes.get(2);
Type wcExtendsObjectBType = elementTypes.get(3);
Type catBType = elementTypes.get(4);
Type wcExtendsObjectAType = elementTypes.get(5);
SubtypeAssert.assertThat(wcBType).isSubtypeOf(wcExtendsObjectBType);
SubtypeAssert.assertThat(wcExtendsObjectBType).isSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcExtendsAnimalBType).isSubtypeOf(wcExtendsObjectBType);
SubtypeAssert.assertThat(wcExtendsObjectBType).isNotSubtypeOf(wcExtendsAnimalBType);
SubtypeAssert.assertThat(wcExtendsCatBType).isSubtypeOf(wcExtendsObjectBType);
SubtypeAssert.assertThat(wcExtendsObjectBType).isNotSubtypeOf(wcExtendsCatBType);
SubtypeAssert.assertThat(wcExtendsAnimalBType).isNotSubtypeOf(wcExtendsCatBType);
SubtypeAssert.assertThat(wcExtendsCatBType).isSubtypeOf(wcExtendsAnimalBType);
SubtypeAssert.assertThat(wcExtendsObjectAType).isNotSubtypeOf(wcExtendsObjectBType);
SubtypeAssert.assertThat(wcExtendsObjectBType).isSubtypeOf(wcExtendsObjectAType);
SubtypeAssert.assertThat(wcExtendsObjectAType).isNotSubtypeOf(wcExtendsObjectBType);
SubtypeAssert.assertThat(catBType).isSubtypeOf(wcExtendsCatBType);
SubtypeAssert.assertThat(catBType).isSubtypeOf(wcExtendsAnimalBType);
SubtypeAssert.assertThat(catBType).isSubtypeOf(wcExtendsObjectAType);
SubtypeAssert.assertThat(catBType).isSubtypeOf(wcExtendsObjectBType);
}
@Test
public void test_type_hierarchy_with_wildcard_classes() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"A<?>",
"B<?>",
"C<?>",
"B<? extends Animal>",
"B<? extends Object>",
"A<? extends Object>",
"A<? super Animal>",
"Object");
Type wcAType = elementTypes.get(0);
Type wcBType = elementTypes.get(1);
Type wcCType = elementTypes.get(2);
Type wcExtendsAnimalBType = elementTypes.get(3);
Type wcExtendsObjectBType = elementTypes.get(4);
Type wcExtendsObjectAType = elementTypes.get(5);
Type wcSuperAnimalAType = elementTypes.get(6);
Type objectType = elementTypes.get(7);
SubtypeAssert.assertThat(wcBType).isSubtypeOf(wcAType);
SubtypeAssert.assertThat(wcCType).isNotSubtypeOf(wcAType);
SubtypeAssert.assertThat(wcCType).isNotSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcBType).isSubtypeOfObject();
SubtypeAssert.assertThat(wcBType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(objectType).isNotSubtypeOf(wcBType);
SubtypeAssert.assertThat(wcExtendsAnimalBType).isSubtypeOfObject();
SubtypeAssert.assertThat(wcExtendsAnimalBType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(objectType).isNotSubtypeOf(wcExtendsAnimalBType);
SubtypeAssert.assertThat(wcExtendsObjectBType).isSubtypeOfObject();
SubtypeAssert.assertThat(wcExtendsObjectBType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(objectType).isNotSubtypeOf(wcExtendsObjectBType);
SubtypeAssert.assertThat(wcExtendsObjectAType).isSubtypeOfObject();
SubtypeAssert.assertThat(wcExtendsObjectAType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(objectType).isNotSubtypeOf(wcExtendsObjectAType);
SubtypeAssert.assertThat(wcSuperAnimalAType).isSubtypeOfObject();
SubtypeAssert.assertThat(wcSuperAnimalAType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(objectType).isNotSubtypeOf(wcSuperAnimalAType);
}
@Test
public void test_type_hierarchy_directly_between_wildcards_and_other_types() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"A<? extends Cat>",
"A<? super Cat>",
"A<?>",
"Object",
"Animal",
"Cat",
"Integer");
Type wcExtendsCatType = ((ParametrizedTypeJavaType) elementTypes.get(0)).typeSubstitution.substitutedTypes().get(0);
Type wcSuperCatType = ((ParametrizedTypeJavaType) elementTypes.get(1)).typeSubstitution.substitutedTypes().get(0);
Type wcUnboundedType = ((ParametrizedTypeJavaType) elementTypes.get(2)).typeSubstitution.substitutedTypes().get(0);
Type objectType = elementTypes.get(3);
Type animalType = elementTypes.get(4);
Type catType = elementTypes.get(5);
Type integerType = elementTypes.get(6);
SubtypeAssert.assertThat(wcExtendsCatType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(wcExtendsCatType).isSubtypeOf(catType);
SubtypeAssert.assertThat(wcExtendsCatType).isSubtypeOf(animalType);
SubtypeAssert.assertThat(wcExtendsCatType).isSubtypeOf("java.lang.Object");
SubtypeAssert.assertThat(wcExtendsCatType).isSubtypeOf("org.foo.Cat");
SubtypeAssert.assertThat(wcExtendsCatType).isSubtypeOf("org.foo.Animal");
SubtypeAssert.assertThat(wcSuperCatType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(wcSuperCatType).isNotSubtypeOf(catType);
SubtypeAssert.assertThat(wcSuperCatType).isNotSubtypeOf(animalType);
SubtypeAssert.assertThat(wcSuperCatType).isSubtypeOf("java.lang.Object");
SubtypeAssert.assertThat(wcSuperCatType).isNotSubtypeOf("org.foo.Cat");
SubtypeAssert.assertThat(wcSuperCatType).isNotSubtypeOf("org.foo.Animal");
SubtypeAssert.assertThat(wcUnboundedType).isSubtypeOf(objectType);
SubtypeAssert.assertThat(wcUnboundedType).isNotSubtypeOf(catType);
SubtypeAssert.assertThat(wcUnboundedType).isNotSubtypeOf(animalType);
SubtypeAssert.assertThat(wcUnboundedType).isSubtypeOf("java.lang.Object");
SubtypeAssert.assertThat(wcUnboundedType).isNotSubtypeOf("org.foo.Cat");
SubtypeAssert.assertThat(wcUnboundedType).isNotSubtypeOf("org.foo.Animal");
SubtypeAssert.assertThat(wcExtendsCatType).isNotSubtypeOf(integerType);
SubtypeAssert.assertThat(wcExtendsCatType).isNotSubtypeOf("java.lang.Integer");
}
@Test
public void testWildCardWithObjectType() {
List<Type> elementTypes = declaredTypesOfVariablesFromLastClass(
"interface Set<T> {}",
"class Test<X> {",
"Object object;",
"Set<? extends X> set;",
"X element;",
"}");
Type objectType = elementTypes.get(0);
Type setExtendsXType = elementTypes.get(1);
Type xType = elementTypes.get(2);
Type wildCardXType = ((ParametrizedTypeJavaType) setExtendsXType).typeSubstitution.substitutedTypes().get(0);
SubtypeAssert.assertThat(xType).isSubtypeOf(wildCardXType);
SubtypeAssert.assertThat(objectType).isNotSubtypeOf(wildCardXType);
}
@Test
public void test_variables_types_parameters_and_wildcards() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"A<?>",
"A<T>",
"A<U>",
"Test<?,?,?>",
"Test<T,U,V>",
"Test<?,U,V>",
"Test<?,V,U>",
"Test<?,?,U>",
"Test<?,?,U>");
Type wcAType = elementTypes.get(0);
Type varTAType = elementTypes.get(1);
Type varUAType = elementTypes.get(2);
Type wcTestWcWcWcType = elementTypes.get(3);
Type varTestTUVType = elementTypes.get(4);
Type wcTestWcUVType = elementTypes.get(5);
Type wcTestWcVUType = elementTypes.get(6);
Type wcTestWcWcUType = elementTypes.get(7);
Type wcTestWcWcUType2 = elementTypes.get(8);
SubtypeAssert.assertThat(varTAType).isSubtypeOf(wcAType);
SubtypeAssert.assertThat(varUAType).isSubtypeOf(wcAType);
SubtypeAssert.assertThat(varTestTUVType).isSubtypeOf(wcTestWcWcWcType);
SubtypeAssert.assertThat(wcTestWcUVType).isSubtypeOf(wcTestWcWcWcType);
SubtypeAssert.assertThat(wcTestWcVUType).isSubtypeOf(wcTestWcWcWcType);
SubtypeAssert.assertThat(wcTestWcVUType).isNotSubtypeOf(wcTestWcUVType);
SubtypeAssert.assertThat(wcTestWcWcUType).isSubtypeOf(wcTestWcWcWcType);
SubtypeAssert.assertThat(wcTestWcWcUType).isSubtypeOf(wcTestWcWcUType2);
}
@Test
public void test_method_resolution_based_on_wildcards() {
List<Type> elementTypes = declaredTypes(
"class Animal {}",
"class Cat extends Animal {}",
"class Lion extends Cat {}",
"class A<T> {",
" void foo(A<? extends Animal> a) {",
// call to foo with wildcard
" foo(new A<Animal>());",
" foo(new A<Cat>());",
" foo(new A<Lion>());",
// call to foo with object
" foo(new A<Object>());",
" }",
" void foo(Object o) {}",
" void bar(A<? super Cat> a) {",
// call to bar with wildcard
" bar(new A<Object>());",
" bar(new A<Animal>());",
" bar(new A<Cat>());",
// call to bar with object
" bar(new A<Lion>());",
" }",
" void bar(Object o) {}",
" void qix(A<?> a) {",
" qix(new A<Object>());",
" qix(new A<Animal>());",
" qix(new A<Cat>());",
" qix(new A<Lion>());",
" }",
" void gul(A<Cat> a) {",
" gul(new A<Animal>());",
" gul(new A<Cat>());",
" gul(new A<Object>());",
" }",
" void gul(Object o) {}",
"}");
JavaType aType = (JavaType) elementTypes.get(3);
JavaSymbol.MethodJavaSymbol fooWildCard = getMethodSymbol(aType, "foo", 0);
JavaSymbol.MethodJavaSymbol fooObject = getMethodSymbol(aType, "foo", 1);
assertThat(fooWildCard.usages()).hasSize(3);
assertThat(fooObject.usages()).hasSize(1);
JavaSymbol.MethodJavaSymbol barWildCard = getMethodSymbol(aType, "bar", 0);
JavaSymbol.MethodJavaSymbol barObject = getMethodSymbol(aType, "bar", 1);
assertThat(barWildCard.usages()).hasSize(3);
assertThat(barObject.usages()).hasSize(1);
JavaSymbol.MethodJavaSymbol qix = getMethodSymbol(aType, "qix");
assertThat(qix.usages()).hasSize(4);
JavaSymbol.MethodJavaSymbol gulGenerics = getMethodSymbol(aType, "gul", 0);
JavaSymbol.MethodJavaSymbol gulObject = getMethodSymbol(aType, "gul", 1);
assertThat(gulGenerics.usages()).hasSize(1);
assertThat(gulObject.usages()).hasSize(2);
}
@Test
public void test_method_resolution() {
List<Type> elementTypes = declaredTypes(
"import java.util.Arrays;",
"import java.util.Collection;",
"class A {",
" void foo() {",
" bar(Arrays.asList(\"string\"));",
" }",
" void bar(Collection<String> c) {}",
"}");
JavaType aType = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol bar = getMethodSymbol(aType, "bar");
assertThat(bar.usages()).hasSize(1);
}
@Test
public void test_method_resolution_nested_parametrized_type_with_wildcards() {
List<Type> elementTypes = declaredTypes(
"class A<X> {"
+ " void foo1(A<A<X>> a) {}"
+ " void foo2(A<A<? extends X>> a) {}"
+ " A<A<String>> qix1() { return null; }"
+ " A<A<? extends String>> qix2() { return null; }"
+ " void bar() {"
+ " new A<String>().foo1(qix1());"
+ " new A<String>().foo2(qix2());"
+ " }"
+ "}");
JavaType aType = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol foo1 = getMethodSymbol(aType, "foo1");
assertThat(foo1.usages()).hasSize(1);
JavaSymbol.MethodJavaSymbol foo2 = getMethodSymbol(aType, "foo2");
assertThat(foo2.usages()).hasSize(1);
}
@Test
public void test_method_resolution_with_type_substitution() {
List<Type> elementTypes = declaredTypes(
"class A<X> {"
+ " void foo(A<? extends X> a) {}"
+ " A<String> qix() { return null; }"
+ " void bar() {"
+ " new A<String>().foo(qix());"
+ " }"
+ "}");
JavaSymbol.MethodJavaSymbol foo = getMethodSymbol((JavaType) elementTypes.get(0), "foo", 0);
assertThat(foo.usages()).hasSize(1);
}
@Test
public void test_method_resolution_based_on_wildcards_with_nested_generics() {
List<Type> elementTypes = declaredTypes(
"interface I<Z> {}",
"class A<X extends I<? extends X>> {"
+ " A(X x) {}"
+ " <Y extends I<? extends Y>> void foo(Y y) { new A<>(y); }"
+ "}");
JavaSymbol.MethodJavaSymbol constructor = getMethodSymbol((JavaType) elementTypes.get(1), "<init>", 0);
assertThat(constructor.usages()).hasSize(1);
}
private static MethodJavaSymbol getMethodSymbol(JavaType aType, String methodName, int index) {
return (JavaSymbol.MethodJavaSymbol) aType.symbol.members.lookup(methodName).get(index);
}
private static MethodJavaSymbol getMethodSymbol(JavaType aType, String methodName) {
return getMethodSymbol(aType, methodName, 0);
}
@Test
public void test_wildcard_grid() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"A",
"A<?>",
"A<Animal>",
"A<Cat>",
"A<? extends Animal>",
"A<? extends Cat>",
"A<? super Animal>",
"A<? super Cat>",
"A<T>",
"A<? extends T>",
"A<? super T>");
Type a = elementTypes.get(0);
Type aWc = elementTypes.get(1);
Type aAnimal = elementTypes.get(2);
Type aCat = elementTypes.get(3);
Type aExtendsAnimal = elementTypes.get(4);
Type aExtendsCat = elementTypes.get(5);
Type aSuperAnimal = elementTypes.get(6);
Type aSuperCat = elementTypes.get(7);
Type aT = elementTypes.get(8);
Type aExtendsT = elementTypes.get(9);
Type aSuperT = elementTypes.get(10);
SubtypeAssert.assertThat(a)
.isSubtypeOf(a)
.isNotSubtypeOf(aWc, aAnimal, aCat, aExtendsAnimal, aExtendsCat, aSuperAnimal, aSuperCat, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aWc)
.isSubtypeOf(a, aWc)
.isNotSubtypeOf(aAnimal, aCat, aExtendsAnimal, aExtendsCat, aSuperAnimal, aSuperCat, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aAnimal)
.isSubtypeOf(a, aWc, aAnimal, aExtendsAnimal, aSuperAnimal, aSuperCat)
.isNotSubtypeOf(aCat, aExtendsCat, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aCat)
.isSubtypeOf(a, aWc, aCat, aExtendsAnimal, aExtendsCat, aSuperCat)
.isNotSubtypeOf(aAnimal, aSuperAnimal, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aExtendsAnimal)
.isSubtypeOf(a, aWc, aExtendsAnimal)
.isNotSubtypeOf(aAnimal, aCat, aExtendsCat, aSuperAnimal, aSuperCat, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aExtendsCat)
.isSubtypeOf(a, aWc, aExtendsAnimal, aExtendsCat)
.isNotSubtypeOf(aAnimal, aCat, aSuperAnimal, aSuperCat, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aSuperAnimal)
.isSubtypeOf(a, aWc, aSuperAnimal, aSuperCat)
.isNotSubtypeOf(aAnimal, aCat, aExtendsAnimal, aExtendsCat, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aSuperCat)
.isSubtypeOf(a, aWc, aSuperCat)
.isNotSubtypeOf(aAnimal, aCat, aExtendsAnimal, aExtendsCat, aSuperAnimal, aT, aExtendsT, aSuperT);
SubtypeAssert.assertThat(aT)
.isSubtypeOf(a, aWc, aT, aExtendsT, aSuperT)
.isNotSubtypeOf(aAnimal, aCat, aExtendsAnimal, aExtendsCat, aSuperAnimal, aSuperCat);
SubtypeAssert.assertThat(aExtendsT)
.isSubtypeOf(a, aWc, aExtendsT)
.isNotSubtypeOf(aAnimal, aCat, aExtendsAnimal, aExtendsCat, aSuperAnimal, aSuperCat, aT, aSuperT);
SubtypeAssert.assertThat(aSuperT)
.isSubtypeOf(a, aWc, aSuperT)
.isNotSubtypeOf(aAnimal, aCat, aExtendsAnimal, aExtendsCat, aSuperAnimal, aSuperCat, aT, aExtendsT);
}
@Test
public void testParameterizedTypeHierarchy() {
List<Type> elementTypes = declaredTypesUsingHierarchy(
"B<Cat>",
"A<Animal>");
SubtypeAssert.assertThat(elementTypes.get(0)).isNotSubtypeOf(elementTypes.get(1));
elementTypes = declaredTypesUsingHierarchy(
"B<Cat>",
"A<Cat>");
SubtypeAssert.assertThat(elementTypes.get(0)).isSubtypeOf(elementTypes.get(1));
}
@Test
public void testTypeHierarchyWithGenericClasses() {
List<Type> elementTypes = declaredTypesOfVariablesFromLastClass(
"interface Predicate<T> {}",
"class ObjectPredicate implements Predicate<Object> {}",
"class Test<X> {",
" Predicate<X> myPredicate;",
" ObjectPredicate objectPredicate;",
" Predicate<Object> oPredicate;",
"}");
Type xPredicateType = elementTypes.get(0);
Type objectPredicateType = elementTypes.get(1);
Type oPredicateType = elementTypes.get(2);
SubtypeAssert.assertThat(objectPredicateType).isSubtypeOf(oPredicateType);
SubtypeAssert.assertThat(oPredicateType).isNotSubtypeOf(objectPredicateType);
SubtypeAssert.assertThat(xPredicateType).isNotSubtypeOf(objectPredicateType);
SubtypeAssert.assertThat(objectPredicateType).isNotSubtypeOf(xPredicateType);
Type xPredicateRAWType = elementTypes.get(0).erasure();
SubtypeAssert.assertThat(xPredicateRAWType).isNotSubtypeOf(xPredicateType);
SubtypeAssert.assertThat(xPredicateType).isSubtypeOf(xPredicateRAWType);
}
@Test
public void test_method_resolution_for_parametrized_method_with_provided_substitution() {
JavaType type = (JavaType) declaredTypesFromFile("src/test/files/resolve/ParametrizedMethodsWithProvidedSubstitution.java").get(0);
methodHasUsagesWithSameTypeAs(type, "f1", 0, "bString", "bb");
methodHasUsagesWithSameTypeAs(type, "f1", 1, "aType");
methodHasUsagesWithSameTypeAs(type, "f2", 0, "integer", "string", "aType");
methodHasUsagesWithSameTypeAs(type, "f2", 1, "aType");
methodHasUsagesWithSameTypeAs(type, "f3", "integer");
methodHasUsagesWithSameTypeAs(type, "f4", (String) null);
Type stringArray = getMethodInvocationType(getMethodSymbol(type, "f4", 0), 0);
assertThat(stringArray.isArray()).isTrue();
assertThat(((ArrayJavaType) stringArray).elementType.is("java.lang.String")).isTrue();
stringArray = getMethodInvocationType(getMethodSymbol(type, "f4", 1), 0);
assertThat(stringArray.isArray()).isTrue();
assertThat(((ArrayJavaType) stringArray).elementType.isArray()).isTrue();
assertThat(((ArrayJavaType) ((ArrayJavaType) stringArray).elementType).elementType.is("java.lang.String")).isTrue();
methodHasUsagesWithSameTypeAs(type, "f5", "cStringInteger", "cStringInteger", "cAB");
methodHasUsagesWithSameTypeAs(type, "f6", "wcSuperA");
methodHasUsagesWithSameTypeAs(type, "f7", "integer");
methodHasUsagesWithSameTypeAs(type, "f8", 0, "object");
methodHasUsagesWithSameTypeAs(type, "f8", 1, "bType", "dType");
methodHasUsagesWithSameTypeAs(type, "f9", 0, "object", "object");
methodHasUsagesWithSameTypeAs(type, "f9", 1, "dType");
methodHasUsagesWithSameTypeAs(type, "f10", "integer");
}
@Test
public void test_method_resolution_with_parametrized_methods() {
JavaType aType = (JavaType) declaredTypesFromFile("src/test/files/resolve/ParametrizedMethodsWithTypeInference.java").get(0);
methodHasUsagesWithSameTypeAs(aType, "f1", "bString", "bObject");
// FIXME type is 'T' when T can not be inferred. Should be Object?
methodHasUsagesWithSameTypeAs(aType, "f2", "integer", null, "object");
methodHasUsagesWithSameTypeAs(aType, "f3", "integer");
Type arrayType = getMethodInvocationType(getMethodSymbol(aType, "f4"), 0);
assertThat(arrayType.isArray()).isTrue();
assertThat(((ArrayJavaType) arrayType).elementType.is("java.lang.String")).isTrue();
methodHasUsagesWithSameTypeAs(aType, "f5", "cStringInteger", "cStringInteger");
methodHasUsagesWithSameTypeAs(aType, "f6", "wcSuperA", null, null);
methodHasUsagesWithSameTypeAs(aType, "f7", "integer");
methodHasUsagesWithSameTypeAs(aType, "f8", 0, "object");
methodHasUsagesWithSameTypeAs(aType, "f8", 1, "bString");
methodHasUsagesWithSameTypeAs(aType, "f9", 0, "object", "object");
methodHasUsagesWithSameTypeAs(aType, "f9", 1, "dType");
methodHasUsagesWithSameTypeAs(aType, "f10", "integer", "number", "aType");
methodHasUsagesWithSameTypeAs(aType, "f11", "cStringA");
methodHasUsagesWithSameTypeAs(aType, "f12", "aType");
methodHasUsagesWithSameTypeAs(aType, "f13", "integer");
methodHasUsagesWithSameTypeAs(aType, "f14", "bString");
methodHasUsagesWithSameTypeAs(aType, "f15", "object");
methodHasUsagesWithSameTypeAs(aType, "f16", "bObject", "bObject", "bObject", "comparable", "bString");
methodHasUsagesWithSameTypeAs(aType, "f17", "object");
}
@Test
public void test_method_resolution_with_unchecked_conversions() {
// JLS8 5.1.9 + JLS8 15.12.2.6
JavaType bType = (JavaType) declaredTypesFromFile("src/test/files/resolve/UncheckedConversion.java").get(0);
MethodJavaSymbol foo = getMethodSymbol(bType, "foo");
Type type = getMethodInvocationType(foo, 0);
assertThat(type.isArray()).isTrue();
assertThat(((ArrayJavaType) type).elementType.is("java.lang.Object")).isTrue();
type = getMethodInvocationType(foo, 1);
assertThat(type.isArray()).isTrue();
assertThat(((ArrayJavaType) type).elementType.is("org.foo.A")).isTrue();
type = getMethodInvocationType(foo, 2);
assertThat(type.isArray()).isTrue();
assertThat(((ArrayJavaType) type).elementType.is("org.foo.A")).isTrue();
methodHasUsagesWithSameTypeAs(bType, "bar", "objectType", "aType", "aType");
methodHasUsagesWithSameTypeAs(bType, "qix", "bRawType", "bAType", "bAType");
methodHasUsagesWithSameTypeAs(bType, "gul", "bRawType", "bBAType", "bBAType");
methodHasUsagesWithSameTypeAs(bType, "lot", "aType", "cType", "cType");
}
@Test
public void test_method_resolution_for_parametrized_method_with_provided_cascading_substitution() {
List<Type> elementTypes = declaredTypes(
"class Test {"
+ " <T extends X, X extends A> void foo(T t) {}"
+ " void foo(Object o) {}"
+ " void test() {"
+ " this.<B, A>foo(new B());"
+ " this.<A, A>foo(new B());"
+ " this.<A, B>foo(new B());"
+ " }"
+ "}"
+ "class A {}"
+ "class B extends A {}");
JavaType type = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(type, "foo", 0);
assertThat(methodSymbol.usages()).hasSize(2);
methodSymbol = getMethodSymbol(type, "foo", 1);
assertThat(methodSymbol.usages()).hasSize(1);
}
@Test
public void test_method_resolution_for_parametrized_method_with_type_variable_inheritance() {
List<Type> elementTypes = declaredTypes(
"class Test<T> {"
+ " <S extends T> void foo(S s) {}"
+ " void test() {"
// type substitution provided
+ " new Test<A>().<A>foo(new A());"
+ " new Test<A>().<B>foo(new B());"
// type inference
+ " new Test<A>().foo(new A());"
+ " new Test<A>().foo(new B());"
+ " }"
+ "}"
+ "class A {}"
+ "class B extends A {}");
JavaType type = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(type, "foo");
assertThat(methodSymbol.usages()).hasSize(4);
elementTypes = declaredTypes(
"class Test<T> {"
+ " <S extends T> void foo(S s) {}"
+ " void test() {"
// does not compile - not resolved
+ " new Test<B>().foo(new A());"
+ " new Test<B>().<A>foo(new A());"
+ " }"
+ "}"
+ "class A {}"
+ "class B extends A {}");
type = (JavaType) elementTypes.get(0);
methodSymbol = getMethodSymbol(type, "foo");
assertThat(methodSymbol.usages()).hasSize(0);
}
@Test
public void test_array_of_generics() throws Exception {
List<Type> elementTypes = declaredTypes(
"class Test<T> {"
+ " void foo(T[][]... ts) {}"
+ " void bar(Class<?>... ts) {}"
+ " void test(Class type) {"
+ " new Test<A>().foo(new A[12][14]);"
+" bar(new Class[]{Class.class});"
+ " }"
+ "}" +
"class A{}");
JavaType type = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(type, "foo");
assertThat(methodSymbol.usages()).hasSize(1);
methodSymbol = getMethodSymbol(type, "bar");
assertThat(methodSymbol.usages()).hasSize(1);
}
@Test
public void test_method_resolution_for_parametrized_method_and_nested_Parametrized_types() {
List<Type> elementTypes = declaredTypesFromFile("src/test/files/resolve/generics/parametrizedMethodWithTypeInferenceAndTypeInheritance.java");
JavaType type = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(type, "foo");
assertThat(methodSymbol.usages()).hasSize(2);
assertThat(getMethodSymbol(type, "usesInteger").usages()).hasSize(1);
assertThat(getMethodSymbol(type, "usesString").usages()).hasSize(1);
}
@Test
public void test_method_resolution_for_parametrized_method_with_inference_from_call_site() {
List<Type> elementTypes = declaredTypes(
"class A<E> {"
+ " static <T> A<T> foo() { return new A<T>(); }"
+ " void tst() {"
+ " A<String> a = A.foo();"
+ " }"
+ "}");
JavaType type = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(type, "foo");
assertThat(methodSymbol.usages()).hasSize(1);
Type methodInvocationType = getMethodInvocationType(methodSymbol, 0);
assertThat(methodInvocationType.erasure().is("A")).isTrue();
assertThat(methodInvocationType instanceof ParametrizedTypeJavaType).isTrue();
assertThat(((ParametrizedTypeJavaType) methodInvocationType).typeSubstitution.substitutedTypes().get(0).is("java.lang.String")).isTrue();
}
@Test
public void test_method_resolution_of_parametrized_method_from_parametrized() throws IOException {
List<Type> elementTypes = declaredTypesFromFile("src/test/files/resolve/GenericMethods.java");
JavaType aType = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(aType, "cast");
assertThat(methodSymbol.usages()).hasSize(3);
JavaType bType = (JavaType) elementTypes.get(3);
JavaType objectType = (JavaType) bType.symbol.superClass();
JavaType cType = (JavaType) elementTypes.get(4);
JavaType i3Type = (JavaType) elementTypes.get(5);
assertThat(getMethodSymbol(cType, "bar").usages()).hasSize(2);
assertThat(getMethodSymbol(i3Type, "foo").usages()).hasSize(2);
assertThat(getMethodSymbol(objectType, "toString").usages()).hasSize(2);
assertThat(getMethodSymbol(bType, "print").usages()).hasSize(4);
}
@Test
public void test_method_resolution_with_substitution_in_type_hierarchy() {
List<Type> elementTypes = declaredTypes(
"class A {"
+ " <T> T foo(B<T> b) {"
+ " return null;"
+ " }"
+ " Object foo(Object o) {"
+ " return null;"
+ " }"
+ " void tst() {"
+ " stringType = foo(new C());"
+ " stringType = foo(new D());"
+ " objectType = foo(new A());"
+ " objectType = foo(new E());"
+ " }"
+ " String stringType;"
+ " Object objectType;"
+ "}"
+ "class B<T> {}"
+ "class C extends B<String> {}"
+ "class D extends C {}"
+ "class E extends A {}");
JavaType type = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(type, "foo");
assertThat(methodSymbol.usages()).hasSize(2);
}
@Test
public void parametrized_method_resolution_with_bounded_type_variable() {
List<Type> elementTypes = declaredTypesFromFile("src/test/files/resolve/GenericMethodsBoundedTypeVariables.java");
JavaType type = (JavaType) elementTypes.get(0);
JavaSymbol.MethodJavaSymbol methodSymbol = getMethodSymbol(type, "foo");
// FIXME SONARJAVA-1859 should be 12 - explicit type arguments not handled
assertThat(methodSymbol.usages()).hasSize(6);
List<Integer> lines = methodSymbol.usages().stream().map(IdentifierTree::identifierToken).map(SyntaxToken::line).collect(Collectors.toList());
assertThat(lines).containsExactly(9, 10, 18, 19, 27, 28);
}
private static void methodHasUsagesWithSameTypeAs(JavaType type, String methodName, String... variables) {
methodHasUsagesWithSameTypeAs(type, methodName, 0, variables);
}
private static void methodHasUsagesWithSameTypeAs(JavaType type, String methodName, int methodIndex, String... variables) {
JavaSymbol.MethodJavaSymbol method = getMethodSymbol(type, methodName, methodIndex);
List<IdentifierTree> usages = method.usages();
assertThat(usages).overridingErrorMessage("Method '" + methodName + "' should have " + variables.length + " reference(s) but has " + usages.size() + ".")
.hasSize(variables.length);
for (int i = 0; i < variables.length; i++) {
String variableName = variables[i];
if (variableName != null) {
Type methodInvocationType = getMethodInvocationType(method, i);
Type variableType = getVariableType(type, variableName);
assertThat(methodInvocationType).overridingErrorMessage("Type of expression should be the same as type of variable '" + variableName + "'.").isSameAs(variableType);
}
}
}
private static Type getVariableType(JavaType owner, String variableName) {
return ((JavaSymbol.VariableJavaSymbol) owner.symbol.members.lookup(variableName).get(0)).type();
}
private static Type getMethodInvocationType(MethodJavaSymbol method, int usageIndex) {
Tree current = method.usages().get(usageIndex);
while (!current.is(Tree.Kind.METHOD_INVOCATION)) {
current = current.parent();
}
return ((MethodInvocationTree) current).symbolType();
}
static class SubtypeAssert extends AbstractAssert<SubtypeAssert, Type> {
public SubtypeAssert(Type type) {
super(type, SubtypeAssert.class);
}
static SubtypeAssert assertThat(Type type) {
return new SubtypeAssert(type);
}
SubtypeAssert isSubtypeOf(Type... types) {
for (Type type : types) {
Assertions.assertThat(actual.isSubtypeOf(type)).overridingErrorMessage(isSubtypeOfMsg(type)).isTrue();
}
return this;
}
SubtypeAssert isNotSubtypeOf(Type... types) {
for (Type type : types) {
Assertions.assertThat(actual.isSubtypeOf(type)).overridingErrorMessage(isNotSubtypeOfMsg(type)).isFalse();
}
return this;
}
SubtypeAssert isNotSubtypeOfObject() {
Assertions.assertThat(actual.isSubtypeOf("java.lang.Object")).isFalse();
return this;
}
SubtypeAssert isSubtypeOfObject() {
Assertions.assertThat(actual.isSubtypeOf("java.lang.Object")).isTrue();
return this;
}
SubtypeAssert isSubtypeOf(String fullyQualifiedName) {
Assertions.assertThat(actual.isSubtypeOf(fullyQualifiedName)).overridingErrorMessage(isSubtypeOfMsg(fullyQualifiedName)).isTrue();
return this;
}
SubtypeAssert isNotSubtypeOf(String fullyQualifiedName) {
Assertions.assertThat(actual.isSubtypeOf(fullyQualifiedName)).overridingErrorMessage(isNotSubtypeOfMsg(fullyQualifiedName)).isFalse();
return this;
}
SubtypeAssert isSubtypeOf(Type expected) {
Assertions.assertThat(actual.isSubtypeOf(expected)).overridingErrorMessage(isSubtypeOfMsg(expected)).isTrue();
return this;
}
SubtypeAssert isNotSubtypeOf(Type expected) {
Assertions.assertThat(actual.isSubtypeOf(expected)).overridingErrorMessage(isNotSubtypeOfMsg(expected)).isFalse();
return this;
}
private String isNotSubtypeOfMsg(Type type) {
return isNotSubtypeOfMsg(prettyPrint(type));
}
private String isNotSubtypeOfMsg(String type) {
return "'" + prettyPrint(actual) + "' should not be be a subtype of '" + type + "'";
}
private String isSubtypeOfMsg(Type type) {
return isSubtypeOfMsg(prettyPrint(type));
}
private String isSubtypeOfMsg(String type) {
return "'" + prettyPrint(actual) + "' should be a subtype of '" + type + "'";
}
private static String prettyPrint(Type actual) {
String result = actual.toString();
JavaType javaType = (JavaType) actual;
if (javaType instanceof ParametrizedTypeJavaType) {
result += "<" + getParameters((ParametrizedTypeJavaType) javaType) + ">";
}
return result;
}
private static String getParameters(ParametrizedTypeJavaType javaType) {
List<JavaType> substitutedTypes = javaType.typeSubstitution.substitutedTypes();
List<String> names = new ArrayList<>(substitutedTypes.size());
for (JavaType type : substitutedTypes) {
names.add(type.toString());
}
return StringUtils.join(names, ",");
}
}
/**
* Used hierarchy:
* <pre>
* interface A<T> {}
* interface B<T> extends A<T> {}
* interface C<T> {}
* class Animal {}
* class Cat extends Animal {}
* class Test<T, U, V> {}
* </pre>
* @param types
* @return
*/
private static List<Type> declaredTypesUsingHierarchy(String... types) {
String[] linesBefore = new String[] {
"package org.foo;",
"interface A<T> {}",
"interface B<T> extends A<T> {}",
"interface C<T> {}",
"class Animal {}",
"class Cat extends Animal {}",
"class Test<T,U,V> {"};
String[] linesAfter = new String[] {"}"};
String[] variables = new String[types.length];
for (int i = 0; i < types.length; i++) {
variables[i] = types[i] + " o" + i + ";";
}
return declaredTypesOfVariablesFromLastClass((String[]) ArrayUtils.addAll(ArrayUtils.addAll(linesBefore, variables), linesAfter));
}
private static List<Type> declaredTypesOfVariablesFromLastClass(String... lines) {
CompilationUnitTree tree = treeOf(lines);
List<Tree> declaredClasses = tree.types();
Tree last = declaredClasses.get(declaredClasses.size() - 1);
if (!(last instanceof ClassTree)) {
return Collections.emptyList();
}
ClassTree testClass = (ClassTree) last;
List<Type> types = new ArrayList<>();
for (Tree member : testClass.members()) {
if (member instanceof VariableTree) {
types.add(((VariableTree) member).type().symbolType());
}
}
return types;
}
private static List<Type> declaredTypes(String... lines) {
CompilationUnitTree tree = treeOf(lines);
List<Type> results = Lists.newLinkedList();
for (Tree classTree : tree.types()) {
Type type = ((ClassTree) classTree).symbol().type();
results.add(type);
}
return results;
}
private static CompilationUnitTree treeOf(String... lines) {
StringBuilder builder = new StringBuilder();
for (String line : lines) {
builder.append(line).append(System.lineSeparator());
}
CompilationUnitTree cut = (CompilationUnitTree) JavaParser.createParser(StandardCharsets.UTF_8).parse(builder.toString());
SemanticModel.createFor(cut, Lists.newArrayList(new File("target/test-classes"), new File("target/classes")));
return cut;
}
private static List<Type> declaredTypesFromFile(String path) {
CompilationUnitTree tree = treeOf(new File(path));
List<Type> results = Lists.newLinkedList();
for (Tree classTree : tree.types()) {
Type type = ((ClassTree) classTree).symbol().type();
results.add(type);
}
return results;
}
private static CompilationUnitTree treeOf(File file) {
CompilationUnitTree cut = (CompilationUnitTree) JavaParser.createParser(StandardCharsets.UTF_8).parse(file);
SemanticModel.createFor(cut, Lists.newArrayList(new File("target/test-classes"), new File("target/classes")));
return cut;
}
}