Wednesday 18 January 2012

Jigsaw: Worldclock with the Maven plugin

Last updated: 2012-02-02

In the previous post I created a Maven plugin for Jigsaw, now I'll adapt Worldclock to use it.
The transformed source code is also available at: https://svn.java.net/svn/worldclock-application~svn/tags/application6-jigsaw-maven 


1. Create the directory
mkdir -p ~/dev/worldclock
2. Get the source
cd ~/dev/worldclock
svn checkout https://svn.java.net/svn/worldclock-application~svn/tags/application6-jigsaw ~/dev/worldclock/application

3. Cleanup
Remove the files that are not needed.
rm -rf ~/dev/worldclock/application/*.*
rm -rf ~/dev/worldclock/application/src/META-INF

4. Code adjustment
The current combinaison of Jigsaw/JDK8/Ubuntu does not support the sytem tray properly, so adjust the Worldclock.java to bypass it
gedit ~/dev/worldclock/application/src/lh/worldclock/WorldClock.java &

on line 106 replace:
if (SystemTray.isSupported())
with:
if (false)

on line 135 replace:
if (showWindowOnStart)
with:
if (true)

5. Move the source
Move the files to create the java sources for the application:
mkdir -p ~/dev/worldclock/application/src/main/java/lh.worlclock.application/lh/worldclock
mv ~/dev/worldclock/application/src/lh/worldclock/*.java ~/dev/worldclock/application/src/main/java/lh.worlclock.application/lh/worldclock
Move the application resources:
mkdir -p ~/dev/worldclock/application/src/main/resources/lh/worldclock
mv ~/dev/worldclock/application/src/lh/worldclock/*.* ~/dev/worldclock/application/src/main/resources/lh/worldclock
Move the panel source:
mkdir -p ~/dev/worldclock/panel/src/main/java/lh.worldclock.panel/lh/worldclock/core
mv ~/dev/worldclock/application/src/lh/worldclock/core/*.java ~/dev/worldclock/panel/src/main/java/lh.worldclock.panel/lh/worldclock/core
Move the panel resources:
mkdir -p ~/dev/worldclock/panel/src/main/resources/lh/worldclock/core
mv ~/dev/worldclock/application/src/lh/worldclock/core/*.jpg ~/dev/worldclock/panel/src/main/resources/lh/worldclock/core
More cleanup:
rm -rf ~/dev/worldclock/application/src/lh/worldclock/core
6. Add module-info for Panel
gedit  ~/dev/worldclock/panel/src/main/java/lh.worldclock.panel/module-info.java &

module lh.worldclock.panel @ 0.7
{
  requires jdk.base;
  requires jdk.desktop;
  exports lh.worldclock.core.*;
}

7. Add module-info for Application
gedit ~/dev/worldclock/application/src/main/java/lh.worlclock.application/module-info.java &

module lh.worldclock.application @ 0.7
{
  requires jdk.base;
  requires jdk.desktop;
  requires lh.worldclock.panel @ 0.7;
  class lh.worldclock.WorldClock;
}

8. Add the pom for Panel
gedit ~/dev/worldclock/panel/pom.xml &

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>lh.worldclock</groupId>
  <artifactId>panel</artifactId>
  <packaging>jmod</packaging>
  <version>0.7-SNAPSHOT</version>
  <name>Worldclock Panel</name>
  <url>http://worldclock-application.java.net/</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>lh.jigsaw</groupId>
                <artifactId>jigsaw-maven-plugin</artifactId>
                <version>0.1-SNAPSHOT</version>
                <extensions>true</extensions>
                <configuration>
                  <libraryDirectory>../modules</libraryDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

9. Add the pom for Application
gedit ~/dev/worldclock/application/pom.xml &

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>lh.worldclock</groupId>
  <artifactId>application</artifactId>
  <packaging>jmod</packaging>
  <version>0.7-SNAPSHOT</version>
  <name>Worldclock Application</name>
  <url>http://worldclock-application.java.net/</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
   <build>
        <plugins>
            <plugin>
                <groupId>lh.jigsaw</groupId>
                <artifactId>jigsaw-maven-plugin</artifactId>
                <version>0.1-SNAPSHOT</version>
                <extensions>true</extensions>
                <configuration>
                  <libraryDirectory>../modules</libraryDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

10. Install Panel
cd ~/dev/worldclock/panel/
mvn install

11. Install Application
cd ~/dev/worldclock/application/
mvn install

12. Run
 cd ~/dev/worldclock/application/
mvn lh.jigsaw:jigsaw-maven-plugin:run
(use ctrl+c on the console to exit)



Jigsaw and Maven: Take 2, a Maven plugin

Last updated: 2012-11-01

This post will create a Maven plugin for Jigsaw. This plugin is based on HK2 Maven plugin.
The post includes all the code needed to build the plugin.
(the resulting source code, except for changes in Jigsaw's source, is available at: https://svn.kenai.com/svn/lh-playground~svn/jigsaw )

Setting up the environment:
1. Build Jigsaw
2. Get Subversion
sudo apt-get install subversion
3. Get Maven

sudo apt-get install maven2
4. Set JAVA_HOME
export JAVA_HOME='/usr/lib/jvm/java-7-openjdk-i386'

Custom Plexus Javac Component:
1. Get the code
cd ~/dev
svn checkout https://svn.codehaus.org/plexus/plexus-components/tags/1.8.1/plexus-compilers/plexus-compiler-javac ~/dev/plexus-compiler-javac
2. Compile without modification
cd ~/dev/plexus-compiler-javac
mvn install
3. Adjust the pom
gedit ~/dev/plexus-compiler-javac/pom.xml &
replace the existing content with

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>org.codehaus.plexus</groupId>
    <artifactId>plexus-compilers</artifactId>
    <version>1.8.1</version>
  </parent>
 
  <artifactId>plexus-compiler-javac</artifactId>

  <name>Jigsaw Plexus Javac Component</name>
  <version>1.8.1-jigsaw</version>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.plexus</groupId>
            <artifactId>plexus-compiler-javac</artifactId>
            <version>1.8.1</version>
            <exclusions>
              <exclusion>
                <groupId>org.codehaus.plexus</groupId>
                <artifactId>plexus-component-api</artifactId>
              </exclusion>
            </exclusions>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-utils</artifactId>
    </dependency>
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-compiler-api</artifactId>
      <version>1.8.1</version>
    </dependency>
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-compiler-test</artifactId>
      <version>1.8.1</version>
    </dependency>
  </dependencies>
 
</project>
 4. Adjust the code
gedit ~/dev/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java &

add the imports:
import java.util.Locale;
import javax.tools.*;
import javax.tools.Diagnostic.Kind;



locate the call to compileInProcess (line 165)
messages = compileInProcess( args );
replace it with:
messages = compileInProcessJSR199( buildCompilerArgumentsJSR199( config ), sourceFiles );

add the method:
        public static List<String> buildCompilerArgumentsJSR199( CompilerConfiguration config )
    {
        List<String> args = new ArrayList<String>();

        // ----------------------------------------------------------------------
        // Set output
        // ----------------------------------------------------------------------

        File destinationDir = new File( config.getOutputLocation() );

        args.add( "-d" );

        args.add( destinationDir.getAbsolutePath() );

        // ----------------------------------------------------------------------
        // Set the class and source paths
        // ----------------------------------------------------------------------

        List classpathEntries = config.getClasspathEntries();
        if ( classpathEntries != null && !classpathEntries.isEmpty())
        {
            args.add( "-classpath" );

            args.add( getPathString( classpathEntries ) );
        }

        List sourceLocations = config.getSourceLocations();
        if ( sourceLocations != null && !sourceLocations.isEmpty() )
        {
            //always pass source path, even if sourceFiles are declared,
            //needed for jsr269 annotation processing, see MCOMPILER-98
            args.add( "-sourcepath" );

            args.add( getPathString( sourceLocations ) );
        }

        //now add jdk 1.6 annotation processing related parameters

        if ( config.getGeneratedSourcesDirectory() != null )
        {
            config.getGeneratedSourcesDirectory().mkdirs();

            args.add( "-s" );
            args.add( config.getGeneratedSourcesDirectory().getAbsolutePath() );
        }
        if ( config.getProc() != null )
        {
            args.add( "-proc:" + config.getProc() );
        }
        if ( config.getAnnotationProcessors() != null )
        {
            args.add("-processor");
            String[] procs = config.getAnnotationProcessors();
            StringBuilder buffer = new StringBuilder();
            for (int i = 0; i < procs.length; i++)
            {
                if (i > 0)
                {
                    buffer.append( "," );
                }

                buffer.append( procs[i] );
            }
            args.add( buffer.toString() );
        }

        if ( config.isOptimize() )
        {
            args.add( "-O" );
        }

        if ( config.isDebug() )
        {
            if ( StringUtils.isNotEmpty( config.getDebugLevel() ) )
            {
                args.add( "-g:" + config.getDebugLevel() );
            }
            else
            {
                args.add( "-g" );
            }
        }

        if ( config.isVerbose() )
        {
            args.add( "-verbose" );
        }

        if ( config.isShowDeprecation() )
        {
            args.add( "-deprecation" );

            // This is required to actually display the deprecation messages
            config.setShowWarnings( true );
        }

        if ( !config.isShowWarnings() )
        {
            args.add( "-nowarn" );
        }

        // TODO: this could be much improved
        if ( StringUtils.isEmpty( config.getTargetVersion() ) )
        {
            // Required, or it defaults to the target of your JDK (eg 1.5)
            args.add( "-target" );
            args.add( "1.1" );
        }
        else
        {
            args.add( "-target" );
            args.add( config.getTargetVersion() );
        }

        if ( !suppressSource( config ) && StringUtils.isEmpty( config.getSourceVersion() ) )
        {
            // If omitted, later JDKs complain about a 1.1 target
            args.add( "-source" );
            args.add( "1.3" );
        }
        else if ( !suppressSource( config ) )
        {
            args.add( "-source" );
            args.add( config.getSourceVersion() );
        }

        if ( !suppressEncoding( config ) && !StringUtils.isEmpty( config.getSourceEncoding() ) )
        {
            args.add( "-encoding" );
            args.add( config.getSourceEncoding() );
        }

        for ( Iterator it = config.getCustomCompilerArguments().entrySet().iterator(); it.hasNext(); )
        {
            Map.Entry entry = (Map.Entry) it.next();

            String key = (String) entry.getKey();

            if ( StringUtils.isEmpty( key ) )
            {
                continue;
            }

            args.add( key );

            String value = (String) entry.getValue();

            if ( StringUtils.isEmpty( value ) )
            {
                continue;
            }

            args.add( value );
        }

        return args;
    }

add the method:
    List compileInProcessJSR199(List<String> args, String[] sources)
    {
      
        List<CompilerError> messages = new ArrayList<CompilerError>();
              
        JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
        getLogger().info("javac =" + javac);
              
        getLogger().info("args = " + args);

        DiagnosticCollector<JavaFileObject> filesDiagnostics = new DiagnosticCollector<JavaFileObject>();
      
        StandardJavaFileManager filesManager = javac.getStandardFileManager(filesDiagnostics, null, null);
      
        Iterable<? extends JavaFileObject> compilationUnit = filesManager.getJavaFileObjects(sources);
                      
        javac.getTask(null, null, filesDiagnostics, args, null, compilationUnit).call();
      
        for (Diagnostic<? extends JavaFileObject> diag: filesDiagnostics.getDiagnostics())
        {
          messages.add(new CompilerError(diag.toString(), diag.getKind() == Kind.ERROR));
        }
      
        try
        {
          filesManager.close();
        }
        catch (IOException ex)
        {
          messages.add(new CompilerError("ex closing file manager: " + ex.getMessage(), false));
        }              
      
        return messages;
    }

5. Compile the modifed code
skip the tests as some are now failing due to the different way to handle errors/warnings
 cd ~/dev/plexus-compiler-javac
mvn -DskipTests=true  install
6. Set JAVA_HOME
 export JAVA_HOME=~/dev/jigsaw/build/linux-i586/jdk-module-image
Custom Maven plugin:
1. Create the directories
mkdir -p ~/dev/jigsaw-maven-plugin/src/main/java/lh/jigsaw/plugin
mkdir -p ~/dev/jigsaw-maven-plugin/src/main/resources/META-INF/plexus
2. Add the pom
gedit ~/dev/jigsaw-maven-plugin/pom.xml &

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>lh.jigsaw</groupId>
  <artifactId>jigsaw-maven-plugin</artifactId>
  <packaging>maven-plugin</packaging>
  <version>0.1-SNAPSHOT</version>

  <name>Jigsaw Maven Plugin</name>
  <description>Maven2 plugin for creating Jigsaw modules</description>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
        <dependencies>            <dependency>              <groupId>org.codehaus.plexus</groupId>              <artifactId>plexus-compiler-javac</artifactId>              <version>1.8.1-jigsaw</version>              <exclusions>                <exclusion>                  <groupId>org.codehaus.plexus</groupId>                  <artifactId>plexus-component-api</artifactId>                </exclusion>              </exclusions>            </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>2.0.4</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.3.2</version>
    </dependency>
   
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-compiler-api</artifactId>
      <version>1.8.1</version>
      <exclusions>
        <exclusion>
          <groupId>org.codehaus.plexus</groupId>
          <artifactId>plexus-component-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-compiler-manager</artifactId>
      <version>1.8.1</version>
      <exclusions>
        <exclusion>
          <groupId>org.codehaus.plexus</groupId>
          <artifactId>plexus-component-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-compiler-javac</artifactId>
      <version>1.8.1-jigsaw</version>
      <exclusions>
        <exclusion>
          <groupId>org.codehaus.plexus</groupId>
          <artifactId>plexus-component-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-container-default</artifactId>
      <version>1.0-alpha-9-stable-1</version>
    </dependency>

    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-utils</artifactId>
      <version>2.0.5</version>
    </dependency>

    <dependency>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-resources-plugin</artifactId>
      <version>2.5</version>
    </dependency>
       
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-toolchain</artifactId>
      <version>1.0</version>
    </dependency>
       
  </dependencies>
</project>
3. Add the components.xml
gedit ~/dev/jigsaw-maven-plugin/src/main/resources/META-INF/plexus/components.xml &

<component-set>
  <!-- this defines a custom life cycle for .jmod -->
  <components>
    <component>
      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
      <role-hint>jmod</role-hint>
      <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
      <configuration>
        <lifecycles>
          <lifecycle>
            <id>default</id>
            <phases>
              <initialize>lh.jigsaw:jigsaw-maven-plugin:initialise</initialize>
              <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
              <compile>lh.jigsaw:jigsaw-maven-plugin:lh-compile</compile>
              <process-test-resources>org.apache.maven.plugins:maven-resources-plugin:testResources</process-test-resources>
              <test-compile>org.apache.maven.plugins:maven-compiler-plugin:testCompile</test-compile>
              <test>org.apache.maven.plugins:maven-surefire-plugin:test</test>
              <package>lh.jigsaw:jigsaw-maven-plugin:jpkg,org.apache.maven.plugins:maven-jar-plugin:jar</package>
              <install>lh.jigsaw:jigsaw-maven-plugin:jmod,org.apache.maven.plugins:maven-install-plugin:install</install>
              <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
            </phases>
          </lifecycle>
        </lifecycles>
      </configuration>
    </component>
    <component>
      <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
      <role-hint>jmod</role-hint>
      <implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
      <configuration>
        <extension>jmod</extension>
        <type>jmod</type>
        <packaging>jmod</packaging>
        <language>java</language>
        <addedToClasspath>true</addedToClasspath>
      </configuration>
    </component>
  </components>
</component-set>
 4. Add InitialiseMojo:
gedit ~/dev/jigsaw-maven-plugin/src/main/java/lh/jigsaw/plugin/InitialiseMojo.java &

package lh.jigsaw.plugin;

import java.io.File;
import java.io.IOException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.openjdk.jigsaw.SimpleLibrary;

/**
 * Initialise the library if it doesn't exist, will fail the build if the directory is not a valid library<br>
 * It will use ${project.build.directory}/library as the default location for the project library, the JDK library
 * as its parent.
 *
 * @requiresProject
 * @goal initialise
 * @requiresDependencyResolution runtime
 * @phase initialize
 *
 * @author ludovic
 */
