All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.com.cedarsolutions.tools.copyright.CopyrightTool Maven / Gradle / Ivy

Go to download

Gradle plugins and other functionality for use with a standardized build process.

There is a newer version: 0.9.8
Show newest version
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *              C E D A R
 *          S O L U T I O N S       "Software done right."
 *           S O F T W A R E
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Copyright (c) 2013 Kenneth J. Pronovici.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License, Version 2.0.
 * See LICENSE for more information about the licensing terms.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Author   : Kenneth J. Pronovici 
 * Language : Java 6
 * Project  : Common Java Functionality
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package com.cedarsolutions.tools.copyright;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import com.cedarsolutions.util.StringUtils;
import com.cedarsolutions.util.FilesystemUtils;
import com.cedarsolutions.util.CommandLine;
import com.cedarsolutions.util.CommandLineArguments;
import com.cedarsolutions.util.CommandLineUtils;
import com.cedarsolutions.util.CommandLineResult;
import com.cedarsolutions.util.JaxbUtils;
import com.cedarsolutions.xml.bindings.hg.log.Log;
import com.cedarsolutions.xml.bindings.hg.log.Logentry;

/**
 * Cedar Solutions copyright tool.
 *
 * 

* This tool updates copyright statements in Cedar Solutions source files that * follow the file header standard. The update is based on information in the * Mercurial revision control repository. Every source file with a standard * header will be updated to include every year that exists in revision * control. *

* * @author Kenneth J. Pronovici */ public class CopyrightTool { /** Command-line arguments. */ private CopyrightToolArguments arguments; /** * Constructor in terms of command-line arguments. * *

* Note: This constructor calls system.exit(2) if command-line arguments * are invalid. It does this to strictly control the output from the * program (we want to make sure that the usage is displayed simply and * prominently). *

* * @param args Array of command-line arguments, as from main() */ public CopyrightTool(String[] args) { try { this.arguments = new CopyrightToolArguments(args); } catch (RuntimeException e) { System.err.println(generateHelp()); System.exit(2); } } /** Java entry point. */ public static void main(String[] args) throws Exception { new CopyrightTool(args).internalMain(); } /** Command-line arguments. */ public CopyrightToolArguments getArguments() { return this.arguments; } /** Internal implementation of the main routine. */ public void internalMain() { String mercurial = this.arguments.getMercurial(); String repository = this.arguments.getRepository(); Pattern licensePattern = this.arguments.getLicensePattern(); List patterns = this.arguments.getPatterns(); System.out.println(""); System.out.println("Copyright Tool"); System.out.println(""); System.out.println("Mercurial......: " + mercurial); System.out.println("Repository.....: " + repository); System.out.println("License Pattern: " + licensePattern); System.out.println("Patterns.......: "); for (Pattern pattern : patterns) { System.out.println(" " + pattern); } System.out.println(""); System.out.println("Analyzing Mercurial repository..."); Map> map = generateFileYearsMap(mercurial, repository, patterns); int source = updateSourceFiles(repository, map); int license = updateLicenseFiles(mercurial, repository, licensePattern, map); if (source + license > 0) { System.out.println("Note: you must review and commit these changes."); } System.out.println(""); } /** * Update copyright statements in source files. * @param repository Mercurial repository to be modified * @param fileYearsMap Map as from generateFileYearsMap() * @return Number of files that were updated. */ private static int updateSourceFiles(String repository, Map> fileYearsMap) { int updated = 0; System.out.println("Updating copyright statements in source files..."); for (String trackedFile : fileYearsMap.keySet()) { String path = FilesystemUtils.join(repository, trackedFile); updated += updateCopyrightStatement(path, fileYearsMap.get(trackedFile)); } System.out.println("Updated " + updated + " out of " + fileYearsMap.size() + " source files."); return updated; } /** * Update copyright statements in license files. * @param mercurial Path to the Mercurial (hg) executable. * @param repository Mercurial repository to be modified * @param licensePattern Regular expression pattern for license files * @param fileYearsMap Map as from generateFileYearsMap() * @return Number of files that were updated. */ private static int updateLicenseFiles(String mercurial, String repository, Pattern licensePattern, Map> fileYearsMap) { int updated = 0; List overallYears = generateOverallYears(fileYearsMap); List trackedFiles = getTrackedFiles(mercurial, repository, licensePattern); System.out.println("Updating copyright statements in license files..."); for (String trackedFile : trackedFiles) { String path = FilesystemUtils.join(repository, trackedFile); updated += updateCopyrightStatement(path, overallYears); } System.out.println("Updated " + updated + " out of " + trackedFiles.size() + " license files."); return updated; } /** * Generate a map from file name to years that file was modified in. * @param mercurial Path to the Mercurial (hg) executable. * @param repository Mercurial repository to be modified * @param patterns Regular expression patterns which specify the files to update. * @return Map from file name to years. */ protected static Map> generateFileYearsMap(String mercurial, String repository, List patterns) { Map> map = new HashMap>(); List trackedFiles = getTrackedFiles(mercurial, repository, patterns); for (String trackedFile : trackedFiles) { List years = getYearsForTrackedFile(mercurial, repository, trackedFile); map.put(trackedFile, years); } return map; } /** * Generate an overall range of years among all files in the repository. * @param fileYearsMap Map as from generateFileYearsMap() * @return Range of years that files were modified in, sorted. */ protected static List generateOverallYears(Map> fileYearsMap) { List overallYears = new ArrayList(); for (String file : fileYearsMap.keySet()) { for (Integer year : fileYearsMap.get(file)) { if (!overallYears.contains(year)) { overallYears.add(year); } } } Collections.sort(overallYears); return overallYears; } /** * Get a list of the files tracked in the repository, filtered down to the ones which match a pattern. * @param mercurial Path to the Mercurial (hg) executable. * @param repository Mercurial repository to be modified * @param patterns Regular expression pattern which specifies the files to update. * @return List of tracked files. */ protected static List getTrackedFiles(String mercurial, String repository, Pattern pattern) { List patterns = new ArrayList(); patterns.add(pattern); return getTrackedFiles(mercurial, repository, patterns); } /** * Get a list of the files tracked in the repository, filtered down to the ones which match a set of patterns. * @param mercurial Path to the Mercurial (hg) executable. * @param repository Mercurial repository to be modified * @param patterns Regular expression patterns which specify the files to update. * @return List of tracked files. */ protected static List getTrackedFiles(String mercurial, String repository, List patterns) { List trackedFiles = new ArrayList(); CommandLine command = new CommandLine(mercurial); command.addArg("locate"); // It works best to execute the command from within the repository location. // Mercurial is not always consistent about behavior if you specify the path to the repo. CommandLineResult result = CommandLineUtils.executeCommand(command, repository, true); if (result.getExitCode() != 0) { throw new RuntimeException("Command failed: " + command.toString()); } for (String file : StringUtils.splitLines(result.getOutput())) { String normalized = FilesystemUtils.normalize(file); for (Pattern pattern : patterns) { if (StringUtils.matches(normalized, pattern)) { trackedFiles.add(normalized); break; } } } return trackedFiles; } /** * Get the years in which a tracked file was modified in Mercurial. * @param mercurial Path to the Mercurial (hg) executable. * @param repository Mercurial repository to be modified * @param trackedFile Tracked file to analyze * @return List of years in which the tracked file was modified. */ protected static List getYearsForTrackedFile(String mercurial, String repository, String trackedFile) { List years = new ArrayList(); CommandLine command = new CommandLine(mercurial); command.addArg("log"); command.addArg("--follow"); command.addArg("--style"); command.addArg("xml"); command.addArg(trackedFile); // It works best to execute the command from within the repository location. // Mercurial is not always consistent about behavior if you specify the path to the repo. CommandLineResult result = CommandLineUtils.executeCommand(command, repository, true); if (result.getExitCode() != 0) { throw new RuntimeException("Command failed: " + command.toString()); } String xml = StringUtils.trimToNull(result.getOutput()); if (xml != null) { Log log = JaxbUtils.getInstance().unmarshalDocument(Log.class, xml, false); for (Logentry logentry : log.getLogentry()) { try { Integer year = Integer.valueOf(StringUtils.substring(logentry.getDate(), 0, 4)); if (!years.contains(year)) { years.add(year); } } catch (Exception e) { } } } return years; } /** * Update the copyright statement in a file. * @param file File to modify * @param years List of years that the file was edited in * @return Number of files updated (either 0 or 1). */ protected static int updateCopyrightStatement(String file, List years) { String contents = FilesystemUtils.getFileContentsAsString(file); String range = generateCopyrightRange(years); String regex = "(Copyright \\(c\\) )(.*)( Kenneth J. Pronovici)"; String replacement = "$1" + range + "$3"; String result = contents.replaceAll(regex, replacement); if (!result.equals(contents)) { // No point in writing the file unless something has changed FilesystemUtils.writeFileContents(file, result); return 1; } return 0; } /** * Generate a copyright year range from a list of years. * *

* A range is something like "2011,2013-2015,2017". Consecutive * years are bundled together with a "-" and other years are just * added with a comma. *

* * @param years List of years * @return Copyright year range for the list of years, or null if no range can be derived. */ protected static String generateCopyrightRange(List years) { if (years == null || years.size() == 0) { return null; } else { Integer lastYear = null; int rangeStart = 0; int last = 0; int current = 0; boolean firstTime = true; StringBuffer buffer = new StringBuffer(); Collections.sort(years); for (Integer currentYear : years) { if (firstTime) { current = currentYear.intValue(); buffer.append(currentYear.toString()); rangeStart = current; lastYear = currentYear; firstTime = false; } else { last = lastYear.intValue(); current = currentYear.intValue(); if (current - last > 1) { if (last > rangeStart) { buffer.append("-"); buffer.append(lastYear.toString()); } buffer.append(","); buffer.append(currentYear.toString()); rangeStart = current; } lastYear = currentYear; } } if (lastYear != null) { last = lastYear.intValue(); if (last > rangeStart) { buffer.append("-"); buffer.append(lastYear.toString()); } } return buffer.toString(); } } /** Generate a command-line help statement. */ protected static String generateHelp() { return "Usage: CopyrightTool hgpath repository pattern [pattern, ...]\n" + "\n" + " hgpath Path to the Mercurial executable, including .exe on Windows\n" + " repository Path to the Mercurial repository to be updated\n" + " pattern Regular expression pattern matching files to be updated" + "\n" + "Update copyright statements in the indicated Mercurial repositories.\n" + "The code is assumed to follow Cedar Solutions standards. After running\n" + "the tool, you'll need to review changes and commit them.\n" + "\n" + "The regular expression patterns must be designed to match paths that are\n" + "relative to the repository root. These patterns can assume that the path " + "separator has been normalized to / on both Windows and UNIX-like systems."; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy