Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix #465] Fixes missing package in CtTypeReference with noclasspath mode #466

Merged
merged 4 commits into from
Jan 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 101 additions & 37 deletions src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,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;
Expand Down Expand Up @@ -281,6 +282,8 @@ public class BuilderContext {

boolean isLambdaParameterImplicitlyTyped = true;

boolean ignoreComputeImports = false;

/**
* Stack of all parents elements
*/
Expand Down Expand Up @@ -412,6 +415,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<Object> 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 <T> CtExecutableReference<T> getExecutableReference(MethodBinding exec) {
if (exec == null) {
Expand All @@ -423,18 +477,9 @@ public <T> CtExecutableReference<T> getExecutableReference(MethodBinding exec) {
ref.setType((CtTypeReference<T>) 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.
Expand Down Expand Up @@ -535,27 +580,12 @@ public <T> CtTypeReference<T> 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) {
Expand Down Expand Up @@ -2391,10 +2421,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<Object> typeReference = factory.Core().createTypeReference();
if (messageSend.receiver instanceof SingleNameReference) {
CtTypeReference<Object> 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<Object>) 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<Object> typeReference = factory.Core().createTypeReference();
typeReference.setSimpleName(messageSend.receiver.toString());
ref.setDeclaringType(typeReference);
}
}
} else {
ref.setDeclaringType(references.getTypeReference(messageSend.receiver.resolvedType));
Expand Down Expand Up @@ -2747,8 +2798,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 {
Expand Down Expand Up @@ -2825,8 +2883,14 @@ public boolean visit(SingleNameReference singleNameReference, BlockScope scope)
} else if (singleNameReference.binding instanceof ProblemBinding) {
if (context.stack.peek().element instanceof CtInvocation) {
final CtTypeAccess<Object> ta = factory.Core().createTypeAccess();
final CtTypeReference<Object> typeReference = factory.Core().createTypeReference();
CtTypeReference<Object> 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<Object>) declaring;
}
ta.setType(typeReference);
context.enter(ta, singleNameReference);
return true;
Expand Down
8 changes: 4 additions & 4 deletions src/test/java/spoon/test/imports/ImportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
36 changes: 36 additions & 0 deletions src/test/java/spoon/test/reference/TypeReferenceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Object> aClass = launcher.getFactory().Class().get("Demo");
final Set<CtTypeReference<?>> referencedTypes = aClass.getReferencedTypes();

boolean containsDemoReference = false;
boolean containsVoidReference = false;
boolean containsStringReference = false;
boolean containsJoinerReference = false;

for (Iterator<CtTypeReference<?>> 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<K> {
}
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/spoon/test/variable/AccessTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
8 changes: 8 additions & 0 deletions src/test/resources/noclasspath/Demo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import com.google.common.base.Joiner;

public class Demo {

public static void main(String[] args) {
Joiner.on();
}
}