public class InitialiseMojo extends AbstractMojo
{
  /**
    * library directory
    *
    * @parameter default-value="${project.build.directory}/library"
    * @required
    */
  private File libraryDirectory; 

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException
  {
    getLog().info("InitialiseMojo");
    try
    {     
      if (libraryDirectory.exists())
      {
        SimpleLibrary.open(libraryDirectory);
      }
      else
      {
        File homeLibrary = new File(System.getProperty("java.home"),
                                    "lib/modules");
        SimpleLibrary.create(libraryDirectory, homeLibrary);
      }
     
    }
    catch (IOException ex)
    {
      throw new MojoExecutionException("Library '" + libraryDirectory + "' is not valid.", ex);
    }
  } 
}
5. Add  AbstractCompilerMojo:
This is needed so that the CompilerMojo subclass parent get its dependencies properly injected. The sole change from the original from the Maven compiler plugin is the package name.
gedit ~/dev/jigsaw-maven-plugin/src/main/java/lh/jigsaw/plugin/AbstractCompilerMojo.java &

package lh.jigsaw.plugin;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.CompilationFailureException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.compiler.Compiler;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerError;
import org.codehaus.plexus.compiler.CompilerException;
import org.codehaus.plexus.compiler.CompilerOutputStyle;
import org.codehaus.plexus.compiler.manager.CompilerManager;
import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;

