├── .gitattributes ├── .travis.yml ├── src ├── test │ ├── projects │ │ ├── antlr-example │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── org │ │ │ │ │ └── antlrfun │ │ │ │ │ ├── Hello.g4 │ │ │ │ │ └── HelloWalker.java │ │ │ ├── README.md │ │ │ └── pom.xml │ │ ├── processor │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── hello-world │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── custom-configuration │ │ │ ├── src │ │ │ │ ├── internal │ │ │ │ │ └── fr │ │ │ │ │ │ └── inria │ │ │ │ │ │ └── gforge │ │ │ │ │ │ └── spoon │ │ │ │ │ │ └── App.java │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── hello-world-inputs │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ ├── test │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App2.java │ │ │ └── pom.xml │ │ ├── hello-world-exception │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── hello-world-spoon-xml │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ ├── java │ │ │ │ │ └── fr │ │ │ │ │ │ └── inria │ │ │ │ │ │ └── gforge │ │ │ │ │ │ └── spoon │ │ │ │ │ │ └── App.java │ │ │ │ │ └── resources │ │ │ │ │ └── spoon.xml │ │ │ └── pom.xml │ │ ├── hello-world-with-imports │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── multi-module │ │ │ ├── module1 │ │ │ │ ├── src │ │ │ │ │ └── main │ │ │ │ │ │ └── java │ │ │ │ │ │ └── fr │ │ │ │ │ │ └── inria │ │ │ │ │ │ └── gforge │ │ │ │ │ │ └── spoon │ │ │ │ │ │ └── App.java │ │ │ │ └── pom.xml │ │ │ ├── module2 │ │ │ │ ├── src │ │ │ │ │ └── main │ │ │ │ │ │ └── java │ │ │ │ │ │ └── fr │ │ │ │ │ │ └── inria │ │ │ │ │ │ └── gforge │ │ │ │ │ │ └── spoon │ │ │ │ │ │ └── App.java │ │ │ │ └── pom.xml │ │ │ └── pom.xml │ │ ├── hello-world-config-exception │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── hello-world-with-properties │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── hello-world-with-comments-disabled │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── hello-world-config-exception-ignored │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ ├── hello-world-commented │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── fr │ │ │ │ │ └── inria │ │ │ │ │ └── gforge │ │ │ │ │ └── spoon │ │ │ │ │ └── App.java │ │ │ └── pom.xml │ │ └── hello-world-no-sources │ │ │ └── pom.xml │ ├── java │ │ └── fr │ │ │ └── inria │ │ │ └── gforge │ │ │ └── spoon │ │ │ ├── mojo │ │ │ ├── ProcessorWithException.java │ │ │ ├── CountStatementProcessor.java │ │ │ ├── ProcessorWithProperty.java │ │ │ └── SpoonMojoTest.java │ │ │ └── unit │ │ │ ├── XMLLoaderTest.java │ │ │ └── SpoonConfigurationBuilderTest.java │ └── resources │ │ └── fr │ │ └── inria │ │ └── gforge │ │ └── spoon │ │ └── unit │ │ └── spoon.xml └── main │ └── java │ └── fr │ └── inria │ └── gforge │ └── spoon │ ├── logging │ ├── ReportDao.java │ ├── ReportBuilder.java │ ├── ReportFactory.java │ ├── ReportBuilderImpl.java │ └── ReportDaoImpl.java │ ├── configuration │ ├── SpoonMavenPluginException.java │ ├── SimpleSpoonConfiguration.java │ ├── SpoonConfigurationFactory.java │ ├── SpoonConfigurationBuilder.java │ ├── XMLSpoonConfiguration.java │ └── AbstractSpoonConfigurationBuilder.java │ ├── metrics │ ├── SpoonLauncherDecorator.java │ └── PerformanceDecorator.java │ ├── util │ ├── XMLLoader.java │ ├── LogWrapper.java │ ├── TemplateLoader.java │ └── ClasspathHacker.java │ ├── ProcessorProperties.java │ ├── SpoonMojoCheck.java │ ├── SpoonModel.java │ └── SpoonMojoGenerate.java ├── .gitignore ├── LICENSE ├── README.md └── pom.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=crlf 2 | * text eol=lf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | git: 2 | depth: 1 3 | 4 | language: java 5 | 6 | jdk: 7 | - openjdk8 8 | -------------------------------------------------------------------------------- /src/test/projects/antlr-example/src/main/java/org/antlrfun/Hello.g4: -------------------------------------------------------------------------------- 1 | grammar Hello; 2 | r : 'hello' ID; 3 | ID : [a-z]+ ; 4 | WS : [ \t\r\n]+ -> skip ; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | target/ -------------------------------------------------------------------------------- /src/test/projects/processor/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/custom-configuration/src/internal/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-inputs/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/custom-configuration/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-exception/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-inputs/test/main/java/fr/inria/gforge/spoon/App2.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App2 { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-spoon-xml/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-with-imports/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/multi-module/module1/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/multi-module/module2/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/logging/ReportDao.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.logging; 2 | 3 | import java.util.Map; 4 | 5 | interface ReportDao { 6 | 7 | /** 8 | * Saves results. 9 | */ 10 | void save(Map reportsData); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-config-exception/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-with-properties/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-with-comments-disabled/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-config-exception-ignored/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | System.out.println("Hello World!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-commented/src/main/java/fr/inria/gforge/spoon/App.java: -------------------------------------------------------------------------------- 1 | //package fr.inria.gforge.spoon; 2 | // 3 | ///** 4 | // * Hello world! 5 | // */ 6 | //public class App { 7 | // public static void main(String[] args) { 8 | // System.out.println("Hello World!"); 9 | // } 10 | //} 11 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/configuration/SpoonMavenPluginException.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.configuration; 2 | 3 | public class SpoonMavenPluginException extends Exception { 4 | SpoonMavenPluginException(String msg) { 5 | super(msg); 6 | } 7 | 8 | SpoonMavenPluginException(String msg, Throwable throwable) { 9 | super(msg, throwable); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/projects/antlr-example/src/main/java/org/antlrfun/HelloWalker.java: -------------------------------------------------------------------------------- 1 | package org.antlrfun; 2 | 3 | public class HelloWalker extends HelloBaseListener { 4 | public void enterR(HelloParser.RContext ctx) { 5 | System.out.println("Entering R : " + ctx.ID().getText()); 6 | } 7 | 8 | public void exitR(HelloParser.RContext ctx) { 9 | System.out.println("Exiting R"); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/metrics/SpoonLauncherDecorator.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.metrics; 2 | 3 | import org.apache.maven.plugin.MojoExecutionException; 4 | 5 | public interface SpoonLauncherDecorator { 6 | 7 | /** 8 | * Executes the launcher of spoon with some other stuffs specified on 9 | * subclasses of this decorator. 10 | */ 11 | void execute() throws MojoExecutionException; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/projects/antlr-example/README.md: -------------------------------------------------------------------------------- 1 | antlr4-maven-example 2 | ==================== 3 | 4 | ANTLR4 Maven project with Hello World 5 | 6 | "mvn compile exec:java" will compile and run the main method. 7 | 8 | Project is setup according to the standard Maven structure. 9 | 10 | Generated source is in src/main/generated-sources 11 | 12 | This example is inspired by the following Github repository: https://github.com/mauriceoc/antlr4-maven-example 13 | 14 | -------------------------------------------------------------------------------- /src/test/java/fr/inria/gforge/spoon/mojo/ProcessorWithException.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.mojo; 2 | 3 | 4 | import spoon.SpoonException; 5 | import spoon.processing.AbstractProcessor; 6 | import spoon.reflect.declaration.CtElement; 7 | 8 | /** 9 | * Created by urli on 05/10/2017. 10 | */ 11 | public class ProcessorWithException extends AbstractProcessor { 12 | 13 | @Override 14 | public void process(CtElement element) { 15 | throw new SpoonException("Try to throw spoonException"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/projects/multi-module/module1/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | fr.inria.gforge.spoon 7 | multi-module 8 | 1.0-SNAPSHOT 9 | 10 | 11 | module1 12 | 13 | -------------------------------------------------------------------------------- /src/test/projects/multi-module/module2/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | fr.inria.gforge.spoon 7 | multi-module 8 | 1.0-SNAPSHOT 9 | 10 | 11 | module2 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/util/XMLLoader.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.util; 2 | 3 | import fr.inria.gforge.spoon.SpoonModel; 4 | 5 | import javax.xml.bind.JAXBContext; 6 | import javax.xml.bind.Unmarshaller; 7 | import java.io.InputStream; 8 | 9 | public final class XMLLoader { 10 | private XMLLoader() { 11 | } 12 | 13 | public static SpoonModel load(InputStream inputStream) throws Exception { 14 | JAXBContext jaxbContext = JAXBContext.newInstance(SpoonModel.class); 15 | Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); 16 | return (SpoonModel) jaxbUnmarshaller.unmarshal(inputStream); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/projects/hello-world/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-commented/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-spoon-xml/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/ProcessorProperties.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | import java.util.Properties; 4 | 5 | /** 6 | * Created by urli on 21/09/2017. 7 | */ 8 | public class ProcessorProperties { 9 | private String name; 10 | private Properties properties; 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public Properties getProperties() { 21 | return properties; 22 | } 23 | 24 | public void setProperties(Properties properties) { 25 | this.properties = properties; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/fr/inria/gforge/spoon/unit/XMLLoaderTest.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.unit; 2 | 3 | import fr.inria.gforge.spoon.SpoonModel; 4 | import fr.inria.gforge.spoon.util.XMLLoader; 5 | import org.junit.Test; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | /** 10 | * Created at 19/11/2013 10:14.
11 | * 12 | * @author Christophe DUFOUR 13 | */ 14 | public class XMLLoaderTest { 15 | @Test 16 | public void simpleTest() throws Exception { 17 | final SpoonModel model = XMLLoader.load(XMLLoaderTest.class.getResourceAsStream("spoon.xml")); 18 | 19 | assertThat(model.getProcessors().size()).isEqualTo(9); 20 | assertThat(model.getTemplates().size()).isEqualTo(16); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-config-exception/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | ./foo 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-with-imports/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | true 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-no-sources/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | true 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-with-comments-disabled/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | false 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/SpoonMojoCheck.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | import org.apache.maven.plugins.annotations.LifecyclePhase; 4 | import org.apache.maven.plugins.annotations.Mojo; 5 | import org.apache.maven.plugins.annotations.Parameter; 6 | import org.apache.maven.plugins.annotations.ResolutionScope; 7 | 8 | import java.io.File; 9 | 10 | @SuppressWarnings("UnusedDeclaration") 11 | @Mojo( 12 | name = "check", 13 | defaultPhase = LifecyclePhase.VERIFY, 14 | requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) 15 | 16 | public class SpoonMojoCheck extends SpoonMojoGenerate { 17 | @Override 18 | public String getOutputType() { 19 | return "nooutput"; 20 | } 21 | 22 | @Override 23 | public boolean isCompileOriginalSources() { 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-config-exception-ignored/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | true 16 | ./foo 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/projects/processor/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | 16 | fr.inria.gforge.spoon.mojo.CountStatementProcessor 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/test/projects/custom-configuration/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | src/internal/fr/inria/gforge/spoon 16 | target/generate-source-with-spoon 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-exception/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | 16 | fr.inria.gforge.spoon.mojo.ProcessorWithException 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-inputs/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | 16 | src/main/java 17 | test/main/java 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/test/projects/multi-module/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | multi-module 7 | 1.0-SNAPSHOT 8 | pom 9 | 10 | 11 | module1 12 | module2 13 | 14 | 15 | 16 | 17 | fr.inria.gforge.spoon 18 | spoon-maven-plugin 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/configuration/SimpleSpoonConfiguration.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.configuration; 2 | 3 | import fr.inria.gforge.spoon.SpoonMojoGenerate; 4 | import fr.inria.gforge.spoon.logging.ReportBuilder; 5 | 6 | import java.io.File; 7 | 8 | class SimpleSpoonConfiguration extends AbstractSpoonConfigurationBuilder { 9 | 10 | protected SimpleSpoonConfiguration(SpoonMojoGenerate spoon, 11 | ReportBuilder reportBuilder) { 12 | super(spoon, reportBuilder); 13 | } 14 | 15 | @Override 16 | public SpoonConfigurationBuilder addProcessors() { 17 | final String[] processors = spoon.getProcessorsPath(); 18 | if (processors != null && processors.length != 0) { 19 | parameters.add("-p"); 20 | parameters.add(implode(processors, File.pathSeparator)); 21 | reportBuilder.setProcessors(processors); 22 | } 23 | return this; 24 | } 25 | 26 | @Override 27 | public SpoonConfigurationBuilder addTemplates() { 28 | return this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/logging/ReportBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.logging; 2 | 3 | public interface ReportBuilder { 4 | 5 | /** 6 | * Sets project name. 7 | */ 8 | ReportBuilder setProjectName(String name); 9 | 10 | /** 11 | * Sets processors. 12 | */ 13 | ReportBuilder setProcessors(String[] processors); 14 | 15 | /** 16 | * Sets name of the module. 17 | */ 18 | ReportBuilder setModuleName(String name); 19 | 20 | /** 21 | * Sets input directory. 22 | */ 23 | ReportBuilder setInput(String input); 24 | 25 | /** 26 | * Sets output directory. 27 | */ 28 | ReportBuilder setOutput(String output); 29 | 30 | /** 31 | * Sets source classpath. 32 | */ 33 | ReportBuilder setSourceClasspath(String sourceClasspath); 34 | 35 | /** 36 | * Sets performance metric. 37 | */ 38 | ReportBuilder setPerformance(long performance); 39 | 40 | /** 41 | * Builds the report in a XML file. 42 | */ 43 | void buildReport(); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/logging/ReportFactory.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.logging; 2 | 3 | import fr.inria.gforge.spoon.SpoonMojoGenerate; 4 | import fr.inria.gforge.spoon.util.LogWrapper; 5 | 6 | import java.io.File; 7 | import java.util.Calendar; 8 | 9 | public final class ReportFactory { 10 | 11 | private ReportFactory() { 12 | } 13 | 14 | /** 15 | * Get a {@link fr.inria.gforge.spoon.logging.ReportBuilder} to build a report. 16 | */ 17 | public static ReportBuilder newReportBuilder(SpoonMojoGenerate spoon) { 18 | final long timestamp = Calendar.getInstance().getTimeInMillis(); 19 | final String resultFilename = spoon.getProject().getBuild().getDirectory() 20 | + File.separator + "spoon-maven-plugin" 21 | + File.separator + "result-spoon-" + timestamp + ".xml"; 22 | final File resultFile = new File(resultFilename); 23 | LogWrapper.info(spoon, String.format("Spoon report directory: %s", resultFile.getParentFile().getAbsolutePath())); 24 | return new ReportBuilderImpl(resultFile); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/util/LogWrapper.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.util; 2 | 3 | import fr.inria.gforge.spoon.SpoonMojoGenerate; 4 | 5 | public final class LogWrapper { 6 | 7 | private LogWrapper() { 8 | } 9 | 10 | public static void error(SpoonMojoGenerate mojo, String message, Throwable exception) { 11 | if (mojo.isDebug()) { 12 | mojo.getLog().error(message, exception); 13 | } 14 | } 15 | 16 | public static void warn(SpoonMojoGenerate mojo, String message) { 17 | if (mojo.isDebug()) { 18 | mojo.getLog().warn(message); 19 | } 20 | } 21 | 22 | public static void warn(SpoonMojoGenerate mojo, String message, Throwable exception) { 23 | if (mojo.isDebug()) { 24 | mojo.getLog().warn(message, exception); 25 | } 26 | } 27 | 28 | public static void info(SpoonMojoGenerate mojo, String message) { 29 | if (mojo.isDebug()) { 30 | mojo.getLog().info(message); 31 | } 32 | } 33 | 34 | public static void debug(SpoonMojoGenerate mojo, String message) { 35 | if (mojo.isDebug()) { 36 | mojo.getLog().debug(message); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/metrics/PerformanceDecorator.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.metrics; 2 | 3 | import fr.inria.gforge.spoon.logging.ReportBuilder; 4 | import org.apache.maven.plugin.MojoExecutionException; 5 | import spoon.Launcher; 6 | 7 | public class PerformanceDecorator implements SpoonLauncherDecorator { 8 | 9 | private final Launcher launcher; 10 | private final ReportBuilder reportBuilder; 11 | 12 | public PerformanceDecorator(ReportBuilder reportBuilder, Launcher launcher) { 13 | this.reportBuilder = reportBuilder; 14 | this.launcher = launcher; 15 | } 16 | 17 | @Override 18 | public void execute() throws MojoExecutionException { 19 | // Computes performance. 20 | long startTime = System.currentTimeMillis(); 21 | try { 22 | launcher.run(); 23 | } catch (Exception e) { 24 | throw new MojoExecutionException( 25 | "Exception during the spoonify of the target project.", e); 26 | } 27 | long endTime = System.currentTimeMillis(); 28 | long time = endTime - startTime; 29 | 30 | // Saves performance. 31 | reportBuilder.setPerformance(time); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/util/TemplateLoader.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.util; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | 10 | public final class TemplateLoader { 11 | private static File tmpFolder; 12 | 13 | private TemplateLoader() { 14 | } 15 | 16 | private static File getTmpFolder() { 17 | if (tmpFolder == null) { 18 | tmpFolder = new File("Spoon"); 19 | if (!tmpFolder.exists()) { 20 | tmpFolder.mkdir(); 21 | } 22 | } 23 | return tmpFolder; 24 | } 25 | 26 | public static File loadToTmpFolder(InputStream in, String packageName, 27 | String fileName) throws IOException { 28 | File tmpFolder = getTmpFolder(); 29 | String[] packageData = packageName.split("\\."); 30 | File f = tmpFolder; 31 | for (String p : packageData) { 32 | f = new File(f, p); 33 | if (!f.exists()) { 34 | f.mkdir(); 35 | } 36 | } 37 | File javaFile = new File(f, fileName); 38 | IOUtils.copy(in, new FileOutputStream(javaFile)); 39 | return javaFile; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/util/ClasspathHacker.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.reflect.Method; 6 | import java.net.URL; 7 | import java.net.URLClassLoader; 8 | 9 | public final class ClasspathHacker { 10 | private ClasspathHacker() { 11 | } 12 | 13 | public static void addFile(String filename) throws IOException { 14 | addFile(new File(filename)); 15 | } 16 | 17 | public static void addFile(File file) throws IOException { 18 | addURL(file.toURI().toURL()); 19 | } 20 | 21 | public static void addURL(URL u) throws IOException { 22 | URLClassLoader systemClassLoader = (URLClassLoader) Thread 23 | .currentThread().getContextClassLoader(); 24 | Class classLoaderClass = URLClassLoader.class; 25 | 26 | try { 27 | Method method = classLoaderClass.getDeclaredMethod("addURL", 28 | new Class[] { URL.class }); 29 | method.setAccessible(true); 30 | method.invoke(systemClassLoader, new Object[] { u }); 31 | } catch (Throwable t) { 32 | t.printStackTrace(); 33 | throw new IOException( 34 | "Error, could not add URL to system classloader"); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/configuration/SpoonConfigurationFactory.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.configuration; 2 | 3 | import fr.inria.gforge.spoon.SpoonMojoGenerate; 4 | import fr.inria.gforge.spoon.logging.ReportBuilder; 5 | import fr.inria.gforge.spoon.util.LogWrapper; 6 | import fr.inria.gforge.spoon.util.XMLLoader; 7 | 8 | import java.io.InputStream; 9 | 10 | public final class SpoonConfigurationFactory { 11 | private static final String SPOON_CONFIGURATION_FILENAME = "spoon.xml"; 12 | 13 | private SpoonConfigurationFactory() { 14 | } 15 | 16 | public static SpoonConfigurationBuilder getConfig(SpoonMojoGenerate spoon, ReportBuilder reportBuilder) throws Exception { 17 | final InputStream in = SpoonMojoGenerate.class.getClassLoader().getResourceAsStream(SPOON_CONFIGURATION_FILENAME); 18 | if (in != null) { 19 | // Spoon configuration file is in the classpath. 20 | LogWrapper.info(spoon, "Generate spoon with a spoon.xml file."); 21 | return new XMLSpoonConfiguration(spoon, reportBuilder, XMLLoader.load(in)); 22 | } 23 | // There aren't spoon configuration file, we use data given at the plugin. 24 | LogWrapper.info(spoon, "Generate spoon without a spoon.xml file."); 25 | return new SimpleSpoonConfiguration(spoon, reportBuilder); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/SpoonModel.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | import javax.xml.bind.annotation.XmlElement; 4 | import javax.xml.bind.annotation.XmlElementWrapper; 5 | import javax.xml.bind.annotation.XmlRootElement; 6 | import javax.xml.bind.annotation.XmlType; 7 | import java.util.List; 8 | 9 | /** 10 | * Simple class to store data loaded from XML config file 11 | */ 12 | @XmlType(name = "spoonModel") 13 | @XmlRootElement(name = "spoonModel") 14 | public class SpoonModel { 15 | private List templates; 16 | private List processors; 17 | private String fileGenerator; 18 | 19 | public String getFileGenerator() { 20 | return fileGenerator; 21 | } 22 | 23 | @XmlElement(name = "fileGenerator") 24 | public void setFileGenerator(String fileGenerator) { 25 | this.fileGenerator = fileGenerator; 26 | } 27 | 28 | public List getProcessors() { 29 | return processors; 30 | } 31 | 32 | @XmlElementWrapper(name = "processors") 33 | @XmlElement(name = "processor") 34 | public void setProcessors(List processors) { 35 | this.processors = processors; 36 | } 37 | 38 | public List getTemplates() { 39 | return templates; 40 | } 41 | 42 | @XmlElementWrapper(name = "templates") 43 | @XmlElement(name = "template") 44 | public void setTemplates(List templates) { 45 | this.templates = templates; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/fr/inria/gforge/spoon/mojo/CountStatementProcessor.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.mojo; 2 | 3 | import spoon.processing.AbstractProcessor; 4 | import spoon.reflect.code.CtStatement; 5 | 6 | import java.io.File; 7 | import java.io.FileWriter; 8 | import java.io.PrintWriter; 9 | 10 | public class CountStatementProcessor extends AbstractProcessor { 11 | 12 | private int count = 0; 13 | 14 | @Override 15 | public void process(CtStatement element) { 16 | count++; 17 | } 18 | 19 | @Override 20 | public void processingDone() { 21 | super.processingDone(); 22 | PrintWriter writer = null; 23 | try { 24 | // Creates output file for performance. 25 | final String project = "target" + File.separator + "test-projects" + File.separator + "SpoonMojoTest_testSpoonGoalWithAProcessor_processor"; 26 | final String performancePath = project + File.separator + "target" + File.separator + "spoon-maven-plugin" + File.separator + "spoon-nb-statement.txt"; 27 | final File performanceFile = new File(performancePath); 28 | 29 | if (!performanceFile.getParentFile().exists()) { 30 | performanceFile.getParentFile().mkdirs(); 31 | } 32 | if (!performanceFile.exists()) { 33 | performanceFile.createNewFile(); 34 | } 35 | writer = new PrintWriter(new FileWriter(performanceFile, true)); 36 | 37 | // Saves number of statement. 38 | writer.println(count); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } finally { 42 | if (writer != null) { 43 | writer.close(); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/logging/ReportBuilderImpl.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.logging; 2 | 3 | import java.io.File; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | class ReportBuilderImpl implements ReportBuilder { 8 | 9 | enum ReportKey { 10 | PROJECT_NAME, FRAGMENT_MODE, PROCESSORS, MODULE_NAME, INPUT, OUTPUT, SOURCE_CLASSPATH, PERFORMANCE 11 | } 12 | 13 | private final Map reportsData = new HashMap(); 14 | private final ReportDao reportDao; 15 | 16 | ReportBuilderImpl(File resultFile) { 17 | reportDao = new ReportDaoImpl(resultFile); 18 | } 19 | 20 | @Override 21 | public ReportBuilder setProjectName(String name) { 22 | reportsData.put(ReportKey.PROJECT_NAME, name); 23 | return this; 24 | } 25 | 26 | @Override 27 | public ReportBuilder setProcessors(String[] processors) { 28 | reportsData.put(ReportKey.PROCESSORS, processors); 29 | return this; 30 | } 31 | 32 | @Override 33 | public ReportBuilder setModuleName(String name) { 34 | reportsData.put(ReportKey.MODULE_NAME, name); 35 | return this; 36 | } 37 | 38 | @Override 39 | public ReportBuilder setInput(String input) { 40 | reportsData.put(ReportKey.INPUT, input); 41 | return this; 42 | } 43 | 44 | @Override 45 | public ReportBuilder setOutput(String output) { 46 | reportsData.put(ReportKey.OUTPUT, output); 47 | return this; 48 | } 49 | 50 | @Override 51 | public ReportBuilder setSourceClasspath(String sourceClasspath) { 52 | reportsData.put(ReportKey.SOURCE_CLASSPATH, sourceClasspath); 53 | return this; 54 | } 55 | 56 | @Override 57 | public ReportBuilder setPerformance(long performance) { 58 | reportsData.put(ReportKey.PERFORMANCE, performance); 59 | return this; 60 | } 61 | 62 | @Override 63 | public void buildReport() { 64 | reportDao.save(reportsData); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/configuration/SpoonConfigurationBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.configuration; 2 | 3 | public interface SpoonConfigurationBuilder { 4 | 5 | 6 | /** 7 | * Enables Comment generation 8 | */ 9 | 10 | SpoonConfigurationBuilder addEnableComments(); 11 | 12 | /** 13 | * Adds input folder in parameters of spoon. 14 | */ 15 | SpoonConfigurationBuilder addInputFolder() throws SpoonMavenPluginException; 16 | 17 | /** 18 | * Adds output folder in parameters of spoon. 19 | */ 20 | SpoonConfigurationBuilder addOutputFolder(); 21 | 22 | /** 23 | * Adds compliance in parameters of spoon. 24 | */ 25 | SpoonConfigurationBuilder addCompliance(); 26 | 27 | /** 28 | * Adds source classpath in parameters of spoon. 29 | */ 30 | SpoonConfigurationBuilder addSourceClasspath() throws SpoonMavenPluginException; 31 | 32 | /** 33 | * Adds processors in parameters of spoon. 34 | */ 35 | SpoonConfigurationBuilder addProcessors(); 36 | 37 | /** 38 | * Adds templates in parameters of spoon. 39 | */ 40 | SpoonConfigurationBuilder addTemplates(); 41 | 42 | /** 43 | * Adds not assume a full classpath in parameters of spoon. 44 | */ 45 | SpoonConfigurationBuilder addNoClasspath(); 46 | 47 | /** 48 | * Shouldn't rewrite source code in fully qualified mode. 49 | */ 50 | SpoonConfigurationBuilder addWithImports(); 51 | 52 | /** 53 | * Adds not assume a build only outdated files in parameters of spoon. 54 | */ 55 | SpoonConfigurationBuilder addBuildOnlyOutdatedFiles(); 56 | 57 | /** 58 | * Adds no copy resources in parameters of spoon 59 | */ 60 | SpoonConfigurationBuilder addNoCopyResources(); 61 | 62 | /** 63 | * Adds the output type 64 | */ 65 | SpoonConfigurationBuilder addOutputType(); 66 | 67 | /** 68 | * Builds parameters for spoon. 69 | */ 70 | String[] build(); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/projects/antlr-example/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.antlrfun 5 | antlrfun 6 | 0.0.1-SNAPSHOT 7 | Fun With Antlr 4 8 | 9 | 10 | UTF-8 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | org.antlr 18 | antlr4-runtime 19 | 4.1 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.antlr 27 | antlr4-maven-plugin 28 | 4.1 29 | 30 | src/main/java 31 | target/generated-sources 32 | 33 | 34 | 35 | 36 | antlr4 37 | 38 | 39 | 40 | 41 | 42 | fr.inria.gforge.spoon 43 | spoon-maven-plugin 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-with-properties/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | fr.inria.gforge.spoon 6 | hello-world 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | fr.inria.gforge.spoon 13 | spoon-maven-plugin 14 | 15 | true 16 | 17 | fr.inria.gforge.spoon.mojo.ProcessorWithProperty 18 | 19 | 20 | 21 | fr.inria.gforge.spoon.mojo.ProcessorWithProperty 22 | 23 | 24 | oldClassName 25 | App 26 | 27 | 28 | newClassName 29 | NewName 30 | 31 | 32 | comments 33 | ["hello","hello, world"] 34 | 35 | 36 | other 37 | [0,2,4] 38 | 39 | 40 | mapTest 41 | {"un":1,"deux":"two","trois":"3,1"} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/test/resources/fr/inria/gforge/spoon/unit/spoon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.dooapp.processor.wrapper.GenerateMissingWrapperClassProcessor 4 | com.dooapp.processor.wrapper.AddSuperClassOnBeanProcessor 5 | com.dooapp.processor.wrapper.AddSuperClassOnWrapperProcessor 6 | com.dooapp.processor.property.GenerateWrapperMethodsProcessor 7 | com.dooapp.processor.property.GenerateCreateFieldPropertyContent 8 | com.dooapp.processor.collection.GenerateWrapperCollectionMethodsProcessor 9 | com.dooapp.processor.builder.GenerateMissingBuilderClassProcessor 10 | com.dooapp.processor.builder.AddSuperClassOnBuilderProcessor 11 | com.dooapp.processor.builder.GenerateMethodsProcessor 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/projects/hello-world-spoon-xml/src/main/resources/spoon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.dooapp.processor.wrapper.GenerateMissingWrapperClassProcessor 4 | com.dooapp.processor.wrapper.AddSuperClassOnBeanProcessor 5 | com.dooapp.processor.wrapper.AddSuperClassOnWrapperProcessor 6 | com.dooapp.processor.property.GenerateWrapperMethodsProcessor 7 | com.dooapp.processor.property.GenerateCreateFieldPropertyContent 8 | com.dooapp.processor.collection.GenerateWrapperCollectionMethodsProcessor 9 | com.dooapp.processor.builder.GenerateMissingBuilderClassProcessor 10 | com.dooapp.processor.builder.AddSuperClassOnBuilderProcessor 11 | com.dooapp.processor.builder.GenerateMethodsProcessor 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/configuration/XMLSpoonConfiguration.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.configuration; 2 | 3 | import fr.inria.gforge.spoon.SpoonMojoGenerate; 4 | import fr.inria.gforge.spoon.SpoonModel; 5 | import fr.inria.gforge.spoon.logging.ReportBuilder; 6 | import fr.inria.gforge.spoon.util.LogWrapper; 7 | import fr.inria.gforge.spoon.util.TemplateLoader; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | class XMLSpoonConfiguration extends AbstractSpoonConfigurationBuilder { 14 | /** 15 | * Used when the plugin is launched with a xml file for the configuration, 16 | * a spoon.xml file. 17 | */ 18 | private SpoonModel model; 19 | 20 | public XMLSpoonConfiguration(SpoonMojoGenerate spoon, ReportBuilder reportBuilder, 21 | SpoonModel model) { 22 | super(spoon, reportBuilder); 23 | this.model = model; 24 | } 25 | 26 | @Override 27 | public SpoonConfigurationBuilder addProcessors() { 28 | if (model != null && model.getProcessors() != null && !model 29 | .getProcessors().isEmpty()) { 30 | String[] processors = model.getProcessors().toArray( 31 | new String[model.getProcessors().size()]); 32 | parameters.add("-p"); 33 | parameters.add(buildProcessors(processors)); 34 | reportBuilder.setProcessors(processors); 35 | } 36 | return this; 37 | } 38 | 39 | /** 40 | * Builds the path for the list of processors. 41 | */ 42 | private String buildProcessors(String[] processors) { 43 | return implode(processors, File.pathSeparator); 44 | } 45 | 46 | @Override 47 | public SpoonConfigurationBuilder addTemplates() { 48 | if (model != null && model.getTemplates() != null && !model 49 | .getTemplates().isEmpty()) { 50 | parameters.add("-t"); 51 | parameters.add(buildTemplates()); 52 | } 53 | return this; 54 | } 55 | 56 | /** 57 | * Builds the path for the list of templates. 58 | */ 59 | private String buildTemplates() { 60 | String[] templateString = new String[model.getTemplates().size()]; 61 | for (int i = 0; i < (model.getTemplates().size()); i++) { 62 | String templateLoaded = loadTemplateFile( 63 | model.getTemplates().get(i)); 64 | if (templateLoaded != null) { 65 | templateString[i] = templateLoaded; 66 | } 67 | } 68 | return implode(templateString, File.pathSeparator); 69 | } 70 | 71 | /** 72 | * Loads the template file at the path given. 73 | */ 74 | private String loadTemplateFile(String templateName) { 75 | String name = templateName.replace('.', '/') + ".java"; 76 | InputStream in = SpoonMojoGenerate.class.getClassLoader().getResourceAsStream(name); 77 | String packageName = templateName.substring(0, templateName.lastIndexOf('.')); 78 | String fileName = templateName.substring(templateName.lastIndexOf('.') + 1) + ".java"; 79 | try { 80 | return TemplateLoader.loadToTmpFolder(in, packageName, fileName) 81 | .getAbsolutePath(); 82 | } catch (IOException e) { 83 | LogWrapper.warn(spoon, String.format("Template %s cannot be loaded.", templateName), e); 84 | return null; 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/fr/inria/gforge/spoon/mojo/ProcessorWithProperty.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.mojo; 2 | 3 | import org.codehaus.plexus.util.StringUtils; 4 | import spoon.processing.AbstractProcessor; 5 | import spoon.processing.Property; 6 | import spoon.reflect.declaration.CtClass; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by urli on 21/09/2017. 13 | */ 14 | public class ProcessorWithProperty extends AbstractProcessor { 15 | 16 | @Property 17 | String oldClassName; 18 | 19 | @Property 20 | String newClassName; 21 | 22 | @Property 23 | List comments; 24 | 25 | @Property 26 | List other; 27 | 28 | @Property 29 | Map mapTest; 30 | 31 | @Override 32 | public void process(CtClass element) { 33 | if (!(comments != null && comments.size() == 2 && comments.get(0).equals("hello") && comments.get(1).equals("hello, world"))) { 34 | element.addComment(this.getFactory().createInlineComment("Content comments: "+ StringUtils.join(comments.iterator(),","))); 35 | return; 36 | } 37 | 38 | if (other != null && other.size() == 3) { 39 | for (int i = 0; i < 3; i++) { 40 | if (! (other.get(i) == i*2)) { 41 | element.addComment(this.getFactory().createInlineComment("Content other: "+ StringUtils.join(other.iterator(),","))); 42 | return; 43 | } 44 | } 45 | } else { 46 | return; 47 | } 48 | 49 | if (mapTest != null && mapTest.keySet().size() == 3) { 50 | for (String s : mapTest.keySet()) { 51 | if (s.equals("un")) { 52 | if (!mapTest.get(s).equals(1)) { 53 | element.addComment(this.getFactory().createInlineComment("Error un : Content mapTest keys: "+ StringUtils.join(mapTest.keySet().iterator(),":")+" and value: "+StringUtils.join(mapTest.values().iterator(),":"))); 54 | return; 55 | } 56 | } else if (s.equals("deux")) { 57 | if (!mapTest.get(s).equals("two")) { 58 | element.addComment(this.getFactory().createInlineComment("Error deux : Content mapTest keys: "+ StringUtils.join(mapTest.keySet().iterator(),":")+" and value: "+StringUtils.join(mapTest.values().iterator(),":"))); 59 | return; 60 | } 61 | }else if (s.equals("trois")) { 62 | if (!mapTest.get(s).equals("3,1")) { 63 | element.addComment(this.getFactory().createInlineComment("Error trois : Content mapTest keys: "+ StringUtils.join(mapTest.keySet().iterator(),":")+" and value: "+StringUtils.join(mapTest.values().iterator(),":"))); 64 | return; 65 | } 66 | } else { 67 | element.addComment(this.getFactory().createInlineComment("Wrong key: Content mapTest keys: "+ StringUtils.join(mapTest.keySet().iterator(),":")+" and value: "+StringUtils.join(mapTest.values().iterator(),":"))); 68 | return; 69 | } 70 | } 71 | } 72 | 73 | if (element.getSimpleName().equals(this.oldClassName)) { 74 | element.setSimpleName(this.newClassName); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/logging/ReportDaoImpl.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.logging; 2 | 3 | import org.w3c.dom.Document; 4 | import org.w3c.dom.Element; 5 | 6 | import javax.xml.parsers.DocumentBuilder; 7 | import javax.xml.parsers.DocumentBuilderFactory; 8 | import javax.xml.parsers.ParserConfigurationException; 9 | import javax.xml.transform.Transformer; 10 | import javax.xml.transform.TransformerException; 11 | import javax.xml.transform.TransformerFactory; 12 | import javax.xml.transform.dom.DOMSource; 13 | import javax.xml.transform.stream.StreamResult; 14 | import java.io.File; 15 | import java.util.Map; 16 | 17 | import static fr.inria.gforge.spoon.logging.ReportBuilderImpl.ReportKey; 18 | import static fr.inria.gforge.spoon.logging.ReportBuilderImpl.ReportKey.*; 19 | 20 | class ReportDaoImpl implements ReportDao { 21 | private final File resultFile; 22 | 23 | ReportDaoImpl(File resultFile) { 24 | this.resultFile = resultFile; 25 | } 26 | 27 | @Override 28 | public void save(Map reportsData) { 29 | try { 30 | if (!resultFile.getParentFile().exists()) { 31 | resultFile.getParentFile().mkdirs(); 32 | resultFile.createNewFile(); 33 | } 34 | report(reportsData); 35 | } catch (Exception e) { 36 | throw new RuntimeException("Error to save result of the plugin", e); 37 | } 38 | } 39 | 40 | private void report(Map data) 41 | throws ParserConfigurationException, TransformerException { 42 | DocumentBuilderFactory bFactory = DocumentBuilderFactory.newInstance(); 43 | DocumentBuilder docBuilder = bFactory.newDocumentBuilder(); 44 | 45 | // Adds all elements. 46 | final Document doc = docBuilder.newDocument(); 47 | final Element root = addRoot(doc, data); 48 | addProcessors(doc, root, data); 49 | 50 | addElement(doc, root, data, INPUT, (String) data.get(INPUT)); 51 | addElement(doc, root, data, OUTPUT, (String) data.get(OUTPUT)); 52 | addElement(doc, root, data, SOURCE_CLASSPATH, (String) data.get(SOURCE_CLASSPATH)); 53 | addElement(doc, root, data, PERFORMANCE, Long.toString((Long) data.get(PERFORMANCE))); 54 | 55 | // write the content into xml file 56 | TransformerFactory transfFactory = TransformerFactory.newInstance(); 57 | Transformer transformer = transfFactory.newTransformer(); 58 | DOMSource source = new DOMSource(doc); 59 | StreamResult result = new StreamResult(resultFile.getAbsolutePath()); 60 | transformer.transform(source, result); 61 | } 62 | 63 | /** 64 | * Adds root element. 65 | */ 66 | private Element addRoot(Document document, 67 | Map reportsData) { 68 | Element rootElement = document.createElement("project"); 69 | if (reportsData.containsKey(ReportKey.PROJECT_NAME)) { 70 | rootElement.setAttribute("name", 71 | (String) reportsData.get(ReportKey.PROJECT_NAME)); 72 | } 73 | document.appendChild(rootElement); 74 | return rootElement; 75 | } 76 | 77 | /** 78 | * Adds processors, child of root element. 79 | */ 80 | private Element addProcessors(Document document, Element root, 81 | Map reportsData) { 82 | if (reportsData.containsKey(ReportKey.PROCESSORS)) { 83 | // Adds root tag "processors". 84 | Element processors = document.createElement("processors"); 85 | root.appendChild(processors); 86 | 87 | // Adds all processors in child of "processors" tag. 88 | String[] tabProcessors = (String[]) reportsData 89 | .get(ReportKey.PROCESSORS); 90 | for (String processor : tabProcessors) { 91 | Element current = document.createElement("processor"); 92 | current.appendChild(document.createTextNode(processor)); 93 | processors.appendChild(current); 94 | } 95 | return processors; 96 | } 97 | return null; 98 | } 99 | 100 | /** 101 | * Generic method to add a element for a parent element given. 102 | */ 103 | private Element addElement(Document document, Element parent, 104 | Map reportsData, ReportKey key, String value) { 105 | if (reportsData.containsKey(key)) { 106 | Element child = document.createElement(key.name().toLowerCase()); 107 | child.appendChild(document.createTextNode(value)); 108 | parent.appendChild(child); 109 | return child; 110 | } 111 | return null; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/configuration/AbstractSpoonConfigurationBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.configuration; 2 | 3 | import fr.inria.gforge.spoon.SpoonMojoGenerate; 4 | import fr.inria.gforge.spoon.logging.ReportBuilder; 5 | import fr.inria.gforge.spoon.util.LogWrapper; 6 | import org.apache.maven.artifact.DependencyResolutionRequiredException; 7 | import org.apache.maven.project.MavenProject; 8 | 9 | import java.io.File; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | abstract class AbstractSpoonConfigurationBuilder 16 | implements SpoonConfigurationBuilder { 17 | 18 | protected final List parameters = new LinkedList(); 19 | protected final SpoonMojoGenerate spoon; 20 | protected final ReportBuilder reportBuilder; 21 | 22 | protected AbstractSpoonConfigurationBuilder(SpoonMojoGenerate spoon, 23 | ReportBuilder reportBuilder) { 24 | this.spoon = spoon; 25 | this.reportBuilder = reportBuilder; 26 | if (this.spoon.getLog().isDebugEnabled()) { 27 | parameters.add("--level"); 28 | parameters.add("DEBUG"); 29 | } else if (this.spoon.getLog().isInfoEnabled()) { 30 | parameters.add("--level"); 31 | parameters.add("INFO"); 32 | } 33 | 34 | } 35 | 36 | @Override 37 | public SpoonConfigurationBuilder addInputFolder() throws SpoonMavenPluginException { 38 | final List srcDir = new ArrayList<>(); 39 | 40 | if (spoon.getSrcFolders().length > 0) { 41 | srcDir.addAll(Arrays.asList(spoon.getSrcFolders())); 42 | } else if (spoon.getSrcFolder() != null) { 43 | srcDir.add(spoon.getSrcFolder()); 44 | } else { 45 | if (!spoon.getSkipGeneratedSources()) { 46 | for (String s : spoon.getProject().getCompileSourceRoots()) { 47 | srcDir.add(new File(s)); 48 | } 49 | } else { 50 | srcDir.add(new File(spoon.getProject().getBuild().getSourceDirectory())); 51 | } 52 | } 53 | 54 | srcDir.removeIf(file -> !file.exists()); 55 | 56 | if (srcDir.isEmpty()) { 57 | throw new SpoonMavenPluginException(String.format("No source directory for %s project.", spoon.getProject().getName())); 58 | } 59 | 60 | String inputs = ""; 61 | for (int i = 0; i < srcDir.size(); i++) { 62 | File file = srcDir.get(i); 63 | inputs += file.getAbsolutePath(); 64 | if (i != srcDir.size() - 1) { 65 | inputs += File.pathSeparatorChar; 66 | } 67 | } 68 | 69 | parameters.add("-i"); 70 | parameters.add(inputs); 71 | reportBuilder.setInput(inputs); 72 | return this; 73 | } 74 | 75 | @Override 76 | public SpoonConfigurationBuilder addOutputFolder() { 77 | // Create output folder if it doesn't exist. 78 | if (!spoon.getOutFolder().exists()) { 79 | spoon.getOutFolder().mkdirs(); 80 | } 81 | 82 | parameters.add("-o"); 83 | parameters.add(spoon.getOutFolder().getAbsolutePath()); 84 | if (!spoon.isCompileOriginalSources()) { 85 | spoon.getProject().getCompileSourceRoots().clear(); 86 | spoon.getProject().addCompileSourceRoot(spoon.getOutFolder().getAbsolutePath()); 87 | } 88 | reportBuilder.setOutput(spoon.getOutFolder().getAbsolutePath()); 89 | return this; 90 | } 91 | 92 | @Override 93 | public SpoonConfigurationBuilder addEnableComments() { 94 | if (!spoon.isEnableComments()){ 95 | parameters.add("--disable-comments"); 96 | } 97 | return this; 98 | } 99 | 100 | 101 | @Override 102 | public SpoonConfigurationBuilder addCompliance() { 103 | parameters.add("--compliance"); 104 | parameters.add("" + spoon.getCompliance()); 105 | return this; 106 | } 107 | 108 | @Override 109 | public SpoonConfigurationBuilder addSourceClasspath() throws SpoonMavenPluginException { 110 | final MavenProject project = spoon.getProject(); 111 | List compileClasspath; 112 | try { 113 | compileClasspath = project.getCompileClasspathElements(); 114 | } catch (DependencyResolutionRequiredException e) { 115 | throw new SpoonMavenPluginException("Cannot get compile classpath elements.", e); 116 | } 117 | if (compileClasspath.size() > 1) { 118 | final StringBuilder classpath = new StringBuilder(); 119 | // Start at one because we don't would like the first compile classpath. 120 | for (int i = 1; i < compileClasspath.size(); i++) { 121 | classpath.append(compileClasspath.get(i)).append(File.pathSeparatorChar); 122 | } 123 | LogWrapper.debug(spoon, String.format("Source classpath: %s", classpath.toString())); 124 | parameters.add("--source-classpath"); 125 | parameters.add(classpath.toString()); 126 | reportBuilder.setSourceClasspath(classpath.toString()); 127 | } 128 | return this; 129 | } 130 | 131 | @Override 132 | public SpoonConfigurationBuilder addNoClasspath() { 133 | if (spoon.isNoClasspath()) { 134 | parameters.add("-x"); 135 | } 136 | return this; 137 | } 138 | 139 | @Override 140 | public SpoonConfigurationBuilder addWithImports() { 141 | if (spoon.isWithImports()) { 142 | parameters.add("--with-imports"); 143 | } 144 | return this; 145 | } 146 | 147 | @Override 148 | public SpoonConfigurationBuilder addBuildOnlyOutdatedFiles() { 149 | if (spoon.isBuildOnlyOutdatedFiles()) { 150 | parameters.add("--buildOnlyOutdatedFiles"); 151 | } 152 | return this; 153 | } 154 | 155 | @Override 156 | public SpoonConfigurationBuilder addNoCopyResources() { 157 | if (spoon.isNoCopyResources()) { 158 | parameters.add("--no-copy-resources"); 159 | } 160 | return this; 161 | } 162 | 163 | @Override 164 | public SpoonConfigurationBuilder addOutputType() { 165 | parameters.add("--output-type"); 166 | parameters.add(spoon.getOutputType()); 167 | return this; 168 | } 169 | 170 | @Override 171 | public String[] build() { 172 | LogWrapper.info(spoon, "Running spoon with parameters:"); 173 | LogWrapper.info(spoon, parameters.toString()); 174 | return parameters.toArray(new String[parameters.size()]); 175 | } 176 | 177 | /** 178 | * Concatenates a tab in a string with a path separator given. 179 | */ 180 | protected String implode(String[] tabToConcatenate, String pathSeparator) { 181 | final StringBuilder builder = new StringBuilder(); 182 | for (int i = 0; i < tabToConcatenate.length; i++) { 183 | builder.append(tabToConcatenate[i]); 184 | if (i < tabToConcatenate.length - 1) { 185 | builder.append(pathSeparator); 186 | } 187 | } 188 | return builder.toString(); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/test/java/fr/inria/gforge/spoon/unit/SpoonConfigurationBuilderTest.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.unit; 2 | 3 | import fr.inria.gforge.spoon.SpoonMojoGenerate; 4 | import fr.inria.gforge.spoon.configuration.SpoonConfigurationBuilder; 5 | import fr.inria.gforge.spoon.configuration.SpoonConfigurationFactory; 6 | import fr.inria.gforge.spoon.logging.ReportBuilder; 7 | import fr.inria.gforge.spoon.logging.ReportFactory; 8 | import org.apache.maven.plugin.testing.MojoRule; 9 | import org.apache.maven.plugin.testing.resources.TestResources; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | 13 | import java.io.File; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | 17 | public final class SpoonConfigurationBuilderTest { 18 | @Rule 19 | public MojoRule rule = new MojoRule(); 20 | @Rule 21 | public TestResources resources = new TestResources(); 22 | 23 | 24 | @Test 25 | public void testConfigurationOfTheEnableComments() throws Exception { 26 | final File basedir = resources.getBasedir("hello-world-with-comments-disabled"); 27 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 28 | 29 | final String[] config = configurationBuilder.addEnableComments().build(); 30 | assertThat(config.length).isEqualTo(3); 31 | assertThat(config[0]).isEqualTo("--level"); 32 | assertThat(config[1]).isEqualTo("INFO"); 33 | assertThat(config[2]).isEqualTo("--disable-comments"); 34 | } 35 | 36 | 37 | @Test 38 | public void testConfigurationOfTheEnableCommentsKeepsDefaults() throws Exception { 39 | final File basedir = resources.getBasedir("hello-world"); 40 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 41 | 42 | final String[] config = configurationBuilder.addEnableComments().build(); 43 | assertThat(config.length).isEqualTo(2); 44 | assertThat(config[0]).isEqualTo("--level"); 45 | assertThat(config[1]).isEqualTo("INFO"); 46 | } 47 | 48 | @Test 49 | public void testConfigurationOfTheDefaultInputFolder() throws Exception { 50 | final File basedir = resources.getBasedir("hello-world"); 51 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 52 | 53 | final String[] config = configurationBuilder.addInputFolder().build(); 54 | assertThat(config.length).isEqualTo(4); 55 | assertThat(config[0]).isEqualTo("--level"); 56 | assertThat(config[1]).isEqualTo("INFO"); 57 | assertThat(config[2]).isEqualTo("-i"); 58 | assertThat(config[3]).isEqualTo(basedir + File.separator + "src" + File.separator + "main" + File.separator + "java"); 59 | } 60 | 61 | @Test 62 | public void testConfigurationOfTheCustomInputFolder() throws Exception { 63 | final File basedir = resources.getBasedir("custom-configuration"); 64 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 65 | 66 | final String[] config = configurationBuilder.addInputFolder().build(); 67 | assertThat(config.length).isEqualTo(4); 68 | assertThat(config[0]).isEqualTo("--level"); 69 | assertThat(config[1]).isEqualTo("INFO"); 70 | assertThat(config[2]).isEqualTo("-i"); 71 | assertThat(config[3]).isEqualTo( 72 | basedir + File.separator + "src" + File.separator + "internal" + File.separator + "fr" + File.separator + "inria" + File.separator + "gforge" + File.separator + "spoon"); 73 | } 74 | 75 | @Test 76 | public void testConfigurationOfTheDefaultOutputFolder() throws Exception { 77 | final File basedir = resources.getBasedir("hello-world"); 78 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 79 | 80 | final String[] config = configurationBuilder.addOutputFolder().build(); 81 | assertThat(config.length).isEqualTo(4); 82 | assertThat(config[0]).isEqualTo("--level"); 83 | assertThat(config[1]).isEqualTo("INFO"); 84 | assertThat(config[2]).isEqualTo("-o"); 85 | assertThat(config[3]).isEqualTo(basedir + File.separator + "target" + File.separator + "generated-sources" + File.separator + "spoon"); 86 | } 87 | 88 | @Test 89 | public void testConfigurationOfTheCustomOutputFolder() throws Exception { 90 | final File basedir = resources.getBasedir("custom-configuration"); 91 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 92 | 93 | final String[] config = configurationBuilder.addOutputFolder().build(); 94 | assertThat(config.length).isEqualTo(4); 95 | assertThat(config[0]).isEqualTo("--level"); 96 | assertThat(config[1]).isEqualTo("INFO"); 97 | assertThat(config[2]).isEqualTo("-o"); 98 | assertThat(config[3]).isEqualTo(basedir + File.separator + "target" + File.separator + "generate-source-with-spoon"); 99 | } 100 | 101 | @Test 102 | public void testConfigurationWithEnableComments() throws Exception { 103 | final File basedir = resources.getBasedir("processor"); 104 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 105 | 106 | final String[] config = configurationBuilder.addProcessors().build(); 107 | assertThat(config.length).isEqualTo(4); 108 | assertThat(config[0]).isEqualTo("--level"); 109 | assertThat(config[1]).isEqualTo("INFO"); 110 | assertThat(config[2]).isEqualTo("-p"); 111 | assertThat(config[3]).isEqualTo("fr.inria.gforge.spoon.mojo.CountStatementProcessor"); 112 | } 113 | 114 | 115 | @Test 116 | public void testConfigurationWithProcessors() throws Exception { 117 | final File basedir = resources.getBasedir("processor"); 118 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 119 | 120 | final String[] config = configurationBuilder.addProcessors().build(); 121 | assertThat(config.length).isEqualTo(4); 122 | assertThat(config[0]).isEqualTo("--level"); 123 | assertThat(config[1]).isEqualTo("INFO"); 124 | assertThat(config[2]).isEqualTo("-p"); 125 | assertThat(config[3]).isEqualTo("fr.inria.gforge.spoon.mojo.CountStatementProcessor"); 126 | } 127 | 128 | @Test 129 | public void testConfigurationWithImports() throws Exception { 130 | final File basedir = resources.getBasedir("hello-world-with-imports"); 131 | final SpoonConfigurationBuilder configurationBuilder = getSpoonConfigurationBuilder(basedir); 132 | 133 | final String[] config = configurationBuilder.addWithImports().build(); 134 | assertThat(config.length).isEqualTo(3); 135 | assertThat(config[0]).isEqualTo("--level"); 136 | assertThat(config[1]).isEqualTo("INFO"); 137 | assertThat(config[2]).isEqualTo("--with-imports"); 138 | } 139 | 140 | private SpoonConfigurationBuilder getSpoonConfigurationBuilder(File basedir) throws Exception { 141 | final SpoonMojoGenerate spoon = (SpoonMojoGenerate) rule.lookupConfiguredMojo(basedir, "generate"); 142 | final ReportBuilder reportBuilder = ReportFactory.newReportBuilder(spoon); 143 | return SpoonConfigurationFactory.getConfig(spoon, reportBuilder); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://img.shields.io/maven-central/v/fr.inria.gforge.spoon/spoon-maven-plugin.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22fr.inria.gforge.spoon%22%20AND%20a%3A%22spoon-maven-plugin%22) 2 | [![Build Status](https://travis-ci.org/SpoonLabs/spoon-maven-plugin.svg?branch=master)](https://travis-ci.org/SpoonLabs/spoon-maven-plugin) 3 | 4 | # Spoon-maven-plugin 5 | 6 | Spoon-maven-plugin is a maven plugin for performing code analysis or transformation during build. 7 | 8 | It can be used for instance: 9 | * to implement architectural checks, design pattern check and make the build fail if required 10 | * execute pre-compilation source code transformations, for instance to automatically add logging, error-handling code, dependency injection, etc. 11 | 12 | To report an issue, please use the main Spoon issue tracker: . 13 | 14 | ## Goals 15 | 16 | The plugin provided two goals: 17 | * `generate` to execute source-code transformation 18 | * `check` to execute source-code analysis 19 | 20 | Please note that the two goals take exactly the same arguments and configuration. 21 | The only change is that `generate` will create java source files in the `target` directory, where `check` will only fail the build in case of analysis error. 22 | 23 | ## Basic usage 24 | 25 | To use spoon-maven-plugin, you need to declare it on the `build` tag in the `pom.xml` file of your project and specify an execution during the `generate-source` phase of the maven lifecycle. 26 | 27 | The usage below is the minimum to execute the plugin and run spoon on your project. 28 | 29 | ```xml 30 | 31 | fr.inria.gforge.spoon 32 | spoon-maven-plugin 33 | LATEST 34 | 35 | 36 | generate-sources 37 | 38 | generate 39 | 40 | 41 | 42 | 43 | ``` 44 | 45 | Consequently, when `mvn clean install` is run on your project, the source code is first rewritten by spoon before compilation. 46 | Note that Spoon takes as input the original sources of the project, but also the sources generated by another tools (like antlr, for example). 47 | 48 | 49 | ## How to skip generated sources? 50 | 51 | If you want Spoon to not pass generated sources as input, just provide the following configuration: 52 | 53 | ```xml 54 | 55 | fr.inria.gforge.spoon 56 | spoon-maven-plugin 57 | LATEST 58 | 59 | true 60 | 61 | 62 | 63 | generate-sources 64 | 65 | generate 66 | 67 | 68 | 69 | 70 | ``` 71 | 72 | or in command line: `mvn compile -DskipGeneratedSources=true` 73 | 74 | ## How to add processors? 75 | 76 | Spoon can use processors to analyse and transform source code. 77 | 78 | To add processors, one must: 79 | 80 | 1. add a dependency in the `plugin` block. 81 | 2. add a processor with its full qualified name in the `configuration` block. 82 | 83 | In the example below, we add processor `fr.inria.gforge.spoon.processors.CountStatementProcessor` and the dependency necessary to locate the processor. 84 | 85 | ```xml 86 | 87 | 88 | 89 | fr.inria.gforge.spoon.processors.CountStatementProcessor 90 | 91 | 92 | 93 | 94 | 95 | fr.inria.gforge.spoon 96 | spoon-processors 97 | 1.0-SNAPSHOT 98 | 99 | 100 | ``` 101 | 102 | ## How to pass properties to a processor? 103 | 104 | Spoon allow to pass custom properties to the processor you want to use, directly in the `pom.xml`. 105 | 106 | For passing properties, one must: 107 | 108 | 1. Add annotated fields to the processor being configured from the POM 109 | 2. Add XML configuration for passing the values to those fields 110 | 3. Use JSON format to specify the value of the fields 111 | 112 | For example, let us consider the following Processor that changes the name of a specific class. The usage of `Property` annotation for the fields (eg `@Property String oldClassName`) means that this field will be set through the POM. 113 | 114 | ```java 115 | package my.app.pkg; 116 | 117 | import spoon.processing.AbstractProcessor; 118 | import spoon.processing.Property; 119 | import spoon.reflect.declaration.CtClass; 120 | 121 | public class ProcessorWithProperty extends AbstractProcessor { 122 | 123 | @Property 124 | String oldClassName; 125 | 126 | @Property 127 | String newClassName; 128 | 129 | @Override 130 | public void process(CtClass element) { 131 | if (element.getSimpleName().equals(this.oldClassName)) { 132 | element.setSimpleName(this.newClassName); 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | Then the following configuration sets the processor's fields: 139 | 140 | ```xml 141 | 142 | fr.inria.gforge.spoon 143 | spoon-maven-plugin 144 | 145 | 146 | my.app.pkg.ProcessorWithProperty 147 | 148 | 149 | 150 | my.app.pkg.ProcessorWithProperty 151 | 152 | 153 | oldClassName 154 | App 155 | 156 | 157 | newClassName 158 | NewName 159 | 160 | 161 | 162 | 163 | 164 | 165 | ``` 166 | Please note that you have to specify for which processor the properties should be used with the `name` attribute (here `my.app.pkg.ProcessorWithProperty`). 167 | Values must use JSON formatting. 168 | Primitive types are supported as well as list, maps and custom types. 169 | Value types are automatically inferred from their field type and JSON values are deserialized using Jackson library. 170 | As en examples, list are created as JSON lists: 171 | 172 | ```xml 173 | ["one","two","three","value containing a, comma"] 174 | ``` 175 | 176 | Maps and objects are created like this: 177 | 178 | ```xml 179 | {"one":1,"two":2,"three":"a value with a,comma"} 180 | ``` 181 | 182 | ## How to change source and output folder? 183 | 184 | You can specify at spoon its input and output directories with, respectively, `srcFolder` and `outFolder` tags. 185 | 186 | ## How to compile original sources? 187 | 188 | By default, spoon generate your source code and compile these sources but you can specify at the plugin that you want to compile your original sources with the tag `compileOriginalSources` sets to true. 189 | 190 | ## How to specify a custom version for Spoon? 191 | 192 | Spoon maven plugin defines a default value for spoon's version but you can override it to another one. 193 | 194 | For example, if you would like the version 2.4 of spoon and not the version 3.0, you must add the dependency below. 195 | 196 | ```xml 197 | 198 | fr.inria.gforge.spoon 199 | spoon-maven-plugin 200 | 201 | 202 | generate-sources 203 | 204 | generate 205 | 206 | 207 | 208 | 209 | 210 | fr.inria.gforge.spoon 211 | spoon-core 212 | 2.4 213 | 214 | 215 | 216 | ``` 217 | 218 | ## How to make the build fail? 219 | 220 | If you want to use a custom Processor for assessing properties (checkstyle-like, architectural rule checking, pattern checking, etc) and make the build fail if those properties are not verified, throw a `SpoonException` in the method `process()` of the processor. 221 | 222 | ## Reports 223 | 224 | The plugin creates some reports about its context and the execution of spoon on your project. These reports are available according to your definition of the plugin. 225 | 226 | - If you have a simple project, you can retrieve the report at this location: `target/spoon-maven-plugin/result-spoon.xml`. 227 | - If you have a multi-module project, you can retrieve on each sub module at the some location: `target/spoon-maven-plugin/result-spoon.xml`. 228 | - If you have a multi-module project but you didn't declared your plugin at the root `pom.xml` file, you can retrieve the report on each sub module from your declaration. 229 | 230 | ## Skipping 231 | 232 | It's possible to skip the plugin execution using `skip` property as in : 233 | 234 | ```xml 235 | 236 | fr.inria.gforge.spoon 237 | spoon-maven-plugin 238 | 239 | true 240 | 241 | 242 | ``` 243 | 244 | or from command line using `-Dspoon.skip=true`. 245 | 246 | ## 247 | 248 | ## Command-line usage 249 | 250 | If you add the following to your `~/.m2/settings.xml` 251 | 252 | ```xml 253 | 254 | fr.inria.gforge.spoon 255 | 256 | ``` 257 | Then, the plugin is automatically discovered ([through prefix resolution](https://maven.apache.org/guides/introduction/introduction-to-plugin-prefix-mapping.html)), and you can simply run `mvn spoon:generate`. 258 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.sonatype.oss 7 | oss-parent 8 | 7 9 | 10 | 11 | fr.inria.gforge.spoon 12 | spoon-maven-plugin 13 | maven-plugin 14 | 3.2-SNAPSHOT 15 | Spoon maven plugin 16 | A maven plugin to run spoon on a target project. 17 | https://github.com/SpoonLabs/spoon-maven-plugin 18 | 19 | 20 | UTF-8 21 | 22 | 23 | 1.8 24 | 7.0.0 25 | 2.6 26 | 2.2.11 27 | 2.3.1 28 | 3.5 29 | 2.2.1 30 | 3.3.9 31 | 32 | 33 | 4.12 34 | 3.3.0 35 | 3.8.0 36 | 37 | 38 | -Xdoclint:none 39 | 40 | 41 | 42 | 43 | maven.inria.fr-snapshot 44 | Maven Repository for Spoon Snapshot 45 | http://maven.inria.fr/artifactory/spoon-public-snapshot/ 46 | 47 | 48 | 49 | 50 | 51 | https://github.com/SpoonLabs/spoon-maven-plugin 52 | scm:git:git://github.com/SpoonLabs/spoon-maven-plugin.git 53 | scm:git:ssh://git@github.com/SpoonLabs/spoon-maven-plugin.git 54 | HEAD 55 | 56 | 57 | 58 | 59 | GNU General Public License (GPL) 60 | http://www.gnu.org/licenses/gpl.html 61 | 62 | 63 | 64 | 65 | Inria 66 | http://www.inria.fr 67 | 68 | 69 | 70 | 71 | Christophe Dufour 72 | 73 | Contributor 74 | 75 | 76 | 77 | Gérard Paligot 78 | 79 | Contributor 80 | 81 | 82 | 83 | 84 | 85 | 86 | ossrh-snapshot 87 | Snapshots 88 | https://oss.sonatype.org/content/repositories/snapshots/ 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven 95 | maven-core 96 | 3.3.9 97 | 98 | 99 | org.apache.maven 100 | maven-compat 101 | 3.2.2 102 | test 103 | 104 | 105 | org.apache.maven 106 | maven-plugin-api 107 | ${maven-plugin-api} 108 | 109 | 110 | org.apache.maven 111 | maven-project 112 | ${maven-project.version} 113 | 114 | 115 | org.apache.maven.plugin-tools 116 | maven-plugin-annotations 117 | ${maven-plugin-annotations.version} 118 | provided 119 | 120 | 121 | fr.inria.gforge.spoon 122 | spoon-core 123 | ${spoon.version} 124 | 125 | 126 | javax.xml.bind 127 | jaxb-api 128 | ${jaxb-api.version} 129 | 130 | 131 | org.glassfish.jaxb 132 | jaxb-runtime 133 | ${jaxb-runtime.version} 134 | runtime 135 | 136 | 137 | commons-io 138 | commons-io 139 | ${commons-io.version} 140 | 141 | 142 | junit 143 | junit 144 | ${junit.version} 145 | test 146 | 147 | 148 | org.apache.maven.plugin-testing 149 | maven-plugin-testing-harness 150 | ${maven-plugin-testing-harness.version} 151 | test 152 | 153 | 154 | org.assertj 155 | assertj-core 156 | ${assertj-core.version} 157 | test 158 | 159 | 160 | 161 | 162 | 163 | 164 | org.apache.maven.plugins 165 | maven-plugin-plugin 166 | 3.5 167 | 168 | spoon 169 | 170 | 171 | 172 | default-descriptor 173 | 174 | descriptor 175 | 176 | process-classes 177 | 178 | 179 | help-descriptor 180 | 181 | helpmojo 182 | 183 | process-classes 184 | 185 | 186 | 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-compiler-plugin 191 | 3.7.0 192 | 193 | ${java.version} 194 | ${java.version} 195 | 196 | 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-release-plugin 201 | 2.5 202 | 203 | true 204 | false 205 | release 206 | deploy 207 | 208 | 209 | 210 | 211 | org.sonatype.plugins 212 | nexus-staging-maven-plugin 213 | 1.6.2 214 | true 215 | 216 | ossrh 217 | https://oss.sonatype.org/ 218 | true 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | release 228 | 229 | 230 | 231 | org.apache.maven.plugins 232 | maven-source-plugin 233 | 2.3 234 | 235 | 236 | attach-sources 237 | 238 | jar-no-fork 239 | 240 | 241 | 242 | 243 | 244 | 245 | org.apache.maven.plugins 246 | maven-javadoc-plugin 247 | 2.9.1 248 | 249 | -Xdoclint:none 250 | 251 | 252 | 253 | attach-javadocs 254 | 255 | jar 256 | 257 | 258 | 259 | 260 | 261 | 262 | org.apache.maven.plugins 263 | maven-gpg-plugin 264 | 1.5 265 | 266 | 267 | sign-artifacts 268 | verify 269 | 270 | sign 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /src/test/java/fr/inria/gforge/spoon/mojo/SpoonMojoTest.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon.mojo; 2 | 3 | import fr.inria.gforge.spoon.configuration.SpoonMavenPluginException; 4 | import org.apache.commons.io.filefilter.WildcardFileFilter; 5 | import org.apache.maven.plugin.Mojo; 6 | import org.apache.maven.plugin.MojoExecutionException; 7 | import org.apache.maven.plugin.testing.MojoRule; 8 | import org.apache.maven.plugin.testing.resources.TestResources; 9 | import org.junit.Ignore; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | import spoon.SpoonException; 13 | 14 | import java.io.File; 15 | import java.io.FileFilter; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | public final class SpoonMojoTest { 20 | @Rule 21 | public MojoRule rule = new MojoRule(); 22 | @Rule 23 | public TestResources resources = new TestResources(); 24 | 25 | @Test 26 | public void testSpoonGoalGenerateResultFileForSimpleProject() throws Exception { 27 | File basedir = resources.getBasedir("hello-world"); 28 | rule.executeMojo(basedir, "generate"); 29 | 30 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 31 | assertThat(dirOutputResults).exists(); 32 | 33 | final File[] files = dirOutputResults.listFiles(); 34 | assertThat(files.length).isEqualTo(1); 35 | assertThat(files[0].getName()).startsWith("result-spoon"); 36 | } 37 | 38 | @Test 39 | public void testSpoonGoalGenerateResultFileForProjectWithoutComments() throws Exception { 40 | File basedir = resources.getBasedir("hello-world-with-comments-disabled"); 41 | rule.executeMojo(basedir, "generate"); 42 | 43 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 44 | assertThat(dirOutputResults).exists(); 45 | 46 | final File[] files = dirOutputResults.listFiles(); 47 | assertThat(files.length).isEqualTo(1); 48 | assertThat(files[0].getName()).startsWith("result-spoon"); 49 | } 50 | 51 | @Test 52 | public void testSpoonGoalGenerateResultFileForSimpleCommentedProject() throws Exception { 53 | File basedir = resources.getBasedir("hello-world-commented"); 54 | rule.executeMojo(basedir, "generate"); 55 | 56 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 57 | assertThat(dirOutputResults).exists(); 58 | 59 | final File[] files = dirOutputResults.listFiles(); 60 | assertThat(files.length).isEqualTo(1); 61 | assertThat(files[0].getName()).startsWith("result-spoon"); 62 | } 63 | 64 | @Test 65 | public void testSpoonGoalWithAProcessor() throws Exception { 66 | File basedir = resources.getBasedir("processor"); 67 | rule.executeMojo(basedir, "generate"); 68 | 69 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 70 | assertThat(dirOutputResults).exists(); 71 | 72 | final WildcardFileFilter filter = new WildcardFileFilter("result-spoon-*.xml"); 73 | final File[] files = dirOutputResults.listFiles((FileFilter) filter); 74 | assertThat(files.length).isEqualTo(1); 75 | assertThat(files[0].getName()).startsWith("result-spoon"); 76 | 77 | final File resultFileProcessor = new File(basedir, "target/spoon-maven-plugin/spoon-nb-statement.txt"); 78 | assertThat(resultFileProcessor).exists(); 79 | } 80 | 81 | @Test 82 | public void testSpoonGoalWithCustomPConfiguration() throws Exception { 83 | File basedir = resources.getBasedir("custom-configuration"); 84 | rule.executeMojo(basedir, "generate"); 85 | 86 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 87 | assertThat(dirOutputResults).exists(); 88 | 89 | final WildcardFileFilter filter = new WildcardFileFilter("result-spoon-*.xml"); 90 | final File[] files = dirOutputResults.listFiles((FileFilter) filter); 91 | assertThat(files.length).isEqualTo(1); 92 | assertThat(files[0].getName()).startsWith("result-spoon"); 93 | 94 | File generateFiles = new File(basedir, "target/generate-source-with-spoon"); 95 | assertThat(generateFiles).exists(); 96 | } 97 | 98 | @Test 99 | public void testSpoonGoalWithSeveralInputs() throws Exception { 100 | File basedir = resources.getBasedir("hello-world-inputs"); 101 | rule.executeMojo(basedir, "generate"); 102 | 103 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 104 | assertThat(dirOutputResults).exists(); 105 | 106 | final File[] files = dirOutputResults.listFiles(); 107 | assertThat(files.length).isEqualTo(1); 108 | assertThat(files[0].getName()).startsWith("result-spoon"); 109 | } 110 | 111 | @Test 112 | public void testSpoonGoalWithProperties() throws Exception { 113 | File basedir = resources.getBasedir("hello-world-with-properties"); 114 | rule.executeMojo(basedir, "generate"); 115 | 116 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 117 | assertThat(dirOutputResults).exists(); 118 | 119 | 120 | final File[] files = dirOutputResults.listFiles(); 121 | assertThat(files.length).isEqualTo(1); 122 | assertThat(files[0].getName()).startsWith("result-spoon"); 123 | 124 | final File contentSource = new File(basedir, "target/generated-sources/spoon/fr/inria/gforge/spoon"); 125 | assertThat(contentSource).exists(); 126 | 127 | final File[] sourceFiles = contentSource.listFiles(); 128 | assertThat(sourceFiles.length).isEqualTo(1); 129 | assertThat(sourceFiles[0].getName()).isEqualTo("NewName.java"); 130 | } 131 | 132 | @Test 133 | public void testSpoonThrowException() throws Exception { 134 | File basedir = resources.getBasedir("hello-world-exception"); 135 | try { 136 | rule.executeMojo(basedir, "generate"); 137 | } catch (MojoExecutionException e) { 138 | assertThat(e.getCause().getCause()).isInstanceOf(SpoonException.class); 139 | } 140 | 141 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 142 | assertThat(dirOutputResults).doesNotExist(); 143 | } 144 | 145 | @Test 146 | public void testSpoonConfigThrowException() throws Exception { 147 | File basedir = resources.getBasedir("hello-world-config-exception"); 148 | try { 149 | rule.executeMojo(basedir, "generate"); 150 | } catch (MojoExecutionException e) { 151 | assertThat(e.getCause()).isInstanceOf(SpoonMavenPluginException.class); 152 | } 153 | 154 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 155 | assertThat(dirOutputResults).doesNotExist(); 156 | } 157 | 158 | @Test 159 | public void testSpoonConfigCatchException() throws Exception { 160 | File basedir = resources.getBasedir("hello-world-config-exception-ignored"); 161 | rule.executeMojo(basedir, "generate"); 162 | 163 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 164 | assertThat(dirOutputResults).doesNotExist(); 165 | } 166 | 167 | @Test 168 | public void testSpoonNoSources() throws Exception { 169 | File basedir = resources.getBasedir("hello-world-no-sources"); 170 | rule.executeMojo(basedir, "generate"); 171 | 172 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 173 | assertThat(dirOutputResults).doesNotExist(); 174 | } 175 | 176 | @Test 177 | public void testSpoonCheckGoal() throws Exception { 178 | File basedir = resources.getBasedir("hello-world"); 179 | rule.executeMojo(basedir, "check"); 180 | 181 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 182 | assertThat(dirOutputResults).exists(); 183 | 184 | final File contentSource = new File(basedir, "target/generated-sources/spoon/fr/inria/gforge/spoon"); 185 | assertThat(contentSource).doesNotExist(); 186 | } 187 | 188 | @Ignore 189 | @Test 190 | public void testSpoonGoalGenerateResultFileForMultimoduleProject() throws Exception { 191 | File basedir = resources.getBasedir("multi-module"); 192 | rule.executeMojo(basedir, "generate"); 193 | 194 | File dirOutputResults = new File(basedir, "module1/target/spoon-maven-plugin"); 195 | assertThat(dirOutputResults).exists(); 196 | 197 | File[] files = dirOutputResults.listFiles(); 198 | assertThat(files.length).isEqualTo(1); 199 | assertThat(files[0].getName()).startsWith("result-spoon"); 200 | 201 | dirOutputResults = new File(basedir, "module2/target/spoon-maven-plugin"); 202 | assertThat(dirOutputResults).exists(); 203 | 204 | files = dirOutputResults.listFiles(); 205 | assertThat(files.length).isEqualTo(1); 206 | assertThat(files[0].getName()).startsWith("result-spoon"); 207 | } 208 | 209 | @Ignore 210 | @Test 211 | public void testSpoonCheckGoalForMultimoduleProject() throws Exception { 212 | File basedir = resources.getBasedir("multi-module"); 213 | rule.executeMojo(basedir, "check"); 214 | 215 | File dirOutputResults = new File(basedir, "module1/target/spoon-maven-plugin"); 216 | assertThat(dirOutputResults).exists(); 217 | 218 | File[] files = dirOutputResults.listFiles(); 219 | assertThat(files.length).isEqualTo(1); 220 | assertThat(files[0].getName()).startsWith("result-spoon"); 221 | 222 | File contentSource = new File(basedir, "module1/target/generated-sources/spoon/fr/inria/gforge/spoon"); 223 | assertThat(contentSource).doesNotExist(); 224 | 225 | dirOutputResults = new File(basedir, "module2/target/spoon-maven-plugin"); 226 | assertThat(dirOutputResults).exists(); 227 | 228 | files = dirOutputResults.listFiles(); 229 | assertThat(files.length).isEqualTo(1); 230 | assertThat(files[0].getName()).startsWith("result-spoon"); 231 | 232 | contentSource = new File(basedir, "module2/target/generated-sources/spoon/fr/inria/gforge/spoon"); 233 | assertThat(contentSource).doesNotExist(); 234 | } 235 | 236 | /** 237 | * The current implementation of this test does not work, but calling a "mvn compile" in the targeted project does work 238 | * So please test it in doing so. 239 | * @throws Exception 240 | */ 241 | @Ignore 242 | @Test 243 | public void testSpoonGoalGenerateResultFileForProjectWithGeneratedSources() throws Exception { 244 | File basedir = resources.getBasedir("antlr-example"); 245 | 246 | rule.executeMojo(basedir, "generate"); 247 | 248 | final File dirOutputResults = new File(basedir, "target/spoon-maven-plugin"); 249 | assertThat(dirOutputResults).exists(); 250 | 251 | final File[] files = dirOutputResults.listFiles(); 252 | assertThat(files.length).isEqualTo(1); 253 | assertThat(files[0].getName()).startsWith("result-spoon"); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/main/java/fr/inria/gforge/spoon/SpoonMojoGenerate.java: -------------------------------------------------------------------------------- 1 | package fr.inria.gforge.spoon; 2 | 3 | import fr.inria.gforge.spoon.configuration.SpoonConfigurationBuilder; 4 | import fr.inria.gforge.spoon.configuration.SpoonConfigurationFactory; 5 | import fr.inria.gforge.spoon.configuration.SpoonMavenPluginException; 6 | import fr.inria.gforge.spoon.logging.ReportBuilder; 7 | import fr.inria.gforge.spoon.logging.ReportFactory; 8 | import fr.inria.gforge.spoon.metrics.PerformanceDecorator; 9 | import fr.inria.gforge.spoon.metrics.SpoonLauncherDecorator; 10 | import fr.inria.gforge.spoon.util.ClasspathHacker; 11 | import fr.inria.gforge.spoon.util.LogWrapper; 12 | import org.apache.maven.artifact.Artifact; 13 | import org.apache.maven.plugin.AbstractMojo; 14 | import org.apache.maven.plugin.MojoExecutionException; 15 | import org.apache.maven.plugin.MojoFailureException; 16 | import org.apache.maven.plugins.annotations.LifecyclePhase; 17 | import org.apache.maven.plugins.annotations.Mojo; 18 | import org.apache.maven.plugins.annotations.Parameter; 19 | import org.apache.maven.plugins.annotations.ResolutionScope; 20 | import org.apache.maven.project.MavenProject; 21 | import spoon.Launcher; 22 | import spoon.compiler.Environment; 23 | import spoon.processing.ProcessorPropertiesImpl; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.lang.management.ManagementFactory; 28 | import java.net.MalformedURLException; 29 | import java.net.URL; 30 | import java.net.URLClassLoader; 31 | import java.util.Properties; 32 | import java.util.stream.Stream; 33 | 34 | @SuppressWarnings("UnusedDeclaration") 35 | @Mojo( 36 | name = "generate", 37 | defaultPhase = LifecyclePhase.GENERATE_SOURCES, 38 | requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) 39 | public class SpoonMojoGenerate extends AbstractMojo { 40 | /** 41 | * Tells to spoon that it should copy comments 42 | */ 43 | @Parameter( 44 | property = "enableComments", 45 | defaultValue = "true") 46 | private boolean enableComments; 47 | 48 | /** 49 | * Input directory for Spoon. 50 | */ 51 | @Parameter(property = "folder.src") 52 | private File srcFolder; 53 | /** 54 | * Input directories for Spoo,. 55 | */ 56 | @Parameter(property = "folder.src") 57 | private File[] srcFolders; 58 | /** 59 | * Output directory where Spoon must generate his output (spooned source code). 60 | */ 61 | @Parameter( 62 | property = "folder.out", 63 | defaultValue = "${project.build.directory}/generated-sources/spoon") 64 | private File outFolder; 65 | /** 66 | * Tells to spoon that it must not assume a full classpath. 67 | */ 68 | @Parameter( 69 | property = "noClasspath", 70 | defaultValue = "false") 71 | private boolean noClasspath; 72 | /** 73 | * Tells to spoon we shouldn't rewrite source code in fully qualified mode. 74 | */ 75 | @Parameter( 76 | property = "withImports", 77 | defaultValue = "false") 78 | private boolean withImports; 79 | /** 80 | * Tells to spoon that it must not assume a full classpath. 81 | */ 82 | @Parameter( 83 | property = "buildOnlyOutdatedFiles", 84 | defaultValue = "false") 85 | private boolean buildOnlyOutdatedFiles; 86 | /** 87 | * Tells to spoon that it must not assume a full classpath. 88 | */ 89 | @Parameter( 90 | property = "noCopyResources", 91 | defaultValue = "false") 92 | private boolean noCopyResources; 93 | /** 94 | * List of processors. 95 | */ 96 | @Parameter(property = "processors") 97 | private String[] processors; 98 | 99 | /** 100 | * Active debug mode to see logs. 101 | */ 102 | @Parameter( 103 | property = "Debug mode", 104 | defaultValue = "false") 105 | private boolean debug; 106 | /** 107 | * Active the compilation of original sources. 108 | */ 109 | @Parameter( 110 | property = "Compile original sources and not source spooned", 111 | defaultValue = "false") 112 | private boolean compileOriginalSources; 113 | /** 114 | * Specify the java version for spoon. 115 | */ 116 | @Parameter( 117 | property = "Java version for spoon", 118 | defaultValue = "8") 119 | private int compliance; 120 | 121 | @Parameter( 122 | property = "Output type", 123 | defaultValue = "classes" 124 | ) 125 | private String outputType; 126 | 127 | @Parameter( 128 | property = "Skip the generated sources as source input for Spoon", 129 | defaultValue = "false" 130 | ) 131 | private boolean skipGeneratedSources; 132 | 133 | @Parameter( 134 | property = "Prevent the build from crashing in case of Spoon error: a warning will only be triggered", 135 | defaultValue = "false" 136 | ) 137 | private boolean skipSpoonErrors; 138 | 139 | @Parameter 140 | private ProcessorProperties[] processorProperties; 141 | 142 | 143 | /** 144 | * Skip execution. 145 | * 146 | * @since 2.6 147 | */ 148 | @Parameter( 149 | property = "spoon.skip", 150 | defaultValue = "false") 151 | private boolean skip; 152 | 153 | /** 154 | * Project spooned with maven information. 155 | */ 156 | @Parameter( 157 | defaultValue = "${project}", 158 | required = true, 159 | readonly = true) 160 | private MavenProject project; 161 | 162 | protected ReportBuilder reportBuilder; 163 | protected Launcher spoonLauncher; 164 | 165 | protected String[] buildArguments(SpoonConfigurationBuilder spoonConfigurationBuilder) throws SpoonMavenPluginException { 166 | spoonConfigurationBuilder.addInputFolder() 167 | .addOutputFolder() 168 | .addCompliance() 169 | .addNoClasspath() 170 | .addWithImports() 171 | .addBuildOnlyOutdatedFiles() 172 | .addNoCopyResources() 173 | .addSourceClasspath() 174 | .addEnableComments() 175 | .addProcessors() 176 | .addTemplates() 177 | .addOutputType(); 178 | 179 | return spoonConfigurationBuilder.build(); 180 | } 181 | 182 | protected void initMojo() throws Exception { 183 | // Initializes builders for report and config of spoon. 184 | try { 185 | this.reportBuilder = ReportFactory.newReportBuilder(this); 186 | } catch (RuntimeException e) { 187 | LogWrapper.warn(this, e.getMessage(), e); 188 | return; 189 | } 190 | final SpoonConfigurationBuilder spoonBuilder = SpoonConfigurationFactory.getConfig(this, this.reportBuilder); 191 | 192 | // Saves project name. 193 | this.reportBuilder.setProjectName(project.getName()); 194 | this.reportBuilder.setModuleName(project.getName()); 195 | 196 | addArtifactsInClasspathOfTargetClassLoader(); 197 | 198 | // Initializes and launch launcher of spoon. 199 | this.spoonLauncher = new Launcher(); 200 | 201 | this.spoonLauncher.setArgs(this.buildArguments(spoonBuilder)); 202 | 203 | if (processorProperties != null) { 204 | this.initSpoonProperties(spoonLauncher); 205 | } 206 | } 207 | 208 | @Override 209 | public void execute() throws MojoExecutionException, MojoFailureException { 210 | if (this.skip) { 211 | return ; 212 | } 213 | 214 | if (project.getPackaging().equals("pom")) { 215 | return ; 216 | } 217 | 218 | try { 219 | this.initMojo(); 220 | final SpoonLauncherDecorator performance = new PerformanceDecorator(this.reportBuilder, this.spoonLauncher); 221 | performance.execute(); 222 | reportBuilder.buildReport(); 223 | } catch (SpoonMavenPluginException e) { 224 | if (this.getSkipSpoonErrors()) { 225 | LogWrapper.warn(this, e.getMessage()+"\n This project will be ignored.", e); 226 | } else { 227 | throw new MojoExecutionException(e.getMessage(), e); 228 | } 229 | } catch (Exception e) { 230 | LogWrapper.error(this, e.getMessage(), e); 231 | throw new MojoExecutionException(e.getMessage(), e); 232 | } 233 | } 234 | 235 | private void initSpoonProperties(Launcher launcher) throws MojoExecutionException { 236 | Environment environment = launcher.getEnvironment(); 237 | 238 | for (ProcessorProperties processorProperties : this.getProcessorProperties()) { 239 | spoon.processing.ProcessorProperties properties = new ProcessorPropertiesImpl(); 240 | 241 | Properties xmlProperties = processorProperties.getProperties(); 242 | 243 | for (Object key : xmlProperties.keySet()) { 244 | String sKey = (String) key; 245 | String value = (String) xmlProperties.get(key); 246 | 247 | properties.set(sKey, value); 248 | } 249 | 250 | environment.setProcessorProperties(processorProperties.getName(), properties); 251 | } 252 | } 253 | 254 | private void addArtifactsInClasspathOfTargetClassLoader() throws IOException { 255 | // Changes classpath of the target class loader. 256 | if (project.getArtifacts() == null || project.getArtifacts().isEmpty()) { 257 | LogWrapper.info(this, "There is not artifact in this project."); 258 | } else { 259 | for (Artifact artifact : project.getArtifacts()) { 260 | LogWrapper.debug(this, artifact.toString()); 261 | ClasspathHacker.addFile(artifact.getFile()); 262 | } 263 | } 264 | 265 | // Displays final classpath of the target classloader. 266 | LogWrapper.info(this, "Running spoon with classpath:"); 267 | final URL[] urlClassLoader = urlsFromClassLoader(ClassLoader.getSystemClassLoader()); 268 | for (URL currentURL : urlClassLoader) { 269 | LogWrapper.info(this, currentURL.toString()); 270 | } 271 | } 272 | 273 | private static URL[] urlsFromClassLoader(ClassLoader classLoader) { 274 | if (classLoader instanceof URLClassLoader) { 275 | return ((URLClassLoader) classLoader).getURLs(); 276 | } 277 | return Stream.of(ManagementFactory.getRuntimeMXBean().getClassPath().split(File.pathSeparator)) 278 | .map(SpoonMojoGenerate::toURL).toArray(URL[]::new); 279 | } 280 | 281 | private static URL toURL(String classPathEntry) { 282 | try { 283 | return new File(classPathEntry).toURI().toURL(); 284 | } 285 | catch (MalformedURLException ex) { 286 | throw new IllegalArgumentException( 287 | "URL could not be created from '" + classPathEntry + "'", ex); 288 | } 289 | } 290 | 291 | public File getSrcFolder() { 292 | return srcFolder; 293 | } 294 | 295 | public File[] getSrcFolders() { 296 | return srcFolders; 297 | } 298 | 299 | public File getOutFolder() { 300 | return outFolder; 301 | } 302 | 303 | public boolean isNoClasspath() { 304 | return noClasspath; 305 | } 306 | 307 | public boolean isWithImports() { 308 | return withImports; 309 | } 310 | 311 | public boolean isBuildOnlyOutdatedFiles() { 312 | return buildOnlyOutdatedFiles; 313 | } 314 | 315 | public boolean isNoCopyResources() { 316 | return noCopyResources; 317 | } 318 | 319 | public String[] getProcessorsPath() { 320 | return processors; 321 | } 322 | 323 | public boolean isDebug() { 324 | return debug; 325 | } 326 | 327 | public boolean isCompileOriginalSources() { 328 | return compileOriginalSources; 329 | } 330 | 331 | public int getCompliance() { 332 | return compliance; 333 | } 334 | 335 | public MavenProject getProject() { 336 | return project; 337 | } 338 | 339 | public void setEnableComments(boolean enableComments) { 340 | this.enableComments = enableComments; 341 | } 342 | 343 | public boolean isEnableComments() { 344 | return enableComments; 345 | } 346 | 347 | public ProcessorProperties[] getProcessorProperties() { 348 | return processorProperties; 349 | } 350 | 351 | public String getOutputType() { 352 | return outputType; 353 | } 354 | 355 | public boolean getSkipGeneratedSources() { 356 | return skipGeneratedSources; 357 | } 358 | 359 | public boolean getSkipSpoonErrors() { 360 | return skipSpoonErrors; 361 | } 362 | } 363 | --------------------------------------------------------------------------------