001package lombok.maven;
002
003import java.io.File;
004import java.io.IOException;
005import java.lang.reflect.InvocationTargetException;
006import java.nio.charset.Charset;
007import java.nio.charset.UnsupportedCharsetException;
008import java.util.ArrayList;
009import java.util.List;
010import java.util.Map;
011
012import org.apache.commons.lang3.JavaVersion;
013import org.apache.commons.lang3.StringUtils;
014import org.apache.commons.lang3.SystemUtils;
015import org.apache.maven.artifact.Artifact;
016import org.apache.maven.plugin.AbstractMojo;
017import org.apache.maven.plugin.MojoExecutionException;
018import org.apache.maven.plugin.logging.Log;
019import org.apache.maven.plugin.descriptor.PluginDescriptor;
020import org.apache.maven.plugins.annotations.Component;
021import org.apache.maven.plugins.annotations.Parameter;
022import org.apache.maven.project.MavenProject;
023import org.sonatype.plexus.build.incremental.BuildContext;
024
025import lombok.launch.Delombok;
026
027/**
028 * Abstract mojo to Delombok java source with lombok annotations.
029 *
030 * @author <a href="mailto:anthony@whitford.com">Anthony Whitford</a>
031 * @see <a href="http://projectlombok.org/features/delombok.html">Delombok</a>
032 */
033public abstract class AbstractDelombokMojo extends AbstractMojo {
034
035    /**
036     * Specifies whether the delombok generation should be skipped.
037     */
038    @Parameter(property="lombok.delombok.skip", defaultValue="false", required=true)
039    protected boolean skip;
040
041    /**
042     * Encoding.
043     */
044    @Parameter(property="lombok.encoding", defaultValue="${project.build.sourceEncoding}", required=true)
045    protected String encoding;
046
047    /**
048     * Verbose flag.  Print the name of each file as it is being delombok-ed.
049     */
050    @Parameter(property="lombok.verbose", defaultValue="false", required=true)
051    protected boolean verbose;
052
053    /**
054     * Add output directory flag.  Adds the output directory to the Maven build path.
055     */
056    @Parameter(property="lombok.addOutputDirectory", defaultValue="true", required=true)
057    protected boolean addOutputDirectory;
058
059    /**
060     * Formatting preferences.
061     */
062    @Parameter
063    protected Map<String, String> formatPreferences;
064
065    /**
066     * The Maven project to act upon.
067     */
068    @Parameter(property="project", required=true, readonly=true)
069    protected MavenProject project;
070
071    /**
072     * The plugin dependencies.
073     */
074    @Parameter(property="plugin.artifacts", required=true, readonly=true)
075    private List<Artifact> pluginArtifacts;
076
077    @Parameter(property="plugin", required=true, readonly=true)
078    protected PluginDescriptor pluginDescriptor;
079
080    /**
081     * Build Context for improved Maven-Eclipse integration.
082     */
083    @Component
084    private BuildContext buildContext;
085
086    protected abstract String getGoalDescription ();
087
088    protected abstract File getOutputDirectory();
089
090    protected abstract File getSourceDirectory();
091
092    protected abstract String getSourcePath();
093
094    protected abstract void addSourceRoot(String path);
095
096    @Override
097    public void execute() throws MojoExecutionException {
098        final Log logger = getLog();
099        assert null != logger;
100
101        final String goal = getGoalDescription();
102        logger.debug("Starting " + goal);
103        final File outputDirectory = getOutputDirectory();
104        logger.debug("outputDirectory: " + outputDirectory);
105        final File sourceDirectory = getSourceDirectory();
106        logger.debug("sourceDirectory: " + sourceDirectory);
107        final String sourcePath = getSourcePath();
108        logger.debug("sourcePath: " + sourcePath);
109
110        if (this.skip) {
111            logger.warn("Skipping " + goal);
112        } else if (sourceDirectory.exists()) {
113            // Build a classPath for delombok...
114            final StringBuilder classPathBuilder = new StringBuilder();
115            for (final Object artifact : project.getArtifacts()) {
116                classPathBuilder.append(((Artifact)artifact).getFile()).append(File.pathSeparatorChar);
117            }
118            for (final Artifact artifact : pluginArtifacts) {
119                classPathBuilder.append(artifact.getFile()).append(File.pathSeparatorChar);
120            }
121            // delombok needs tools.jar (prior to Java 9)...
122            if (!SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9)) {
123                final String javaHome = System.getProperty("java.home");
124                final File toolsJar = new File (javaHome,
125                    ".." + File.separatorChar + "lib" + File.separatorChar + "tools.jar");
126                if (toolsJar.exists()) {
127                    try {
128                        pluginDescriptor.getClassRealm().addURL(toolsJar.toURI().toURL());
129                    } catch (final IOException e) {
130                        logger.warn("Unable to add tools.jar; " + toolsJar);
131                    }
132                } else {
133                    logger.warn("Unable to detect tools.jar; java.home is " + javaHome);
134                }
135            }
136            final String classPath = classPathBuilder.toString();
137            logger.debug("classpath: " + classPath);
138            try {
139                final Delombok delombok = new Delombok();
140                delombok.setVerbose(this.verbose);
141                delombok.setClasspath(classPath);
142
143                if (StringUtils.isNotBlank(this.encoding)) {
144                    try {
145                        delombok.setCharset(this.encoding);
146                    } catch (final UnsupportedCharsetException e) {
147                        logger.error("The encoding parameter is invalid; Please check!", e);
148                        throw new MojoExecutionException("Unknown charset: " + this.encoding, e);
149                    }
150                } else {
151                    logger.warn("No encoding specified; using default: " + Charset.defaultCharset());
152                }
153
154                if (null != formatPreferences && !formatPreferences.isEmpty()) {
155                    try {
156                        // Construct a list array just like the command-line option...
157                        final List<String> formatOptions = new ArrayList<String>(formatPreferences.size());
158                        for (final Map.Entry<String, String> entry : formatPreferences.entrySet()) {
159                            final String key = entry.getKey();
160                            // "pretty" is an exception -- it has no value...
161                            formatOptions.add( "pretty".equalsIgnoreCase(key) ? key : (key + ':' + entry.getValue()) );
162                        }
163                        delombok.setFormatPreferences(delombok.formatOptionsToMap(formatOptions));
164                    } catch (final Exception e) {
165                        logger.error("The formatPreferences parameter is invalid; Please check!", e);
166                        throw new MojoExecutionException("Invalid formatPreferences: " + this.formatPreferences, e);
167                    }
168                }
169
170                try {
171                    delombok.setOutput(outputDirectory);
172                    delombok.setSourcepath(getSourcePath());
173                    delombok.addDirectory(sourceDirectory);
174                    if (buildContext.hasDelta(sourceDirectory)) {
175                        delombok.delombok();
176                        logger.info(goal + " complete.");
177
178                        if (this.addOutputDirectory) {
179                            // adding generated sources to Maven project
180                            addSourceRoot(outputDirectory.getCanonicalPath());
181                            // Notify build context about a file created, updated or deleted...
182                            buildContext.refresh(outputDirectory);
183                        }
184                    } else {
185                        logger.info(goal + " skipped; No deltas detected.");
186                    }
187                } catch (final IOException e) {
188                    logger.error("Unable to delombok!", e);
189                    throw new MojoExecutionException("I/O problem during delombok", e);
190                }
191            } catch (final ClassNotFoundException e) {
192                throw new MojoExecutionException("Unable to delombok", e);
193            } catch (final IllegalAccessException e) {
194                throw new MojoExecutionException("Unable to delombok", e);
195            } catch (final InvocationTargetException e) {
196                throw new MojoExecutionException("Unable to delombok", e);
197            } catch (final InstantiationException e) {
198                throw new MojoExecutionException("Unable to delombok", e);
199            } catch (final NoSuchMethodException e) {
200                throw new MojoExecutionException("Unable to delombok", e);
201            }
202        } else {
203            logger.warn("Skipping " + goal + "; no source to process.");
204        }
205    }
206}