/**
 * TODO: At least one step could be optimized, currently the plugin will do two
 * scans of all the source code if the compiler has to have the entire set of
 * sources. This is currently the case for at least the C# compiler and most
 * likely all the other .NET compilers too.
 *
 * @author others
 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
 * @version $Id: AbstractCompilerMojo.java 990573 2010-08-29 12:12:21Z bentmann $
 * @since 2.0
 */
public abstract class AbstractCompilerMojo
    extends AbstractMojo
{
    // ----------------------------------------------------------------------
    // Configurables
    // ----------------------------------------------------------------------

    /**
     * Indicates whether the build will continue even if there are compilation errors; defaults to true.
     *
     * @parameter expression="${maven.compiler.failOnError}" default-value="true"
     * @since 2.0.2
     */
    private boolean failOnError = true;

    /**
     * Set to true to include debugging information in the compiled class files.
     *
     * @parameter expression="${maven.compiler.debug}" default-value="true"
     */
    private boolean debug = true;

    /**
     * Set to true to show messages about what the compiler is doing.
     *
     * @parameter expression="${maven.compiler.verbose}" default-value="false"
     */
    private boolean verbose;

    /**
     * Sets whether to show source locations where deprecated APIs are used.
     *
     * @parameter expression="${maven.compiler.showDeprecation}" default-value="false"
     */
    private boolean showDeprecation;

    /**
     * Set to true to optimize the compiled code using the compiler's optimization methods.
     *
     * @parameter expression="${maven.compiler.optimize}" default-value="false"
     */
    private boolean optimize;

    /**
     * Set to true to show compilation warnings.
     *
     * @parameter expression="${maven.compiler.showWarnings}" default-value="false"
     */
    private boolean showWarnings;

    /**
     * The -source argument for the Java compiler.
     *
     * @parameter expression="${maven.compiler.source}" default-value="1.5"
     */
    protected String source;

    /**
     * The -target argument for the Java compiler.
     *
     * @parameter expression="${maven.compiler.target}" default-value="1.5"
     */
    protected String target;

    /**
     * The -encoding argument for the Java compiler.
     *
     * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
     */
    private String encoding;

    /**
     * Sets the granularity in milliseconds of the last modification
     * date for testing whether a source needs recompilation.
     *
     * @parameter expression="${lastModGranularityMs}" default-value="0"
     */
    private int staleMillis;

    /**
     * The compiler id of the compiler to use. See this
     * <a href="non-javac-compilers.html">guide</a> for more information.
     *
     * @parameter expression="${maven.compiler.compilerId}" default-value="javac"
     */
    private String compilerId;

    /**
     * Version of the compiler to use, ex. "1.3", "1.5", if fork is set to true.
     *
     * @parameter expression="${maven.compiler.compilerVersion}"
     */
    private String compilerVersion;

    /**
     * Allows running the compiler in a separate process.
     * If "false" it uses the built in compiler, while if "true" it will use an executable.
     *
     * @parameter expression="${maven.compiler.fork}" default-value="false"
     */
    private boolean fork;

    /**
     * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
     * if fork is set to true.
     *
     * @parameter expression="${maven.compiler.meminitial}"
     * @since 2.0.1
     */
    private String meminitial;

    /**
     * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
     * if fork is set to true.
     *
     * @parameter expression="${maven.compiler.maxmem}"
     * @since 2.0.1
     */
    private String maxmem;

    /**
     * Sets the executable of the compiler to use when fork is true.
     *
     * @parameter expression="${maven.compiler.executable}"
     */
    private String executable;

    /**
     * <p>
     * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
     * If not set, both compilation and annotation processing are performed at the same time.
     * </p>
     * <p>
     * Allowed values are:
     *    none - no annotation processing is performed.
     *    only - only annotation processing is done, no compilation.
     * </p>
     *
     * @parameter
     * @since 2.2
     */
    private String proc;

    /**
     * <p>
     *  Names of annotation processors to run. Only applies to JDK 1.6+
     * If not set, the default annotation processors discovery process applies.
     * </p>
     *
     * @parameter
     * @since 2.2
     */
    private String[] annotationProcessors;

    /**
     * <p>
     * Sets the arguments to be passed to the compiler (prepending a dash) if fork is set to true.
     * </p>
     * <p>
     * This is because the list of valid arguments passed to a Java compiler
     * varies based on the compiler version.
     * </p>
     *
     * @parameter
     * @since 2.0.1
     */
    protected Map<String, String> compilerArguments;

    /**
     * <p>
     * Sets the unformatted argument string to be passed to the compiler if fork is set to true.
     * </p>
     * <p>
     * This is because the list of valid arguments passed to a Java compiler
     * varies based on the compiler version.
     * </p>
     *
     * @parameter
     */
    protected String compilerArgument;

    /**
     * Sets the name of the output file when compiling a set of
     * sources to a single file.
     *
     * @parameter expression="${project.build.finalName}"
     */
    private String outputFileName;
   
    /**
     * Keyword list to be appended to the -g  command-line switch. Legal values are none or a comma-separated list of the following keywords: lines, vars, and source.
     * If debuglevel is not specified, by default, nothing will be appended to -g. If debug is not turned on, this attribute will be ignored.
     *
     * @parameter expression="${maven.compiler.debuglevel}"
     * @since 2.1
     */
    private String debuglevel;   

    /** @component */
    private ToolchainManager toolchainManager;
   
    // ----------------------------------------------------------------------
    // Read-only parameters
    // ----------------------------------------------------------------------

    /**
     * The directory to run the compiler from if fork is true.
     *
     * @parameter default-value="${basedir}"
     * @required
     * @readonly
     */
    private File basedir;

    /**
     * The target directory of the compiler if fork is true.
     *
     * @parameter default-value="${project.build.directory}"
     * @required
     * @readonly
     */
    private File buildDirectory;

    /**
     * Plexus compiler manager.
     *
     * @component
     */
    private CompilerManager compilerManager;
   
    /**
     * The current build session instance. This is used for
     * toolchain manager API calls.
     *
     * @parameter default-value="${session}"
     * @required
     * @readonly
     */
    private MavenSession session;

    protected abstract SourceInclusionScanner getSourceInclusionScanner( int staleMillis );

    protected abstract SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding );

    protected abstract List<String> getClasspathElements();

    protected abstract List<String> getCompileSourceRoots();

    protected abstract File getOutputDirectory();
   
    protected abstract String getSource();
   
    protected abstract String getTarget();
   
    protected abstract String getCompilerArgument();
   
    protected abstract Map<String, String> getCompilerArguments();

    protected abstract File getGeneratedSourcesDirectory();

    @SuppressWarnings( "unchecked" )
    public void execute()
        throws MojoExecutionException, CompilationFailureException
    {
        // ----------------------------------------------------------------------
        // Look up the compiler. This is done before other code than can
        // cause the mojo to return before the lookup is done possibly resulting
        // in misconfigured POMs still building.
        // ----------------------------------------------------------------------

        Compiler compiler;

        getLog().debug( "Using compiler '" + compilerId + "'." );

        try
        {
            compiler = compilerManager.getCompiler( compilerId );
        }
        catch ( NoSuchCompilerException e )
        {
            throw new MojoExecutionException( "No such compiler '" + e.getCompilerId() + "'." );
        }
       
        //-----------toolchains start here ----------------------------------
        //use the compilerId as identifier for toolchains as well.
        Toolchain tc = getToolchain();
        if ( tc != null )
        {
            getLog().info( "Toolchain in compiler-plugin: " + tc );
            if ( executable  != null )
            {
                getLog().warn( "Toolchains are ignored, 'executable' parameter is set to " + executable );
            }
            else
            {
                fork = true;
                //TODO somehow shaky dependency between compilerId and tool executable.
                executable = tc.findTool( compilerId );
            }
        }
        // ----------------------------------------------------------------------
        //
        // ----------------------------------------------------------------------

        List<String> compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() );

        if ( compileSourceRoots.isEmpty() )
        {
            getLog().info( "No sources to compile" );

            return;
        }

        if ( getLog().isDebugEnabled() )
        {
            getLog().debug( "Source directories: " + compileSourceRoots.toString().replace( ',', '\n' ) );
            getLog().debug( "Classpath: " + getClasspathElements().toString().replace( ',', '\n' ) );
            getLog().debug( "Output directory: " + getOutputDirectory() );
        }

        // ----------------------------------------------------------------------
        // Create the compiler configuration
        // ----------------------------------------------------------------------

        CompilerConfiguration compilerConfiguration = new CompilerConfiguration();

        compilerConfiguration.setOutputLocation( getOutputDirectory().getAbsolutePath() );

        compilerConfiguration.setClasspathEntries( getClasspathElements() );

        compilerConfiguration.setSourceLocations( compileSourceRoots );

        compilerConfiguration.setOptimize( optimize );

        compilerConfiguration.setDebug( debug );

        if ( debug && StringUtils.isNotEmpty( debuglevel ) )
        {
            String[] split = StringUtils.split( debuglevel, "," );
            for ( int i = 0; i < split.length; i++ )
            {
                if ( !( split[i].equalsIgnoreCase( "none" ) || split[i].equalsIgnoreCase( "lines" )
                    || split[i].equalsIgnoreCase( "vars" ) || split[i].equalsIgnoreCase( "source" ) ) )
                {
                    throw new IllegalArgumentException( "The specified debug level: '" + split[i]
                        + "' is unsupported. " + "Legal values are 'none', 'lines', 'vars', and 'source'." );
                }
            }
            compilerConfiguration.setDebugLevel( debuglevel );
        }       
       
        compilerConfiguration.setVerbose( verbose );

        compilerConfiguration.setShowWarnings( showWarnings );

        compilerConfiguration.setShowDeprecation( showDeprecation );

        compilerConfiguration.setSourceVersion( getSource() );

        compilerConfiguration.setTargetVersion( getTarget() );

        compilerConfiguration.setProc( proc );

        compilerConfiguration.setGeneratedSourcesDirectory( getGeneratedSourcesDirectory() );

        compilerConfiguration.setAnnotationProcessors( annotationProcessors );

        compilerConfiguration.setSourceEncoding( encoding );
       
        Map<String, String> effectiveCompilerArguments = getCompilerArguments();

        String effectiveCompilerArgument = getCompilerArgument();

        if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null ) )
        {
            LinkedHashMap<String, String> cplrArgsCopy = new LinkedHashMap<String, String>();
            if ( effectiveCompilerArguments != null )
            {
                for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
                {
                    String key = (String) me.getKey();
                    String value = (String) me.getValue();
                    if ( !key.startsWith( "-" ) )
                    {
                        key = "-" + key;
                    }
                    cplrArgsCopy.put( key, value );
                }
            }
            if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
            {
                cplrArgsCopy.put( effectiveCompilerArgument, null );
            }
            compilerConfiguration.setCustomCompilerArguments( cplrArgsCopy );
        }

        compilerConfiguration.setFork( fork );

        if ( fork )
        {
            if ( !StringUtils.isEmpty( meminitial ) )
            {
                String value = getMemoryValue( meminitial );

                if ( value != null )
                {
                    compilerConfiguration.setMeminitial( value );
                }
                else
                {
                    getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
                }
            }

            if ( !StringUtils.isEmpty( maxmem ) )
            {
                String value = getMemoryValue( maxmem );

                if ( value != null )
                {
                    compilerConfiguration.setMaxmem( value );
                }
                else
                {
                    getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
                }
            }
        }

        compilerConfiguration.setExecutable( executable );

        compilerConfiguration.setWorkingDirectory( basedir );

        compilerConfiguration.setCompilerVersion( compilerVersion );

        compilerConfiguration.setBuildDirectory( buildDirectory );

        compilerConfiguration.setOutputFileName( outputFileName );

        // TODO: have an option to always compile (without need to clean)
        Set<File> staleSources;

        boolean canUpdateTarget;

        try
        {
            staleSources =
                computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );

            canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );

            if ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) &&
                !canUpdateTarget )
            {
                getLog().info( "RESCANNING!" );
                // TODO: This second scan for source files is sub-optimal
                String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );

                Set<File> sources = computeStaleSources( compilerConfiguration, compiler,
                                                   getSourceInclusionScanner( inputFileEnding ) );

                compilerConfiguration.setSourceFiles( sources );
            }
            else
            {
                compilerConfiguration.setSourceFiles( staleSources );
            }
        }
        catch ( CompilerException e )
        {
            throw new MojoExecutionException( "Error while computing stale sources.", e );
        }

        if ( staleSources.isEmpty() )
        {
            getLog().info( "Nothing to compile - all classes are up to date" );

            return;
        }

        // ----------------------------------------------------------------------
        // Dump configuration
        // ----------------------------------------------------------------------

        if ( getLog().isDebugEnabled() )
        {
            getLog().debug( "Classpath:" );

            for ( String s : getClasspathElements() )
            {
                getLog().debug( " " + s );
            }

            getLog().debug( "Source roots:" );

            for ( String root : getCompileSourceRoots() )
            {
                getLog().debug( " " + root );
            }

            try
            {
                if ( fork )
                {
                    if ( compilerConfiguration.getExecutable() != null )
                    {
                        getLog().debug( "Excutable: " );
                        getLog().debug( " " + compilerConfiguration.getExecutable() );
                    }
                }

                String[] cl = compiler.createCommandLine( compilerConfiguration );
                if ( cl != null && cl.length > 0 )
                {
                    StringBuffer sb = new StringBuffer();
                    sb.append( cl[0] );
                    for ( int i = 1; i < cl.length; i++ )
                    {
                        sb.append( " " );
                        sb.append( cl[i] );
                    }
                    getLog().debug( "Command line options:" );
                    getLog().debug( sb );
                }
            }
            catch ( CompilerException ce )
            {
                getLog().debug( ce );
            }
        }

        // ----------------------------------------------------------------------
        // Compile!
        // ----------------------------------------------------------------------

        if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
        {
            getLog().warn(
                           "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
                               + ", i.e. build is platform dependent!" );
        }

        List<CompilerError> messages;

        try
        {
            messages = compiler.compile( compilerConfiguration );
        }
        catch ( Exception e )
        {
            // TODO: don't catch Exception
            throw new MojoExecutionException( "Fatal error compiling", e );
        }

        List<CompilerError> warnings = new ArrayList<CompilerError>();
        List<CompilerError> errors = new ArrayList<CompilerError>();
        if ( messages != null )
        {
            for ( CompilerError message : messages )
            {
                if ( message.isError() )
                {
                    errors.add( message );
                }
                else
                {
                    warnings.add( message );
                }
            }
        }

        if ( failOnError && !errors.isEmpty() )
        {
            if ( !warnings.isEmpty() )
            {
                getLog().info( "-------------------------------------------------------------" );
                getLog().warn( "COMPILATION WARNING : " );
                getLog().info( "-------------------------------------------------------------" );
                for ( CompilerError warning : warnings )
                {
                    getLog().warn( warning.toString() );
                }
                getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
                getLog().info( "-------------------------------------------------------------" );
            }
           
            getLog().info( "-------------------------------------------------------------" );
            getLog().error( "COMPILATION ERROR : " );
            getLog().info( "-------------------------------------------------------------" );
           
            for ( CompilerError error : errors )
            {
                    getLog().error( error.toString() );
            }
            getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
            getLog().info( "-------------------------------------------------------------" );
           
            throw new CompilationFailureException( errors );
        }
        else
        {
            for ( CompilerError message : messages )
            {
                getLog().warn( message.toString() );
            }
        }
    }

    private String getMemoryValue( String setting )
    {
        String value = null;

        // Allow '128' or '128m'
        if ( isDigits( setting ) )
        {
            value = setting + "m";
        }
        else
        {
            if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) ) &&
                ( setting.toLowerCase().endsWith( "m" ) ) )
            {
                value = setting;
            }
        }
        return value;
    }

    //TODO remove the part with ToolchainManager lookup once we depend on
    //3.0.9 (have it as prerequisite). Define as regular component field then.
    private Toolchain getToolchain()
    {
        Toolchain tc = null;
        if ( toolchainManager != null )
        {
            tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
        }
        return tc;
    }

    private boolean isDigits( String string )
    {
        for ( int i = 0; i < string.length(); i++ )
        {
            if ( !Character.isDigit( string.charAt( i ) ) )
            {
                return false;
            }
        }
        return true;
    }

    @SuppressWarnings( "unchecked" )
    private Set<File> computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler,
                                     SourceInclusionScanner scanner )
        throws MojoExecutionException, CompilerException
    {
        CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();

        SourceMapping mapping;

        File outputDirectory;

        if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
        {
            mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ), compiler
                .getOutputFileEnding( compilerConfiguration ) );

            outputDirectory = getOutputDirectory();
        }
        else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
        {
            mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ), compiler
                .getOutputFile( compilerConfiguration ) );

            outputDirectory = buildDirectory;
        }
        else
        {
            throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." );
        }

        scanner.addSourceMapping( mapping );

        Set<File> staleSources = new HashSet<File>();

        for ( String sourceRoot : getCompileSourceRoots() )
        {
            File rootFile = new File( sourceRoot );

            if ( !rootFile.isDirectory() )
            {
                continue;
            }

            try
            {
                staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) );
            }
            catch ( InclusionScanException e )
            {
                throw new MojoExecutionException(
                    "Error scanning source root: \'" + sourceRoot + "\' " + "for stale files to recompile.", e );
            }
        }

        return staleSources;
    }

    /**
     * @todo also in ant plugin. This should be resolved at some point so that it does not need to
     * be calculated continuously - or should the plugins accept empty source roots as is?
     */
    private static List<String> removeEmptyCompileSourceRoots( List<String> compileSourceRootsList )
    {
        List<String> newCompileSourceRootsList = new ArrayList<String>();
        if ( compileSourceRootsList != null )
        {
            // copy as I may be modifying it
            for ( String srcDir : compileSourceRootsList )
            {
                if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() )
                {
                    newCompileSourceRootsList.add( srcDir );
                }
            }
        }
        return newCompileSourceRootsList;
    }
}
6. Add  CompilerMojo:
Jigsaw adjustments to the compiler mojo.
gedit ~/dev/jigsaw-maven-plugin/src/main/java/lh/jigsaw/plugin/CompilerMojo.java &

