Skip to content
New issue

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

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

Already on GitHub? # to your account

fix(visibility): Field access declared in a default class. #543

Merged
merged 1 commit into from
Feb 26, 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
177 changes: 176 additions & 1 deletion src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,153 @@ private String createTypeName(char[][] typeName) {
return s;
}

/**
* Checks if a type is specified in imports.
*
* @param typeName
* Type name.
* @return qualified name of the expected type.
*/
private String hasTypeInImports(String typeName) {
if (typeName == null) {
return null;
}
for (ImportReference anImport : context.compilationunitdeclaration.imports) {
final String importType = CharOperation.charToString(anImport.getImportName()[anImport.getImportName().length - 1]);
if (importType != null && importType.equals(typeName)) {
return CharOperation.toString(anImport.getImportName());
}
}
return null;
}

/**
* Checks to know if a name is a package or not.
*
* @param packageName
* Package name.
* @return boolean
*/
private boolean isPackage(char[][] packageName) {
for (CompilationUnitDeclaration unit : ((TreeBuilderCompiler) context.compilationunitdeclaration.scope.environment.typeRequestor).unitsToProcess) {
final char[][] tokens = unit.currentPackage.tokens;
if (packageName.length > tokens.length) {
continue;
}
boolean isFound = true;
for (int i = 0; i < packageName.length; i++) {
char[] chars = packageName[i];
if (!CharOperation.equals(chars, tokens[i])) {
isFound = false;
break;
}
}
if (isFound) {
return true;
}
}
return false;
}

/**
* Searches a type in the project.
*
* @param qualifiedName
* Qualified name of the expected type.
* @return type binding.
*/
private TypeBinding searchTypeBinding(String qualifiedName) {
if (qualifiedName == null) {
return null;
}
for (CompilationUnitDeclaration unitsToProcess : ((TreeBuilderCompiler) context.compilationunitdeclaration.scope.environment.typeRequestor).unitsToProcess) {
for (TypeDeclaration type : unitsToProcess.types) {
if (qualifiedName.equals(CharOperation.toString(type.binding.compoundName))) {
return type.binding;
}
if (type.memberTypes != null) {
for (TypeDeclaration memberType : type.memberTypes) {
if (qualifiedName.equals(CharOperation.toString(memberType.binding.compoundName))) {
return type.binding;
}
}
}
}
}
return null;
}

/**
* Searches a type from an entry-point according to a simple name.
*
* @param type
* Entry-point to search.
* @param simpleName
* Expected type name.
* @return type binding.
*/
private TypeBinding searchTypeBinding(ReferenceBinding type, String simpleName) {
if (simpleName == null || type == null) {
return null;
}

if (type.memberTypes() != null) {
for (ReferenceBinding memberType : type.memberTypes()) {
if (simpleName.equals(CharOperation.charToString(memberType.sourceName()))) {
return memberType;
}
}
}

return searchTypeBinding(type.superclass(), simpleName);
}

/**
* Builds a type reference from a qualified name when a type specified in the name isn't available.
*
* @param tokens
* Qualified name.
* @param receiverType
* Last type in the qualified name.
* @param enclosingType
* Enclosing type of the type name.
* @param listener
* Listener to know if we must build the type reference.
* @return a type reference.
*/
private <T> CtTypeReference<T> getQualifiedTypeReference(char[][] tokens, TypeBinding receiverType, ReferenceBinding enclosingType, OnAccessListener listener) {
if (enclosingType != null && Collections.disjoint(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.PROTECTED), getModifiers(enclosingType.modifiers))) {
String access = "";
int i = 0;
for (; i < tokens.length; i++) {
final char[][] qualified = Arrays.copyOfRange(tokens, 0, i + 1);
if (!isPackage(qualified)) {
access = CharOperation.toString(qualified);
break;
}
}
if (!access.contains(CtPackage.PACKAGE_SEPARATOR)) {
access = hasTypeInImports(access);
}
final TypeBinding accessBinding = searchTypeBinding(access);
if (accessBinding != null && listener.onAccess(tokens, i)) {
final TypeBinding superClassBinding = searchTypeBinding(accessBinding.superclass(), CharOperation.charToString(tokens[i + 1]));
if (superClassBinding != null) {
return references.getTypeReference(superClassBinding.clone(accessBinding));
} else {
return references.getTypeReference(receiverType);
}
} else {
return references.getTypeReference(receiverType);
}
}
return null;
}

interface OnAccessListener {
boolean onAccess(char[][] tokens, int index);
}

