package spoon.test.fieldaccesses; import org.junit.Test; import spoon.Launcher; import spoon.reflect.code.CtArrayWrite; import spoon.reflect.code.CtExpression; 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.CtStatement; import spoon.reflect.code.CtTypeAccess; import spoon.reflect.code.CtUnaryOperator; import spoon.reflect.code.CtVariableWrite; import spoon.reflect.code.UnaryOperatorKind; import spoon.reflect.declaration.CtClass; 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; import spoon.reflect.visitor.CtScanner; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.Query; import spoon.reflect.visitor.filter.NameFilter; import spoon.reflect.visitor.filter.TypeFilter; import spoon.test.fieldaccesses.testclasses.B; import spoon.test.fieldaccesses.testclasses.Kuu; import spoon.test.fieldaccesses.testclasses.Panini; import spoon.test.fieldaccesses.testclasses.Pozole; import spoon.test.fieldaccesses.testclasses.Tacos; import spoon.testing.utils.ModelUtils; import java.util.List; import java.util.logging.Logger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static spoon.testing.Assert.assertThat; import static spoon.testing.utils.ModelUtils.build; import static spoon.testing.utils.ModelUtils.buildClass; public class FieldAccessTest { @Test public void testModelBuildingFieldAccesses() throws Exception { CtType<?> type = build("spoon.test.fieldaccesses", "Mouse"); assertEquals("Mouse", type.getSimpleName()); CtMethod<?> meth1 = type.getElements( new NameFilter<CtMethod<?>>("meth1")).get(0); CtMethod<?> meth1b = type.getElements( new NameFilter<CtMethod<?>>("meth1b")).get(0); assertEquals( 3, meth1.getElements( new TypeFilter<CtFieldAccess<?>>(CtFieldAccess.class)) .size()); assertEquals( 2, meth1b.getElements( new TypeFilter<CtFieldAccess<?>>(CtFieldAccess.class)) .size()); CtMethod<?> meth2 = type.getElements( new NameFilter<CtMethod<?>>("meth2")).get(0); assertEquals( 2, meth2.getElements( new TypeFilter<CtFieldAccess<?>>(CtFieldAccess.class)) .size()); CtMethod<?> meth3 = type.getElements( new NameFilter<CtMethod<?>>("meth3")).get(0); assertEquals( 3, meth3.getElements( new TypeFilter<CtFieldAccess<?>>(CtFieldAccess.class)) .size()); CtMethod<?> meth4 = type.getElements( new NameFilter<CtMethod<?>>("meth4")).get(0); assertEquals( 1, meth4.getElements( new TypeFilter<CtFieldAccess<?>>(CtFieldAccess.class)) .size()); } @Test public void testBCUBug20140402() throws Exception { CtType<?> type = build("spoon.test.fieldaccesses", "BCUBug20140402"); assertEquals("BCUBug20140402", type.getSimpleName()); CtLocalVariable<?> var = type.getElements( new TypeFilter<CtLocalVariable<?>>(CtLocalVariable.class)).get(0); CtFieldAccess<?> expr = (CtFieldAccess<?>) var.getDefaultExpression(); assertEquals( "length", expr.getVariable().toString()); assertEquals( "int", expr.getType().getSimpleName()); // in the model the top-most field access is .length get(0) // and the second one is ".data" get(1) CtFieldAccess<?> fa = expr.getElements(new TypeFilter<CtFieldAccess<?>>(CtFieldAccess.class)).get(1); // we check that we have the data assertEquals( "data", fa.getVariable().toString()); assertEquals( "java.lang.Object[]", fa.getType().toString()); // testing the proxy method setAssignment/getAssignment on local variables var.setAssignment(null); assertEquals(null, var.getAssignment()); assertEquals("int a", var.toString()); // testing the proxy method setAssignment/getAssignment on fields CtField<?> field = type.getElements( new TypeFilter<CtField<?>>(CtField.class)).get(0); assertNotNull(field.getAssignment()); field.setAssignment(null); assertEquals(null, field.getAssignment()); assertEquals("java.lang.Object[] data;", field.toString()); } @Test public void testBUG20160112() throws Exception { CtType<?> type = build("spoon.test.fieldaccesses", "BUG20160112"); assertEquals("BUG20160112", type.getSimpleName()); CtOperatorAssignment<?, ?> ass = type.getElements( new TypeFilter<CtOperatorAssignment<?, ?>>(CtOperatorAssignment.class)).get(0); assertNotNull("z+=a.us", ass); CtExpression<?> righthand = ass.getAssignment(); assertTrue("a.us should be CtFieldRead", righthand instanceof CtFieldRead); } @Test public void testTargetedAccessPosition() throws Exception { CtType<?> type = build("spoon.test.fieldaccesses", "TargetedAccessPosition"); List<CtFieldAccess<?>> vars = type.getElements( new TypeFilter<CtFieldAccess<?>>(CtFieldAccess.class)); //vars is [t.ta.ta, t.ta] assertEquals(2, vars.size()); assertEquals(vars.get(1), vars.get(0).getTarget()); // 6 is length(t.ta.ta) - 1 assertEquals(6, vars.get(0).getPosition().getSourceEnd() - vars.get(0).getPosition().getSourceStart()); // 3 is length(t.ta) - 1 assertEquals(3, vars.get(0).getTarget().getPosition().getSourceEnd() - vars.get(0).getTarget().getPosition().getSourceStart()); // 0 is length(t)-1 assertEquals(0, ((CtFieldAccess<?>) vars.get(0).getTarget()).getTarget().getPosition().getSourceEnd() - ((CtFieldAccess<?>) vars.get(0).getTarget()).getTarget().getPosition().getSourceStart()); } @Test public void testFieldAccessInLambda() throws Exception { Factory build = null; try { build = build(MyClass.class); } catch (NullPointerException ignore) { fail(); } final CtFieldAccess logFieldAccess = Query.getElements(build, new TypeFilter<>(CtFieldAccess.class)).get(0); assertEquals(Logger.class, logFieldAccess.getType().getActualClass()); assertEquals("LOG", logFieldAccess.getVariable().getSimpleName()); assertEquals(MyClass.class, logFieldAccess.getVariable().getDeclaringType().getActualClass()); String expectedLambda = "() -> {" + System.lineSeparator() + " spoon.test.fieldaccesses.MyClass.LOG.info(\"bla\");" + System.lineSeparator() + "}"; assertEquals(expectedLambda, logFieldAccess.getParent(CtLambda.class).toString()); } @Test public void testFieldAccessInAnonymousClass() throws Exception { final Factory factory = build(Panini.class); final CtType<Panini> panini = factory.Type().get(Panini.class); final CtFieldRead fieldInAnonymous = panini.getElements(new TypeFilter<>(CtFieldRead.class)).get(0); assertEquals("ingredient", fieldInAnonymous.getTarget().toString()); assertEquals("next", fieldInAnonymous.getVariable().getSimpleName()); assertEquals("ingredient.next", fieldInAnonymous.toString()); } @Test public void testFieldAccessNoClasspath() throws Exception { Launcher launcher = new Launcher(); launcher.addInputResource("src/test/resources/import-resources/fr/inria/"); launcher.getEnvironment().setNoClasspath(true); launcher.run(); CtType<?> ctType = launcher.getFactory().Class().get("FooNoClassPath"); CtFieldAccess ctFieldAccess = ctType .getElements(new TypeFilter<>(CtFieldAccess.class)).get(0); assertEquals("(game.board.width)", ctFieldAccess.toString()); CtFieldReference ctFieldReferenceWith = ctFieldAccess.getVariable(); assertEquals("width", ctFieldReferenceWith.getSimpleName()); CtFieldAccess ctFieldAccessBoard = (CtFieldAccess) ctFieldAccess.getTarget(); assertEquals("game.board", ctFieldAccessBoard.toString()); CtFieldReference ctFieldReferenceBoard = ctFieldAccessBoard.getVariable(); assertEquals("board", ctFieldReferenceBoard.getSimpleName()); CtFieldAccess ctFieldAccessGame = (CtFieldAccess) ctFieldAccessBoard.getTarget(); assertEquals("game.board", ctFieldAccessBoard.toString()); CtFieldReference ctFieldReferenceGame = ctFieldAccessGame.getVariable(); assertEquals("game", ctFieldReferenceGame.getSimpleName()); } @Test public void testIncrementationOnAVarIsAUnaryOperator() throws Exception { // contract: When we use var++, the variable is a read access with an unary operator. final CtType<Panini> aMole = buildClass(Panini.class); final CtMethod<?> make = aMole.getMethodsByName("make").get(0); final List<CtUnaryOperator<?>> unaryOperators = make.getElements(new TypeFilter<CtUnaryOperator<?>>(CtUnaryOperator.class)); final CtFieldWrite<Object> fieldRead = aMole.getFactory().Core().createFieldWrite(); fieldRead.setTarget(aMole.getFactory().Code().createThisAccess(aMole.getReference(), true)); final CtFieldReference fieldReference = aMole.getField("i").getReference(); fieldRead.setVariable(fieldReference); assertEquals(2, unaryOperators.size()); final CtUnaryOperator<?> first = unaryOperators.get(0); assertEquals(UnaryOperatorKind.POSTINC, first.getKind()); assertEquals(fieldRead, first.getOperand()); assertEquals("(i)++", first.toString()); final CtUnaryOperator<?> second = unaryOperators.get(1); assertEquals(UnaryOperatorKind.PREINC, second.getKind()); assertEquals(fieldRead, second.getOperand()); assertEquals("++(i)", second.toString()); } @Test public void testFieldWriteWithPlusEqualsOperation() throws Exception { // contract: When we use var += value, the var is a write access. final CtType<Panini> aPanini = buildClass(Panini.class); final CtMethod<?> prepare = aPanini.getMethodsByName("prepare").get(0); final List<CtFieldWrite<?>> fields = prepare.getElements(new TypeFilter<>(CtFieldWrite.class)); assertEquals(1, fields.size()); assertEquals(aPanini.getField("i").getReference(), fields.get(0).getVariable()); assertEquals("i += 0", fields.get(0).getParent().toString()); assertEquals("i", fields.get(0).toString()); final List<CtVariableWrite<?>> variables = prepare.getElements(new TypeFilter<>(CtVariableWrite.class)); assertEquals(2, variables.size()); assertEquals("j += 0", variables.get(1).getParent().toString()); assertEquals("j", variables.get(1).toString()); final List<CtArrayWrite<?>> arrays = prepare.getElements(new TypeFilter<>(CtArrayWrite.class)); assertEquals(1, arrays.size()); assertEquals("array[0] += 0", arrays.get(0).getParent().toString()); assertEquals("array[0]", arrays.get(0).toString()); } @Test public void testTypeDeclaredInAnonymousClass() throws Exception { // contract: Type declared in an anonymous class shouldn't include the anonymous qualified name // in its own fully qualified name. final CtType<Pozole> aPozole = buildClass(Pozole.class); final List<CtField> elements = aPozole.getElements(new TypeFilter<>(CtField.class)); assertEquals(1, elements.size()); 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.setArgs(new String[] {"--output-type", "nooutput" }); 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.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()); } @Test public void testTypeOfFieldAccess() throws Exception { CtType<Panini> aPanini = ModelUtils.buildClass(Panini.class); List<CtFieldAccess> fieldAccesses = aPanini.getMethod("prepare").getElements(new TypeFilter<>(CtFieldAccess.class)); assertEquals(1, fieldAccesses.size()); assertNotNull(fieldAccesses.get(0).getType()); assertEquals(fieldAccesses.get(0).getVariable().getType(), fieldAccesses.get(0).getType()); } @Test public void testFieldAccessWithoutAnyImport() throws Exception { final Launcher launcher = new Launcher(); launcher.setArgs(new String[] {"--output-type", "nooutput" }); launcher.addInputResource("./src/test/java/spoon/test/fieldaccesses/testclasses/Kuu.java"); launcher.addInputResource("./src/test/java/spoon/test/fieldaccesses/testclasses/Mole.java"); launcher.run(); final CtType<Kuu> aType = launcher.getFactory().Type().get(Kuu.class); final DefaultJavaPrettyPrinter printer = new DefaultJavaPrettyPrinter(aType.getFactory().getEnvironment()); assertEquals(0, printer.computeImports(aType).size()); assertEquals("spoon.test.fieldaccesses.testclasses.Mole.Delicious delicious", aType.getMethodsByName("m").get(0).getParameters().get(0).toString()); } @Test public void testFieldAccessOnUnknownType() throws Exception { final Launcher launcher = new Launcher(); launcher.addInputResource("./src/test/resources/noclasspath/FieldAccessRes.java"); launcher.getEnvironment().setNoClasspath(true); launcher.buildModel(); class CounterScanner extends CtScanner { private int visited = 0; @Override public <T> void visitCtFieldWrite(CtFieldWrite<T> fieldWrite) { visited++; assertEquals("array", ((CtVariableWrite) fieldWrite.getTarget()).getVariable().getSimpleName()); assertEquals("length", fieldWrite.getVariable().getSimpleName()); } } CounterScanner scanner = new CounterScanner(); launcher.getFactory().Class().get("FieldAccessRes").accept(scanner); assertEquals(1, scanner.visited); } @Test public void testGetReference() throws Exception { final Launcher launcher = new Launcher(); launcher.getEnvironment().setShouldCompile(true); launcher.setArgs(new String[] {"--output-type", "nooutput" }); launcher.addInputResource("./src/test/java/spoon/test/fieldaccesses/testclasses/"); launcher.getEnvironment().setAutoImports(true); launcher.run(); final CtClass<B> aClass = launcher.getFactory().Class().get(B.class); // now static fields are used with the name of the parent class assertEquals("A.myField", aClass.getElements(new TypeFilter<>(CtFieldWrite.class)).get(0).toString()); assertEquals("finalField", aClass.getElements(new TypeFilter<>(CtFieldWrite.class)).get(1).toString()); } @Test public void testFieldAccessAutoExplicit() throws Exception { CtClass mouse = (CtClass)ModelUtils.buildClass(Mouse.class); CtMethod method = mouse.filterChildren((CtMethod m)->"meth1".equals(m.getSimpleName())).first(); CtFieldReference ageFR = method.filterChildren((CtFieldReference fr)->"age".equals(fr.getSimpleName())).first(); //first is the field printed with implicit "this." assertEquals("age", ageFR.getParent().toString()); //add local variable declaration which hides the field declaration method.getBody().insertBegin((CtStatement) mouse.getFactory().createCodeSnippetStatement("int age = 1").compile()); //now the field access must use explicit "this." assertEquals("this.age", ageFR.getParent().toString()); } }