package lh.jigsaw.plugin;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.CompilationFailureException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;

/**
 * Jigsaw adjustments to the compiler mojo
 *
 * @since 2.0
 * @goal lh-compile
 * @phase compile
 * @threadSafe
 * @requiresDependencyResolution compile
 */
public class CompilerMojo extends AbstractCompilerMojo
{

  /**
   * @parameter expression="${project}"
   * @required
   * @readonly
   */
  private MavenProject project;

  /**
   * The source directories containing the sources to be compiled.
   *
   * @parameter default-value="${project.compileSourceRoots}"
   * @required
   * @readonly
   */
  private List<String> compileSourceRoots;

  /**
   * <p>
   * Specify where to place generated source files created by annotation processing.
   * Only applies to JDK 1.6+
   * </p>
   * @parameter default-value="${project.build.directory}/generated-sources/annotations"
   * @since 2.2
   */
  private File generatedSourcesDirectory;

  /**
   * Project classpath.
   *
   * @parameter default-value="${project.compileClasspathElements}"
   * @required
   * @readonly
   */
  private List<String> classpathElements;

  /**
   * The directory for compiled classes.
   *
   * @parameter default-value="${project.build.outputDirectory}"
   * @required
   * @readonly
   */
  private File outputDirectory;

  /**
   * Project artifacts.
   *
   * @parameter default-value="${project.artifact}"
   * @required
   * @readonly
   * @todo this is an export variable, really
   */
  private Artifact projectArtifact;

