From 9932cdec4623d48e85461c08f87767fa07639726 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 15 Oct 2016 10:58:16 +0200 Subject: [PATCH] refactor(classloader): reduces to the maximum the use of classloaders should simplify debug when Maven classloaders, JDT classloaders, IDE classloaders interplay. --- src/main/java/spoon/SpoonException.java | 2 +- src/main/java/spoon/SpoonTask.java | 10 +- src/main/java/spoon/compiler/Environment.java | 13 ++- .../compiler/builder/ClasspathOptions.java | 21 ---- .../spoon/support/StandardEnvironment.java | 78 ++++++------- .../compiler/jdt/CompilerClassLoader.java | 26 ----- .../compiler/jdt/JDTBasedSpoonCompiler.java | 55 +--------- .../compiler/jdt/JDTSnippetCompiler.java | 9 +- .../reference/CtTypeReferenceImpl.java | 48 ++++---- .../SpoonClassNotFoundException.java | 6 +- .../test/compilation/CompilationTest.java | 103 ++++++++++++++---- src/test/resources/reference-test/Foo.java | 4 + 12 files changed, 180 insertions(+), 195 deletions(-) delete mode 100644 src/main/java/spoon/support/compiler/jdt/CompilerClassLoader.java create mode 100644 src/test/resources/reference-test/Foo.java diff --git a/src/main/java/spoon/SpoonException.java b/src/main/java/spoon/SpoonException.java index 5089cf25fab..d4ee81ba30e 100644 --- a/src/main/java/spoon/SpoonException.java +++ b/src/main/java/spoon/SpoonException.java @@ -28,7 +28,7 @@ public SpoonException(String msg) { public SpoonException(Throwable e) { super(e); } - public SpoonException(String msg, Exception e) { + public SpoonException(String msg, Throwable e) { super(msg, e); } } diff --git a/src/main/java/spoon/SpoonTask.java b/src/main/java/spoon/SpoonTask.java index 4053bc71b19..f22049c8c5b 100644 --- a/src/main/java/spoon/SpoonTask.java +++ b/src/main/java/spoon/SpoonTask.java @@ -16,17 +16,17 @@ */ package spoon; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Vector; - import org.apache.tools.ant.BuildException; import org.apache.tools.ant.taskdefs.Java; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + /** * This class implements an Ant task for Spoon that encapsulates * {@link spoon.Launcher}. diff --git a/src/main/java/spoon/compiler/Environment.java b/src/main/java/spoon/compiler/Environment.java index 845d895761c..aeda0594bb8 100644 --- a/src/main/java/spoon/compiler/Environment.java +++ b/src/main/java/spoon/compiler/Environment.java @@ -1,4 +1,4 @@ -/** +/** * Copyright (C) 2006-2016 INRIA and contributors * Spoon - http://spoon.gforge.inria.fr/ * @@ -210,12 +210,15 @@ void report(Processor processor, Level level, int getWarningCount(); /** - * Gets the class loader used to compile/process the input source code. + * Returns the {@code ClassLoader} which is used by JDT and to resolve classes from references. + * + * By default, returns a class loader able to load classes from the + * Spoon-specific class path set with {@link #setSourceClasspath(String[])} */ ClassLoader getInputClassLoader(); /** - * Sets the class loader used to compile/process the input source code. + * Sets a specific classloader for JDT and reference resolution */ void setInputClassLoader(ClassLoader classLoader); @@ -246,9 +249,9 @@ void report(Processor processor, Level level, void setSourceClasspath(String[] sourceClasspath); /** - * Returns a {@code ClassLoader} which is able to load classes from the - * class path returned by {@link #getSourceClasspath()} + * Use {@link #getInputClassLoader()} */ + @Deprecated ClassLoader getClassLoader(); /** diff --git a/src/main/java/spoon/compiler/builder/ClasspathOptions.java b/src/main/java/spoon/compiler/builder/ClasspathOptions.java index 0df03ffce2b..31e8fcf36b0 100644 --- a/src/main/java/spoon/compiler/builder/ClasspathOptions.java +++ b/src/main/java/spoon/compiler/builder/ClasspathOptions.java @@ -17,8 +17,6 @@ package spoon.compiler.builder; import java.io.File; -import java.net.URL; -import java.net.URLClassLoader; public class ClasspathOptions> extends Options { public ClasspathOptions() { @@ -41,25 +39,6 @@ public T classpath(String... classpaths) { return classpath(join(File.pathSeparator, classpaths)); } - public T classpathFromListOrClassLoader(String... classpaths) { - return (classpaths != null && classpaths.length > 0) ? classpath(classpaths) : classpathFromCurrentClassLoader(); - } - - public T classpathFromCurrentClassLoader() { - ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); - if (currentClassLoader instanceof URLClassLoader) { - final URL[] urls = ((URLClassLoader) currentClassLoader).getURLs(); - if (urls != null && urls.length > 0) { - String classpath = "."; - for (URL url : urls) { - classpath += File.pathSeparator + url.getFile(); - } - classpath(classpath); - } - } - return myself; - } - public T bootclasspath(String bootclasspath) { if (bootclasspath == null) { return myself; diff --git a/src/main/java/spoon/support/StandardEnvironment.java b/src/main/java/spoon/support/StandardEnvironment.java index 43df2742b32..65d02627788 100644 --- a/src/main/java/spoon/support/StandardEnvironment.java +++ b/src/main/java/spoon/support/StandardEnvironment.java @@ -16,23 +16,9 @@ */ package spoon.support; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.xml.sax.SAXException; - import spoon.Launcher; import spoon.SpoonException; import spoon.compiler.Environment; @@ -53,6 +39,20 @@ import spoon.support.compiler.FileSystemFolder; import spoon.support.processing.XmlProcessorProperties; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + /** * This class implements a simple Spoon environment that reports messages in the * standard output stream (Java-compliant). @@ -84,8 +84,6 @@ public class StandardEnvironment implements Serializable, Environment { private String[] sourceClasspath = null; - private URLClassLoader classLoader = null; - private boolean preserveLineNumbers = false; private boolean copyResources = true; @@ -383,12 +381,35 @@ public void setTabulationSize(int tabulationSize) { this.tabulationSize = tabulationSize; } + private ClassLoader classloader; + @Override - public ClassLoader getClassLoader() { - if (classLoader == null) { - classLoader = new URLClassLoader(urlClasspath(), Thread.currentThread().getContextClassLoader()); + public void setInputClassLoader(ClassLoader aClassLoader) { + if (aClassLoader instanceof URLClassLoader) { + final URL[] urls = ((URLClassLoader) aClassLoader).getURLs(); + if (urls != null && urls.length > 0) { + List classpath = new ArrayList<>(); + for (URL url : urls) { + classpath.add(url.toString()); + } + setSourceClasspath(classpath.toArray(new String[0])); + } + return; } - return classLoader; + this.classloader = aClassLoader; + } + + @Override + public ClassLoader getInputClassLoader() { + if (classloader != null) { + return classloader; + } + return new URLClassLoader(urlClasspath(), Thread.currentThread().getContextClassLoader()); + } + + @Override + public ClassLoader getClassLoader() { + return getInputClassLoader(); } /** @@ -417,7 +438,6 @@ public String[] getSourceClasspath() { public void setSourceClasspath(String[] sourceClasspath) { verifySourceClasspath(sourceClasspath); this.sourceClasspath = sourceClasspath; - this.classLoader = null; } private void verifySourceClasspath(String[] sourceClasspath) throws InvalidClassPathException { @@ -449,22 +469,6 @@ public int getWarningCount() { return warningCount; } - private ClassLoader inputClassLoader; - - @Override - public ClassLoader getInputClassLoader() { - if (inputClassLoader == null) { - return Thread.currentThread().getContextClassLoader(); - } else { - return this.inputClassLoader; - } - } - - @Override - public void setInputClassLoader(ClassLoader classLoader) { - this.inputClassLoader = classLoader; - } - @Override public boolean isPreserveLineNumbers() { return preserveLineNumbers; diff --git a/src/main/java/spoon/support/compiler/jdt/CompilerClassLoader.java b/src/main/java/spoon/support/compiler/jdt/CompilerClassLoader.java deleted file mode 100644 index 484b5c721df..00000000000 --- a/src/main/java/spoon/support/compiler/jdt/CompilerClassLoader.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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; - -import java.net.URL; -import java.net.URLClassLoader; - -public class CompilerClassLoader extends URLClassLoader { - public CompilerClassLoader(URL[] urls, ClassLoader parent) { - super(urls, parent); - } -} diff --git a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java index 71dac31af2e..cb070f6e09d 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java @@ -46,9 +46,9 @@ import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; +import spoon.reflect.visitor.AstParentConsistencyChecker; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.Filter; -import spoon.reflect.visitor.AstParentConsistencyChecker; import spoon.reflect.visitor.PrettyPrinter; import spoon.reflect.visitor.Query; import spoon.support.QueueProcessingManager; @@ -59,7 +59,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -134,7 +133,6 @@ private void checkModel() { @Override public boolean compile(InputType... types) { - initInputClassLoader(); factory.getEnvironment().debugMessage("compiling sources: " + factory.CompilationUnit().getMap().keySet()); long t = System.currentTimeMillis(); javaCompliance = factory.getEnvironment().getComplianceLevel(); @@ -143,7 +141,7 @@ public boolean compile(InputType... types) { final String[] args = new JDTBuilderImpl() // - .classpathOptions(new ClasspathOptions().encoding(this.encoding).classpathFromListOrClassLoader(getSourceClasspath()).binaries(getBinaryOutputDirectory())) // + .classpathOptions(new ClasspathOptions().encoding(this.encoding).classpath(getSourceClasspath()).binaries(getBinaryOutputDirectory())) // .complianceOptions(new ComplianceOptions().compliance(javaCompliance)) // .annotationProcessingOptions(new AnnotationProcessingOptions().compileProcessors()) // .advancedOptions(new AdvancedOptions().preserveUnusedVars().continueExecution().enableJavadoc()) // @@ -161,8 +159,6 @@ public boolean compile(InputType... types) { @Override public void instantiateAndProcess(List processors) { - initInputClassLoader(); - // processing (consume all the processors) ProcessingManager processing = new QueueProcessingManager(factory); for (String processorName : processors) { @@ -175,8 +171,6 @@ public void instantiateAndProcess(List processors) { @Override public void process(Collection> processors) { - initInputClassLoader(); - // processing (consume all the processors) ProcessingManager processing = new QueueProcessingManager(factory); for (Processor processorName : processors) { @@ -194,7 +188,6 @@ public void generateProcessedSourceFiles(OutputType outputType) { @Override public void generateProcessedSourceFiles(OutputType outputType, Filter> typeFilter) { - initInputClassLoader(); switch (outputType) { case CLASSES: generateProcessedSourceFilesUsingTypes(typeFilter); @@ -351,12 +344,11 @@ protected boolean buildSources(JDTBuilder jdtBuilder) { if (sources.getAllJavaFiles().isEmpty()) { return true; } - initInputClassLoader(); JDTBatchCompiler batchCompiler = createBatchCompiler(InputType.FILES); String[] args; if (jdtBuilder == null) { args = new JDTBuilderImpl() // - .classpathOptions(new ClasspathOptions().encoding(this.encoding).classpathFromListOrClassLoader(getSourceClasspath())) // + .classpathOptions(new ClasspathOptions().encoding(this.encoding).classpath(getSourceClasspath())) // .complianceOptions(new ComplianceOptions().compliance(javaCompliance)) // .advancedOptions(new AdvancedOptions().preserveUnusedVars().continueExecution().enableJavadoc()) // .sources(new SourceOptions().sources(sources.getAllJavaFiles())) // @@ -653,47 +645,6 @@ protected InputStream getCompilationUnitInputStream(String path) { return new ByteArrayInputStream(printer.getResult().toString().getBytes()); } - private CompilerClassLoader getCompilerClassLoader(ClassLoader initialClassLoader) { - while (initialClassLoader != null) { - if (initialClassLoader instanceof CompilerClassLoader) { - return (CompilerClassLoader) initialClassLoader; - } - initialClassLoader = initialClassLoader.getParent(); - } - return null; - } - - private boolean hasClassLoader(ClassLoader initialClassLoader, ClassLoader classLoader) { - while (initialClassLoader != null) { - if (initialClassLoader == classLoader) { - return true; - } - initialClassLoader = initialClassLoader.getParent(); - } - return false; - } - - protected void initInputClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (buildOnlyOutdatedFiles && getBinaryOutputDirectory() != null) { - CompilerClassLoader ccl = getCompilerClassLoader(cl); - if (ccl == null) { - try { - Launcher.LOGGER.debug("setting classloader for " + getBinaryOutputDirectory().toURI().toURL()); - Thread.currentThread().setContextClassLoader(new CompilerClassLoader(new URL[] { - getBinaryOutputDirectory().toURI().toURL() - }, factory.getEnvironment().getInputClassLoader())); - } catch (Exception e) { - Launcher.LOGGER.error(e.getMessage(), e); - } - } - } else { - if (!hasClassLoader(Thread.currentThread().getContextClassLoader(), factory.getEnvironment().getInputClassLoader())) { - Thread.currentThread().setContextClassLoader(factory.getEnvironment().getInputClassLoader()); - } - } - } - protected Environment getEnvironment() { return getFactory().getEnvironment(); } diff --git a/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java b/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java index 8e96c3c34af..df3b154c742 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTSnippetCompiler.java @@ -16,12 +16,8 @@ */ package spoon.support.compiler.jdt; -import java.io.File; -import java.util.Arrays; - import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; - import spoon.SpoonException; import spoon.compiler.Environment; import spoon.compiler.builder.AdvancedOptions; @@ -34,6 +30,9 @@ import spoon.support.compiler.SnippetCompilationError; import spoon.support.compiler.VirtualFile; +import java.io.File; +import java.util.Arrays; + public class JDTSnippetCompiler extends JDTBasedSpoonCompiler { public JDTSnippetCompiler(Factory factory, String contents) { @@ -74,7 +73,7 @@ protected boolean buildSources(JDTBuilder jdtBuilder) { if (jdtBuilder == null) { String[] sourceClasspath = getSourceClasspath(); args = new JDTBuilderImpl() // - .classpathOptions(new ClasspathOptions().encoding(this.encoding).classpathFromListOrClassLoader(sourceClasspath)) // + .classpathOptions(new ClasspathOptions().encoding(this.encoding).classpath(sourceClasspath)) // .complianceOptions(new ComplianceOptions().compliance(javaCompliance)) // .advancedOptions(new AdvancedOptions().preserveUnusedVars().continueExecution().enableJavadoc()) // .sources(new SourceOptions().sources(source.getPath())) // diff --git a/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java b/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java index 5f5500251fc..eee91dc1e81 100644 --- a/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java +++ b/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java @@ -16,20 +16,6 @@ */ package spoon.support.reflect.reference; -import static spoon.reflect.ModelElementContainerDefaultCapacities.TYPE_TYPE_PARAMETERS_CONTAINER_DEFAULT_CAPACITY; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import spoon.Launcher; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtPackage; @@ -49,6 +35,20 @@ import spoon.support.util.QualifiedNameBasedSortedSet; import spoon.support.util.RtHelper; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static spoon.reflect.ModelElementContainerDefaultCapacities.TYPE_TYPE_PARAMETERS_CONTAINER_DEFAULT_CAPACITY; + public class CtTypeReferenceImpl extends CtReferenceImpl implements CtTypeReference { private static final long serialVersionUID = 1L; @@ -131,16 +131,26 @@ public Class getActualClass() { } /** - * Finds the class requested in {@link #getActualClass()}, using the - * {@code ClassLoader} of the {@code Environment} + * Finds the class requested in {@link #getActualClass()}. + * + * Looks for the class in the standard Java classpath, but also in the sourceClassPath given as option. */ @SuppressWarnings("unchecked") protected Class findClass() { + // first try the simple way + try { + return (Class) Class.forName(getQualifiedName()); + } catch (Throwable ignore) { + } + + // then we also look into the sourceclasspth entries given try { + // creating a classloader on the fly is not the most efficient + // but it decreases the amount of state to maintain + // since getActualClass is only used in rare cases, that's OK. return (Class) getFactory().getEnvironment().getClassLoader().loadClass(getQualifiedName()); - } catch (java.lang.ClassNotFoundException cnfe) { - throw new SpoonClassNotFoundException("cannot load class: " + getQualifiedName() + " with class loader " - + Thread.currentThread().getContextClassLoader(), cnfe); + } catch (Throwable e) { + throw new SpoonClassNotFoundException("cannot load class: " + getQualifiedName(), e); } } diff --git a/src/main/java/spoon/support/reflect/reference/SpoonClassNotFoundException.java b/src/main/java/spoon/support/reflect/reference/SpoonClassNotFoundException.java index 9c7f2d6377a..19bf58cbf7b 100644 --- a/src/main/java/spoon/support/reflect/reference/SpoonClassNotFoundException.java +++ b/src/main/java/spoon/support/reflect/reference/SpoonClassNotFoundException.java @@ -18,9 +18,11 @@ import spoon.SpoonException; -/** Spoon-specific ClassNotFoundException (simply encapsulates a ClassNotFoundException as a runtime exception) */ +/** Spoon-specific ClassNotFoundException (mostly encapsulates a ClassNotFoundException or a NoClassDefFoundError + * as a runtime exception) + */ public class SpoonClassNotFoundException extends SpoonException { - public SpoonClassNotFoundException(String msg, java.lang.ClassNotFoundException cnfe) { + public SpoonClassNotFoundException(String msg, Throwable cnfe) { super(msg, cnfe); } diff --git a/src/test/java/spoon/test/compilation/CompilationTest.java b/src/test/java/spoon/test/compilation/CompilationTest.java index bb4b9993a05..9f8f2fcd0b4 100644 --- a/src/test/java/spoon/test/compilation/CompilationTest.java +++ b/src/test/java/spoon/test/compilation/CompilationTest.java @@ -1,19 +1,8 @@ package spoon.test.compilation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; - import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; import org.junit.Assert; import org.junit.Test; - import spoon.Launcher; import spoon.compiler.SpoonCompiler; import spoon.reflect.code.BinaryOperatorKind; @@ -27,9 +16,23 @@ import spoon.reflect.factory.CodeFactory; import spoon.reflect.factory.CoreFactory; import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.CtScanner; import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.compiler.jdt.FileCompiler; import spoon.support.compiler.jdt.JDTBasedSpoonCompiler; +import spoon.support.reflect.reference.SpoonClassNotFoundException; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class CompilationTest { @@ -45,7 +48,7 @@ public void compileCommandLineTest() throws Exception { Launcher launcher = new Launcher(); - launcher.run(new String[] { + launcher.run(new String[]{ "-i", sourceFile, "-o", "target/spooned", "--compile", @@ -86,9 +89,9 @@ public void compileTest() throws Exception { CtReturn aReturn = core.createReturn(); CtBinaryOperator binaryOperator = code.createBinaryOperator( - code.createLiteral(10), - code.createLiteral(32), - BinaryOperatorKind.PLUS); + code.createLiteral(10), + code.createLiteral(32), + BinaryOperatorKind.PLUS); aReturn.setReturnedExpression(binaryOperator); // return 10 + 32; @@ -99,7 +102,7 @@ public void compileTest() throws Exception { launcher.getModelBuilder().compile(); - final URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { outputBinDirectory.toURL() }); + final URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{outputBinDirectory.toURL()}); Class aClass = urlClassLoader.loadClass("Simple"); Method m = aClass.getMethod("m"); @@ -111,14 +114,14 @@ public void testNewInstance() throws Exception { // contract: a ctclass can be instantiated, and each modification results in a new valid object Factory factory = new Launcher().getFactory(); CtClass c = factory.Code().createCodeSnippetStatement( - "class X implements spoon.test.compilation.Ifoo { public int foo() {int i=0; return i;} }").compile(); + "class X implements spoon.test.compilation.Ifoo { public int foo() {int i=0; return i;} }").compile(); c.addModifier(ModifierKind.PUBLIC); // required otherwise java.lang.IllegalAccessException at runtime when instantiating CtBlock body = c.getElements(new TypeFilter<>(CtBlock.class)).get(1); Ifoo o = c.newInstance(); assertEquals(0, o.foo()); - for (int i=1; i<=10; i++) { - body.getStatement(0).replace(factory.Code().createCodeSnippetStatement("int i = "+i+";")); + for (int i = 1; i <= 10; i++) { + body.getStatement(0).replace(factory.Code().createCodeSnippetStatement("int i = " + i + ";")); o = c.newInstance(); // each time this is a new class // each time the behavior has changed! @@ -157,7 +160,7 @@ public CompilationUnit[] getCompilationUnits() { launcher.buildModel(); // we indeed only have types declared in a file called *Foo* - for(CtType t : launcher.getFactory().getModel().getAllTypes()) { + for (CtType t : launcher.getFactory().getModel().getAllTypes()) { assertTrue(t.getPosition().getFile().getAbsolutePath().contains("Foo")); } @@ -195,10 +198,66 @@ public CompilationUnit[] getCompilationUnits() { launcher.buildModel(); // we indeed only have types declared in a file in package reference - for(CtType t : launcher.getFactory().getModel().getAllTypes()) { + for (CtType t : launcher.getFactory().getModel().getAllTypes()) { assertTrue(t.getQualifiedName().contains("reference")); } } -} + @Test + public void testClassLoader() throws Exception { + // contract: the environment exposes a classloader configured by the spoonclass path + Launcher launcher = new Launcher(); + + // not in the classpath + try { + Class.forName("spoontest.a.ClassA"); + fail(); + } catch (ClassNotFoundException expected) { + } + + // not in the spoon classpath before setting it + try { + launcher.getEnvironment().getInputClassLoader().loadClass("spoontest.a.ClassA"); + fail(); + } catch (ClassNotFoundException expected) { + } + + launcher.getEnvironment().setSourceClasspath(new String[]{"src/test/resources/reference-test-2/ReferenceTest2.jar"}); + + Class c = launcher.getEnvironment().getInputClassLoader().loadClass("spoontest.a.ClassA"); + assertEquals("spoontest.a.ClassA", c.getName()); + } + + @Test + public void testExoticClassLoader() throws Exception { + // contract: Spoon uses the exotic class loader + + final List l = new ArrayList<>(); + class MyClassLoader extends ClassLoader { + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + l.add(name); + return super.loadClass(name, resolve); + } + } + + Launcher launcher = new Launcher(); + launcher.getEnvironment().setInputClassLoader(new MyClassLoader()); + launcher.getEnvironment().setNoClasspath(true); + launcher.addInputResource("src/test/resources/reference-test/Foo.java"); + launcher.buildModel(); + launcher.getModel().getRootPackage().accept(new CtScanner() { + @Override + public void visitCtTypeReference(CtTypeReference reference) { + try { + // forcing loading it + reference.getTypeDeclaration(); + } catch (SpoonClassNotFoundException ignore) {} + } + }); + + assertEquals(1, l.size()); + assertEquals("KJHKY", l.get(0)); + } +} \ No newline at end of file diff --git a/src/test/resources/reference-test/Foo.java b/src/test/resources/reference-test/Foo.java new file mode 100644 index 00000000000..25ac90078c6 --- /dev/null +++ b/src/test/resources/reference-test/Foo.java @@ -0,0 +1,4 @@ +public class Foo { + KJHKY l; +} +