Java tutorial
/* * Copyright 2014 Igor Maznitsa (http://www.igormaznitsa.com). * * Licensed 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 com.igormaznitsa.jcp.JCPreprocessor; 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 java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.*; import org.apache.maven.project.MavenProject; import com.igormaznitsa.meta.annotation.MustNotContainNull; import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull; import org.apache.commons.lang3.StringUtils; import com.igormaznitsa.meta.common.utils.Assertions; /** * The Mojo makes preprocessing of defined or project root source folders and place result in defined or predefined folder, also it can replace the source folder for a maven * project to use the preprocessed sources. * * @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com) */ @Mojo(name = "preprocess", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true, requiresProject = true) public class PreprocessorMojo extends AbstractMojo implements PreprocessorLogger { /** * The Project source roots for non-test mode. */ @Parameter(alias = "compileSourceRoots", defaultValue = "${project.compileSourceRoots}", required = true, readonly = true) private List<String> compileSourceRoots; /** * The Project source roots for test mode. */ @Parameter(alias = "testCompileSourceRoots", defaultValue = "${project.testCompileSourceRoots}", required = true, readonly = true) private List<String> testCompileSourceRoots; /** * The Maven Project to be preprocessed. */ @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; /** * The Directly defined source directory, it will make plugin to preprocess the folder instead of project and maven defined ones. By default it is empty and is not used. */ @Parameter(alias = "source", defaultValue = "") private String source; /** * Copy file attributes for copied and generated files. * @since 6.1.2 */ @Parameter(alias = "copyFileAttributes", defaultValue = "false") private boolean copyFileAttributes; /** * The Destination folder where generated sources will be placed in non-test mode. */ @Parameter(alias = "destination", defaultValue = "${project.build.directory}/generated-sources/preprocessed") private File destination; /** * Destination folder where generated sources will be placed in test-mode. */ @Parameter(alias = "testDestination", defaultValue = "${project.build.directory}/generated-test-sources/preprocessed") private File testDestination; /** * The Input text encoding to be used for preprocessing, by default it uses defined in project properties. */ @Parameter(alias = "inEncoding", defaultValue = "${project.build.sourceEncoding}") private String inEncoding; /** * The Encoding for preprocessed text output, by default it uses defined in project properties. */ @Parameter(alias = "outEncoding", defaultValue = "${project.build.sourceEncoding}") private String outEncoding; /** * 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; /** * List of file extensions to be excluded from the preprocessing process. By default excluded XML files. */ @Parameter(alias = "excluded") private String excluded; /** * List of file extensions to be preprocessed. By default java,txt,htm,html */ @Parameter(alias = "processing") private String processing; /** * Flag to interpret unknown variable as FALSE. */ @Parameter(alias = "unknownVarAsFalse", defaultValue = "false") private boolean unknownVarAsFalse; /** * Make dry run of the preprocessor without any saving of result. */ @Parameter(alias = "disableOut", defaultValue = "false") private boolean disableOut; /** * Turn on the verbose mode for preprocessing process. */ @Parameter(alias = "verbose", defaultValue = "false") private boolean verbose; /** * Clear the destination folder before preprocessing (if it exists). */ @Parameter(alias = "clear", defaultValue = "false") private boolean clear; /** * Be precise in processing of the last next line char in files, it will not be added if it is not presented if to turn on the mode.. */ @Parameter(alias = "careForLastNextLine", defaultValue = "false") private boolean careForLastNextLine; /** * Disable overriding of the source root folders for maven project after preprocessing. */ @Parameter(alias = "keepSrcRoot", defaultValue = "false") private boolean keepSrcRoot; /** * Remove all Java like commentaries from preprocessed sources. */ @Parameter(alias = "removeComments", defaultValue = "false") private boolean removeComments; /** * List of global preprocessing variables. */ @Parameter(alias = "globalVars") private Properties globalVars; /** * List of sub-folders in source folders to be excluded from preprocessing, ANT path pattern format allowed. */ @Parameter(alias = "excludedFolders") private String[] excludedFolders = new String[0]; /** * List of external configuration files. */ @Parameter(alias = "cfgFiles") private File[] cfgFiles; /** * Disable removing lines from preprocessed files, it allows to keep line numeration similar to original sources. */ @Parameter(alias = "keepLines", defaultValue = "true") private boolean keepLines; /** * Manage mode to allow whitespace between the // and the #. */ @Parameter(alias = "allowWhitespace", defaultValue = "false") private boolean allowWhitespace; /** * Preserve indents in lines marked by '//$' and '//$$' directives. The Directives will be replaced by white spaces chars. */ @Parameter(alias = "preserveIndent", defaultValue = "false") private boolean preserveIndent; /** * Allow usage of the preprocessor for test sources (since 5.3.4 version). */ @Parameter(alias = "useTestSources", defaultValue = "false") private boolean useTestSources; /** * Skip preprocessing. * @since 6.1.1 */ @Parameter(alias = "skip", property = "jcp.preprocess.skip", defaultValue = "false") private boolean skip; /** * Flag to compare generated content with existing file and if it is the same then to not override the file, it brings overhead */ @Parameter(alias = "compareDestination", defaultValue = "false") private boolean compareDestination; public void setExcludedFolders(@Nonnull @MustNotContainNull final String... antPatterns) { this.excludedFolders = Assertions.assertDoesntContainNull(Assertions.assertNotNull(antPatterns)); } @Nonnull @MustNotContainNull public String[] getExcludedFolders() { return this.excludedFolders; } public void setIgnoreMissingSources(final boolean flag) { this.ignoreMissingSources = flag; } public boolean isIgnoreMissingSources() { return this.ignoreMissingSources; } public void setSkip(final boolean flag) { this.skip = flag; } public boolean isSkip() { return this.skip; } public void setPreserveIndent(final boolean flag) { this.preserveIndent = flag; } public boolean getPreserveIndent() { return this.preserveIndent; } public void setCopyFileAttributes(final boolean flag) { this.copyFileAttributes = flag; } public boolean getCopyFileAttributes() { return this.copyFileAttributes; } public void setUseTestSources(final boolean flag) { this.useTestSources = flag; } public boolean getUseTestSources() { return this.useTestSources; } public void setClear(final boolean flag) { this.clear = flag; } public boolean getClear() { return this.clear; } public void setCareForLastNextLine(final boolean flag) { this.careForLastNextLine = flag; } public boolean getCarForLastNextLine() { return this.careForLastNextLine; } public void setKeepSrcRoot(final boolean flag) { this.keepSrcRoot = flag; } public boolean getKeepSrcRoot() { return this.keepSrcRoot; } public void setGlobalVars(@Nonnull final Properties vars) { this.globalVars = vars; } @Nonnull public Properties getGlobalVars() { return this.globalVars; } public void setCfgFiles(@Nonnull @MustNotContainNull final File[] files) { this.cfgFiles = files; } @Nonnull @MustNotContainNull public File[] getCfgFiles() { return this.cfgFiles; } public void setCompareDestination(final boolean flag) { this.compareDestination = flag; } public boolean isCompareDestination() { return this.compareDestination; } public void setSource(@Nonnull final String source) { this.source = source; } @Nonnull public String getSource() { return this.source; } public void setDestination(@Nonnull final File destination) { this.destination = destination; } @Nonnull public File getDestination() { return this.destination; } public void setTestDestination(@Nonnull final File destination) { this.testDestination = destination; } @Nonnull public File getTestDestination() { return this.testDestination; } public void setInEncoding(@Nonnull final String value) { this.inEncoding = value; } @Nonnull public String getInEncoding() { return this.inEncoding; } public void setOutEncoding(@Nonnull final String value) { this.outEncoding = value; } @Nonnull public String getOutEncoding() { return this.outEncoding; } public void setExcluded(@Nullable final String excluded) { this.excluded = excluded; } @Nullable public String getExcluded() { return this.excluded; } public void setUnknownVarAsFalse(final boolean flag) { this.unknownVarAsFalse = flag; } public boolean getUnknownVarAsFalse() { return this.unknownVarAsFalse; } public void setProcessing(@Nullable final String processing) { this.processing = processing; } @Nullable public String getProcessing() { return this.processing; } public void setDisableOut(final boolean value) { this.disableOut = value; } public boolean getDisableOut() { return this.disableOut; } public void setVerbose(final boolean verbose) { this.verbose = verbose; } public boolean getVerbose() { return this.verbose; } public void setKeepLines(final boolean keepLines) { this.keepLines = keepLines; } public boolean getKeepLines() { return this.keepLines; } public void setAllowWhitespace(final boolean flag) { this.allowWhitespace = flag; } public boolean getAllowWhitespace() { return this.allowWhitespace; } public void setRemoveComments(final boolean value) { this.removeComments = value; } public boolean getRemoveComments() { return this.removeComments; } @Nullable private String makeSourceRootList() { String result = null; if (this.source != null && !this.source.isEmpty()) { result = this.source; } else if (this.project != null) { final StringBuilder accum = new StringBuilder(); for (final String srcRoot : (this.getUseTestSources() ? this.testCompileSourceRoots : this.compileSourceRoots)) { final boolean folderPresented = new File(srcRoot).isDirectory(); if (!folderPresented) { getLog().debug("Can't find source folder : " + srcRoot); } String textToAppend; if (folderPresented) { textToAppend = srcRoot; } else { if (this.isIgnoreMissingSources()) { textToAppend = null; } else { textToAppend = srcRoot; } } if (textToAppend != null) { if (accum.length() > 0) { accum.append(';'); } accum.append(srcRoot); } } result = accum.toString(); } return result; } private void replaceSourceRootByPreprocessingDestinationFolder(@Nonnull final PreprocessorContext context) throws IOException { if (this.project != null) { final String sourceDirectories = context.getSourceDirectories(); final String[] splitted = sourceDirectories.split(";"); final List<String> sourceRoots = this.getUseTestSources() ? this.testCompileSourceRoots : this.compileSourceRoots; final List<String> sourceRootsAsCanonical = new ArrayList<String>(); for (final String src : sourceRoots) { sourceRootsAsCanonical.add(new File(src).getCanonicalPath()); } for (final String str : splitted) { int index = sourceRoots.indexOf(str); if (index < 0) { // check for canonical paths final File src = new File(str); final String canonicalPath = src.getCanonicalPath(); index = sourceRootsAsCanonical.indexOf(canonicalPath); } if (index >= 0) { info("A Compile source root has been removed from the root list [" + sourceRoots.get(index) + ']'); sourceRoots.remove(index); } } final String destinationDir = context.getDestinationDirectoryAsFile().getCanonicalPath(); sourceRoots.add(destinationDir); info("The New compile source root has been added into the list [" + destinationDir + ']'); } } @Nonnull PreprocessorContext makePreprocessorContext(@Nonnull final String sourceFoldersInPreprocessorFormat) throws IOException { final PreprocessorContext context = new PreprocessorContext(); context.setPreprocessorLogger(this); if (this.project != null) { final MavenPropertiesImporter mavenPropertiesImporter = new MavenPropertiesImporter(context, project, getVerbose() || getLog().isDebugEnabled()); context.registerSpecialVariableProcessor(mavenPropertiesImporter); } context.setSourceDirectories(sourceFoldersInPreprocessorFormat); context.setDestinationDirectory( assertNotNull(this.getUseTestSources() ? this.testDestination.getCanonicalPath() : this.destination.getCanonicalPath())); if (this.inEncoding != null) { context.setInCharacterEncoding(this.inEncoding); } if (this.outEncoding != null) { context.setOutCharacterEncoding(this.outEncoding); } if (this.excluded != null) { context.setExcludedFileExtensions(this.excluded); } if (this.processing != null) { context.setProcessingFileExtensions(this.processing); } info("Preprocess sources : " + context.getSourceDirectories()); info("Preprocess destination : " + context.getDestinationDirectory()); context.setUnknownVariableAsFalse(this.unknownVarAsFalse); context.setCompareDestination(this.compareDestination); context.setClearDestinationDirBefore(this.clear); context.setCareForLastNextLine(this.careForLastNextLine); context.setRemoveComments(this.removeComments); context.setVerbose(getLog().isDebugEnabled() || this.verbose); context.setKeepLines(this.keepLines); context.setFileOutputDisabled(this.disableOut); context.setAllowWhitespace(this.allowWhitespace); context.setPreserveIndent(this.preserveIndent); context.setExcludedFolderPatterns(this.excludedFolders); context.setCopyFileAttributes(this.copyFileAttributes); // process cfg files if (this.cfgFiles != null && this.cfgFiles.length != 0) { for (final File file : this.cfgFiles) { assertNotNull("Detected null where a config file was expected", file); context.addConfigFile(file); } } // process global vars if (this.globalVars != null && !this.globalVars.isEmpty()) { for (final String key : this.globalVars.stringPropertyNames()) { final String value = this.globalVars.getProperty(key); assertNotNull("Can't find defined value for '" + key + "' global variable", value); context.setGlobalVariable(key, Value.recognizeRawString(value)); } } return context; } @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.isSkip()) { getLog().info("Skip preprocessing"); } else { PreprocessorContext context = null; final String sourceFoldersInPreprocessingFormat = makeSourceRootList(); boolean skipPreprocessing = false; if (StringUtils.isEmpty(sourceFoldersInPreprocessingFormat)) { if (isIgnoreMissingSources()) { getLog().warn("Source folders are not provided, preprocessing is skipped."); skipPreprocessing = true; } else { throw new MojoFailureException( "Can't find source folders to preprocess, check parameters and project type!"); } } if (!skipPreprocessing) { try { context = makePreprocessorContext(Assertions.assertNotNull(sourceFoldersInPreprocessingFormat)); } catch (Exception ex) { final PreprocessorException pp = PreprocessorException.extractPreprocessorException(ex); throw new MojoExecutionException(pp == null ? ex.getMessage() : pp.toString(), pp == null ? ex : pp); } try { final JCPreprocessor preprocessor = new JCPreprocessor(context); preprocessor.execute(); if (!getKeepSrcRoot()) { 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(@Nonnull final String message) { getLog().error(message); } @Override public void info(@Nonnull final String message) { getLog().info(message); } @Override public void warning(@Nonnull final String message) { getLog().warn(message); } @Override public void debug(@Nonnull final String message) { getLog().debug(message); } }