  /**
   * A list of inclusion filters for the compiler.
   *
   * @parameter
   */
  private Set<String> includes = new HashSet<>();

  /**
   * A list of exclusion filters for the compiler.
   *
   * @parameter
   */
  private Set<String> excludes = new HashSet<>();

  /**
   * library directory
   *
   * @parameter default-value="${project.build.directory}/library"
   * @required
   */
  private File libraryDirectory;

  @Override
  protected List<String> getCompileSourceRoots()
  {
    return compileSourceRoots;
  }

  @Override
  protected List<String> getClasspathElements()
  {
    return classpathElements;
  }

  @Override
  protected File getOutputDirectory()
  {
    return outputDirectory;
  }

  private void lhJigsawCompilerAdjustments()
  {
    compilerArguments = new HashMap<>(2);

    compilerArguments.put("modulepath", outputDirectory.getAbsolutePath());
    compilerArguments.put("L", libraryDirectory.getAbsolutePath());
  }

  @Override
  public void execute() throws MojoExecutionException, CompilationFailureException
  {
    lhJigsawCompilerAdjustments();

    super.execute();

    projectArtifact.setFile(outputDirectory);
  }

  @Override
  protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis)
  {
    SourceInclusionScanner scanner;

    if (includes.isEmpty() && excludes.isEmpty())
    {
      scanner = new StaleSourceScanner(staleMillis);
    }
    else
    {
      if (includes.isEmpty())
      {
        includes.add("**/*.java");
      }
      scanner = new StaleSourceScanner(staleMillis, includes, excludes);
    }

    return scanner;
  }

  @Override
  protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding)
  {
    SourceInclusionScanner scanner;

    if (includes.isEmpty() && excludes.isEmpty())
    {
      includes = Collections.singleton("**/*." + inputFileEnding);
      scanner = new SimpleSourceInclusionScanner(includes, Collections.EMPTY_SET);
    }
    else
    {
      if (includes.isEmpty())
      {
        includes.add("**/*." + inputFileEnding);
      }
      scanner = new SimpleSourceInclusionScanner(includes, excludes);
    }

    return scanner;
  }

  @Override
  protected String getSource()
  {
    return source;
  }

  @Override
  protected String getTarget()
  {
    return target;
  }

  @Override
  protected String getCompilerArgument()
  {
    return compilerArgument;
  }

  @Override
  protected Map<String, String> getCompilerArguments()
  {
    return compilerArguments;
  }

  @Override
  protected File getGeneratedSourcesDirectory()
  {
    return generatedSourcesDirectory;
  }
}
7. Add JPkgMojo:
Package the module into a .jmod file.
gedit ~/dev/jigsaw-maven-plugin/src/main/java/lh/jigsaw/plugin/JPkgMojo.java &

