diff --git a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java index e59e010a720..90b10150756 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java @@ -206,6 +206,7 @@ import spoon.reflect.reference.CtLocalVariableReference; import spoon.reflect.reference.CtPackageReference; import spoon.reflect.reference.CtParameterReference; +import spoon.reflect.reference.CtReference; import spoon.reflect.reference.CtTypeParameterReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.reference.CtVariableReference; @@ -280,6 +281,8 @@ public class BuilderContext { boolean isLambdaParameterImplicitlyTyped = true; + boolean ignoreComputeImports = false; + /** * Stack of all parents elements */ @@ -411,6 +414,57 @@ public CtTypeReference getBoundedTypeReference(TypeBinding binding) { return ref; } + /** + * Try to get the declaring reference (package or type) from imports of the current + * compilation unit declaration (current class). This method returns a CtReference + * which can be a CtTypeReference if it retrieves the information in an static import, + * a CtPackageReference if it retrieves the information in an standard import, otherwise + * it returns null. + * + * @param expectedName + * Name expected in imports. + * @return CtReference which can be a CtTypeReference, a CtPackageReference or null. + */ + public CtReference getDeclaringReferenceFromImports(char[] expectedName) { + if (context.compilationunitdeclaration != null && context.compilationunitdeclaration.imports != null) { + for (ImportReference anImport : context.compilationunitdeclaration.imports) { + if (CharOperation.equals(anImport.getImportName()[anImport.getImportName().length - 1], expectedName)) { + if (anImport.isStatic()) { + int indexDeclaring = 2; + if ((anImport.bits & ASTNode.OnDemand) != 0) { + // With .* + indexDeclaring = 1; + } + char[][] packageName = CharOperation.subarray(anImport.getImportName(), 0, anImport.getImportName().length - indexDeclaring); + char[][] className = CharOperation.subarray(anImport.getImportName(), anImport.getImportName().length - indexDeclaring, anImport.getImportName().length - (indexDeclaring - 1)); + final PackageBinding aPackage = context.compilationunitdeclaration.scope.environment.createPackage(packageName); + final MissingTypeBinding declaringType = context.compilationunitdeclaration.scope.environment.createMissingType(aPackage, className); + context.ignoreComputeImports = true; + final CtTypeReference typeReference = getTypeReference(declaringType); + context.ignoreComputeImports = false; + return typeReference; + } else { + char[][] chars = CharOperation.subarray(anImport.getImportName(), 0, anImport.getImportName().length - 1); + Binding someBinding = context.compilationunitdeclaration.scope.findImport(chars, false, false); + PackageBinding packageBinding; + if (someBinding != null && someBinding.isValidBinding() && someBinding instanceof PackageBinding) { + packageBinding = (PackageBinding) someBinding; + } else { + packageBinding = context.compilationunitdeclaration.scope.environment.createPackage(chars); + if (packageBinding == null) { + // Big crisis here. We are already in noclasspath mode but JDT doesn't support always + // creation of a package in this mode. So, if we are in this brace, we make the job of JDT... + packageBinding = new PackageBinding(chars, null, context.compilationunitdeclaration.scope.environment); + } + } + return getPackageReference(packageBinding); + } + } + } + } + return null; + } + @SuppressWarnings("unchecked") public CtExecutableReference getExecutableReference(MethodBinding exec) { if (exec == null) { @@ -422,18 +476,9 @@ public CtExecutableReference getExecutableReference(MethodBinding exec) { ref.setType((CtTypeReference) getTypeReference(exec.returnType)); if (exec instanceof ProblemMethodBinding) { - // We try to check in imports if there is the correct package of the type. - if (context.compilationunitdeclaration != null && context.compilationunitdeclaration.imports != null) { - for (ImportReference anImport : context.compilationunitdeclaration.imports) { - if (CharOperation.equals(anImport.getImportName()[anImport.getImportName().length - 1], exec.constantPoolName())) { - char[][] packageName = CharOperation.subarray(anImport.getImportName(), 0, anImport.getImportName().length - 2); - char[][] className = CharOperation.subarray(anImport.getImportName(), anImport.getImportName().length - 2, anImport.getImportName().length - 1); - final PackageBinding aPackage = context.compilationunitdeclaration.scope.environment.createPackage(packageName); - final MissingTypeBinding declaringType = context.compilationunitdeclaration.scope.environment.createMissingType(aPackage, className); - ref.setDeclaringType(getTypeReference(declaringType)); - break; - } - } + final CtReference declaringType = getDeclaringReferenceFromImports(exec.constantPoolName()); + if (declaringType instanceof CtTypeReference) { + ref.setDeclaringType((CtTypeReference) declaringType); } if (exec.isConstructor()) { // super() invocation have a good declaring class. @@ -534,27 +579,12 @@ public CtTypeReference getTypeReference(TypeBinding binding) { ref = factory.Core().createTypeReference(); ref.setSimpleName(new String(binding.sourceName())); ref.setPackage(getPackageReference(binding.getPackage())); - // We try to check in imports if there is the correct package of the type. - if (context.compilationunitdeclaration != null && context.compilationunitdeclaration.imports != null) { - for (ImportReference anImport : context.compilationunitdeclaration.imports) { - if (CharOperation.equals(anImport.getImportName()[anImport.getImportName().length - 1], binding.sourceName())) { - char[][] chars = CharOperation.subarray(anImport.getImportName(), 0, anImport.getImportName().length - 1); - Binding someBinding = context.compilationunitdeclaration.scope.findImport(chars, false, false); - PackageBinding packageBinding; - if (someBinding != null && someBinding.isValidBinding() && someBinding instanceof PackageBinding) { - packageBinding = (PackageBinding) someBinding; - } else { - packageBinding = context.compilationunitdeclaration.scope.environment.createPackage(chars); - if (packageBinding == null) { - // Big crisis here. We are already in noclasspath mode since the check `binding instance MissingTypeBinding` - // but JDT doesn't support always creation of a package in this mode. So, if we are in this brace, we make - // the job of JDT... - packageBinding = new PackageBinding(chars, null, context.compilationunitdeclaration.scope.environment); - } - } - ref.setPackage(getPackageReference(packageBinding)); - break; - } + if (!context.ignoreComputeImports) { + final CtReference declaring = references.getDeclaringReferenceFromImports(binding.sourceName()); + if (declaring instanceof CtPackageReference) { + ref.setPackage((CtPackageReference) declaring); + } else if (declaring instanceof CtTypeReference) { + ref.setDeclaringType((CtTypeReference) declaring); } } } else if (binding instanceof BinaryTypeBinding) { @@ -2386,10 +2416,31 @@ public boolean visit(MessageSend messageSend, BlockScope scope) { ref.setType(references.getTypeReference(messageSend.expectedType())); if (messageSend.receiver.resolvedType == null) { // It is crisis dude! static context, we don't have much more information. - if (messageSend.receiver instanceof SingleNameReference || messageSend.receiver instanceof QualifiedNameReference) { - final CtTypeReference typeReference = factory.Core().createTypeReference(); + if (messageSend.receiver instanceof SingleNameReference) { + CtTypeReference typeReference = factory.Core().createTypeReference(); typeReference.setSimpleName(messageSend.receiver.toString()); + final CtReference declaring = references.getDeclaringReferenceFromImports(((SingleNameReference) messageSend.receiver).token); + if (declaring instanceof CtPackageReference) { + typeReference.setPackage((CtPackageReference) declaring); + } else if (declaring instanceof CtTypeReference) { + typeReference = (CtTypeReference) declaring; + } ref.setDeclaringType(typeReference); + } else if (messageSend.receiver instanceof QualifiedNameReference) { + QualifiedNameReference qualifiedNameReference = (QualifiedNameReference) messageSend.receiver; + + char[][] packageName = CharOperation.subarray(qualifiedNameReference.tokens, 0, qualifiedNameReference.tokens.length - 1); + char[][] className = CharOperation.subarray(qualifiedNameReference.tokens, qualifiedNameReference.tokens.length - 1, qualifiedNameReference.tokens.length); + if (packageName.length > 0) { + final PackageBinding aPackage = context.compilationunitdeclaration.scope.environment.createPackage(packageName); + final MissingTypeBinding declaringType = context.compilationunitdeclaration.scope.environment.createMissingType(aPackage, className); + + ref.setDeclaringType(references.getTypeReference(declaringType)); + } else { + final CtTypeReference typeReference = factory.Core().createTypeReference(); + typeReference.setSimpleName(messageSend.receiver.toString()); + ref.setDeclaringType(typeReference); + } } } else { ref.setDeclaringType(references.getTypeReference(messageSend.receiver.resolvedType)); @@ -2753,8 +2804,15 @@ public boolean visit(QualifiedNameReference qualifiedNameReference, BlockScope s va = factory.Core().createFieldRead(); } va.setVariable(references.getVariableReference((ProblemBinding) qualifiedNameReference.binding)); + // In no classpath mode and with qualified name, the type given by JDT is wrong... + if (va.getVariable() instanceof CtFieldReference) { + final char[][] declaringClass = CharOperation.subarray(qualifiedNameReference.tokens, 0, qualifiedNameReference.tokens.length - 1); + final MissingTypeBinding declaringType = context.compilationunitdeclaration.scope.environment.createMissingType(null, declaringClass); + ((CtFieldReference) va.getVariable()).setDeclaringType(references.getTypeReference(declaringType)); + ((CtFieldReference) va.getVariable()).setStatic(true); + } // In no classpath mode and with qualified name, the binding don't have a good name. - va.getVariable().setSimpleName(createTypeName(qualifiedNameReference.getName())); + va.getVariable().setSimpleName(createTypeName(CharOperation.subarray(qualifiedNameReference.tokens, qualifiedNameReference.tokens.length - 1, qualifiedNameReference.tokens.length))); context.enter(va, qualifiedNameReference); return false; } else { @@ -2831,8 +2889,14 @@ public boolean visit(SingleNameReference singleNameReference, BlockScope scope) } else if (singleNameReference.binding instanceof ProblemBinding) { if (context.stack.peek().element instanceof CtInvocation) { final CtTypeAccess ta = factory.Core().createTypeAccess(); - final CtTypeReference typeReference = factory.Core().createTypeReference(); + CtTypeReference typeReference = factory.Core().createTypeReference(); typeReference.setSimpleName(new String(singleNameReference.binding.readableName())); + final CtReference declaring = references.getDeclaringReferenceFromImports(singleNameReference.token); + if (declaring instanceof CtPackageReference) { + typeReference.setPackage((CtPackageReference) declaring); + } else if (declaring instanceof CtTypeReference) { + typeReference = (CtTypeReference) declaring; + } ta.setType(typeReference); context.enter(ta, singleNameReference); return true; diff --git a/src/test/java/spoon/test/imports/ImportTest.java b/src/test/java/spoon/test/imports/ImportTest.java index 73c4bd3043d..72366bef401 100644 --- a/src/test/java/spoon/test/imports/ImportTest.java +++ b/src/test/java/spoon/test/imports/ImportTest.java @@ -187,25 +187,25 @@ public boolean matches(CtInvocation element) { assertCorrectInvocation(new Expected().name("makeBurritos").target("Tacos.Burritos").declaringType("Burritos").typeIsNull(false), elements.get(7)); // Invocation for a static method in an inner class with the declaring class specified. - assertCorrectInvocation(new Expected().name("staticD").target("C.D").declaringType("C.D").typeIsNull(true), elements.get(8)); + assertCorrectInvocation(new Expected().name("staticD").target("C.D").declaringType("D").typeIsNull(true), elements.get(8)); // Invocation for a static method in an inner class without the declaring class specified. assertCorrectInvocation(new Expected().name("staticD").target("pack2.C.D").declaringType("D").typeIsNull(true), elements.get(9)); // Invocation for a static method in an inner class with the declaring class specified and a return type. - assertCorrectInvocation(new Expected().name("staticD").target("C.D").declaringType("C.D").typeIsNull(false), elements.get(10)); + assertCorrectInvocation(new Expected().name("staticD").target("C.D").declaringType("D").typeIsNull(false), elements.get(10)); // Invocation for a static method in an inner class without the declaring class specified and a return type. assertCorrectInvocation(new Expected().name("staticD").target("pack2.C.D").declaringType("D").typeIsNull(false), elements.get(11)); // Invocation for a static method with the declaring class specified and an import *. - assertCorrectInvocation(new Expected().name("staticE").target("E").declaringType("E").typeIsNull(true), elements.get(12)); + assertCorrectInvocation(new Expected().name("staticE").target("pack3.E").declaringType("E").typeIsNull(true), elements.get(12)); // Invocation for a static method without the declaring class specified and an import *. assertCorrectInvocationWithLimit(new Expected().name("staticE").typeIsNull(true), elements.get(13)); // Invocation for a static method with the declaring class specified, a return type and an import *. - assertCorrectInvocation(new Expected().name("staticE").target("E").declaringType("E").typeIsNull(false), elements.get(14)); + assertCorrectInvocation(new Expected().name("staticE").target("pack3.E").declaringType("E").typeIsNull(false), elements.get(14)); // Invocation for a static method without the declaring class specified, a return type and an import *. assertCorrectInvocationWithLimit(new Expected().name("staticE").typeIsNull(false), elements.get(15)); diff --git a/src/test/java/spoon/test/reference/TypeReferenceTest.java b/src/test/java/spoon/test/reference/TypeReferenceTest.java index 7958451bf04..6d66ed290c9 100644 --- a/src/test/java/spoon/test/reference/TypeReferenceTest.java +++ b/src/test/java/spoon/test/reference/TypeReferenceTest.java @@ -23,7 +23,9 @@ import spoon.test.reference.testclasses.EnumValue; import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -256,6 +258,40 @@ public void testRecursiveTypeReferenceInGenericType() throws Exception { assertTrue(circularRef instanceof CtCircularTypeReference); } + @Test + public void testPackageInNoClasspath () { + final Launcher launcher = new Launcher(); + launcher.addInputResource("./src/test/resources/noclasspath/Demo.java"); + launcher.setSourceOutputDirectory("./target/class"); + launcher.getEnvironment().setNoClasspath(true); + launcher.run(); + + final CtClass aClass = launcher.getFactory().Class().get("Demo"); + final Set> referencedTypes = aClass.getReferencedTypes(); + + boolean containsDemoReference = false; + boolean containsVoidReference = false; + boolean containsStringReference = false; + boolean containsJoinerReference = false; + + for (Iterator> iterator = referencedTypes.iterator(); iterator.hasNext(); ) { + CtTypeReference reference = iterator.next(); + if (reference.toString().equals("Demo")) { + containsDemoReference = true; + } else if (reference.toString().equals("void")) { + containsVoidReference = true; + } else if (reference.toString().equals("java.lang.String")) { + containsStringReference = true; + } else if (reference.toString().equals("com.google.common.base.Joiner")) { + containsJoinerReference = true; + } + } + assertTrue("Reference to Demo is missing", containsDemoReference); + assertTrue("Reference to void is missing", containsVoidReference); + assertTrue("Reference to String is missing", containsStringReference); + assertTrue("Reference to Joiner is missing", containsJoinerReference); + } + class A { class Tacos { } diff --git a/src/test/java/spoon/test/variable/AccessTest.java b/src/test/java/spoon/test/variable/AccessTest.java index acb9add6d1d..2e1cf2e5b26 100644 --- a/src/test/java/spoon/test/variable/AccessTest.java +++ b/src/test/java/spoon/test/variable/AccessTest.java @@ -192,7 +192,7 @@ public void testVariableAccessInNoClasspath() throws Exception { assertNotNull(element.getVariable()); } - assertEquals("java.lang.Class mclass = ((java.lang.Class)(ModelFacade.USE_CASE))", elements.get(0).getParent().toString()); - assertEquals("new PropPanelButton(this , buttonPanel , _navUpIcon , Translator.localize(\"UMLMenu\", \"button.go-up\") , \"navigateNamespace\" , null)", elements.get(2).getParent().toString()); + assertEquals("java.lang.Class mclass = ((java.lang.Class)(org.argouml.model.ModelFacade.USE_CASE))", elements.get(0).getParent().toString()); + assertEquals("new PropPanelButton(this , buttonPanel , _navUpIcon , org.argouml.i18n.Translator.localize(\"UMLMenu\", \"button.go-up\") , \"navigateNamespace\" , null)", elements.get(2).getParent().toString()); } } diff --git a/src/test/resources/noclasspath/Demo.java b/src/test/resources/noclasspath/Demo.java new file mode 100644 index 00000000000..8834c70b8df --- /dev/null +++ b/src/test/resources/noclasspath/Demo.java @@ -0,0 +1,8 @@ +import com.google.common.base.Joiner; + +public class Demo { + + public static void main(String[] args) { + Joiner.on(); + } +} \ No newline at end of file