com.igormaznitsa.jcp.maven.PreprocessMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcp Show documentation
Show all versions of jcp Show documentation
Powerful multi-pass preprocessor to process directives situated in C-styled commentaries
The newest version!
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* 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.
*/
package com.igormaznitsa.jcp.maven;
import static com.igormaznitsa.jcp.utils.GetUtils.ensureNonNull;
import com.igormaznitsa.jcp.JcpPreprocessor;
import com.igormaznitsa.jcp.context.CommentRemoverType;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.logger.PreprocessorLogger;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.AbstractMojoExecutionException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
/**
* Mojo to preprocess either standard maven project source roots or custom source roots and place prepsocessed result into defined target folder.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Mojo(name = "preprocess", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true)
public class PreprocessMojo extends AbstractMojo implements PreprocessorLogger {
/**
* Maven project source roots for compilation phase.
*/
@Setter(AccessLevel.NONE)
@Parameter(alias = "compileSourceRoots", defaultValue = "${project.compileSourceRoots}", required = true, readonly = true)
private List compileSourceRoots = new ArrayList<>();
/**
* Maven project test source roots for test phase.
*/
@Setter(AccessLevel.NONE)
@Parameter(alias = "testCompileSourceRoots", defaultValue = "${project.testCompileSourceRoots}", required = true, readonly = true)
private List testCompileSourceRoots = new ArrayList<>();
/**
* Maven project to be preprocessed.
*/
@Setter(AccessLevel.NONE)
@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project;
/**
* Maven session to be preprocessed.
*
* @since 7.0.5
*/
@Setter(AccessLevel.NONE)
@Parameter(defaultValue = "${session}", required = true, readonly = true)
private MavenSession session;
/**
* Source root folders for preprocessing, if it is empty then project provided folders will be used.
*
* @since 7.0.0
*/
@Parameter(alias = "sources")
private List sources = null;
/**
* End of line string to be used in reprocessed results. It supports java escaping chars.
*
* @since 7.0.0
*/
@Parameter(alias = "eol", property = "jcp.line.separator", defaultValue = "${line.separator}")
private String eol = null;
/**
* Name of a class to be used as action preprocessor extension. The class must have default constructor.
* Empty string will be recognized as missing class name.
*
* @since 7.1.2
* @see com.igormaznitsa.jcp.extension.PreprocessorExtension
*/
@Parameter(alias = "actionPreprocessorExtension", property = "jcp.action.preprocessor.extension", defaultValue = "")
private String actionPreprocessorExtension = "";
/**
* Keep attributes for preprocessing file and copy them to result one.
*
* @since 7.0.0
*/
@Parameter(alias = "keepAttributes", defaultValue = "false")
private boolean keepAttributes = false;
/**
* Target folder to place preprocessing result in regular source processing phase.
*
* @since 7.0.0
*/
@Parameter(alias = "target", defaultValue = "${project.build.directory}${file.separator}generated-sources${file.separator}preprocessed")
private File target = null;
/**
* Target folder to place preprocessing result in test source processing phase.
*
* @since 7.0.0
*/
@Parameter(alias = "targetTest", defaultValue = "${project.build.directory}${file.separator}generated-test-sources${file.separator}preprocessed")
private File targetTest = null;
/**
* Encoding for text read operations.
*
* @since 7.0.0
*/
@Parameter(alias = "sourceEncoding", defaultValue = "${project.build.sourceEncoding}")
private String sourceEncoding = StandardCharsets.UTF_8.name();
/**
* Encoding for text write operations.
*
* @since 7.0.0
*/
@Parameter(alias = "targetEncoding", defaultValue = "${project.build.sourceEncoding}")
private String targetEncoding = StandardCharsets.UTF_8.name();
/**
* Flag to ignore missing source folders, if false then mojo fail for any missing source folder, if true then missing folder will be ignored.
*
* @since 6.1.1
*/
@Parameter(alias = "ignoreMissingSources", defaultValue = "false")
private boolean ignoreMissingSources = false;
/**
* List of file extensions to be excluded from preprocessing. By default excluded xml.
*
* @since 7.0.0
*/
@Parameter(alias = "excludeExtensions")
private List excludeExtensions = Collections.singletonList("xml");
/**
* List of file extensions to be included into preprocessing. By default java,txt,htm,html
*
* @since 7.0.0
*/
@Parameter(alias = "extensions")
private List extensions = new ArrayList<>(Arrays.asList("java", "txt", "htm", "html"));
/**
* Recognize a unknown variable as containing boolean false flag.
*/
@Parameter(alias = "unknownVarAsFalse", defaultValue = "false")
private boolean unknownVarAsFalse = false;
/**
* Dry run, making preprocessing but without output
*
* @since 7.0.0.
*/
@Parameter(alias = "dryRun", defaultValue = "false")
private boolean dryRun = false;
/**
* Verbose mode.
*/
@Parameter(alias = "verbose", defaultValue = "false")
private boolean verbose = false;
/**
* Clear target folder if it exists.
*
* @since 7.0.0
*/
@Parameter(alias = "clearTarget", defaultValue = "false")
private boolean clearTarget = false;
/**
* Set base directory which will be used for relative source paths.
*
* @since 7.0.0
*/
@Parameter(alias = "baseDir", defaultValue = "${project.basedir}")
private File baseDir = new File(".");
/**
* Carefully reproduce last EOL in result files.
*
* @since 7.0.0
*/
@Parameter(alias = "careForLastEol", defaultValue = "false")
private boolean careForLastEol = false;
/**
* Replace source root folders in maven project after preprocessing for following processing.
*
* @since 7.0.0
*/
@Parameter(alias = "replaceSources", defaultValue = "true")
private boolean replaceSources = true;
/**
* Keep comments in result files.
*
* @since 7.0.0
*/
@Parameter(alias = "keepComments", defaultValue = "true")
private String keepComments = CommentRemoverType.KEEP_ALL.name();
/**
* List of variables to be registered in preprocessor as global ones.
*
* @since 7.0.0
*/
@Parameter(alias = "vars")
private Map vars = new HashMap<>();
/**
* List of patterns of folder paths to be excluded from preprocessing, It uses ANT path pattern format.
*
* @since 7.0.0
*/
@Parameter(alias = "excludeFolders")
private List excludeFolders = new ArrayList<>();
/**
* List of external files containing variable definitions.
*
* @since 7.0.0
*/
@Parameter(alias = "configFiles")
private List configFiles = new ArrayList<>();
/**
* Keep preprocessing directives in result files as commented ones, it is useful to not break line numeration in result files.
*/
@Parameter(alias = "keepLines", defaultValue = "true")
private boolean keepLines = true;
/**
* Turn on support of white spaces in preprocessor directives between '//' and the '#'.
*/
@Parameter(alias = "allowWhitespaces", defaultValue = "false")
private boolean allowWhitespaces = false;
/**
* Preserve indents in lines marked by '//$' and '//$$' directives. Directives will be replaced by white spaces chars.
*/
@Parameter(alias = "preserveIndents", defaultValue = "false")
private boolean preserveIndents = false;
/**
* Turn on test sources root use.
*
* @since 5.3.4
*/
@Parameter(alias = "useTestSources", defaultValue = "false")
private boolean useTestSources = false;
/**
* Skip preprocessing. Also can be defined by property 'jcp.preprocess.skip'
*
* @since 6.1.1
*/
@Parameter(alias = "skip", property = "jcp.preprocess.skip", defaultValue = "false")
private boolean skip = false;
/**
* Turn on check of content body compare with existing result file to prevent overwriting, if content is the same then preprocessor will not be writing new result content.
*
* @since 7.0.0
*/
@Parameter(alias = "dontOverwriteSameContent", defaultValue = "false")
private boolean dontOverwriteSameContent = false;
private List formSourceRootList() {
List result = Collections.emptyList();
if (this.getSources() == null) {
if (this.project != null) {
result = (this.isUseTestSources() ? this.testCompileSourceRoots : this.compileSourceRoots)
.stream()
.filter(Objects::nonNull)
.map(File::new)
.peek(x -> {
if (!x.isDirectory()) {
getLog().debug(String.format("Src.folder doesn't exist: %s", x));
}
})
.filter(x -> !this.isIgnoreMissingSources() || x.isDirectory())
.map(File::getAbsolutePath)
.collect(Collectors.toList());
}
} else {
result = new ArrayList<>(this.getSources());
}
return result;
}
private void replaceSourceRootByPreprocessingDestinationFolder(final PreprocessorContext context)
throws IOException {
if (this.project != null) {
final List sourceFolders = context.getSources();
final List sourceRoots =
this.isUseTestSources() ? this.testCompileSourceRoots : this.compileSourceRoots;
final List sourceRootsAsCanonical = new ArrayList<>();
for (final String src : sourceRoots) {
sourceRootsAsCanonical.add(new File(src).getCanonicalPath());
}
for (final PreprocessorContext.SourceFolder folder : sourceFolders) {
int index = sourceRoots.indexOf(folder.getAsString());
if (index < 0) {
// check for canonical paths
final String canonicalPath = folder.getAsFile().getCanonicalPath();
index = sourceRootsAsCanonical.indexOf(canonicalPath);
}
if (index >= 0) {
info("Source root is removed from the source root list: " + sourceRoots.get(index));
sourceRoots.remove(index);
}
}
final String destinationDir = context.getTarget().getCanonicalPath();
sourceRoots.add(destinationDir);
info("Source root is enlisted: " + destinationDir);
}
}
PreprocessorContext makePreprocessorContext() throws AbstractMojoExecutionException {
final PreprocessorContext context = new PreprocessorContext(this.getBaseDir());
context.setPreprocessorLogger(this);
if (this.project != null) {
final MavenPropertiesImporter mavenPropertiesImporter =
new MavenPropertiesImporter(context,
this.project,
this.session,
isVerbose() || getLog().isDebugEnabled()
);
context.registerSpecialVariableProcessor(mavenPropertiesImporter);
}
if (this.actionPreprocessorExtension != null &&
!this.actionPreprocessorExtension.trim().isEmpty()) {
final String extensionClassName = this.actionPreprocessorExtension.trim();
info("Detected request of action preprocessor extension class: " + extensionClassName);
context.setPreprocessorExtension(
PreprocessorUtils.findAndInstantiatePreprocessorExtensionForClassName(
extensionClassName));
}
context.setSources(formSourceRootList());
context.setTarget((this.isUseTestSources() ? this.getTargetTest() : this.getTarget()));
context.setSourceEncoding(Charset.forName(this.getSourceEncoding().trim()));
context.setTargetEncoding(Charset.forName(this.getTargetEncoding().trim()));
context.setExcludeFolders(this.getExcludeFolders());
context.setExcludeExtensions(this.getExcludeExtensions());
context.setExtensions(this.getExtensions());
if (this.getEol() != null) {
context.setEol(StringEscapeUtils.unescapeJava(this.getEol()));
}
info("Source folders: " +
context.getSources().stream().map(PreprocessorContext.SourceFolder::getAsString)
.collect(Collectors.joining(File.pathSeparator)));
info("Target folder: " + context.getTarget());
context.setUnknownVariableAsFalse(this.isUnknownVarAsFalse());
context.setDontOverwriteSameContent(this.isDontOverwriteSameContent());
context.setClearTarget(this.isClearTarget());
context.setCareForLastEol(this.isCareForLastEol());
context.setKeepComments(PreprocessorUtils.findCommentRemoverForId(this.getKeepComments()));
context.setVerbose(getLog().isDebugEnabled() || this.isVerbose());
context.setKeepLines(this.isKeepLines());
context.setDryRun(this.isDryRun());
context.setAllowWhitespaces(this.isAllowWhitespaces());
context.setPreserveIndents(this.isPreserveIndents());
context.setExcludeFolders(this.getExcludeFolders());
context.setKeepAttributes(this.isKeepAttributes());
this.configFiles.forEach(x -> context.registerConfigFile(new File(x)));
// register global vars
try {
this.getVars().entrySet().stream()
.filter(e -> {
final String key = e.getKey();
final String value = e.getValue();
if (value == null) {
if (this.isUnknownVarAsFalse()) {
getLog().warn(String.format(
"Global var '%s' ignored for null value (may be its content is empty in POM)",
key));
return false;
} else {
throw new IllegalStateException(String.format(
"Global var '%s' has null value (may be its content is empty in POM), to ignore it set unknownVarAsFalse as true",
key));
}
} else {
return true;
}
})
.forEach(e -> {
getLog().debug(
String.format("Register global var: '%s' <- '%s'", e.getKey(), e.getValue()));
context.setGlobalVariable(e.getKey(), Value.recognizeRawString(e.getValue()));
});
} catch (final IllegalStateException ex) {
getLog().error(ex.getMessage());
throw new MojoFailureException(ex.getMessage());
}
return context;
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.isSkip()) {
getLog().info("Skip preprocessing");
} else {
final PreprocessorContext context;
try {
context = makePreprocessorContext();
} catch (Exception ex) {
final PreprocessorException newException =
PreprocessorException.extractPreprocessorException(ex);
throw new MojoExecutionException(
newException == null ? ex.getMessage() : newException.toString(),
newException == null ? ex : newException);
}
if (context.getSources().isEmpty()) {
if (this.isIgnoreMissingSources()) {
getLog().warn("Source folders are not provided, preprocessing is ignored.");
} else {
throw new MojoFailureException(
"Source folders are not provided, check parameters and project type");
}
} else {
try {
final JcpPreprocessor preprocessor = new JcpPreprocessor(context);
preprocessor.execute();
if (this.isReplaceSources()) {
replaceSourceRootByPreprocessingDestinationFolder(context);
}
} catch (Exception ex) {
final PreprocessorException pp = PreprocessorException.extractPreprocessorException(ex);
throw new MojoFailureException(
pp == null ? ex.getMessage() : PreprocessorException.referenceAsString('.', pp),
pp == null ? ex : pp);
}
}
}
}
@Override
public void error(final String message) {
getLog().error(ensureNonNull(message, ""));
}
@Override
public void info(final String message) {
getLog().info(ensureNonNull(message, ""));
}
@Override
public void warning(final String message) {
getLog().warn(ensureNonNull(message, ""));
}
@Override
public void debug(final String message) {
getLog().debug(ensureNonNull(message, ""));
}
}