package lh.jigsaw.plugin;

import java.io.File;
import java.io.IOException;
import java.lang.module.ModuleInfo;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.openjdk.jigsaw.JigsawModuleSystem;
import org.openjdk.jigsaw.cli.*;

/**
 * Package the module into a jmod file.<br>
 * Drops the file into ${project.build.directory}/module
 *
 * @requiresProject
 * @goal jpkg
 * @requiresDependencyResolution runtime
 * @phase package
 *
 * @author ludovic
 */
public class JPkgMojo extends AbstractMojo
{

  /**
   * Library directory
   *
   * @parameter default-value="${project.build.directory}/library"
   * @required
   */
  private File libraryDirectory;

  /**
   * Classes directory.
   *
   * @parameter expression="${project.build.outputDirectory}"
   * @required
   * @readonly
   */
  private File classesDir;

  /**
   * Output directory.
   *
   * @parameter expression="${project.build.directory}/module"
   * @required
   * @readonly
   */
  private File outputDir;

  /**
   * Module name
   *
   * @parameter expression="${moduleName}"
   * @readonly
   */
  private String moduleName;

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException
  {
    getLog().info("JPkgMojo");
    try
    {
      if (outputDir.exists())
      {
        deleteDir(outputDir.toPath());
      }

      outputDir.mkdirs();

      String[] params = new String[]
      {
        "-L",
        libraryDirectory.getAbsolutePath(),
        "-m",
        classesDir.getAbsolutePath(),
        "-d",
        outputDir.getAbsolutePath(),
        "jmod",
        getModuleName()
      };
     
      getLog().info(Arrays.toString(params));
     
      Packager.run(params);
    }
    catch (MojoExecutionException ex)
    {
      throw ex;
    }
    catch (Exception ex)
    {
      throw new MojoExecutionException("Could not package '" + moduleName + "'.", ex);
    }

  }

