package spoon.test.intercession; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import spoon.Launcher; import spoon.reflect.ast.IntercessionScanner; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.CtAssignment; import spoon.reflect.code.CtBinaryOperator; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtCodeSnippetStatement; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtIf; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtThrow; import spoon.reflect.code.CtVariableAccess; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtParameter; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeParameter; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.Query; import spoon.reflect.visitor.filter.AbstractFilter; import spoon.support.UnsettableProperty; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static spoon.testing.utils.ModelUtils.createFactory; public class IntercessionTest { Factory factory = createFactory(); @Test public void testInsertBegin() { CtClass<?> clazz = factory .Code() .createCodeSnippetStatement( "" + "class X {" + "public void foo() {" + " int x=0;" + "}" + "};").compile(); CtMethod<?> foo = (CtMethod<?>) clazz.getMethods().toArray()[0]; CtBlock<?> body = foo.getBody(); assertEquals(1, body.getStatements().size()); // adding a new statement; CtReturn<Object> returnStmt = factory.Core().createReturn(); body.insertBegin(returnStmt); assertEquals(2, body.getStatements().size()); assertSame(returnStmt, body.getStatements().get(0)); } @Test public void testInsertEnd() { CtClass<?> clazz = factory .Code() .createCodeSnippetStatement( "" + "class X {" + "public void foo() {" + " int x=0;" + " String foo=\"toto\";" + "}" + "};") .compile(); CtMethod<?> foo = (CtMethod<?>) clazz.getMethods().toArray()[0]; CtMethod<?> fooClone = foo.clone(); Assert.assertEquals(foo, fooClone); CtBlock<?> body = foo.getBody(); assertEquals(2, body.getStatements().size()); // adding a new statement; CtReturn<Object> returnStmt = factory.Core().createReturn(); body.insertEnd(returnStmt); assertEquals(3, body.getStatements().size()); assertSame(returnStmt, body.getStatements().get(2)); Assert.assertNotEquals(foo, fooClone); } @Test public void testEqualConstructor() { CtClass<?> clazz = factory .Code() .createCodeSnippetStatement( "" + "class X { public X() {} };") .compile(); CtConstructor<?> foo = (CtConstructor<?>) clazz.getConstructors().toArray()[0]; CtConstructor<?> fooClone = foo.clone(); Assert.assertEquals(foo, fooClone); CtBlock<?> body = foo.getBody(); // there is an implicit call to super() assertEquals(1, body.getStatements().size()); assertEquals("super()", body.getStatements().get(0).toString()); // adding a new statement; CtStatement stmt = factory.Core().createCodeSnippetStatement(); body.insertEnd(stmt); assertEquals(2, body.getStatements().size()); // constructor are not equals anymore Assert.assertNotEquals(foo, fooClone); } @Test public void test_setThrownExpression() { CtThrow throwStmt = factory.Core().createThrow(); CtExpression<Exception> exp = factory.Code() .createCodeSnippetExpression("e"); throwStmt.setThrownExpression(exp); assertEquals("throw e", throwStmt.toString()); } @Test public void testInsertIfIntercession() { String ifCode = "if (1 == 0)\n" + " return 1;\n" + "else\n" + " return 0;\n" + ""; CtClass<?> clazz = factory .Code() .createCodeSnippetStatement( "" + "class X {" + "public int bar() {" + ifCode + "}" + "};").compile(); CtMethod<?> foo = (CtMethod<?>) clazz.getMethods().toArray()[0]; CtBlock<?> body = foo.getBody(); assertEquals(1, body.getStatements().size()); CtIf ifStmt = (CtIf) foo.getBody().getStatements().get(0); String s = ifStmt.toString().replace("\r", ""); assertEquals(ifCode, s); CtBlock<?> r1 = ifStmt.getThenStatement(); CtBlock<?> r2 = ifStmt.getElseStatement(); assertTrue(r1.isImplicit()); assertTrue(r2.isImplicit()); ifStmt.setThenStatement(r2); assertSame(r2, ifStmt.getThenStatement()); ifStmt.setElseStatement(r1); assertSame(r1, ifStmt.getElseStatement()); s = ifStmt.toString().replace("\r", ""); String ifCodeNew = "if (1 == 0)\n" + " return 0;\n" + "else\n" + " return 1;\n" + ""; assertEquals(ifCodeNew, s); } @Test public void testInsertAfter() { CtClass<?> clazz = factory .Code() .createCodeSnippetStatement( "" + "class X {" + "public void foo() {" + " int x=0;" + " int y=0;" + " int z=x+y;" + "}" + "};") .compile(); CtMethod<?> foo = (CtMethod<?>) clazz.getMethods().toArray()[0]; CtBlock<?> body = foo.getBody(); assertEquals(3, body.getStatements().size()); CtStatement s = body.getStatements().get(2); assertEquals("int z = x + y", s.toString()); // adding a new statement; CtCodeSnippetStatement stmt = factory.Core() .createCodeSnippetStatement(); stmt.setValue("System.out.println(x);"); s.insertAfter(stmt); assertEquals(4, body.getStatements().size()); assertSame(stmt, body.getStatements().get(3)); } @Test public void testSettersAreAllGood() throws Exception { ArrayList classpath = new ArrayList(); for (String classpathEntry : System.getProperty("java.class.path").split(File.pathSeparator)) { if (!classpathEntry.contains("test-classes")) { classpath.add(classpathEntry); } } final Launcher launcher = new Launcher(); launcher.addInputResource("./src/main/java/spoon/reflect/"); launcher.addInputResource("./src/main/java/spoon/support/"); launcher.getModelBuilder().setSourceClasspath((String[]) classpath.toArray(new String[]{})); launcher.buildModel(); final Factory factory = launcher.getFactory(); final List<CtMethod<?>> setters = Query .getElements(factory, new AbstractFilter<CtMethod<?>>(CtMethod.class) { @Override public boolean matches(CtMethod<?> element) { CtType<?> declaringType = element.getDeclaringType(); if (declaringType.getPackage() != null && (declaringType.getPackage().getQualifiedName().startsWith("spoon.support.visitor") || declaringType.getPackage().getQualifiedName().startsWith("spoon.reflect.visitor"))) { return false; } return declaringType.isInterface() && declaringType.getSimpleName().startsWith("Ct") && (element.getSimpleName().startsWith("set") || element.getSimpleName().startsWith("add")); } }); for (CtMethod<?> setter : setters) { final String methodLog = setter.getSimpleName() + " in " + setter.getDeclaringType().getSimpleName(); if (setter.getFormalCtTypeParameters().size() <= 0) { fail("Your setter " + methodLog + " don't have a generic type for its return type."); } boolean isMatch = false; // New type parameter declaration. for (CtTypeParameter typeParameter : setter.getFormalCtTypeParameters()) { if (setter.getType().getSimpleName().equals(typeParameter.getSimpleName())) { isMatch = true; if (setter.getAnnotation(Override.class) != null) { // Override annotation means that the current method come from a super // interface. So the return type can't be the declaring interface. continue; } if (!setter.getDeclaringType().getSimpleName().equals(typeParameter.getSuperclass().getSimpleName())) { fail("Your setter " + methodLog + " has a type reference who don't extends " + setter.getDeclaringType().getSimpleName()); } } } assertTrue("The type of " + methodLog + " don't match with generic types.", isMatch); } } @Test @Ignore // interesting but too fragile with conventions public void testResetCollectionInSetters() throws Exception { final Launcher launcher = new Launcher(); launcher.setArgs(new String[] {"--output-type", "nooutput" }); final Factory factory = launcher.getFactory(); launcher.getEnvironment().setNoClasspath(true); // interfaces. launcher.addInputResource("./src/main/java/spoon/reflect/code"); launcher.addInputResource("./src/main/java/spoon/reflect/declaration"); launcher.addInputResource("./src/main/java/spoon/reflect/reference"); // implementations. launcher.addInputResource("./src/main/java/spoon/support/reflect/code"); launcher.addInputResource("./src/main/java/spoon/support/reflect/declaration"); launcher.addInputResource("./src/main/java/spoon/support/reflect/reference"); launcher.buildModel(); new IntercessionScanner(factory) { @Override protected boolean isToBeProcessed(CtMethod<?> candidate) { return candidate.getSimpleName().startsWith("set") // && candidate.hasModifier(ModifierKind.PUBLIC) // && takeSetterCollection(candidate) // && avoidInterfaces(candidate) // //&& avoidSpecificMethods(candidate) // && avoidThrowUnsupportedOperationException(candidate); } private boolean takeSetterCollection(CtMethod<?> candidate) { final CtTypeReference<?> type = candidate.getParameters().get(0).getType(); final List<CtTypeReference<?>> actualTypeArguments = type.getActualTypeArguments(); return COLLECTIONS.contains(type) && actualTypeArguments.size() == 1 && actualTypeArguments.get(0).isSubtypeOf(CTELEMENT_REFERENCE); } @Override protected void process(CtMethod<?> element) { if (element.getAnnotation(UnsettableProperty.class) != null) { // we don't check the contracts for unsettable setters return; } final CtStatement statement = element.getBody().getStatement(0); if (!(statement instanceof CtIf)) { fail(log(element, "First statement should be an if to check the parameter of the setter")); } final CtIf anIf = (CtIf) statement; if (!createCheckNull(element.getParameters().get(0)).equals(anIf.getCondition())) { fail(log(element, "Condition should test if the parameter is null.\nThe condition was " + anIf.getCondition())); } if (!(anIf.getThenStatement() instanceof CtBlock)) { fail(log(element, "Should have a block in the if condition to have the initialization and the return.")); } if (element.getParameters().get(0).getType().equals(SET_REFERENCE)) { if (!hasCallEmptyInv(anIf.getThenStatement(), SET_REFERENCE)) { fail(log(element, "Should initilize the list with CtElementImpl#emptySet().")); } } else { if (!hasCallEmptyInv(anIf.getThenStatement(), LIST_REFERENCE)) { fail(log(element, "Should initilize the list with CtElementImpl#emptyList().")); } } } private boolean hasCallEmptyInv(CtBlock thenStatement, CtTypeReference<? extends Collection> collectionReference) { if (!(thenStatement.getStatement(0) instanceof CtAssignment)) { return false; } final CtExpression assignment = ((CtAssignment) thenStatement.getStatement(0)).getAssignment(); if (!(assignment instanceof CtInvocation)) { return false; } final CtInvocation inv = (CtInvocation) assignment; if (collectionReference.equals(SET_REFERENCE)) { if (!inv.getExecutable().getSimpleName().equals("emptySet")) { return false; } } else if (collectionReference.equals(LIST_REFERENCE)) { if (!inv.getExecutable().getSimpleName().equals("emptyList")) { return false; } } return true; } /** * Creates <code>list == null && list.isEmpty()</code>. * * @param ctParameter <code>list</code> */ private CtBinaryOperator<Boolean> createCheckNull(CtParameter<?> ctParameter) { final CtVariableAccess<?> variableRead = factory.Code().createVariableRead(ctParameter.getReference(), true); final CtLiteral nullLiteral = factory.Code().createLiteral(null); nullLiteral.setType(factory.Type().nullType()); final CtBinaryOperator<Boolean> checkNull = factory.Code().createBinaryOperator(variableRead, nullLiteral, BinaryOperatorKind.EQ); checkNull.setType(factory.Type().BOOLEAN_PRIMITIVE); final CtMethod<Boolean> isEmptyMethod = ctParameter.getType().getTypeDeclaration().getMethod(factory.Type().booleanPrimitiveType(), "isEmpty"); final CtInvocation<Boolean> isEmpty = factory.Code().createInvocation(variableRead, isEmptyMethod.getReference()); final CtBinaryOperator<Boolean> condition = factory.Code().createBinaryOperator(checkNull, isEmpty, BinaryOperatorKind.OR); return condition.setType(factory.Type().booleanPrimitiveType()); } private String log(CtMethod<?> element, String message) { return message + "\nin " + element.getSignature() + "\ndeclared in " + element.getDeclaringType().getQualifiedName(); } }.scan(factory.getModel().getRootPackage()); } }