public class ReferenceBuilder {

Map<String, CtTypeReference<?>> basestypes = new TreeMap<String, CtTypeReference<?>>();
Expand Down Expand Up @@ -2019,6 +2166,21 @@ public boolean visit(Argument argument, BlockScope scope) {
} else if (argument.type != null) {
p.setType(references.getTypeReference(argument.type.resolvedType));
}

final TypeBinding receiverType = argument.type != null ? argument.type.resolvedType : null;
if (receiverType != null && argument.type instanceof QualifiedTypeReference) {
final QualifiedTypeReference qualifiedNameReference = (QualifiedTypeReference) argument.type;
final CtTypeReference<Object> ref = getQualifiedTypeReference(qualifiedNameReference.tokens, receiverType, receiverType.enclosingType(), new OnAccessListener() {
@Override
public boolean onAccess(char[][] tokens, int index) {
return true;
}
});
if (ref != null) {
p.setType(ref);
}
}

context.enter(p, argument);
if (argument.initialization != null) {
argument.initialization.traverse(this, scope);
Expand Down Expand Up @@ -2890,7 +3052,20 @@ public boolean visit(QualifiedNameReference qualifiedNameReference, BlockScope s
// Only set the declaring type if we are in a static context. See
// StaticAccessTest#testReferences test to have an example about that.
if (ref.isStatic()) {
ref.setDeclaringType(references.getTypeReference(qualifiedNameReference.actualReceiverType));
final TypeBinding receiverType = qualifiedNameReference.actualReceiverType;
if (receiverType != null) {
final CtTypeReference<Object> qualifiedRef = getQualifiedTypeReference(qualifiedNameReference.tokens, receiverType, qualifiedNameReference.fieldBinding().declaringClass.enclosingType(), new OnAccessListener() {
@Override
public boolean onAccess(char[][] tokens, int index) {
return !CharOperation.equals(tokens[index + 1], tokens[tokens.length - 1]);
}
});
if (qualifiedRef != null) {
ref.setDeclaringType(qualifiedRef);
} else {
ref.setDeclaringType(references.getTypeReference(receiverType));
}
}
fa.setTarget(factory.Code().createTypeAccess(ref.getDeclaringType()));
} else if (!ref.isStatic() && !ref.getDeclaringType().isAnonymous()) {
final CtTypeReference<Object> type = references.getTypeReference(qualifiedNameReference.actualReceiverType);
Expand Down
47 changes: 47 additions & 0 deletions src/test/java/spoon/test/fieldaccesses/FieldAccessTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
import spoon.reflect.code.CtFieldAccess;
import spoon.reflect.code.CtFieldRead;
import spoon.reflect.code.CtFieldWrite;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLambda;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtOperatorAssignment;
import spoon.reflect.code.CtTypeAccess;
import spoon.reflect.code.CtUnaryOperator;
import spoon.reflect.code.CtVariableWrite;
import spoon.reflect.code.UnaryOperatorKind;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtFieldReference;
Expand All @@ -23,6 +26,7 @@
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.test.fieldaccesses.testclasses.Panini;
import spoon.test.fieldaccesses.testclasses.Pozole;
import spoon.test.fieldaccesses.testclasses.Tacos;
import spoon.testing.Assert;

import java.util.List;
Expand Down Expand Up @@ -278,4 +282,47 @@ public void testTypeDeclaredInAnonymousClass() throws Exception {
assertTrue(elements.get(0).getType().getDeclaringType().isAnonymous());
assertThat(elements.get(0)).isEqualTo("private final Test test = new Test();");
}

@Test
public void testFieldAccessDeclaredInADefaultClass() throws Exception {
final Launcher launcher = new Launcher();
launcher.addInputResource("./src/test/java/spoon/test/fieldaccesses/testclasses/Tacos.java");
launcher.addInputResource("./src/test/java/spoon/test/fieldaccesses/testclasses/internal/Foo.java");
launcher.addInputResource("./src/test/java/spoon/test/fieldaccesses/testclasses/internal/Bar.java");
launcher.setSourceOutputDirectory("./target/trash");
launcher.run();

final CtType<Object> aTacos = launcher.getFactory().Type().get(Tacos.class);
final CtType<Object> aFoo = launcher.getFactory().Type().get("spoon.test.fieldaccesses.testclasses.internal.Foo");
final CtTypeAccess<Object> aFooAccess = launcher.getFactory().Code().createTypeAccess(aFoo.getReference());
final CtType<Object> aSubInner = launcher.getFactory().Type().get("spoon.test.fieldaccesses.testclasses.internal.Bar$Inner$SubInner");
aFoo.addNestedType(aSubInner);
final CtTypeAccess<Object> aSubInnerAccess = launcher.getFactory().Code().createTypeAccess(aSubInner.getReference());
final CtType<Object> aKnowOrder = launcher.getFactory().Type().get("spoon.test.fieldaccesses.testclasses.internal.Bar$Inner$KnownOrder");
aFoo.addNestedType(aKnowOrder);
final CtTypeAccess<Object> aKnownOrderAccess = launcher.getFactory().Code().createTypeAccess(aKnowOrder.getReference());
final CtMethod<Object> aMethod = aTacos.getMethod("m");
final List<CtInvocation<?>> invs = aMethod.getElements(new TypeFilter<>(CtInvocation.class));

assertEquals(aFooAccess, ((CtFieldAccess) invs.get(0).getArguments().get(0)).getTarget());
assertEquals("inv(spoon.test.fieldaccesses.testclasses.internal.Foo.i)", invs.get(0).toString());
assertEquals(aFooAccess, ((CtFieldAccess) invs.get(1).getArguments().get(0)).getTarget());
assertEquals("inv(spoon.test.fieldaccesses.testclasses.internal.Foo.i)", invs.get(1).toString());
assertEquals(aSubInnerAccess, ((CtFieldAccess) invs.get(2).getArguments().get(0)).getTarget());
assertEquals("inv(spoon.test.fieldaccesses.testclasses.internal.Foo.SubInner.j)", invs.get(2).toString());
assertEquals(aSubInnerAccess, ((CtFieldAccess) invs.get(3).getArguments().get(0)).getTarget());
assertEquals("inv(spoon.test.fieldaccesses.testclasses.internal.Foo.SubInner.j)", invs.get(3).toString());
assertEquals(aKnownOrderAccess, ((CtFieldAccess) invs.get(4).getArguments().get(0)).getTarget());
assertEquals("runIteratorTest(spoon.test.fieldaccesses.testclasses.internal.Foo.KnownOrder.KNOWN_ORDER)", invs.get(4).toString());
assertEquals(aKnownOrderAccess, ((CtFieldAccess) invs.get(5).getArguments().get(0)).getTarget());
assertEquals("runIteratorTest(spoon.test.fieldaccesses.testclasses.internal.Foo.KnownOrder.KNOWN_ORDER)", invs.get(5).toString());

final CtParameter<?> aKnownOrderParameter = aTacos.getMethod("runIteratorTest", aKnowOrder.getReference()).getParameters().get(0);
assertEquals(aKnowOrder.getReference(), aKnownOrderParameter.getType());
assertEquals("spoon.test.fieldaccesses.testclasses.internal.Foo.KnownOrder knownOrder", aKnownOrderParameter.toString());

final CtParameter<?> aSubInnerParameter = aTacos.getMethod("inv", aSubInner.getReference()).getParameters().get(0);
assertEquals(aSubInner.getReference(), aSubInnerParameter.getType());
assertEquals("spoon.test.fieldaccesses.testclasses.internal.Foo.SubInner foo", aSubInnerParameter.toString());
}
}
30 changes: 30 additions & 0 deletions src/test/java/spoon/test/fieldaccesses/testclasses/Tacos.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package spoon.test.fieldaccesses.testclasses;

import spoon.test.fieldaccesses.testclasses.internal.Foo;

public class Tacos {
public void m() {
inv(Foo.i);
inv(spoon.test.fieldaccesses.testclasses.internal.Foo.i);
inv(Foo.SubInner.j);
inv(spoon.test.fieldaccesses.testclasses.internal.Foo.SubInner.j);
runIteratorTest(Foo.KnownOrder.KNOWN_ORDER);
runIteratorTest(spoon.test.fieldaccesses.testclasses.internal.Foo.KnownOrder.KNOWN_ORDER);
}

private void runIteratorTest(spoon.test.fieldaccesses.testclasses.internal.Foo.KnownOrder knownOrder) {
}

private void inv(Foo.SubInner foo) {

}

private void inv(int i) {
}

private static class Burritos {
public boolean add(java.lang.Object e) {
throw new java.lang.UnsupportedOperationException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package spoon.test.fieldaccesses.testclasses.internal;

abstract class Bar {
static abstract class Inner {
public static int i;

public static class SubInner {
public static int j;
}
public enum KnownOrder {
KNOWN_ORDER, UNKNOWN_ORDER
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package spoon.test.fieldaccesses.testclasses.internal;

public abstract class Foo extends Bar.Inner {
class Test {
class Test2 {

}
}
}
3 changes: 1 addition & 2 deletions src/test/java/spoon/test/generics/GenericsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,15 @@
import spoon.test.generics.testclasses.Panini;
import spoon.test.generics.testclasses.Spaghetti;
import spoon.test.generics.testclasses.Tacos;
import spoon.testing.utils.ModelUtils;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static spoon.testing.utils.ModelUtils.*;
import static spoon.testing.utils.ModelUtils.build;
import static spoon.testing.utils.ModelUtils.buildClass;
import static spoon.testing.utils.ModelUtils.canBeBuilt;
import static spoon.testing.utils.ModelUtils.createFactory;

Expand Down