/* * 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.Iterables; import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.java.resolve.JavaSymbol.TypeJavaSymbol; import org.sonar.java.resolve.targets.Annotations; import org.sonar.java.resolve.targets.AnonymousClass; import org.sonar.java.resolve.targets.HasInnerClass; import org.sonar.java.resolve.targets.InnerClassBeforeOuter; import org.sonar.java.resolve.targets.InnerClassConstructors; import org.sonar.java.resolve.targets.NamedClassWithinMethod; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; import java.io.File; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; public class BytecodeCompleterTest { @Rule public LogTester logTester = new LogTester(); //used to load classes in same package public BytecodeCompleterPackageVisibility bytecodeCompleterPackageVisibility = new BytecodeCompleterPackageVisibility(); private BytecodeCompleter bytecodeCompleter; private void accessPackageVisibility() { bytecodeCompleterPackageVisibility.add(1, 2); } @Before public void setUp() throws Exception { bytecodeCompleter = new BytecodeCompleter(Lists.newArrayList(new File("target/test-classes"), new File("target/classes")), new ParametrizedTypeCache()); new Symbols(bytecodeCompleter); } @Test public void class_names_ending_with_$() throws Exception { JavaSymbol.TypeJavaSymbol classSymbol = bytecodeCompleter.getClassSymbol("org/sonar/java/resolve/targets/OuterClassEndingWith$$InnerClassEndingWith$"); assertThat(classSymbol.getName()).isEqualTo("InnerClassEndingWith$"); assertThat(classSymbol.owner().getName()).isEqualTo("OuterClassEndingWith$"); } @Test public void innerClassWithDollarName() throws Exception { JavaSymbol.TypeJavaSymbol classSymbol = bytecodeCompleter.getClassSymbol("org/sonar/java/resolve/targets/UseDollarNames"); //Complete superclass which has an inner class named A$Bq JavaType superclass = classSymbol.getSuperclass(); JavaType superSuperClass = superclass.getSymbol().getSuperclass(); assertThat(superSuperClass.fullyQualifiedName()).isEqualTo("java.lang.Object"); Collection<Symbol> symbols = superclass.getSymbol().memberSymbols(); for (Symbol symbol : symbols) { if(symbol.isTypeSymbol()) { assertThat(symbol.name()).isEqualTo("A$B"); Collection<Symbol> members = ((Symbol.TypeSymbol) symbol).lookupSymbols("C$D"); assertThat(members).isNotEmpty(); } } } @Test public void annotations() throws Exception { bytecodeCompleter.getClassSymbol(Annotations.class.getName().replace('.', '/')).complete(); } @Test public void anonymous_class() { bytecodeCompleter.getClassSymbol(AnonymousClass.class.getName().replace('.', '/')).complete(); } @Test public void named_class_within_method() { bytecodeCompleter.getClassSymbol(NamedClassWithinMethod.class.getName().replace('.', '/')).complete(); } @Test public void inner_class_before_outer() { JavaSymbol.TypeJavaSymbol symbol = bytecodeCompleter.getClassSymbol(InnerClassBeforeOuter.class.getName()); JavaSymbol.TypeJavaSymbol innerClass = symbol.getSuperclass().symbol; JavaSymbol.TypeJavaSymbol outerClass = (JavaSymbol.TypeJavaSymbol) innerClass.owner(); assertThat(outerClass.members().lookup(HasInnerClass.InnerClass.class.getSimpleName())).containsExactly(innerClass); } @Test public void outer_class_before_inner() { JavaSymbol.TypeJavaSymbol outerClass = bytecodeCompleter.getClassSymbol(HasInnerClass.class.getName()); assertThat(outerClass.members().lookup(HasInnerClass.InnerClass.class.getSimpleName())).hasSize(1); } @Test public void inner_classes_constructors_have_outerclass_as_implicit_first_parameter() { JavaSymbol.TypeJavaSymbol outerClass = bytecodeCompleter.getClassSymbol(InnerClassConstructors.class.getName()); List<JavaSymbol> constructors; JavaSymbol.MethodJavaSymbol defaultConstructor; JavaSymbol.TypeJavaSymbol privateInnerClass = (JavaSymbol.TypeJavaSymbol) Iterables.getFirst(outerClass.lookupSymbols("PrivateInnerClass"), null); constructors = privateInnerClass.members().lookup("<init>"); assertThat(constructors).hasSize(1); defaultConstructor = (JavaSymbol.MethodJavaSymbol) constructors.get(0); assertThat(defaultConstructor.parameterTypes()).hasSize(1); assertThat(defaultConstructor.parameterTypes().get(0)).isSameAs(outerClass.type()); JavaSymbol.TypeJavaSymbol staticInnerClass = bytecodeCompleter.getClassSymbol(InnerClassConstructors.StaticInnerClass.class.getName()); constructors = staticInnerClass.members().lookup("<init>"); assertThat(constructors).hasSize(1); defaultConstructor = (JavaSymbol.MethodJavaSymbol) constructors.get(0); assertThat(defaultConstructor.parameterTypes()).isEmpty(); JavaSymbol.TypeJavaSymbol innerClass = bytecodeCompleter.getClassSymbol(InnerClassConstructors.InnerClass.class.getName()); constructors = innerClass.members().lookup("<init>"); assertThat(constructors).hasSize(1); defaultConstructor = (JavaSymbol.MethodJavaSymbol) constructors.get(0); assertThat(defaultConstructor.parameterTypes()).hasSize(1); assertThat(defaultConstructor.parameterTypes().get(0)).isSameAs(outerClass.type()); JavaSymbol.TypeJavaSymbol innerClassWithConstructor = bytecodeCompleter.getClassSymbol(InnerClassConstructors.InnerClassWithConstructor.class.getName()); constructors = innerClassWithConstructor.members().lookup("<init>"); assertThat(constructors).hasSize(1); JavaSymbol.MethodJavaSymbol constructor = (JavaSymbol.MethodJavaSymbol) constructors.get(0); assertThat(constructor.parameterTypes()).hasSize(2); assertThat(constructor.parameterTypes().get(0)).isSameAs(outerClass.type()); assertThat(constructor.parameterTypes().get(1).isPrimitive(Type.Primitives.INT)).isTrue(); } @Test public void completing_symbol_ArrayList() throws Exception { JavaSymbol.TypeJavaSymbol arrayList = bytecodeCompleter.getClassSymbol("java/util/ArrayList"); //Check supertype assertThat(arrayList.getSuperclass().symbol.name).isEqualTo("AbstractList"); assertThat(arrayList.getSuperclass().symbol.owner().name).isEqualTo("java.util"); //Check interfaces assertThat(arrayList.getInterfaces()).hasSize(4); List<String> interfacesName = Lists.newArrayList(); for (JavaType interfaceType : arrayList.getInterfaces()) { interfacesName.add(interfaceType.symbol.name); } assertThat(interfacesName).hasSize(4); assertThat(interfacesName).contains("List", "RandomAccess", "Cloneable", "Serializable"); } @Test public void symbol_type_in_same_package_should_be_resolved() throws Exception { JavaSymbol.TypeJavaSymbol thisTest = bytecodeCompleter.getClassSymbol(Convert.bytecodeName(getClass().getName())); List<JavaSymbol> symbols = thisTest.members().lookup("bytecodeCompleterPackageVisibility"); assertThat(symbols).hasSize(1); JavaSymbol.VariableJavaSymbol symbol = (JavaSymbol.VariableJavaSymbol) symbols.get(0); assertThat(symbol.type.symbol.name).isEqualTo("BytecodeCompleterPackageVisibility"); assertThat(symbol.type.symbol.owner().name).isEqualTo(thisTest.owner().name); } @Test public void void_method_type_should_be_resolved() { JavaSymbol.TypeJavaSymbol thisTest = bytecodeCompleter.getClassSymbol(Convert.bytecodeName(getClass().getName())); List<JavaSymbol> symbols = thisTest.members().lookup("bytecodeCompleterPackageVisibility"); assertThat(symbols).hasSize(1); JavaSymbol.VariableJavaSymbol symbol = (JavaSymbol.VariableJavaSymbol) symbols.get(0); symbols = symbol.getType().symbol.members().lookup("voidMethod"); assertThat(symbols).hasSize(1); JavaSymbol method = symbols.get(0); assertThat(method.type).isInstanceOf(MethodJavaType.class); assertThat(((MethodJavaType) method.type).resultType.symbol.name).isEqualTo("void"); } @Test public void inner_class_should_be_correctly_flagged() { JavaSymbol.TypeJavaSymbol interfaceWithInnerEnum = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.subpackage.FlagCompletion"); List<JavaSymbol> members = interfaceWithInnerEnum.members().lookup("bar"); JavaSymbol.TypeJavaSymbol innerEnum = ((JavaSymbol.MethodJavaSymbol) members.get(0)).getReturnType(); //complete outer class innerEnum.owner().complete(); //verify flag are set for inner class. assertThat(innerEnum.isEnum()).isTrue(); assertThat(innerEnum.isPublic()).isTrue(); assertThat(innerEnum.isStatic()).isTrue(); assertThat(innerEnum.isFinal()).isTrue(); } @Test public void deprecated_classes_should_be_flagged() throws Exception { JavaSymbol.TypeJavaSymbol deprecatedClass = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.DeprecatedClass"); assertThat(deprecatedClass.isDeprecated()).isTrue(); JavaSymbol.TypeJavaSymbol staticInnerClass = (JavaSymbol.TypeJavaSymbol) deprecatedClass.members().lookup("StaticInnerClass").get(0); assertThat(staticInnerClass.isDeprecated()).isTrue(); JavaSymbol.TypeJavaSymbol innerClass = (JavaSymbol.TypeJavaSymbol) deprecatedClass.members().lookup("InnerClass").get(0); assertThat(innerClass.isDeprecated()).isTrue(); JavaSymbol.TypeJavaSymbol deprecatedEnum = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.DeprecatedEnum"); assertThat(deprecatedEnum.isDeprecated()).isTrue(); assertThat(deprecatedEnum.memberSymbols().stream().filter(Symbol::isVariableSymbol).filter(Symbol::isEnum).noneMatch(Symbol::isDeprecated)).isTrue(); JavaSymbol.TypeJavaSymbol partiallyDeprecatedEnum = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.PartiallyDeprecatedEnum"); assertThat(partiallyDeprecatedEnum.isDeprecated()).isFalse(); List<Symbol> deprecatedEnumConstants = partiallyDeprecatedEnum.memberSymbols().stream() .filter(Symbol::isVariableSymbol) .filter(Symbol::isEnum) .filter(Symbol::isDeprecated) .collect(Collectors.toList()); assertThat(deprecatedEnumConstants).hasSize(1); assertThat(deprecatedEnumConstants.get(0).name()).isEqualTo("C"); } @Test public void complete_flags_for_inner_class() throws Exception { JavaSymbol.TypeJavaSymbol classSymbol = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.ProtectedInnerClassChild"); JavaSymbol.MethodJavaSymbol foo = (JavaSymbol.MethodJavaSymbol) classSymbol.members().lookup("foo").get(0); JavaSymbol.TypeJavaSymbol innerClassRef = foo.getReturnType(); assertThat(innerClassRef.isPrivate()).isFalse(); assertThat(innerClassRef.isPublic()).isFalse(); assertThat(innerClassRef.isPackageVisibility()).isFalse(); assertThat(innerClassRef.isDeprecated()).isTrue(); } @Test public void complete_flags_for_varargs_methods() throws Exception { JavaSymbol.TypeJavaSymbol classSymbol = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.ProtectedInnerClassChild"); JavaSymbol.MethodJavaSymbol foo = (JavaSymbol.MethodJavaSymbol) classSymbol.members().lookup("foo").get(0); assertThat((foo.flags & Flags.VARARGS) != 0).isTrue(); } @Test public void annotationOnSymbols() throws Exception { JavaSymbol.TypeJavaSymbol classSymbol = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.AnnotationSymbolMethod"); assertThat(classSymbol.isPublic()).isTrue(); SymbolMetadataResolve metadata = classSymbol.metadata(); assertThat(metadata.annotations()).hasSize(3); assertThat(metadata.valuesForAnnotation("org.sonar.java.resolve.targets.Dummy")).isNull(); assertThat(metadata.valuesForAnnotation("org.sonar.java.resolve.targets.ClassAnnotation")).isEmpty(); assertThat(metadata.valuesForAnnotation("org.sonar.java.resolve.targets.RuntimeAnnotation1")).hasSize(1); assertThat(metadata.valuesForAnnotation("org.sonar.java.resolve.targets.RuntimeAnnotation1").iterator().next().value()).isEqualTo("plopfoo"); assertThat(metadata.valuesForAnnotation("org.sonar.java.resolve.targets.RuntimeAnnotation2")).hasSize(2); Iterator<SymbolMetadata.AnnotationValue> iterator = metadata.valuesForAnnotation("org.sonar.java.resolve.targets.RuntimeAnnotation2").iterator(); Object value = iterator.next().value(); assertAnnotationValue(value); value = iterator.next().value(); assertAnnotationValue(value); } private void assertAnnotationValue(Object value) { if (value instanceof JavaSymbol.VariableJavaSymbol) { JavaSymbol.VariableJavaSymbol var = (JavaSymbol.VariableJavaSymbol) value; assertThat(var.getName()).isEqualTo("ONE"); assertThat(var.type.is("org.sonar.java.resolve.targets.MyEnum")).isTrue(); return; } else if (value instanceof Object[]) { Object[] array = (Object[]) value; assertThat(array).hasSize(4); assertThat(array).contains("one", "two", "three", "four"); return; } fail("value is not array nor variableSymbol"); } @Test public void type_parameters_should_be_read_from_bytecode() { JavaSymbol.TypeJavaSymbol typeParametersSymbol = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.TypeParameters"); typeParametersSymbol.complete(); assertThat(typeParametersSymbol.typeParameters).isNotNull(); assertThat(typeParametersSymbol.typeParameters.scopeSymbols()).hasSize(2); assertThat(typeParametersSymbol.typeVariableTypes).hasSize(2); TypeVariableJavaType TtypeVariableType = typeParametersSymbol.typeVariableTypes.get(0); assertThat(TtypeVariableType.erasure().getSymbol().getName()).isEqualTo("Object"); assertThat(typeParametersSymbol.typeVariableTypes.get(1).erasure().getSymbol().getName()).isEqualTo("CharSequence"); assertThat(typeParametersSymbol.getSuperclass()).isInstanceOf(ParametrizedTypeJavaType.class); assertThat(((ParametrizedTypeJavaType) typeParametersSymbol.getSuperclass()).typeSubstitution.typeVariables()).hasSize(1); TypeVariableJavaType keyTypeVariable = ((ParametrizedTypeJavaType) typeParametersSymbol.getSuperclass()).typeSubstitution.typeVariables().iterator().next(); assertThat(keyTypeVariable.symbol.getName()).isEqualTo("S"); JavaType actual = ((ParametrizedTypeJavaType) typeParametersSymbol.getSuperclass()).typeSubstitution.substitutedType(keyTypeVariable); assertThat(actual).isInstanceOf(ParametrizedTypeJavaType.class); assertThat(((ParametrizedTypeJavaType) actual).typeSubstitution.typeVariables()).hasSize(1); assertThat(typeParametersSymbol.getInterfaces()).hasSize(2); assertThat(typeParametersSymbol.getInterfaces().get(0)).isInstanceOf(ParametrizedTypeJavaType.class); JavaSymbol.MethodJavaSymbol funMethod = (JavaSymbol.MethodJavaSymbol) typeParametersSymbol.members().lookup("fun").get(0); assertThat(funMethod.getReturnType().type).isSameAs(TtypeVariableType); assertThat(funMethod.parameterTypes().get(0)).isSameAs(TtypeVariableType); JavaSymbol.MethodJavaSymbol fooMethod = (JavaSymbol.MethodJavaSymbol) typeParametersSymbol.members().lookup("foo").get(0); TypeVariableJavaType WtypeVariableType = fooMethod.typeVariableTypes.get(0); assertThat(fooMethod.parameterTypes().get(0).isArray()).isTrue(); assertThat(((ArrayJavaType)fooMethod.parameterTypes().get(0)).elementType()).isSameAs(WtypeVariableType); JavaType resultType = ((MethodJavaType) fooMethod.type).resultType; assertThat(resultType).isInstanceOf(ParametrizedTypeJavaType.class); ParametrizedTypeJavaType actualResultType = (ParametrizedTypeJavaType) resultType; assertThat(actualResultType.typeSubstitution.typeVariables()).hasSize(1); assertThat(actualResultType.typeSubstitution.substitutedTypes().iterator().next()).isSameAs(WtypeVariableType); //primitive types assertThat(fooMethod.parameterTypes().get(1).isPrimitive(org.sonar.plugins.java.api.semantic.Type.Primitives.INT)).isTrue(); assertThat(fooMethod.parameterTypes().get(2).isPrimitive(org.sonar.plugins.java.api.semantic.Type.Primitives.LONG)).isTrue(); //read field. JavaSymbol.VariableJavaSymbol field = (JavaSymbol.VariableJavaSymbol) typeParametersSymbol.members().lookup("field").get(0); assertThat(field.type).isInstanceOf(TypeVariableJavaType.class); assertThat(field.type).isSameAs(TtypeVariableType); } @Test public void type_parameters_in_inner_class() { JavaSymbol.TypeJavaSymbol innerClass = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.ParametrizedExtend$InnerClass"); innerClass.complete(); JavaSymbol.MethodJavaSymbol symbol = (JavaSymbol.MethodJavaSymbol) innerClass.members().lookup("innerMethod").get(0); assertThat(symbol.getReturnType().type).isInstanceOf(TypeVariableJavaType.class); assertThat(symbol.getReturnType().getName()).isEqualTo("S"); } @Test public void annotations_on_members() { JavaSymbol.TypeJavaSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.AnnotationsOnMembers"); JavaSymbol.VariableJavaSymbol field = (JavaSymbol.VariableJavaSymbol) clazz.members().lookup("field").get(0); JavaSymbol.MethodJavaSymbol method = (JavaSymbol.MethodJavaSymbol) clazz.members().lookup("method").get(0); JavaSymbol.VariableJavaSymbol parameter = (JavaSymbol.VariableJavaSymbol) method.getParameters().scopeSymbols().get(0); assertThat(field.metadata().valuesForAnnotation("javax.annotation.Nullable")).isNotNull(); assertThat(field.metadata().isAnnotatedWith("javax.annotation.Nullable")).isTrue(); assertThat(method.metadata().valuesForAnnotation("javax.annotation.CheckForNull")).isNotNull(); assertThat(parameter.metadata().valuesForAnnotation("javax.annotation.Nullable")).isNotNull(); } @Test public void annotated_enum_constructor() { //Test to handle difference between signature and descriptor for enum: //see : https://bugs.openjdk.java.net/browse/JDK-8071444 and https://bugs.openjdk.java.net/browse/JDK-8024694 Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.AnnotatedEnumConstructor"); JavaSymbol.MethodJavaSymbol constructor = (JavaSymbol.MethodJavaSymbol) clazz.lookupSymbols("<init>").iterator().next(); assertThat(constructor.getParameters().scopeSymbols()).hasSize(1); for (JavaSymbol arg : constructor.getParameters().scopeSymbols()) { assertThat(arg.metadata().annotations()).hasSize(1); assertThat(arg.metadata().annotations().get(0).symbol().type().is("javax.annotation.Nullable")).isTrue(); } assertThat(logTester.logs(LoggerLevel.WARN)).doesNotContain("Class not found: java.lang.Synthetic"); } @Test public void wildcards_type_equality() { JavaSymbol.TypeJavaSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.Wildcards"); assertTypeAreTheSame(clazz, "equalityUnboundedWildcard1", "equalityUnboundedWildcard2"); assertTypeAreTheSame(clazz, "equalityExtendsWildcard1", "equalityExtendsWildcard2"); assertTypeAreTheSame(clazz, "equalitySuperWildcard1", "equalitySuperWildcard2"); assertTypeAreNotTheSame(clazz, "equalityUnboundedWildcard1", "equalityExtendsWildcard1"); assertTypeAreNotTheSame(clazz, "equalityUnboundedWildcard1", "equalitySuperWildcard1"); assertTypeAreNotTheSame(clazz, "equalityExtendsWildcard1", "equalitySuperWildcard1"); } private static void assertTypeAreTheSame(TypeJavaSymbol clazz, String varName1, String varName2) { JavaSymbol.VariableJavaSymbol var1 = getVariable(clazz, varName1); JavaSymbol.VariableJavaSymbol var2 = getVariable(clazz, varName2); assertThat(var1.type).isSameAs(var2.type); } private static void assertTypeAreNotTheSame(TypeJavaSymbol clazz, String varName1, String varName2) { JavaSymbol.VariableJavaSymbol var1 = getVariable(clazz, varName1); JavaSymbol.VariableJavaSymbol var2 = getVariable(clazz, varName2); assertThat(var1.type).isNotSameAs(var2.type); } private static JavaSymbol.VariableJavaSymbol getVariable(JavaSymbol.TypeJavaSymbol clazz, String name) { return (JavaSymbol.VariableJavaSymbol) clazz.members().lookup(name).get(0); } @Test public void wildcards_in_fields_declaration() { JavaSymbol.TypeJavaSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.Wildcards"); JavaSymbol.VariableJavaSymbol unboudedItems = getVariable(clazz, "unboudedItems"); JavaSymbol.VariableJavaSymbol extendsItems = getVariable(clazz, "extendsItems"); JavaSymbol.VariableJavaSymbol superItems = getVariable(clazz, "superItems"); assertThatWildcardIs(unboudedItems, WildCardType.BoundType.UNBOUNDED, "java.lang.Object"); assertThatWildcardIs(extendsItems, WildCardType.BoundType.EXTENDS, "java.lang.String"); assertThatWildcardIs(superItems, WildCardType.BoundType.SUPER, "java.lang.Number"); } @Test public void wildcards_in_methods_declaration() { JavaSymbol.TypeJavaSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.Wildcards"); JavaSymbol.MethodJavaSymbol returnsUnboundedItems = (JavaSymbol.MethodJavaSymbol) clazz.members().lookup("returnsUnboundedItems").get(0); JavaSymbol.MethodJavaSymbol returnsExtendsItems = (JavaSymbol.MethodJavaSymbol) clazz.members().lookup("returnsExtendsItems").get(0); JavaSymbol.MethodJavaSymbol returnsSuperItems = (JavaSymbol.MethodJavaSymbol) clazz.members().lookup("returnsSuperItems").get(0); assertThatWildcardIs(returnsUnboundedItems, WildCardType.BoundType.UNBOUNDED, "java.lang.Object"); assertThatWildcardIs(returnsExtendsItems, WildCardType.BoundType.EXTENDS, "java.lang.String"); assertThatWildcardIs(returnsSuperItems, WildCardType.BoundType.SUPER, "java.lang.Number"); assertThatWildcardInFirstParamIs(returnsUnboundedItems, WildCardType.BoundType.UNBOUNDED, "java.lang.Object"); assertThatWildcardInFirstParamIs(returnsExtendsItems, WildCardType.BoundType.EXTENDS, "java.lang.String"); assertThatWildcardInFirstParamIs(returnsSuperItems, WildCardType.BoundType.SUPER, "java.lang.Number"); } @Test public void wildcards_in_class_declaration() { JavaSymbol.TypeJavaSymbol wildcardUnboundedClass = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.WildcardUnboundedClass"); JavaSymbol.TypeJavaSymbol WildcardExtendsClass = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.WildcardExtendsClass"); JavaSymbol.TypeJavaSymbol WildcardSuperClass = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.WildcardSuperClass"); assertThatWildcardInTypeParamIs(wildcardUnboundedClass, WildCardType.BoundType.UNBOUNDED, "java.lang.Object"); assertThatWildcardInTypeParamIs(WildcardExtendsClass, WildCardType.BoundType.EXTENDS, "java.lang.String"); assertThatWildcardInTypeParamIs(WildcardSuperClass, WildCardType.BoundType.SUPER, "java.lang.Number"); } private static void assertThatWildcardInTypeParamIs(JavaSymbol.TypeJavaSymbol symbol, WildCardType.BoundType wildcard, String bound) { JavaType javaType = ((TypeVariableJavaType) symbol.typeParameters().lookup("X").iterator().next().getType()).bounds.get(0); assertThatWildcardIs(javaType, wildcard, bound); } private static void assertThatWildcardInFirstParamIs(JavaSymbol.MethodJavaSymbol symbol, WildCardType.BoundType wildcard, String bound) { assertThatWildcardIs(symbol.parameterTypes().get(0), wildcard, bound); } private static void assertThatWildcardIs(JavaSymbol.MethodJavaSymbol symbol, WildCardType.BoundType wildcard, String bound) { assertThatWildcardIs(((MethodJavaType) symbol.type).resultType, wildcard, bound); } private static void assertThatWildcardIs(JavaSymbol.VariableJavaSymbol symbol, WildCardType.BoundType wildcard, String bound) { assertThatWildcardIs(symbol.type, wildcard, bound); } private static void assertThatWildcardIs(Type type, WildCardType.BoundType wildcard, String bound) { assertThat(type).isInstanceOf(ParametrizedTypeJavaType.class); ParametrizedTypeJavaType ptjt = (ParametrizedTypeJavaType) type; JavaType substitutedType = ptjt.typeSubstitution.substitutedTypes().get(0); assertThat(substitutedType).isInstanceOf(WildCardType.class); WildCardType wildcardType = (WildCardType) substitutedType; assertThat(wildcardType.boundType).isEqualTo(wildcard); assertThat(wildcardType.bound.is(bound)).isTrue(); } @Test public void class_not_found_should_have_unknown_super_type_and_no_interfaces() { Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.UnknownClass"); assertThat(clazz.type()).isNotNull(); Type superClass = clazz.superClass(); assertThat(superClass).isNotNull(); assertThat(superClass).isSameAs(Symbols.unknownType); List<Type> interfaces = clazz.interfaces(); assertThat(interfaces).isNotNull(); assertThat(interfaces).isEmpty(); } @Test public void forward_type_parameter_in_methods() throws Exception { Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.ForwardParameterInMethod"); assertThat(clazz.type()).isNotNull(); Collection<Symbol> symbols = clazz.lookupSymbols("bar"); assertThat(symbols).hasSize(1); Symbol method = symbols.iterator().next(); Collection<JavaSymbol> typeParameters = ((JavaSymbol.MethodJavaSymbol) method).typeParameters().scopeSymbols(); assertThat(typeParameters).hasSize(2); JavaSymbol xSymbol = ((JavaSymbol.MethodJavaSymbol) method).typeParameters().lookup("X").iterator().next(); JavaSymbol ySymbol = ((JavaSymbol.MethodJavaSymbol) method).typeParameters().lookup("Y").iterator().next(); assertThat(((TypeVariableJavaType) xSymbol.type).bounds).hasSize(1); JavaType bound = ((TypeVariableJavaType) xSymbol.type).bounds.get(0); assertThat(((ParametrizedTypeJavaType)bound).typeParameters()).hasSize(1); assertThat(((ParametrizedTypeJavaType)bound).substitution(((ParametrizedTypeJavaType)bound).typeParameters().get(0))).isSameAs(ySymbol.type); } @Test public void forward_type_parameter_in_classes() throws Exception { Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.ForwardParameterInClass"); assertThat(clazz.type()).isNotNull(); Collection<Symbol> symbols = clazz.lookupSymbols("bar"); assertThat(symbols).hasSize(1); Collection<JavaSymbol> typeParameters = ((JavaSymbol.TypeJavaSymbol) clazz).typeParameters().scopeSymbols(); assertThat(typeParameters).hasSize(2); JavaSymbol xSymbol = ((JavaSymbol.TypeJavaSymbol) clazz).typeParameters().lookup("X").iterator().next(); JavaSymbol ySymbol = ((JavaSymbol.TypeJavaSymbol) clazz).typeParameters().lookup("Y").iterator().next(); assertThat(((TypeVariableJavaType) xSymbol.type).bounds).hasSize(1); JavaType bound = ((TypeVariableJavaType) xSymbol.type).bounds.get(0); assertThat(((ParametrizedTypeJavaType)bound).typeParameters()).hasSize(1); assertThat(((ParametrizedTypeJavaType)bound).substitution(((ParametrizedTypeJavaType)bound).typeParameters().get(0))).isSameAs(ySymbol.type); } @Test public void test_completion_of_generic_inner_class() throws Exception { Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.UseGenericInnerClass"); Symbol builder = clazz.lookupSymbols("builder").iterator().next(); Symbol.TypeSymbol genericInnerClass = (Symbol.TypeSymbol) builder.type().symbol().owner(); Symbol methodBuilder = genericInnerClass.lookupSymbols("builder").iterator().next(); JavaType resultType = ((MethodJavaType) methodBuilder.type()).resultType; assertThat(resultType).isInstanceOf(ParametrizedTypeJavaType.class); } @Test public void array_types_dimension_taken_into_account() throws Exception { Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.ArrayTypes"); Symbol field = clazz.lookupSymbols("field").iterator().next(); Type type = field.type(); assertThat(type.isArray()).isTrue(); Type elementType = ((Type.ArrayType) type).elementType(); assertThat(elementType.isArray()).isTrue(); elementType = ((Type.ArrayType) elementType).elementType(); assertThat(elementType.is("java.lang.Object")).isTrue(); } @Test public void inner_class_obfuscated() throws Exception { Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("ChartDirector.Layer"); assertThat(clazz.memberSymbols().stream().anyMatch(s-> s.name().equals("fj"))).isTrue(); Symbol.TypeSymbol library = bytecodeCompleter.getClassSymbol("com.jniwrapper.Library"); Optional<Symbol> ar = library.memberSymbols().stream().filter(s -> s.name().equals("ar")).findFirst(); assertThat(ar.isPresent()).isTrue(); // Assert that the inner class fully qualified name does not contain the enclosing class name as it was obfuscated. ar.ifPresent(s -> assertThat(((TypeJavaSymbol) s).getFullyQualifiedName()).isEqualTo("com.jniwrapper.ar")); } @Test public void package_annotations() throws Exception { Symbol.TypeSymbol clazz = bytecodeCompleter.getClassSymbol("org.sonar.java.resolve.targets.MethodSymbols"); assertThat(((TypeJavaSymbol) clazz).packge().metadata().isAnnotatedWith("javax.annotation.ParametersAreNonnullByDefault")).isTrue(); } }