diff --git a/src/main/java/spoon/SpoonModelBuilder.java b/src/main/java/spoon/SpoonModelBuilder.java index 7228e263ca0..4b8509e15fa 100644 --- a/src/main/java/spoon/SpoonModelBuilder.java +++ b/src/main/java/spoon/SpoonModelBuilder.java @@ -23,6 +23,7 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.visitor.Filter; +import spoon.support.compiler.jdt.CompilationUnitFilter; import spoon.support.compiler.jdt.FactoryCompilerConfig; import spoon.support.compiler.jdt.FileCompilerConfig; import spoon.support.compiler.jdt.JDTBatchCompiler; @@ -303,4 +304,29 @@ interface InputType { * Returns the working factory */ Factory getFactory(); + + /** + * Adds {@code filter}. + * + * @param filter + * The {@link CompilationUnitFilter} to add. + */ + void addCompilationUnitFilter(final CompilationUnitFilter filter); + + /** + * Removes {@code filter}. Does nothing, if {@code filter} has not been + * added beforehand. + * + * @param filter + * The {@link CompilationUnitFilter} to remove. + */ + void removeCompilationUnitFilter(final CompilationUnitFilter filter); + + /** + * Returns a copy of the internal list of {@link CompilationUnitFilter}s. + * + * @return + * A copy of the internal list of {@link CompilationUnitFilter}s. + */ + List getCompilationUnitFilter(); } diff --git a/src/main/java/spoon/support/compiler/jdt/CompilationUnitFilter.java b/src/main/java/spoon/support/compiler/jdt/CompilationUnitFilter.java new file mode 100644 index 00000000000..809473fca1b --- /dev/null +++ b/src/main/java/spoon/support/compiler/jdt/CompilationUnitFilter.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2006-2016 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.support.compiler.jdt; + +/** + * This interface is used by instances of {@link spoon.SpoonModelBuilder} to + * exclude particular {@link spoon.reflect.cu.CompilationUnit}s while + * generating a {@link spoon.reflect.CtModel} with + * {@link spoon.SpoonModelBuilder#build(spoon.compiler.builder.JDTBuilder)}. + * + * This interface is useful for large sized software system where traversing + * all files takes several minutes. Unlike the approach of adding a subset of + * the files to examine, filtering unwanted files produces a more precise + * {@link spoon.reflect.CtModel} since all files will be compiled (but not + * transformed). + */ +public interface CompilationUnitFilter { + + /** + * Tests if the file with path {@code path} should be excluded from the + * {@link spoon.reflect.CtModel} create by + * {@link spoon.SpoonModelBuilder#build(spoon.compiler.builder.JDTBuilder)}. + * + * @param path + * Path to the file that may or may not be excluded. + * @return {@code true} if and only if {@code path} should be excluded, + * {@code false} otherwise. + */ + boolean exclude(final String path); +} diff --git a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java index ffc8e8af817..8b0c3f84441 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java @@ -88,6 +88,7 @@ public class JDTBasedSpoonCompiler implements SpoonCompiler { protected File outputDirectory = new File(Launcher.OUTPUTDIR); protected List forceBuildList = new ArrayList<>(); protected String encoding; + protected List compilationUnitFilters = new ArrayList<>(); /** * Default constructor @@ -413,7 +414,15 @@ protected CompilationUnitDeclaration[] buildUnits(JDTBuilder jdtBuilder, SpoonFo protected void buildModel(CompilationUnitDeclaration[] units) { JDTTreeBuilder builder = new JDTTreeBuilder(factory); + unitLoop: for (CompilationUnitDeclaration unit : units) { + final String unitPath = new String(unit.getFileName()); + for (final CompilationUnitFilter cuf : compilationUnitFilters) { + if (cuf.exclude(unitPath)) { + // do not traverse this unit + continue unitLoop; + } + } unit.traverse(builder, unit.scope); if (getFactory().getEnvironment().isCommentsEnabled()) { new JDTCommentBuilder(unit, factory).build(); @@ -615,4 +624,19 @@ protected Environment getEnvironment() { public boolean compileInputSources() { return compile(InputType.FILES); } + + @Override + public void addCompilationUnitFilter(final CompilationUnitFilter filter) { + compilationUnitFilters.add(filter); + } + + @Override + public void removeCompilationUnitFilter(CompilationUnitFilter filter) { + compilationUnitFilters.remove(filter); + } + + @Override + public List getCompilationUnitFilter() { + return new ArrayList<>(compilationUnitFilters); + } } diff --git a/src/test/java/spoon/test/filters/CUFilterTest.java b/src/test/java/spoon/test/filters/CUFilterTest.java new file mode 100644 index 00000000000..ca467552aff --- /dev/null +++ b/src/test/java/spoon/test/filters/CUFilterTest.java @@ -0,0 +1,70 @@ +package spoon.test.filters; + +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.junit.Test; +import spoon.Launcher; +import spoon.reflect.CtModel; +import spoon.reflect.code.CtConstructorCall; +import spoon.reflect.code.CtReturn; +import spoon.support.compiler.jdt.CompilationUnitFilter; + +import static org.junit.Assert.assertEquals; + +public class CUFilterTest { + + @Test + public void testWithoutFilters() { + final Launcher launcher = new Launcher(); + launcher.addInputResource("./src/test/resources/noclasspath/same-package"); + launcher.buildModel(); + final CtModel model = launcher.getModel(); + assertEquals(2, model.getAllTypes().size()); + assertEquals("spoon.test.same.B", model.getAllTypes().iterator().next() + .getMethod("createB").getType().getQualifiedName()); + } + + @Test + public void testSingleExcludeWithFilter() { + final Launcher launcher = new Launcher(); + launcher.getEnvironment().setNoClasspath(true); + launcher.addInputResource("./src/test/resources/noclasspath/same-package"); + launcher.getModelBuilder().addCompilationUnitFilter( + new CompilationUnitFilter() { + @Override + public boolean exclude(final String path) { + return path.endsWith("B.java"); + } + }); + launcher.buildModel(); + final CtModel model = launcher.getModel(); + + assertEquals(1, model.getAllTypes().size()); + // make sure `B` is not available in `model.getAllTypes` + assertEquals("A", model.getAllTypes().iterator().next().getSimpleName()); + // make sure declaration of `B` is known in `model` + final CtReturn ctReturn = model.getAllTypes().iterator().next() + .getMethod("createB").getBody().getStatement(0); + final CtConstructorCall ctConstructorCall = + (CtConstructorCall)ctReturn.getReturnedExpression(); + assertEquals("spoon.test.same.B", ctConstructorCall.getType().getQualifiedName()); + } + + @Test + public void testSingleExcludeWithoutFilter() { + final Launcher launcher = new Launcher(); + launcher.getEnvironment().setNoClasspath(true); + launcher.addInputResource("./src/test/resources/noclasspath/same-package/A.java"); + launcher.buildModel(); + final CtModel model = launcher.getModel(); + + assertEquals(1, model.getAllTypes().size()); + // make sure `B` is not available in `model.getAllTypes` + assertEquals("A", model.getAllTypes().iterator().next().getSimpleName()); + // make sure declaration of `B` is unknown in `model` + final CtReturn ctReturn = model.getAllTypes().iterator().next() + .getMethod("createB").getBody().getStatement(0); + final CtConstructorCall ctConstructorCall = + (CtConstructorCall)ctReturn.getReturnedExpression(); + assertEquals("B", ctConstructorCall.getType().getQualifiedName()); + } +} diff --git a/src/test/resources/noclasspath/same-package/A.java b/src/test/resources/noclasspath/same-package/A.java new file mode 100644 index 00000000000..3e9f4640394 --- /dev/null +++ b/src/test/resources/noclasspath/same-package/A.java @@ -0,0 +1,8 @@ +package spoon.test.same; + +public class A { + + public B createB() { + return new B(); + } +} diff --git a/src/test/resources/noclasspath/same-package/B.java b/src/test/resources/noclasspath/same-package/B.java new file mode 100644 index 00000000000..c2bded12ea8 --- /dev/null +++ b/src/test/resources/noclasspath/same-package/B.java @@ -0,0 +1,8 @@ +package spoon.test.same; + +public class B { + + public String getName() { + return "This is B."; + } +}