Java tutorial
package org.codehaus.mojo.license; /* * #%L * License Maven Plugin * %% * Copyright (C) 2008 - 2012 CodeLutin, Codehaus, Tony Chemit * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Lesser Public License for more details. * * You should have received a copy of the GNU General Lesser Public * License along with this program. If not, see * <http://www.gnu.org/licenses/lgpl-3.0.html>. * #L% */ import freemarker.template.Template; import org.apache.commons.lang3.StringUtils; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.codehaus.mojo.license.header.transformer.FileHeaderTransformer; import org.codehaus.mojo.license.model.License; import org.codehaus.mojo.license.utils.FileUtil; import org.codehaus.mojo.license.utils.MojoHelper; import org.codehaus.plexus.util.FileUtils; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * The goal to remove the header on project source files. * * @author tchemit dev@tchemit.fr * @since 1.11 */ @Mojo(name = "remove-file-header", threadSafe = true) public class RemoveFileHeaderMojo extends AbstractLicenseNameMojo { // ---------------------------------------------------------------------- // Mojo Parameters // ---------------------------------------------------------------------- /** * A flag to skip the goal. * * @since 1.11 */ @Parameter(property = "license.skipRemoveLicense", defaultValue = "false") private boolean skipRemoveLicense; /** * A flag to test plugin but modify no file. * * @since 1.11 */ @Parameter(property = "dryRun", defaultValue = "false") private boolean dryRun; /** * A flag to ignore no files to scan. * <p> * This flag will suppress the "No file to scan" warning. This will allow you to set the plug-in in the root pom of * your project without getting a lot of warnings for aggregation modules / artifacts. * </p> * * @since 1.11 */ @Parameter(property = "license.ignoreNoFileToScan", defaultValue = "false") private boolean ignoreNoFileToScan; /** * To specify the base dir from which we apply the license. * <p> * Should be on form "root1,root2,rootn". * <p> * By default, the main roots are "src, target/generated-sources, target/processed-sources". * <p> * <b>Note:</b> If some of these roots do not exist, they will be simply * ignored. * * @since 1.11 */ @Parameter(property = "license.roots") private String[] roots; /** * Specific files to includes, separated by a comma. By default, it is "** /*". * * @since 1.11 */ @Parameter(property = "license.includes") private String[] includes; /** * Specific files to excludes, separated by a comma. * By default, those file types are excluded: * <ul> * <li>modelisation</li> * <li>images</li> * </ul> * * @since 1.11 */ @Parameter(property = "license.excludes") private String[] excludes; /** * To associate extra extension files to an existing comment style. * <p> * Keys of the map are the extension of extra files to treat, and the value * is the comment style you want to associate. * <p> * For example, to treat file with extensions {@code java2} and {@code jdata} * as {@code java} files (says using the {@code java} comment style, declare this * in your plugin configuration : * <pre> * <extraExtensions> * <java2>java</java2> * <jdata>java</jdata> * </extraExtensions> * </pre> * * @since 1.11 */ @Parameter private Map<String, String> extraExtensions; /** * To associate extra files to an existing comment style. * <p> * Keys of the map are the name of extra files to treat, and the value * is the comment style you want to associate. * <p> * For example, to treat a file named {@code DockerFile} as {@code properties} files * (says using the {@code properties} comment style, declare this in your plugin configuration : * <pre> * <extraFiles> * <DockerFile>properties</DockerFile> * </extraFiles> * </pre> * * @since 1.11 */ @Parameter private Map<String, String> extraFiles; /** * A tag to place on files that will be ignored by the plugin. * <p> * Sometimes, it is necessary to do this when file is under a specific license. * <p> * <b>Note:</b> If no sets, will use the default tag {@code %% Ignore-License} * * @since 1.0 */ @Parameter(property = "license.ignoreTag") private String ignoreTag; // ---------------------------------------------------------------------- // Plexus components // ---------------------------------------------------------------------- /** * All available header transformers. * * @since 1.0 */ @Component(role = FileHeaderTransformer.class) private Map<String, FileHeaderTransformer> transformers; // ---------------------------------------------------------------------- // Private fields // ---------------------------------------------------------------------- /** * timestamp used for generation. */ private long timestamp; /** * The freemarker template used to render the description section of the file header. * * @since 1.1 */ private Template descriptionTemplate0; /** * set of processed files */ private Set<File> processedFiles; /** * Dictionary of treated files indexed by their state. */ private EnumMap<FileState, Set<File>> result; /** * Dictionary of files to treat indexed by their CommentStyle. */ private Map<String, List<File>> filesToTreatByCommentStyle; // ---------------------------------------------------------------------- // AbstractLicenceMojo Implementation // ---------------------------------------------------------------------- @Override public boolean isSkip() { return skipRemoveLicense; } // ---------------------------------------------------------------------- // AbstractLicenseMojo Implementaton // ---------------------------------------------------------------------- @Override public void init() throws Exception { if (StringUtils.isEmpty(ignoreTag)) { // use default value this.ignoreTag = "%" + "%Ignore-License"; } if (isVerbose()) { // print available comment styles (transformers) StringBuilder buffer = new StringBuilder(); buffer.append("config - available comment styles :"); String commentFormat = "\n * %1$s (%2$s)"; for (String transformerName : transformers.keySet()) { FileHeaderTransformer aTransformer = getTransformer(transformers, transformerName); String str = String.format(commentFormat, aTransformer.getName(), aTransformer.getDescription()); buffer.append(str); } getLog().info(buffer.toString()); } // set timestamp used for temporary files this.timestamp = System.nanoTime(); super.init(); if (roots == null || roots.length == 0) { roots = DEFAULT_ROOTS; if (isVerbose()) { getLog().info("Will use default roots " + Arrays.toString(roots)); } } if (includes == null || includes.length == 0) { includes = DEFAULT_INCLUDES; if (isVerbose()) { getLog().info("Will use default includes " + Arrays.toString(includes)); } } if (excludes == null || excludes.length == 0) { excludes = DEFAULT_EXCLUDES; if (isVerbose()) { getLog().info("Will use default excludes" + Arrays.toString(excludes)); } } Map<String, String> extensionToCommentStyle = new TreeMap<String, String>(); // add default extensions from header transformers for (Map.Entry<String, FileHeaderTransformer> entry : transformers.entrySet()) { String commentStyle = entry.getKey(); FileHeaderTransformer aTransformer = entry.getValue(); String[] extensions = aTransformer.getDefaultAcceptedExtensions(); for (String extension : extensions) { if (isVerbose()) { getLog().info("Associate extension " + extension + " to comment style " + commentStyle); } extensionToCommentStyle.put(extension, commentStyle); } } if (extraExtensions != null) { // fill extra extensions for each transformer for (Map.Entry<String, String> entry : extraExtensions.entrySet()) { String extension = entry.getKey(); if (extensionToCommentStyle.containsKey(extension)) { // override existing extension mapping getLog().warn("The extension " + extension + " is already accepted for comment style " + extensionToCommentStyle.get(extension)); } String commentStyle = entry.getValue(); // check transformer exists getTransformer(transformers, commentStyle); if (isVerbose()) { getLog().info( "Associate extension '" + extension + "' to comment style '" + commentStyle + "'"); } extensionToCommentStyle.put(extension, commentStyle); } } if (extraFiles == null) { extraFiles = Collections.emptyMap(); } // get all files to treat indexed by their comment style filesToTreatByCommentStyle = obtainFilesToProcessByCommentStyle(extraFiles, roots, includes, excludes, extensionToCommentStyle, transformers); } @Override public void doAction() throws Exception { long t0 = System.nanoTime(); processedFiles = new HashSet<File>(); result = new EnumMap<FileState, Set<File>>(FileState.class); try { for (Map.Entry<String, List<File>> commentStyleFiles : filesToTreatByCommentStyle.entrySet()) { String commentStyle = commentStyleFiles.getKey(); List<File> files = commentStyleFiles.getValue(); processCommentStyle(commentStyle, files); } } finally { int nbFiles = processedFiles.size(); if (nbFiles == 0 && !ignoreNoFileToScan) { getLog().warn("No file to scan."); } else { String delay = MojoHelper.convertTime(System.nanoTime() - t0); String message = String.format("Scan %s file%s header done in %s.", nbFiles, nbFiles > 1 ? "s" : "", delay); getLog().info(message); } Set<FileState> states = result.keySet(); if (states.size() == 1 && states.contains(FileState.uptodate)) { // all files where up to date getLog().info("All files are up-to-date."); } else { StringBuilder buffer = new StringBuilder(); for (FileState state : FileState.values()) { reportType(result, state, buffer); } getLog().info(buffer.toString()); } } } private boolean isDryRun() { return dryRun; } /** * Process a given comment style to all his detected files. * * @param commentStyle comment style to treat * @param filesToTreat files using this comment style to treat * @throws IOException if any IO error while processing files */ private void processCommentStyle(String commentStyle, List<File> filesToTreat) throws IOException { // obtain license from definition License license = getLicense(getLicenseName(), true); if (isVerbose()) { getLog().info("Process header '" + commentStyle + "'"); getLog().info(" - using " + license.getDescription()); } // use header transformer according to comment style given in header FileHeaderTransformer transformer = getTransformer(transformers, commentStyle); for (File file : filesToTreat) { processFile(transformer, file); } filesToTreat.clear(); } private boolean processFile(FileHeaderTransformer transformer, File file, File processFile) throws IOException { String content; try { content = FileUtil.readAsString(file, getEncoding()); } catch (IOException e) { throw new IOException("Could not obtain content of file " + file); } //check that file is not marked to be ignored if (content.contains(ignoreTag)) { getLog().info(" - ignore file (detected " + ignoreTag + ") " + file); FileState.ignore.addFile(file, result); return false; } String commentStartTag = transformer.getCommentStartTag(); int firstIndex = content.indexOf(commentStartTag); if (firstIndex == -1) { FileState.uptodate.addFile(file, result); return false; } char lastchar = ' '; while (lastchar != '\n') { lastchar = content.charAt((--firstIndex)); } String commentEndTag = transformer.getCommentEndTag(); int lastIndex = content.indexOf(commentEndTag); if (lastIndex == -1) { FileState.uptodate.addFile(file, result); return false; } lastchar = ' '; while (lastchar != '\n') { lastchar = content.charAt((++lastIndex)); } if (isVerbose()) { getLog().info(" - header was removed for " + file); } String contentWithoutHeader = content.substring(0, firstIndex) + content.substring(lastIndex); FileUtils.fileWrite(processFile, contentWithoutHeader); FileState.remove.addFile(file, result); return true; } /** * Process the given file (will copy it, process the clone file and finally finalizeFile after process)... * * @param transformer current file header transformer * @param file original file to process * @throws IOException if any IO error while processing this file */ private void processFile(FileHeaderTransformer transformer, File file) throws IOException { if (processedFiles.contains(file)) { getLog().info(" - skip already processed file " + file); return; } // output file File processFile = new File(file.getAbsolutePath() + "_" + timestamp); boolean doFinalize = false; try { doFinalize = processFile(transformer, file, processFile); } catch (Exception e) { getLog().warn("skip failed file : " + e.getMessage() + (e.getCause() == null ? "" : " Cause : " + e.getCause().getMessage()), e); FileState.fail.addFile(file, result); doFinalize = false; } finally { // whatever was the result, this file is treated. processedFiles.add(file); if (doFinalize) { finalizeFile(file, processFile); } else { FileUtil.deleteFile(processFile); } } } /** * Finalize the process of a file. * <p> * If ad DryRun then just remove processed file, else use process file as original file. * * @param file the original file * @param processFile the processed file * @throws IOException if any IO error while finalizing file */ private void finalizeFile(File file, File processFile) throws IOException { if (isKeepBackup() && !isDryRun()) { File backupFile = FileUtil.getBackupFile(file); if (backupFile.exists()) { // always delete backup file, before the renaming FileUtil.deleteFile(backupFile); } if (isVerbose()) { getLog().debug(" - backup original file " + file); } FileUtil.renameFile(file, backupFile); } if (isDryRun()) { // dry run, delete temporary file FileUtil.deleteFile(processFile); } else { try { // replace file with the updated one FileUtil.renameFile(processFile, file); } catch (IOException e) { getLog().warn(e.getMessage()); } } } }