From 6f7b1fb5f4b84822001771f3067a026eeaffbc5d Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 18 Apr 2016 12:39:57 +0200 Subject: [PATCH] adds support for incremental model (#584) --- src/main/java/spoon/Launcher.java | 8 ++ src/main/java/spoon/SpoonAPI.java | 8 +- src/main/java/spoon/reflect/CtModel.java | 48 ++++++++ src/main/java/spoon/reflect/CtModelImpl.java | 110 ++++++++++++++++++ .../spoon/reflect/declaration/CtPackage.java | 2 +- .../factory/CompilationUnitFactory.java | 8 +- .../java/spoon/reflect/factory/Factory.java | 4 + .../spoon/reflect/factory/FactoryImpl.java | 23 ++-- .../spoon/reflect/factory/PackageFactory.java | 60 ++-------- .../spoon/reflect/factory/TypeFactory.java | 14 +-- .../visitor/filter/AbstractFilter.java | 2 +- .../spoon/support/DefaultCoreFactory.java | 12 +- .../declaration/CtNamedElementImpl.java | 2 +- .../reflect/declaration/CtPackageImpl.java | 53 ++++++++- .../SpoonArchitectureEnforcer.java | 40 +++++++ .../java/spoon/test/factory/FactoryTest.java | 107 +++++++++++++++-- .../spoon/test/factory/testclasses2/Baz.java | 4 + .../spoon/test/parent/ParentContractTest.java | 11 +- 18 files changed, 415 insertions(+), 101 deletions(-) create mode 100644 src/main/java/spoon/reflect/CtModel.java create mode 100644 src/main/java/spoon/reflect/CtModelImpl.java create mode 100644 src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java create mode 100644 src/test/java/spoon/test/factory/testclasses2/Baz.java diff --git a/src/main/java/spoon/Launcher.java b/src/main/java/spoon/Launcher.java index c1b8df7c550..187c3490e18 100644 --- a/src/main/java/spoon/Launcher.java +++ b/src/main/java/spoon/Launcher.java @@ -22,15 +22,18 @@ import com.martiansoftware.jsap.JSAPResult; import com.martiansoftware.jsap.Switch; import com.martiansoftware.jsap.stringparsers.FileStringParser; + import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.log4j.Level; import org.apache.log4j.Logger; + import spoon.compiler.Environment; import spoon.compiler.SpoonCompiler; import spoon.compiler.SpoonResource; import spoon.compiler.SpoonResourceHelper; import spoon.processing.Processor; +import spoon.reflect.CtModel; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; @@ -767,4 +770,9 @@ public void setBinaryOutputDirectory(File outputDirectory) { modelBuilder.setBinaryOutputDirectory(outputDirectory); } + @Override + public CtModel getModel() { + return factory.getModel(); + } + } diff --git a/src/main/java/spoon/SpoonAPI.java b/src/main/java/spoon/SpoonAPI.java index 17e81e30111..323d12fc131 100644 --- a/src/main/java/spoon/SpoonAPI.java +++ b/src/main/java/spoon/SpoonAPI.java @@ -16,16 +16,17 @@ */ package spoon; +import java.io.File; + import spoon.compiler.Environment; import spoon.compiler.SpoonCompiler; import spoon.processing.Processor; +import spoon.reflect.CtModel; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.visitor.Filter; -import java.io.File; - /** * Is the core entry point of Spoon. Implemented by Launcher. */ @@ -144,4 +145,7 @@ public interface SpoonAPI { * Creates a new Spoon compiler (for building the model) */ SpoonCompiler createCompiler(); + + /** Returns the model built from the sources given via {@link #addInputResource(String)} */ + CtModel getModel(); } diff --git a/src/main/java/spoon/reflect/CtModel.java b/src/main/java/spoon/reflect/CtModel.java new file mode 100644 index 00000000000..78e5fc6b80a --- /dev/null +++ b/src/main/java/spoon/reflect/CtModel.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2006-2015 INRIA and contributors + * Spoon - http://spoon.gforge.inria.fr/ + * + * This software is governed by the CeCILL-C License under French law and + * abiding by the rules of distribution of free software. You can use, modify + * and/or redistribute the software under the terms of the CeCILL-C license as + * circulated by CEA, CNRS and INRIA at http://www.cecill.info. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ +package spoon.reflect; + +import java.util.Collection; +import java.util.List; + +import spoon.processing.Processor; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtType; +import spoon.reflect.visitor.Filter; + +/** represents a Java program, modeled by a set of compile-time (Ct) objects + * where each objects is a program element (for instance, a CtClass represents a class). + */ +public interface CtModel { + + /** returns the root package */ + CtPackage getRootPackage(); + + /** returns all top-level types of the model */ + Collection> getAllTypes(); + + /** returns all packages of the model */ + Collection getAllPackages(); + + /** process this model with the given processor */ + void processWith(Processor processor); + + /** Returns all the model elements matching the filter. */ + List getElements(Filter filter); + +} diff --git a/src/main/java/spoon/reflect/CtModelImpl.java b/src/main/java/spoon/reflect/CtModelImpl.java new file mode 100644 index 00000000000..093897b4f2e --- /dev/null +++ b/src/main/java/spoon/reflect/CtModelImpl.java @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2006-2015 INRIA and contributors + * Spoon - http://spoon.gforge.inria.fr/ + * + * This software is governed by the CeCILL-C License under French law and + * abiding by the rules of distribution of free software. You can use, modify + * and/or redistribute the software under the terms of the CeCILL-C license as + * circulated by CEA, CNRS and INRIA at http://www.cecill.info. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ +package spoon.reflect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import spoon.processing.Processor; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.ParentNotInitializedException; +import spoon.reflect.factory.Factory; +import spoon.reflect.visitor.CtVisitor; +import spoon.reflect.visitor.Filter; +import spoon.reflect.visitor.filter.TypeFilter; +import spoon.support.QueueProcessingManager; +import spoon.support.reflect.declaration.CtElementImpl; +import spoon.support.reflect.declaration.CtPackageImpl; + +public class CtModelImpl implements CtModel { + + private static class CtRootPackage extends CtPackageImpl { + { + this.setSimpleName(CtPackage.TOP_LEVEL_PACKAGE_NAME); + this.setParent(new CtElementImpl() { + @Override + public void accept(CtVisitor visitor) { + + } + + @Override + public CtElement getParent() throws ParentNotInitializedException { + return null; + } + }); + } + + @Override + public String getSimpleName() { + return super.getSimpleName(); + } + + @Override + public String getQualifiedName() { + return ""; + } + + @Override + public String toString() { + return packs.size() + " packages"; + } + + } + + private CtPackage rootPackage = new CtRootPackage(); + + public CtModelImpl(Factory f) { + rootPackage.setFactory(f); + } + + @Override + public CtPackage getRootPackage() { + return rootPackage; + } + + + @Override + public Collection> getAllTypes() { + List> types = new ArrayList>(); + for (CtPackage pack : getAllPackages()) { + types.addAll(pack.getTypes()); + } + return types; + } + + + @Override + public Collection getAllPackages() { + return Collections.unmodifiableCollection(rootPackage.getElements(new TypeFilter(CtPackage.class))); + } + + + @Override + public void processWith(Processor processor) { + new QueueProcessingManager(rootPackage.getFactory()).process(getRootPackage()); + } + + @Override + public List getElements(Filter filter) { + return getRootPackage().getElements(filter); + } + +} diff --git a/src/main/java/spoon/reflect/declaration/CtPackage.java b/src/main/java/spoon/reflect/declaration/CtPackage.java index 48949c15f52..e6334e575c4 100644 --- a/src/main/java/spoon/reflect/declaration/CtPackage.java +++ b/src/main/java/spoon/reflect/declaration/CtPackage.java @@ -37,7 +37,7 @@ public interface CtPackage extends CtNamedElement { String TOP_LEVEL_PACKAGE_NAME = "unnamed package"; /** - * Gets the declaring package of the current one. + * Gets the declaring package of the current one. Returns null if the package is not yet in another one. */ CtPackage getDeclaringPackage(); diff --git a/src/main/java/spoon/reflect/factory/CompilationUnitFactory.java b/src/main/java/spoon/reflect/factory/CompilationUnitFactory.java index b6c49ff27ed..c7e583bd0b8 100644 --- a/src/main/java/spoon/reflect/factory/CompilationUnitFactory.java +++ b/src/main/java/spoon/reflect/factory/CompilationUnitFactory.java @@ -39,7 +39,7 @@ public CompilationUnitFactory(Factory factory) { super(factory); } - Map compilationUnits = new TreeMap(); + private transient Map cachedCompilationUnits = new TreeMap(); /** * Gets the compilation unit map. @@ -47,7 +47,7 @@ public CompilationUnitFactory(Factory factory) { * @return a map (path -> {@link CompilationUnit}) */ public Map getMap() { - return compilationUnits; + return cachedCompilationUnits; } /** @@ -62,7 +62,7 @@ public CompilationUnit create() { * Creates or gets a compilation unit for a given file path. */ public CompilationUnit create(String filePath) { - CompilationUnit cu = compilationUnits.get(filePath); + CompilationUnit cu = cachedCompilationUnits.get(filePath); if (cu == null) { if ("".equals(filePath)) { cu = factory.Core().createVirtualCompilationUnit(); @@ -70,7 +70,7 @@ public CompilationUnit create(String filePath) { } cu = factory.Core().createCompilationUnit(); cu.setFile(new File(filePath)); - compilationUnits.put(filePath, cu); + cachedCompilationUnits.put(filePath, cu); } return cu; } diff --git a/src/main/java/spoon/reflect/factory/Factory.java b/src/main/java/spoon/reflect/factory/Factory.java index 0815529df27..5cb9b764e43 100644 --- a/src/main/java/spoon/reflect/factory/Factory.java +++ b/src/main/java/spoon/reflect/factory/Factory.java @@ -17,6 +17,7 @@ package spoon.reflect.factory; import spoon.compiler.Environment; +import spoon.reflect.CtModel; /** * Provides the sub-factories required by Spoon. @@ -27,6 +28,9 @@ */ public interface Factory { + /** returns the Spoon model that has been built with this factory or one of its subfactories */ + CtModel getModel(); + CoreFactory Core(); // used 238 times TypeFactory Type(); // used 107 times diff --git a/src/main/java/spoon/reflect/factory/FactoryImpl.java b/src/main/java/spoon/reflect/factory/FactoryImpl.java index a269da554b9..8f7802dd49d 100644 --- a/src/main/java/spoon/reflect/factory/FactoryImpl.java +++ b/src/main/java/spoon/reflect/factory/FactoryImpl.java @@ -16,7 +16,14 @@ */ package spoon.reflect.factory; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + import spoon.compiler.Environment; +import spoon.reflect.CtModel; +import spoon.reflect.CtModelImpl; import spoon.reflect.cu.CompilationUnit; import spoon.reflect.declaration.CtAnnotationType; import spoon.reflect.declaration.CtClass; @@ -32,11 +39,6 @@ import spoon.support.DefaultInternalFactory; import spoon.support.StandardEnvironment; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; - /** * Implements {@link Factory} */ @@ -214,7 +216,7 @@ public MethodFactory Method() { return methodF; } - private PackageFactory packageF; + private transient PackageFactory packageF; /** * The {@link CtPackage} sub-factory. @@ -227,7 +229,7 @@ public PackageFactory Package() { return packageF; } - private CompilationUnitFactory compilationUnit; + private transient CompilationUnitFactory compilationUnit; /** * The {@link CompilationUnit} sub-factory. @@ -320,4 +322,11 @@ public String dedup(String symbol) { return symbol; } } + + private final CtModel model = new CtModelImpl(this); + + @Override + public CtModel getModel() { + return model; + } } diff --git a/src/main/java/spoon/reflect/factory/PackageFactory.java b/src/main/java/spoon/reflect/factory/PackageFactory.java index a4c448d5c92..4e7e5d4611e 100644 --- a/src/main/java/spoon/reflect/factory/PackageFactory.java +++ b/src/main/java/spoon/reflect/factory/PackageFactory.java @@ -16,57 +16,22 @@ */ package spoon.reflect.factory; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtPackage; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.ParentNotInitializedException; -import spoon.reflect.reference.CtPackageReference; -import spoon.reflect.visitor.CtVisitor; -import spoon.support.reflect.declaration.CtElementImpl; -import spoon.support.reflect.declaration.CtPackageImpl; - import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.StringTokenizer; +import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtType; +import spoon.reflect.reference.CtPackageReference; + /** * The {@link CtPackage} sub-factory. */ public class PackageFactory extends SubFactory implements Serializable { private static final long serialVersionUID = 1L; - private CtPackage rootPackage; - - private static class CtRootPackage extends CtPackageImpl { - { - setSimpleName(CtPackage.TOP_LEVEL_PACKAGE_NAME); - setParent(new CtElementImpl() { - @Override - public void accept(CtVisitor visitor) { - - } - - @Override - public CtElement getParent() throws ParentNotInitializedException { - return null; - } - }); - } - - @Override - public String getSimpleName() { - return super.getSimpleName(); - } - - @Override - public String getQualifiedName() { - return ""; - } - - } - /** * Creates a new package sub-factory. * @@ -75,8 +40,6 @@ public String getQualifiedName() { */ public PackageFactory(Factory factory) { super(factory); - rootPackage = new CtRootPackage(); - rootPackage.setFactory(factory); } /** @@ -101,16 +64,11 @@ public CtPackageReference createReference(Package pack) { return createReference(pack.getName()); } - CtPackageReference topLevel; - /** * Returns a reference on the top level package. */ public CtPackageReference topLevel() { - if (topLevel == null) { - topLevel = createReference(CtPackage.TOP_LEVEL_PACKAGE_NAME); - } - return topLevel; + return factory.getModel().getRootPackage().getReference(); } /** @@ -146,7 +104,7 @@ public CtPackage create(CtPackage parent, String simpleName) { */ public CtPackage getOrCreate(String qualifiedName) { StringTokenizer token = new StringTokenizer(qualifiedName, CtPackage.PACKAGE_SEPARATOR); - CtPackage last = rootPackage; + CtPackage last = factory.getModel().getRootPackage(); while (token.hasMoreElements()) { String name = token.nextToken(); @@ -175,7 +133,7 @@ public CtPackage get(String qualifiedName) { throw new RuntimeException("Invalid package name " + qualifiedName); } StringTokenizer token = new StringTokenizer(qualifiedName, CtPackage.PACKAGE_SEPARATOR); - CtPackage current = rootPackage; + CtPackage current = factory.getModel().getRootPackage(); if (token.hasMoreElements()) { current = current.getPackage(token.nextToken()); while (token.hasMoreElements() && current != null) { @@ -191,14 +149,14 @@ public CtPackage get(String qualifiedName) { * packages and their sub-packages. */ public Collection getAll() { - return getSubPackageList(rootPackage); + return factory.getModel().getAllPackages(); } /** * Return the unnamed top-level package. */ public CtPackage getRootPackage() { - return rootPackage; + return factory.getModel().getRootPackage(); } private List getSubPackageList(CtPackage pack) { diff --git a/src/main/java/spoon/reflect/factory/TypeFactory.java b/src/main/java/spoon/reflect/factory/TypeFactory.java index 74db357b1b4..2b8b00abf3b 100644 --- a/src/main/java/spoon/reflect/factory/TypeFactory.java +++ b/src/main/java/spoon/reflect/factory/TypeFactory.java @@ -35,8 +35,7 @@ */ public class TypeFactory extends SubFactory { - CtTypeReference nullType; - + public final CtTypeReference NULL_TYPE = createReference(CtTypeReference.NULL_TYPE_NAME); public final CtTypeReference VOID = createReference(Void.class); public final CtTypeReference STRING = createReference(String.class); public final CtTypeReference BOOLEAN = createReference(Boolean.class); @@ -63,10 +62,7 @@ public class TypeFactory extends SubFactory { * Returns a reference on the null type (type of null). */ public CtTypeReference nullType() { - if (nullType == null) { - nullType = createReference(CtTypeReference.NULL_TYPE_NAME); - } - return nullType; + return NULL_TYPE; } /** @@ -237,11 +233,7 @@ public boolean matches(CtNewClass element) { * Gets the list of all top-level created types. */ public List> getAll() { - List> types = new ArrayList>(); - for (CtPackage pack : factory.Package().getAll()) { - types.addAll(pack.getTypes()); - } - return types; + return (List>) factory.getModel().getAllTypes(); } /** diff --git a/src/main/java/spoon/reflect/visitor/filter/AbstractFilter.java b/src/main/java/spoon/reflect/visitor/filter/AbstractFilter.java index 9213347802d..b0860b9dfcc 100644 --- a/src/main/java/spoon/reflect/visitor/filter/AbstractFilter.java +++ b/src/main/java/spoon/reflect/visitor/filter/AbstractFilter.java @@ -47,7 +47,7 @@ public AbstractFilter() { public Class getType() { return type; } - + @Override public boolean matches(T element) { return type.isAssignableFrom(element.getClass()); diff --git a/src/main/java/spoon/support/DefaultCoreFactory.java b/src/main/java/spoon/support/DefaultCoreFactory.java index 6a97b0f4206..9f78bae6db0 100644 --- a/src/main/java/spoon/support/DefaultCoreFactory.java +++ b/src/main/java/spoon/support/DefaultCoreFactory.java @@ -80,6 +80,7 @@ import spoon.reflect.declaration.CtParameter; import spoon.reflect.factory.CoreFactory; import spoon.reflect.factory.Factory; +import spoon.reflect.factory.SubFactory; import spoon.reflect.reference.CtArrayTypeReference; import spoon.reflect.reference.CtCatchVariableReference; import spoon.reflect.reference.CtExecutableReference; @@ -177,18 +178,15 @@ * This class implements a default core factory for Spoon's meta-model. This * implementation is done with regular Java classes (POJOs). */ -public class DefaultCoreFactory implements CoreFactory, Serializable { +public class DefaultCoreFactory extends SubFactory implements CoreFactory, Serializable { private static final long serialVersionUID = 1L; - // transient Stack cloningContext = new Stack(); - - Factory mainFactory; - /** * Default constructor. */ public DefaultCoreFactory() { + super(null); } public T clone(T object) { @@ -675,11 +673,11 @@ public CtWhile createWhile() { } public Factory getMainFactory() { - return mainFactory; + return factory; } public void setMainFactory(Factory mainFactory) { - this.mainFactory = mainFactory; + this.factory = mainFactory; } public SourcePosition createSourcePosition(CompilationUnit compilationUnit, int startDeclaration, int startSource, int end, int[] lineSeparatorPositions) { diff --git a/src/main/java/spoon/support/reflect/declaration/CtNamedElementImpl.java b/src/main/java/spoon/support/reflect/declaration/CtNamedElementImpl.java index ebbffca7ca2..74cd49c94dc 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtNamedElementImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtNamedElementImpl.java @@ -25,7 +25,7 @@ public abstract class CtNamedElementImpl extends CtElementImpl implements CtName private static final long serialVersionUID = 1L; - String simpleName; + String simpleName = ""; @Override public CtReference getReference() { diff --git a/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java b/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java index eb93638a914..955ae2ba052 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java @@ -16,15 +16,16 @@ */ package spoon.support.reflect.declaration; +import java.util.Set; +import java.util.TreeSet; + import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.ParentNotInitializedException; import spoon.reflect.reference.CtPackageReference; import spoon.reflect.visitor.CtVisitor; -import java.util.Set; -import java.util.TreeSet; - /** * The implementation for {@link spoon.reflect.declaration.CtPackage}. * @@ -33,7 +34,7 @@ public class CtPackageImpl extends CtNamedElementImpl implements CtPackage { private static final long serialVersionUID = 1L; - private Set packs = new TreeSet(); + protected Set packs = new TreeSet(); private Set> types = new TreeSet>(); @@ -48,11 +49,47 @@ public void accept(CtVisitor v) { @Override public T addPackage(CtPackage pack) { + // they are the same + if (this.getQualifiedName().equals(pack.getQualifiedName())) { + addAllTypes(pack, this); + addAllPackages(pack, this); + return (T) this; + } + + // it already exists + for (CtPackage p1 : packs) { + if (p1.getQualifiedName().equals(pack.getQualifiedName())) { + addAllTypes(pack, p1); + addAllPackages(pack, p1); + return (T) this; + } + } + + this.packs.add(pack); pack.setParent(this); - packs.add(pack); + return (T) this; } + /** add all types of "from" in "to" */ + private void addAllTypes(CtPackage from, CtPackage to) { + for (CtType t : from.getTypes()) { + for (CtType t2: to.getTypes()) { + if (t2.getQualifiedName().equals(t.getQualifiedName()) && !t2.equals(t)) { + throw new IllegalStateException("types with same qualified names and different code cannot be merged"); + } + } + to.addType(t); + } + } + + /** add all packages of "from" in "to" */ + private void addAllPackages(CtPackage from, CtPackage to) { + for (CtPackage p : from.getPackages()) { + to.addPackage(p); + } + } + @Override public boolean removePackage(CtPackage pack) { return packs.remove(pack); @@ -60,7 +97,11 @@ public boolean removePackage(CtPackage pack) { @Override public CtPackage getDeclaringPackage() { - return getParent(CtPackage.class); + try { + return getParent(CtPackage.class); + } catch (ParentNotInitializedException e) { + return null; + } } @Override diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java new file mode 100644 index 00000000000..1fd25e2ff07 --- /dev/null +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java @@ -0,0 +1,40 @@ +package spoon.test.architecture; + +import static org.junit.Assert.fail; + +import org.junit.Test; + +import spoon.Launcher; +import spoon.SpoonAPI; +import spoon.reflect.declaration.CtField; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.ModifierKind; +import spoon.reflect.visitor.filter.AbstractFilter; + +public class SpoonArchitectureEnforcer { + + @Test + public void statelessFactory() throws Exception { + // the factories must be stateless + SpoonAPI spoon = new Launcher(); + spoon.addInputResource("src/main/java/spoon/reflect/factory"); + spoon.buildModel(); + + for (CtType t : spoon.getFactory().Package().getRootPackage().getElements(new AbstractFilter() { + @Override + public boolean matches(CtType element) { + return super.matches(element) + && element.getSimpleName().contains("Factory"); + }; + })) { + for (Object o : t.getFields()) { + CtField f=(CtField)o; + if (f.getSimpleName().equals("factory")) { continue; } + if (f.hasModifier(ModifierKind.FINAL) || f.hasModifier(ModifierKind.TRANSIENT) ) { continue; } + + fail("architectural constraint: a factory must be stateless"); + } + } + + } +} diff --git a/src/test/java/spoon/test/factory/FactoryTest.java b/src/test/java/spoon/test/factory/FactoryTest.java index 8c8bbb9552e..75f3359b312 100644 --- a/src/test/java/spoon/test/factory/FactoryTest.java +++ b/src/test/java/spoon/test/factory/FactoryTest.java @@ -1,29 +1,35 @@ package spoon.test.factory; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static spoon.testing.utils.ModelUtils.build; +import static spoon.testing.utils.ModelUtils.buildClass; + import org.junit.Test; + import spoon.Launcher; -import spoon.reflect.code.CtExpression; +import spoon.SpoonAPI; +import spoon.processing.AbstractProcessor; +import spoon.reflect.CtModel; import spoon.reflect.code.CtFieldRead; import spoon.reflect.code.CtNewArray; import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.CoreFactory; import spoon.reflect.factory.Factory; import spoon.reflect.factory.FactoryImpl; +import spoon.reflect.visitor.filter.AbstractFilter; +import spoon.reflect.visitor.filter.NameFilter; import spoon.support.DefaultCoreFactory; import spoon.support.StandardEnvironment; import spoon.support.reflect.declaration.CtMethodImpl; import spoon.test.factory.testclasses.Foo; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static spoon.testing.utils.ModelUtils.build; -import static spoon.testing.utils.ModelUtils.buildClass; - public class FactoryTest { @Test @@ -89,4 +95,87 @@ public void testClassAccessCreatedFromFactories() throws Exception { assertEquals(1, ((CtNewArray) foo.getAnnotations().get(0).getElementValues().get("classes")).getElements().size()); assertEquals("spoon.test.factory.testclasses.Foo.class", ((CtNewArray) foo.getAnnotations().get(0).getElementValues().get("classes")).getElements().get(0).toString()); } + + @Test + public void testCtModel() throws Exception { + SpoonAPI spoon = new Launcher(); + spoon.addInputResource("src/test/java/spoon/test/factory/testclasses"); + spoon.buildModel(); + + CtModel model = spoon.getModel(); + + // contains Foo and Foo.@Bar + assertEquals(1, model.getAllTypes().size()); + + // [, spoon, spoon.test, spoon.test.factory, spoon.test.factory.testclasses] + assertEquals(5, model.getAllPackages().size()); + + // add to itself is fine + model.getRootPackage().addPackage(model.getRootPackage()); + assertEquals(1, model.getAllTypes().size()); + assertEquals(5, model.getAllPackages().size()); + + model.getRootPackage().getPackage("spoon").addPackage(model.getRootPackage().getPackage("spoon")); + assertEquals(1, model.getAllTypes().size()); + assertEquals(5, model.getAllPackages().size()); + + model.getRootPackage().addPackage(model.getRootPackage().getPackage("spoon")); + assertEquals(1, model.getAllTypes().size()); + assertEquals(5, model.getAllPackages().size()); + + + CtPackage p = spoon.getFactory().Core().clone(model.getRootPackage().getElements(new NameFilter("spoon")).get(0)); + // if we change the implem, merge is impossible + CtField f = spoon.getFactory().Core().createField(); + f.setSimpleName("foo"); + f.setType(spoon.getFactory().Type().BYTE); + p.getElements(new NameFilter("testclasses")).get(0).getType("Foo").addField(f); + try { + model.getRootPackage().addPackage(p); + fail("no exception thrown"); + } catch (IllegalStateException success) {} + } + + @Test + public void testIncrementalModel() throws Exception { + + // Feed some inputResources to a spoon compiler + SpoonAPI spoon = new Launcher(); + spoon.addInputResource("src/test/java/spoon/test/factory/testclasses"); + + // Build model + spoon.buildModel(); + assertEquals(1, spoon.getModel().getAllTypes().size()); + + // Do something with that model.. + CtModel model = spoon.getModel(); + model.processWith(new AbstractProcessor() { + @Override + public void process(CtMethod element) { + element.setDefaultMethod(false); + } + }); + + // Feed some new inputResources + SpoonAPI spoon2 = new Launcher(); + spoon2.addInputResource("src/test/java/spoon/test/factory/testclasses2"); + + // Build models of newly added classes/packages + spoon2.buildModel(); + assertEquals(1, spoon2.getModel().getAllTypes().size()); + + // attach them to the existing model. + model.getRootPackage().addPackage(spoon2.getModel().getRootPackage()); + + // checking the results + assertEquals(6, model.getAllPackages().size()); + assertEquals(2, model.getAllTypes().size()); + assertEquals(1, model.getRootPackage().getElements(new AbstractFilter() { + @Override + public boolean matches(CtPackage element) { + return "spoon.test.factory.testclasses2".equals(element.getQualifiedName()); + } + }).size()); + } + } diff --git a/src/test/java/spoon/test/factory/testclasses2/Baz.java b/src/test/java/spoon/test/factory/testclasses2/Baz.java new file mode 100644 index 00000000000..00f4d3a2312 --- /dev/null +++ b/src/test/java/spoon/test/factory/testclasses2/Baz.java @@ -0,0 +1,4 @@ +package spoon.test.factory.testclasses2; + +public class Baz { +} diff --git a/src/test/java/spoon/test/parent/ParentContractTest.java b/src/test/java/spoon/test/parent/ParentContractTest.java index 13f1968581e..feca3575f9d 100644 --- a/src/test/java/spoon/test/parent/ParentContractTest.java +++ b/src/test/java/spoon/test/parent/ParentContractTest.java @@ -5,6 +5,8 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.mockito.Mockito; + +import spoon.SpoonException; import spoon.reflect.code.CtCatchVariable; import spoon.reflect.code.CtConstructorCall; import spoon.reflect.code.CtInvocation; @@ -20,6 +22,7 @@ import spoon.reflect.reference.CtReference; import spoon.reflect.visitor.CtVisitable; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; @@ -130,7 +133,13 @@ public void testContract() throws Throwable { // we check that setParent has been called verify(mockedArgument).setParent((CtElement) receiver); } catch (AssertionError e) { - Assert.fail("call setParent contract failed for "+setter.toString()+" "+e.toString()); + Assert.fail("call setParent contract failed for "+setter.toString()+" "+e.toString()); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof RuntimeException) { + throw e.getCause(); + } else { + throw new SpoonException(e.getCause()); + } } } }