  private String getModuleName() throws MojoExecutionException
  {
    if (moduleName == null)
    {
      try
      {
        Path path = classesDir.toPath().resolve("module-info.class");
        byte[] bytes = java.nio.file.Files.readAllBytes(path);
        JigsawModuleSystem jms = JigsawModuleSystem.instance();
        ModuleInfo mif = jms.parseModuleInfo(bytes);
        moduleName = mif.id().name();
        getLog().info("module name read from module-info = " + moduleName);

      }
      catch (IOException ex)
      {
        throw new MojoExecutionException("Could not read the module name, check that module-info.class exist.", ex);
      }
    }
    return moduleName;
  }

  private static void deleteDir(Path dir) throws IOException
  {
    try (DirectoryStream<Path> s = Files.newDirectoryStream(dir))
    {
      for (Path p : s)
      {
        if (Files.isDirectory(p))
        {
          deleteDir(p);
        }
        Files.delete(p);
      }
    }
  }
}
8 Add JModMojo:
Installs the jmod file into the library.
gedit ~/dev/jigsaw-maven-plugin/src/main/java/lh/jigsaw/plugin/JModMojo.java &

package lh.jigsaw.plugin;

import java.io.File;
import java.io.IOException;
import java.lang.module.ModuleId;
import java.lang.module.ModuleInfo;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.openjdk.jigsaw.JigsawModuleSystem;
import org.openjdk.jigsaw.cli.Librarian;

/**
 * Installs the jmod file into the library.<br>
 * Remove the module from that library if it already exist before adding the new content.
 *
 * @requiresProject
 * @goal jmod
 * @requiresDependencyResolution runtime
 * @phase install
 *
 * @author ludovic
 */
public class JModMojo extends AbstractMojo
{

