From 4f401023d73dbaf4065da02c37046a3f2175e367 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 18 Apr 2016 12:39:57 +0200 Subject: [PATCH] introduces CtModel --- src/main/java/spoon/Launcher.java | 8 ++ src/main/java/spoon/SpoonAPI.java | 8 +- src/main/java/spoon/reflect/CtModel.java | 26 ++++++ src/main/java/spoon/reflect/CtModelImpl.java | 93 +++++++++++++++++++ .../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 | 8 +- .../spoon/support/DefaultCoreFactory.java | 12 +-- .../SpoonArchitectureEnforcer.java | 41 ++++++++ .../java/spoon/test/factory/FactoryTest.java | 34 +++++-- 12 files changed, 239 insertions(+), 86 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 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..756f0a3f86a --- /dev/null +++ b/src/main/java/spoon/reflect/CtModel.java @@ -0,0 +1,26 @@ +package spoon.reflect; + +import java.util.Collection; + +import spoon.processing.Processor; +import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtType; + +/** 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 types of the model */ + Collection> getAllTypes(); + + /** returns all packages of the model */ + Collection getAllPackages(); + + /** process this model with the given processor */ + void processWith(Processor abstractProcessor); + +} diff --git a/src/main/java/spoon/reflect/CtModelImpl.java b/src/main/java/spoon/reflect/CtModelImpl.java new file mode 100644 index 00000000000..6c50c228dd0 --- /dev/null +++ b/src/main/java/spoon/reflect/CtModelImpl.java @@ -0,0 +1,93 @@ +package spoon.reflect; + +import java.util.Collection; +import java.util.Collections; + +import spoon.processing.ProcessingManager; +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.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 +// T addPackage(CtPackage pack) { +// packs.add(pack); +// return (T)this; +// } + + @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() { + return Collections.unmodifiableCollection(rootPackage.getElements(new TypeFilter>(CtType.class))); + } + + + @Override + public Collection getAllPackages() { + return Collections.unmodifiableCollection(rootPackage.getElements(new TypeFilter<>(CtPackage.class))); + } + + + @Override + public void processWith(Processor abstractProcessor) { + // processing (consume all the processors) + ProcessingManager processing = new QueueProcessingManager(rootPackage.getFactory()); + processing.process(getRootPackage()); + } + + +} 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..f1a6fda3900 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 getSubPackageList(factory.getModel().getRootPackage()); } /** * 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 acd32962bc4..4d9fb6f8aca 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; } /** 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/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java new file mode 100644 index 00000000000..191df942c46 --- /dev/null +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java @@ -0,0 +1,41 @@ +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.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..0bce360c4a2 100644 --- a/src/test/java/spoon/test/factory/FactoryTest.java +++ b/src/test/java/spoon/test/factory/FactoryTest.java @@ -1,8 +1,17 @@ package spoon.test.factory; +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; + 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; @@ -16,14 +25,6 @@ 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 +90,19 @@ 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(2, model.getAllTypes().size()); + + // [, spoon, spoon.test, spoon.test.factory, spoon.test.factory.testclasses] + assertEquals(5, model.getAllPackages().size()); + } }