  /**
   * library directory
   *
   * @parameter default-value="${project.build.directory}/library"
   * @required
   */
  private File libraryDirectory;

  /**
   * Classes directory.
   *
   * @parameter expression="${project.build.outputDirectory}"
   * @required
   * @readonly
   */
  private File classesDir;

  /**
   * module dir
   *
   * @parameter expression="${project.build.directory}/module/"
   * @required
   * @readonly
   */
  private File moduleDir;

  /**
   * Module name
   *
   * @parameter expression="${moduleName}""
   * @readonly
   */
  private String moduleName;

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException
  {
    getLog().info("JModMojo");
    try
    {
      File[] files = moduleDir.listFiles();
      if (files == null)
      {
        throw new MojoExecutionException("no files in <target>/module directory");
      }
      File moduleFile = null;
      for (File f : files)
      {
        if (f.getName().endsWith("jmod"))
        {
          moduleFile = f;
          break;
        }
      }
      if (moduleFile == null)
      {
        throw new MojoExecutionException("no jmod file in <target>/module directory");
      }

      // allow to update the module by removing it, may have to reindex?
      ModuleId moduleId = getModuleId();
      Path installedModule = libraryDirectory.toPath().resolve(moduleId.name()).resolve(moduleId.version().toString());
      if (Files.exists(installedModule))
      {
        String[] params = new String[]
        {
          "-L",
          libraryDirectory.getAbsolutePath(),
          "remove",
          "-f",
          moduleId.toString()
        };

        getLog().info(Arrays.toString(params));

        Librarian.run(params);

      }

      String[] params = new String[]
      {
        "-L",
        libraryDirectory.getAbsolutePath(),
        "install",
        moduleFile.getAbsolutePath()
      };
     
      getLog().info(Arrays.toString(params));
     
      Librarian.run(params);
    }
    catch (MojoExecutionException ex)
    {
      throw ex;
    }
    catch (Exception ex)
    {
      throw new MojoExecutionException("Could not install '" + moduleName + "' into the '" + libraryDirectory + "' library.", ex);
    }

  }

  private String getModuleName() throws MojoExecutionException
  {
    if (moduleName == null)
    {
      try
      {
        Path path = classesDir.toPath().resolve("module-info.class");
        byte[] bytes = java.nio.file.Files.readAllBytes(path);
        JigsawModuleSystem jms = JigsawModuleSystem.instance();
        ModuleInfo mif = jms.parseModuleInfo(bytes);
        moduleName = mif.id().name();
        getLog().info("module name read from module-info = " + moduleName);

      }
      catch (IOException ex)
      {
        throw new MojoExecutionException("Could not read the module name, check that module-info.class exist.", ex);
      }
    }
    return moduleName;
  }

  private ModuleId getModuleId() throws MojoExecutionException
  {
    ModuleId id;
    try
    {
      Path path = classesDir.toPath().resolve("module-info.class");
      byte[] bytes = java.nio.file.Files.readAllBytes(path);
      JigsawModuleSystem jms = JigsawModuleSystem.instance();
      ModuleInfo mif = jms.parseModuleInfo(bytes);
      id = mif.id();
      getLog().info("module name read from module-info = " + moduleName);

    }
    catch (IOException ex)
    {
      throw new MojoExecutionException("Could not read the module name, check that module-info.class exist.", ex);
    }
   
    return id;
  }
}
9. Add RunMojo:
Runs the project's module from the library.
gedit ~/dev/jigsaw-maven-plugin/src/main/java/lh/jigsaw/plugin/RunMojo.java &

package lh.jigsaw.plugin;

import java.io.File;
import java.io.IOException;
import java.lang.module.ModuleInfo;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.openjdk.jigsaw.JigsawModuleSystem;

/**
 * Runs the project's module from the library.
 *
 * @requiresProject
 * @goal run
 * @requiresDependencyResolution runtime
 *
 * @author ludovic
 */
public class RunMojo extends AbstractMojo
{

  /**
   * library directory
   *
   * @parameter default-value="${project.build.directory}/library"
   * @required
   */
  private File libraryDirectory;

  /**
   * Classes directory.
   *
   * @parameter expression="${project.build.outputDirectory}"
   * @required
   * @readonly
   */
  private File classesDir;

  /**
   * Module name
   *
   * @parameter expression="${moduleName}""
   * @readonly
   */
  private String moduleName;

  /**
   * vm args
   * @parameter default-value="${vmargs}"
   */
  String vmargs;

  /**
   * args
   * @parameter default-value="${args}"
   */
  String args;

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException
  {
    getLog().info("RunMojo");
    try
    {
      List<String> cmdline = new ArrayList<>();

      Path javaCmdDirPath = Paths.get(System.getProperty("java.home"));
      Path javaCmdPath = javaCmdDirPath.resolve("bin/java");
      cmdline.add(javaCmdPath.toString());

      cmdline.add("-L");
      cmdline.add(libraryDirectory.toString());

      cmdline.addAll(split(vmargs));

      cmdline.add("-m");
      cmdline.add(getModuleName());

      cmdline.addAll(split(args));

      ProcessBuilder pb = new ProcessBuilder(cmdline);
      System.out.println("cmd: " + pb.command());

      pb = pb.inheritIO();
      Process p = pb.start();
      int ret = p.waitFor();
      System.out.println("ret = " + ret);
    }
    catch (Exception ex)
    {
      getLog().error(ex);
    }

  }

  private String getModuleName()
  {
    if (moduleName == null)
    {
      try
      {
        Path path = classesDir.toPath().resolve("module-info.class");
        byte[] bytes = java.nio.file.Files.readAllBytes(path);
        JigsawModuleSystem jms = JigsawModuleSystem.instance();
        ModuleInfo mif = jms.parseModuleInfo(bytes);
        moduleName = mif.id().name();
        System.out.println("name = " + moduleName);

      }
      catch (IOException ex)
      {
        getLog().error(ex);
      }
    }
    return moduleName;
  }

  private List<String> split(String arg)
  {
    if (arg == null)
    {
      return Collections.emptyList();
    }

    String[] elts = arg.split(" ");
    return Arrays.asList(elts);
  }
}
10. Compile and install the plugin:
cd ~/dev/jigsaw-maven-plugin
mvn install
Using the plugin
See Jigsaw: Worldclock with the Maven plugin