org.kuali.maven.plugins.externals.MojoHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.maven.plugins.externals.MojoHelper.java

Source

/**
 * Copyright 2011-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 *
 * 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 org.kuali.maven.plugins.externals;

import static org.apache.commons.io.filefilter.FileFilterUtils.and;
import static org.apache.commons.io.filefilter.FileFilterUtils.directoryFileFilter;
import static org.apache.commons.io.filefilter.FileFilterUtils.nameFileFilter;
import static org.apache.commons.io.filefilter.FileFilterUtils.notFileFilter;

import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.swing.tree.DefaultMutableTreeNode;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.kuali.common.util.Version;
import org.kuali.common.util.VersionUtils;
import org.kuali.maven.common.Extractor;
import org.kuali.maven.common.PropertiesUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.tmatesoft.svn.core.SVNCommitInfo;

public class MojoHelper {

    private static final String QUALIFIER_DELIMETER = "-";
    private static final String MAVEN_SNAPSHOT_TOKEN = "SNAPSHOT";
    private static final char[] DIGITS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

    private static final String majorQualifierFoundersReleasePrefix = "FR";

    private static final String minorQualiferMilestonePrefix = "M";
    private static final String minorQualiferReleaseCandidatePrefix = "RC";

    SVNUtils svnUtils = SVNUtils.getInstance();
    POMUtils pomUtils = new POMUtils();
    Extractor extractor = new Extractor();
    PropertiesUtils propertiesUtils = new PropertiesUtils();
    NumberFormat nf = NumberFormat.getInstance();
    private final Log logger;

    protected static MojoHelper instance;

    protected MojoHelper(Mojo mojo) {
        super();
        nf.setMaximumFractionDigits(3);
        nf.setMinimumFractionDigits(3);
        nf.setGroupingUsed(false);

        logger = mojo.getLog();
    }

    public synchronized static MojoHelper getInstance(Mojo mojo) {
        if (instance == null) {
            instance = new MojoHelper(mojo);
        }
        return instance;
    }

    public void incrementVersions(AbstractTagMojo mojo) {
        List<File> files = getPoms(mojo.getProject().getBasedir(), mojo.getPom(), mojo.getIgnoreDirectories());
        List<DefaultMutableTreeNode> nodes = getNodes(files);
        DefaultMutableTreeNode node = getTree(mojo.getProject().getBasedir(), nodes, mojo.getPom());
        incrementVersions(node);
        updateGavs(node);
        updateProperties(node, mojo.getProject().getProperties(), mojo.getMappings());
        updateXml(node);
        writePoms(node, mojo.getProject().getBasedir());
        List<SVNExternal> externals = svnUtils.getExternals(mojo.getProject().getBasedir());
        logger.info("Committing pom changes");
        commitChanges(mojo.getProject().getBasedir(), externals,
                "[externals-maven-plugin] prepare for next development iteration");
    }

    protected List<DefaultMutableTreeNode> getChildren(DefaultMutableTreeNode node) {
        Enumeration<?> e = node.children();
        List<DefaultMutableTreeNode> children = new ArrayList<DefaultMutableTreeNode>();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) e.nextElement();
            children.add(child);
        }
        return children;
    }

    public void incrementVersions(DefaultMutableTreeNode node) {
        Project project = (Project) node.getUserObject();
        GAV gav = project.getGav();
        String oldVersion = gav.getVersion();
        if (!StringUtils.isBlank(oldVersion)) {
            String newVersion = getNextVersion(oldVersion);
            GAV newGav = new GAV();
            newGav.setGroupId(gav.getGroupId());
            newGav.setArtifactId(gav.getArtifactId());
            newGav.setVersion(newVersion);
            project.setNewGav(newGav);
            logger.info(StringUtils.repeat("  ", node.getLevel()) + gav.getArtifactId() + ":" + gav.getArtifactId()
                    + ":" + oldVersion + "->" + newVersion);
        }
        List<DefaultMutableTreeNode> children = getChildren(node);
        for (DefaultMutableTreeNode child : children) {
            incrementVersions(child);
        }
    }

    public void createAndUpdateTags(AbstractTagMojo mojo) {

        // Extract the Jenkins build number. Defaults to zero if no BUILD_NUMBER is set
        int buildNumber = getBuildNumber(mojo.getProject(), mojo.getBuildNumberProperty());

        // Create a GAV object from the Maven project
        GAV gav = getGav(mojo.getProject());

        // Scan the file system for pom.xml files
        List<File> files = getPoms(mojo.getProject().getBasedir(), mojo.getPom(), mojo.getIgnoreDirectories());

        // Convert the list of files into a list of node objects
        List<DefaultMutableTreeNode> nodes = getNodes(files);

        // Build a tree from the list
        DefaultMutableTreeNode node = getTree(mojo.getProject().getBasedir(), nodes, mojo.getPom());

        // Extract svn:externals info from the root of the checkout
        List<SVNExternal> externals = svnUtils.getExternals(mojo.getProject().getBasedir());

        // Make sure the modules listed in the pom match the svn:externals definitions and the mappings provided in the plugin config
        validate(mojo.getProject(), externals, mojo.getMappings());

        // Calculate the build tag for the root
        BuildTag rootTag = getBuildTag(mojo.getProject().getBasedir(), gav, mojo.getTagStyle(), buildNumber);

        // Update build info for the root node
        updateBuildInfo(node, rootTag, mojo.getTagStyle(), buildNumber);

        // Calculate build tags for each module
        List<BuildTag> moduleTags = getBuildTags(mojo.getProject().getProperties(), externals, mojo.getMappings(),
                mojo.getTagStyle(), buildNumber);

        // Update build information for nodes that represent an svn:external
        updateBuildInfo(nodes, moduleTags, mojo.getMappings(), mojo.getTagStyle(), buildNumber);

        // Recursively update the mojo.getProject() gav's and parent gav's
        updateGavs(node);

        // Recursively update the corresponding Maven pom's
        updateXml(node);

        // Update the properties in the root pom that hold version info for the modules
        updateProperties(node, mojo.getProject().getProperties(), mojo.getMappings());

        // Update the <scm> info in the root pom
        updateScm(node, mojo.getScmUrlPrefix());

        // Create new svn:externals definitions based on the newly created tags
        List<SVNExternal> newExternals = getExternals(moduleTags, mojo.getMappings());

        // Create the module tags
        createTags(moduleTags, mojo.getCreateTagMessage());

        // Create the root tag
        createTag(rootTag, mojo.getCreateTagMessage());

        // The directory the tag was checked out to
        File checkoutDir = mojo.getCheckoutDir();

        // Update svn:externals definitions on the root tag so they point to the new module tags
        SVNCommitInfo info = svnUtils.setExternals(rootTag.getTagUrl(), newExternals, mojo.getExternalsMessage());
        logger.info("Set " + newExternals.size() + " externals @ " + rootTag.getTagUrl());
        logger.info("Committed revision " + info.getNewRevision() + ".");
        logger.info("Checking out - " + rootTag.getTagUrl());
        logger.info("Checkout dir - " + checkoutDir.getAbsolutePath());
        if (checkoutDir.exists()) {
            logger.info("Deleting " + checkoutDir.getAbsolutePath());
            deleteDirectory(checkoutDir);
        }
        long start = System.currentTimeMillis();
        long revision = svnUtils.checkout(rootTag.getTagUrl(), checkoutDir, null, null);
        logTime("Total checkout time: ", System.currentTimeMillis() - start);
        logger.info("Checked out revision " + revision + ".");

        // Update the poms in the directory where the tag has been checked out
        writePoms(node, mojo.getProject().getBasedir(), checkoutDir);

        // Update the svn.externals file in the tag
        updateExternalsFile(newExternals, mojo.getFile());

        // Commit the changes to the tag
        commitChanges(checkoutDir, newExternals, mojo.getUpdateTagMessage());
    }

    public GAV getGav(MavenProject project) {
        GAV gav = new GAV();
        gav.setGroupId(project.getGroupId());
        gav.setArtifactId(project.getArtifactId());
        gav.setVersion(project.getVersion());
        return gav;
    }

    public String toString(GAV gav) {
        StringBuilder sb = new StringBuilder();
        sb.append(gav.getGroupId());
        sb.append(":");
        sb.append(gav.getArtifactId());
        sb.append(":");
        sb.append(gav.getVersion());
        return sb.toString();
    }

    public String getGroupId(DefaultMutableTreeNode node) {
        List<Project> projects = getProjectPath(node);
        for (Project project : projects) {
            GAV gav = project.getGav();
            if (!StringUtils.isBlank(gav.getGroupId())) {
                return gav.getGroupId();
            }
        }
        throw new IllegalStateException("Unable to determine a version");
    }

    public String getVersion(DefaultMutableTreeNode node) {
        List<Project> projects = getProjectPath(node);
        for (Project project : projects) {
            GAV gav = project.getGav();
            if (!StringUtils.isBlank(gav.getVersion())) {
                return gav.getVersion();
            }
        }
        throw new IllegalStateException("Unable to determine a version");
    }

    protected List<Project> getProjectPath(DefaultMutableTreeNode node) {
        Object[] projectObjects = node.getUserObjectPath();
        List<Project> projects = new ArrayList<Project>();
        for (Object projectObject : projectObjects) {
            projects.add((Project) projectObject);
        }
        Collections.reverse(projects);
        return projects;
    }

    public String getDisplayString(DefaultMutableTreeNode node) {
        Project project = (Project) node.getUserObject();
        GAV gav = project.getGav();
        GAV parent = project.getParent();
        int level = node.getLevel();
        StringBuilder sb = new StringBuilder();
        sb.append(StringUtils.repeat(" ", level));
        sb.append(toString(parent));
        sb.append(" -> ");
        sb.append(toString(gav));
        sb.append("\n");
        Enumeration<?> children = node.children();
        while (children.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
            sb.append(getDisplayString(child));
        }
        return sb.toString();
    }

    public String getDisplayString(DefaultMutableTreeNode node, File basedir, String pomFile) {
        Project project = (Project) node.getUserObject();
        File pom = project.getPom();
        String pomPath = pom.getAbsolutePath();
        String displayPath = pomPath.replace(basedir.getAbsolutePath(), "");
        displayPath = displayPath.replace(pomFile, "");
        if (!node.isRoot()) {
            displayPath = displayPath.substring(0, displayPath.length() - 1);
            int pos = displayPath.lastIndexOf(File.separator);
            displayPath = displayPath.substring(pos);
            displayPath = displayPath.replace("/", "");
        }
        int level = node.getLevel();
        StringBuilder sb = new StringBuilder();
        sb.append(StringUtils.repeat(" ", level));
        sb.append(displayPath);
        sb.append("\n");
        Enumeration<?> children = node.children();
        while (children.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
            sb.append(getDisplayString(child, basedir, pomFile));
        }
        return sb.toString();
    }

    /**
     * Convert each pom.xml into a <code>Project</code> object and then store each <code>Project</code> as the user object in a
     * <code>DefaultMutableTreeNode</code>
     */
    protected List<DefaultMutableTreeNode> getNodes(List<File> files) {
        List<DefaultMutableTreeNode> nodes = new ArrayList<DefaultMutableTreeNode>();
        for (File file : files) {
            String pomContents = read(file);
            GAV parent = pomUtils.getParentGAV(pomContents);
            GAV gav = pomUtils.getGAV(pomContents);
            Project project = new Project();
            project.setPom(file);
            project.setPomContents(pomContents);
            project.setGav(gav);
            project.setParent(parent);
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(project);
            nodes.add(node);
        }
        return nodes;
    }

    public Map<String, DefaultMutableTreeNode> getMap(List<DefaultMutableTreeNode> nodes) {
        Map<String, DefaultMutableTreeNode> map = new HashMap<String, DefaultMutableTreeNode>();
        for (DefaultMutableTreeNode node : nodes) {
            Project project = (Project) node.getUserObject();
            File file = project.getPom();
            map.put(file.getAbsolutePath(), node);
        }
        return map;
    }

    /**
     * Assemble the list of nodes into a tree, based on the directory structure.
     */
    public DefaultMutableTreeNode getTree(File basedir, List<DefaultMutableTreeNode> nodes, String pomFile) {
        Map<String, DefaultMutableTreeNode> map = getMap(nodes);
        for (DefaultMutableTreeNode child : nodes) {
            Project project = (Project) child.getUserObject();
            File pom = project.getPom();
            File pomDir = pom.getParentFile();
            File parentPom = new File(pomDir.getParentFile(), pomFile);
            String parentPomPath = parentPom.getAbsolutePath();
            DefaultMutableTreeNode parent = map.get(parentPomPath);
            if (parent != null) {
                parent.add(child);
            }
        }
        String rootPom = basedir + File.separator + pomFile;
        DefaultMutableTreeNode root = map.get(rootPom);

        return root;
    }

    protected IOFileFilter getIgnoreDirectoryFilter(String dir) {
        return notFileFilter(and(directoryFileFilter(), nameFileFilter(dir)));
    }

    protected IOFileFilter getIgnoreDirectoriesFilter(String csv) {
        return getIgnoreDirectoriesFilter(csv.split(","));
    }

    protected IOFileFilter getIgnoreDirectoriesFilter(String... directories) {
        IOFileFilter[] filters = new IOFileFilter[directories.length];
        for (int i = 0; i < filters.length; i++) {
            String dir = directories[i].trim();
            filters[i] = getIgnoreDirectoryFilter(dir);
        }
        return FileFilterUtils.and(filters);
    }

    public List<File> getPoms(File basedir, String pomFile, String ignoreDirectoriesCSV) {
        IOFileFilter fileFilter = nameFileFilter(pomFile);
        IOFileFilter dirFilter = getIgnoreDirectoriesFilter(ignoreDirectoriesCSV);
        List<File> files = new ArrayList<File>(FileUtils.listFiles(basedir, fileFilter, dirFilter));
        Collections.sort(files);
        return files;
    }

    public List<SVNExternal> getExternals(List<BuildTag> moduleTags, List<Mapping> mappings) {
        List<SVNExternal> externals = new ArrayList<SVNExternal>();
        for (int i = 0; i < mappings.size(); i++) {
            Mapping mapping = mappings.get(i);
            BuildTag moduleTag = moduleTags.get(i);
            SVNExternal external = new SVNExternal();
            external.setPath(mapping.getModule());
            external.setUrl(moduleTag.getTagUrl());
            externals.add(external);
        }
        return externals;
    }

    public boolean exists(String url) {
        ResourceLoader loader = new DefaultResourceLoader();
        Resource resource = loader.getResource(url);
        return resource.exists();
    }

    public void createTag(BuildTag buildTag, String message) {
        createTags(Collections.singletonList(buildTag), message);
    }

    public void createTags(List<BuildTag> buildTags, String message) {
        for (BuildTag buildTag : buildTags) {
            String src = buildTag.getSourceUrl();
            long revision = buildTag.getSourceRevision();
            String dst = buildTag.getTagUrl();
            boolean exists = exists(dst);
            if (exists) {
                logger.info("Skip existing tag [" + dst + "]");
                buildTag.setSkipped(true);
            } else {
                SVNCommitInfo info = svnUtils.copy(src, revision, dst, message);
                logger.info("Created [" + dst + "]");
                logger.debug("Comitted revision " + info.getNewRevision());
            }
        }
    }

    public void updateExternalsFile(List<SVNExternal> externals, File externalsFile) {
        StringBuilder sb = new StringBuilder();
        for (SVNExternal external : externals) {
            sb.append(external.getPath());
            sb.append(" ");
            sb.append(external.getUrl());
            sb.append("\n");
        }
        write(externalsFile, sb.toString());
        logger.info("Updated svn:externals control file - " + externalsFile.getAbsolutePath());
    }

    public void commitChanges(File dir, List<SVNExternal> externals, String msg) {
        List<File> workingCopyPaths = new ArrayList<File>();
        workingCopyPaths.add(dir);
        for (SVNExternal external : externals) {
            String path = dir.getAbsolutePath() + File.separator + external.getPath();
            workingCopyPaths.add(new File(path));
        }
        File[] commitDirs = workingCopyPaths.toArray(new File[workingCopyPaths.size()]);
        SVNCommitInfo info = svnUtils.commit(commitDirs, msg, null, null);
        logger.info("Committed revision " + info.getNewRevision() + ".");
    }

    public void logTime(String msg, long elapsed) {
        double millis = elapsed * 1.0D;
        double millisPerSecond = 1000;
        double millisPerMinute = 60 * millisPerSecond;
        double millisPerHour = 60 * millisPerMinute;
        if (millis > millisPerHour) {
            logger.info(msg + nf.format(millis / millisPerHour) + "h");
        } else if (millis > millisPerMinute) {
            logger.info(msg + nf.format(millis / millisPerMinute) + "m");
        } else {
            logger.info(msg + nf.format(millis / millisPerSecond) + "s");
        }
    }

    public void writePoms(DefaultMutableTreeNode node, File baseDir) {
        int count = 0;
        Enumeration<?> e = node.depthFirstEnumeration();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode element = (DefaultMutableTreeNode) e.nextElement();
            Project project = (Project) element.getUserObject();
            File pom = project.getPom();
            String oldContents = read(pom);
            String newContents = project.getPomContents();
            if (!oldContents.equals(newContents)) {
                logger.debug("Updating " + pom.getAbsolutePath());
                write(pom, newContents);
                count++;
            }
        }
        logger.info("Updated " + count + " Maven pom's");
    }

    public void writePoms(DefaultMutableTreeNode node, File baseDir, File checkoutDir) {
        int count = 0;
        Enumeration<?> e = node.depthFirstEnumeration();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode element = (DefaultMutableTreeNode) e.nextElement();
            Project project = (Project) element.getUserObject();
            File pom = project.getPom();
            String relativePath = getRelativePath(baseDir, pom);
            File newPom = new File(checkoutDir.getAbsolutePath() + File.separator + relativePath);
            String oldContents = read(pom);
            String newContents = project.getPomContents();
            if (!oldContents.equals(newContents)) {
                logger.debug("Updating " + newPom.getAbsolutePath());
                write(newPom, newContents);
                count++;
            }
        }
        logger.info("Updated " + count + " Maven pom's");
    }

    protected String getRelativePath(File dir, File file) {
        String dirPath = dir.getAbsolutePath();
        String filePath = file.getAbsolutePath();
        return filePath.replace(dirPath, "");
    }

    public void updateScm(DefaultMutableTreeNode root, String scmUrlPrefix) {
        Project project = (Project) root.getUserObject();
        BuildTag buildTag = project.getBuildTag();
        String url = buildTag.getTagUrl();
        String oldXml = project.getPomContents();
        String newXml = pomUtils.updateScm(oldXml, scmUrlPrefix, url);
        project.setPomContents(newXml);
    }

    protected String getGroupId(Project project) {
        GAV gav = project.getGav();
        GAV parent = project.getParent();
        String groupId = gav.getGroupId();
        String parentGroupId = parent.getGroupId();
        if (!StringUtils.isBlank(groupId)) {
            return groupId;
        } else {
            return parentGroupId;
        }
    }

    protected String getVersion(Project project) {
        GAV gav = project.getGav();
        GAV parent = project.getParent();
        String version = gav.getVersion();
        String parentVersion = parent.getVersion();
        if (!StringUtils.isBlank(version)) {
            return version;
        } else {
            return parentVersion;
        }
    }

    public void updateGavs(DefaultMutableTreeNode node) {
        Project project = (Project) node.getUserObject();
        if (project.getNewGav() != null) {
            project.setGav(project.getNewGav());
        }
        Enumeration<?> children = node.children();
        while (children.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
            Project childProject = (Project) child.getUserObject();
            String oldParentVersion = childProject.getParent().getVersion();
            String newParentVersion = getVersion(node);
            if (!oldParentVersion.equals(newParentVersion)) {
                childProject.getParent().setVersion(newParentVersion);
            }
            updateGavs(child);
        }
    }

    public void updateProperties(DefaultMutableTreeNode node, Properties properties, List<Mapping> mappings) {
        Project project = (Project) node.getUserObject();
        Properties versionProperties = getVersionProperties(properties, mappings, node);
        String oldXml = project.getPomContents();
        String newXml = pomUtils.updateProperties(oldXml, versionProperties);
        project.setPomContents(newXml);
    }

    public Properties getVersionProperties(Properties properties, List<Mapping> mappings,
            DefaultMutableTreeNode node) {
        Properties newProperties = new Properties();
        for (Mapping mapping : mappings) {
            String artifactId = mapping.getModule();
            String key = mapping.getVersionProperty();
            String oldValue = properties.getProperty(key);
            if (StringUtils.isBlank(oldValue)) {
                throw new IllegalStateException("No existing value for '" + key + "'");
            }
            DefaultMutableTreeNode moduleNode = findNode(node, artifactId);
            String newValue = getVersion(moduleNode);
            newProperties.setProperty(key, newValue);
        }
        return newProperties;
    }

    protected DefaultMutableTreeNode findNode(DefaultMutableTreeNode node, String artifactId) {
        Enumeration<?> e = node.breadthFirstEnumeration();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode element = (DefaultMutableTreeNode) e.nextElement();
            Project project = (Project) element.getUserObject();
            GAV gav = project.getGav();
            if (gav.getArtifactId().equals(artifactId)) {
                return element;
            }
        }
        throw new IllegalStateException("Unable to locate " + artifactId);
    }

    public void updateXml(DefaultMutableTreeNode node) {
        Project project = (Project) node.getUserObject();
        String version = project.getGav().getVersion();
        if (!StringUtils.isBlank(version)) {
            String oldXml = project.getPomContents();
            String newXml = pomUtils.updateVersion(oldXml, version);
            project.setPomContents(newXml);
        }
        String parentVersion = project.getParent().getVersion();
        String oldXml = project.getPomContents();
        String newXml = pomUtils.updateParentVersion(oldXml, parentVersion);
        project.setPomContents(newXml);
        Enumeration<?> children = node.children();
        while (children.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
            updateXml(child);
        }
    }

    protected void log(DefaultMutableTreeNode node) {
        StringBuilder sb = new StringBuilder();
        logger.info(sb.toString());
    }

    public Map<String, DefaultMutableTreeNode> getGavMap(DefaultMutableTreeNode node) {
        Enumeration<?> e = node.breadthFirstEnumeration();
        Map<String, DefaultMutableTreeNode> map = new HashMap<String, DefaultMutableTreeNode>();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode element = (DefaultMutableTreeNode) e.nextElement();
            Project project = (Project) element.getUserObject();
            GAV gav = project.getGav();
            String gavId = toString(gav);
            map.put(gavId, element);
        }
        return map;
    }

    public void validateMappings(Properties properties, List<Mapping> mappings, DefaultMutableTreeNode node) {
        for (Mapping mapping : mappings) {
            boolean valid = isValid(properties, mapping, node);
            if (!valid) {
                throw new IllegalStateException("Version mismatch on " + mapping.getModule());
            }
        }
    }

    public boolean isValid(Properties properties, Mapping mapping, DefaultMutableTreeNode node) {
        DefaultMutableTreeNode match = findNode(node, mapping.getModule());
        Project project = (Project) match.getUserObject();
        GAV gav = project.getGav();
        String propertyVersion = properties.getProperty(mapping.getVersionProperty());
        String gavVersion = gav.getVersion();

        if (propertyVersion.equals(gavVersion)) {
            return true;
        } else {
            logger.error(String.format("(artifactId, propertyVersion, gavVersion) = (%s, %s, %s)",
                    project.getGav().getArtifactId(), propertyVersion, gavVersion));
            return false;
        }
    }

    public void validateParents(DefaultMutableTreeNode node, Map<String, DefaultMutableTreeNode> map) {
        Enumeration<?> e = node.breadthFirstEnumeration();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode element = (DefaultMutableTreeNode) e.nextElement();
            if (element.isRoot()) {
                continue;
            }
            Project project = (Project) element.getUserObject();
            GAV parentGav = project.getParent();
            String parentGavId = toString(parentGav);
            DefaultMutableTreeNode parent = map.get(parentGavId);
            if (parent == null) {
                throw new IllegalStateException(parentGavId + " could not be located");
            }
        }
    }

    public void fillInGavs(DefaultMutableTreeNode node) {
        Project project = (Project) node.getUserObject();
        GAV gav = project.getGav();
        String groupId = getGroupId(node);
        String version = getVersion(node);
        if (gav.getGroupId() == null) {
            gav.setGroupId(groupId);
            logger.debug("Update " + gav.getArtifactId() + "->" + groupId);
        }
        if (gav.getVersion() == null) {
            gav.setVersion(version);
            logger.debug("Update " + gav.getArtifactId() + "->" + version);
        }
        Enumeration<?> e = node.children();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) e.nextElement();
            fillInGavs(child);
        }
    }

    public void updateBuildInfo(DefaultMutableTreeNode node, BuildTag buildTag, TagStyle tagStyle,
            int buildNumber) {
        Project project = (Project) node.getUserObject();
        project.setBuildTag(buildTag);
        GAV oldGav = project.getGav();
        String newVersion = getNewVersion(oldGav.getVersion(), buildNumber, buildTag.getSourceRevision(), tagStyle);
        GAV newGav = new GAV();
        newGav.setGroupId(oldGav.getGroupId());
        newGav.setArtifactId(oldGav.getArtifactId());
        newGav.setVersion(newVersion);
        project.setNewGav(newGav);
        logger.info("GAV Update - [" + toString(oldGav) + "->" + newVersion + "]");
    }

    public void updateBuildInfo(List<DefaultMutableTreeNode> nodes, List<BuildTag> moduleTags,
            List<Mapping> mappings, TagStyle tagStyle, int buildNumber) {
        for (int i = 0; i < mappings.size(); i++) {
            Mapping mapping = mappings.get(i);
            BuildTag moduleTag = moduleTags.get(i);
            DefaultMutableTreeNode node = findNode(nodes, mapping.getModule());
            updateBuildInfo(node, moduleTag, tagStyle, buildNumber);
        }
    }

    protected DefaultMutableTreeNode findNode(List<DefaultMutableTreeNode> nodes, String artifactId) {
        for (DefaultMutableTreeNode node : nodes) {
            Project project = (Project) node.getUserObject();
            if (project.getGav().getArtifactId().equals(artifactId)) {
                return node;
            }
        }
        throw new IllegalStateException("Unable to locate " + artifactId);
    }

    public List<BuildTag> getBuildTags(Properties properties, List<SVNExternal> externals, List<Mapping> mappings,
            TagStyle tagStyle, int buildNumber) {
        Collections.sort(externals);
        Collections.sort(mappings);
        List<BuildTag> buildTags = new ArrayList<BuildTag>();
        for (int i = 0; i < externals.size(); i++) {
            SVNExternal external = externals.get(i);
            Mapping mapping = mappings.get(i);
            BuildTag buildTag = getBuildTag(properties, external, mapping, tagStyle, buildNumber);
            buildTags.add(buildTag);
        }
        return buildTags;
    }

    public BuildTag getBuildTag(File workingCopy, GAV gav, TagStyle tagStyle, int buildNumber) {
        String sourceUrl = svnUtils.getUrl(workingCopy);
        long sourceRevision = svnUtils.getLastRevision(workingCopy);
        String version = gav.getVersion();

        String tag = getTag(sourceUrl, version, gav.getArtifactId(), buildNumber, sourceRevision, tagStyle);

        BuildTag buildTag = new BuildTag();
        buildTag.setSourceUrl(sourceUrl);
        buildTag.setSourceRevision(sourceRevision);
        buildTag.setTagUrl(tag);
        return buildTag;
    }

    public BuildTag getBuildTag(Properties properties, SVNExternal external, Mapping mapping, TagStyle tagStyle,
            int buildNumber) {
        File workingCopy = external.getWorkingCopyPath();
        String sourceUrl = svnUtils.getUrl(workingCopy);
        long sourceRevision = svnUtils.getLastRevision(workingCopy);
        String version = properties.getProperty(mapping.getVersionProperty());
        String tag = getTag(sourceUrl, version, mapping.getModule(), buildNumber, sourceRevision, tagStyle);

        BuildTag buildTag = new BuildTag();
        buildTag.setSourceUrl(sourceUrl);
        buildTag.setSourceRevision(sourceRevision);
        buildTag.setTagUrl(tag);
        return buildTag;
    }

    /**
     * Assuming version is in the form <code>1.0.0-beta-SNAPSHOT</code>, this method returns <code>1.0.0-beta-r3201</code> when
     * <code>TagStyle=REVISION</code>, <code>1.0.0-beta-build-187</code> when <code>TagStyle=BUILDNUMBER</code>, and <code>1.0.0-beta</code>
     * when <code>TagStyle=RELEASE</code>
     */
    public String getNewVersion(String version, int buildNumber, long revision, TagStyle tagStyle) {
        String trimmed = trimSnapshot(version);
        switch (tagStyle) {
        case REVISION:
            return trimmed + "-r" + revision;
        case BUILDNUMBER:
            return trimmed + "-build-" + buildNumber;
        case RELEASE:
            return trimmed;
        default:
            throw new IllegalArgumentException(tagStyle + " is unknown");
        }
    }

    /**
     * Assuming version is in the form <code>1.0.0-beta-SNAPSHOT</code>, this method will increment the 3rd digit by 1, and return
     * <code>1.0.1-beta-SNAPSHOT</code>
     */
    public String getNextVersion(String version) {
        if (!version.contains(MAVEN_SNAPSHOT_TOKEN)) {
            throw new IllegalArgumentException(version + " is not a " + MAVEN_SNAPSHOT_TOKEN);
        }
        Version v = VersionUtils.getVersion(version);

        String qualifier = v.getQualifier();

        if (qualifier != null && !qualifier.isEmpty()) {
            String qualifierParts[] = qualifier.split(QUALIFIER_DELIMETER);

            if (qualifierParts.length > 0) {

                StringBuilder nextQualifer = new StringBuilder();

                if (qualifierContainsVersionedPrefix(majorQualifierFoundersReleasePrefix, qualifierParts[0])) {

                    // founders release qualifier is present.
                    if (qualifierParts.length == 2) {
                        // founders release - milestone or release candidate
                        nextQualifer.append(qualifierParts[0]);
                        nextQualifer.append(QUALIFIER_DELIMETER);

                        if (qualifierContainsVersionedPrefix(minorQualiferMilestonePrefix, qualifierParts[1])) {
                            // milestone

                            String nextMilestone = incrementQualifier(minorQualiferMilestonePrefix,
                                    qualifierParts[1]);

                            nextQualifer.append(nextMilestone);
                        } else if (qualifierContainsVersionedPrefix(minorQualiferReleaseCandidatePrefix,
                                qualifierParts[1])) {
                            // release candidate
                            String nextReleaseCandidate = incrementQualifier(minorQualiferReleaseCandidatePrefix,
                                    qualifierParts[1]);

                            nextQualifer.append(nextReleaseCandidate);

                        } else {
                            // invalid minor qualifier
                            throw new IllegalArgumentException("invalid minor qualifier: " + qualifierParts[1]);
                        }

                        v.setQualifier(nextQualifer.toString());
                    } else {
                        // only contains the main qualifier

                        String nextMajorQualifer = incrementQualifier(majorQualifierFoundersReleasePrefix,
                                qualifierParts[0]);

                        v.setQualifier(nextMajorQualifer);
                    }

                } else {

                    // invalid major qualifier
                    throw new IllegalArgumentException("invalid major qualifier: " + qualifierParts[0]);
                }
            }

        } else {
            Integer oldIncremental = new Integer(v.getIncremental());
            Integer newIncremental = oldIncremental + 1;
            v.setIncremental(newIncremental.toString());
        }

        StringBuilder sb = new StringBuilder();
        sb.append(v.getMajor());
        sb.append(".");
        sb.append(v.getMinor());
        sb.append(".");
        sb.append(v.getIncremental());
        sb.append(QUALIFIER_DELIMETER);
        if (!StringUtils.isBlank(v.getQualifier())) {
            sb.append(v.getQualifier());
            sb.append(QUALIFIER_DELIMETER);
        }

        sb.append(MAVEN_SNAPSHOT_TOKEN);
        return sb.toString();
    }

    private String incrementQualifier(String targetPrefix, String qualifier) {

        // increment this part
        if (qualifier.toLowerCase().startsWith(targetPrefix.toLowerCase())) {

            String baseQualifier = qualifier.substring(0, targetPrefix.length());
            String token = "";
            try {
                token = qualifier.substring(targetPrefix.length());
                Integer oldVersion = new Integer(token);
                Integer newVersion = oldVersion + 1;

                return baseQualifier + newVersion;
            } catch (NumberFormatException e) {

                throw new RuntimeException(
                        "failed to convert " + token + " suffix of " + qualifier + "into an Integer", e);
            }

        } else {
            throw new IllegalArgumentException(
                    "'" + qualifier + "' does not contain a part starting with '" + targetPrefix);
        }

    }

    /**
     * A main qualifier is a Founders Release FR[0-9]+
     */
    protected boolean isKnownMainQualifier(String qualifier) {
        return qualifierContainsVersionedPrefix(majorQualifierFoundersReleasePrefix, qualifier);
    }

    /**
     * A sub qualifier is a milestone M[0-9]+ or release candidate RC[0-9]+
     */
    protected boolean isKnownSubQualifier(String qualifier) {

        if (qualifierContainsVersionedPrefix(minorQualiferMilestonePrefix, qualifier)
                || qualifierContainsVersionedPrefix(minorQualiferReleaseCandidatePrefix, qualifier))
            return true;
        else
            return false;

    }

    private boolean qualifierContainsVersionedPrefix(String targetPrefix, String qualifier) {

        if (StringUtils.isBlank(qualifier)) {
            return false;
        } else if (qualifier.length() < targetPrefix.length()) {
            return false;
        } else {
            if (qualifier.toLowerCase().startsWith(targetPrefix.toLowerCase())) {

                try {
                    String suffix = StringUtils.substring(qualifier, targetPrefix.length());
                    Integer.parseInt(suffix);
                } catch (NumberFormatException e) {

                    return false;
                }

            } else {
                return false;
            }
        }
        return true;
    }

    protected boolean isDigit(char c) {
        for (char digit : DIGITS) {
            if (c == digit) {
                return true;
            }
        }
        return false;
    }

    public String getTag(String url, String version, String artifactId, int buildNumber, long revision,
            TagStyle tagStyle) {
        switch (tagStyle) {
        case REVISION:
            return getRevisionTag(url, version, artifactId, revision);
        case BUILDNUMBER:
            return getBuildNumberTag(url, version, artifactId, buildNumber);
        case RELEASE:
            return getReleaseTag(url, version, artifactId);
        default:
            throw new IllegalArgumentException(tagStyle + " is unknown");
        }
    }

    public int getBuildNumber(MavenProject project, String buildNumberProperty) {
        Properties properties = propertiesUtils.getMavenProperties(project);
        String buildNumber = properties.getProperty(buildNumberProperty);
        if (StringUtils.isBlank(buildNumber)) {
            logger.warn(buildNumberProperty + " is blank");
            return 0;
        } else {
            return new Integer(buildNumber);
        }
    }

    public String getReleaseTag(String url, String version, String artifactId) {
        String tagBase = extractor.getTagBase(url);
        if (StringUtils.isBlank(tagBase)) {
            throw new IllegalArgumentException("Unable to calculate tag base from [" + url + "]");
        }

        String trimmed = trimSnapshot(version);

        StringBuilder sb = new StringBuilder();
        sb.append(tagBase);
        sb.append("/");
        sb.append(artifactId);
        sb.append(QUALIFIER_DELIMETER);
        sb.append(trimmed);
        return sb.toString();
    }

    public String getBuildNumberTag(String url, String version, String artifactId, int buildNumber) {
        StringBuilder sb = new StringBuilder();
        sb.append(getBaseTag(url, version, artifactId));
        sb.append("/");
        sb.append("build-" + buildNumber);
        return sb.toString();
    }

    public String getRevisionTag(String url, String version, String artifactId, long revision) {
        StringBuilder sb = new StringBuilder();
        sb.append(getBaseTag(url, version, artifactId));
        sb.append("/");
        sb.append("r" + revision);
        return sb.toString();
    }

    protected String getBaseTag(String url, String version, String artifactId) {
        String tagBase = extractor.getTagBase(url);
        if (StringUtils.isBlank(tagBase)) {
            throw new IllegalArgumentException("Unable to calculate tag base from [" + url + "]");
        }

        Version v = parseVersion(version);
        String trimmed = trimSnapshot(version);

        StringBuilder sb = new StringBuilder();
        sb.append(tagBase);
        sb.append("/");
        sb.append("builds");
        sb.append("/");
        sb.append(artifactId);
        sb.append(QUALIFIER_DELIMETER);
        sb.append(v.getMajor());
        sb.append(".");
        sb.append(v.getMinor());
        sb.append("/");
        sb.append(trimmed);
        return sb.toString();
    }

    public void validate(MavenProject project, List<SVNExternal> externals, List<Mapping> mappings) {
        validate(externals, mappings);
        validate(project, mappings);
        validateProjectModules(project, externals);
    }

    public void validateProjectModules(MavenProject project, List<SVNExternal> externals) {
        List<String> modules = project.getModules();
        if (isEmpty(modules) && isEmpty(externals)) {
            return;
        } else if (isEmpty(externals) && !isEmpty(modules)) {
            throw new IllegalArgumentException(
                    "No externals detected but " + modules.size() + " modules were detected");
        } else if (!isEmpty(externals) && isEmpty(modules)) {
            throw new IllegalArgumentException(
                    externals.size() + " externals were detected but no modules were detected");
        } else if (externals.size() != modules.size()) {
            throw new IllegalArgumentException("Mismatch. " + externals.size() + " externals were detected. "
                    + modules.size() + " modules were detected");
        }
        Collections.sort(modules);
        Collections.sort(externals);
        for (int i = 0; i < modules.size(); i++) {
            String module1 = modules.get(i);
            String module2 = externals.get(i).getPath();
            if (!module1.equals(module2)) {
                throw new IllegalArgumentException("Mismatch. " + module1 + " <> " + module2);
            }
        }
    }

    public void validate(List<SVNExternal> externals, List<Mapping> mappings) {
        if (isEmpty(externals) && isEmpty(mappings)) {
            return;
        } else if (isEmpty(externals) && !isEmpty(mappings)) {
            throw new IllegalArgumentException(
                    "No externals detected but " + mappings.size() + " mappings were supplied");
        } else if (!isEmpty(externals) && isEmpty(mappings)) {
            throw new IllegalArgumentException(
                    externals.size() + " externals were detected but no mappings were supplied");
        } else if (externals.size() != mappings.size()) {
            throw new IllegalArgumentException("Mismatch. " + externals.size() + " externals were detected. "
                    + mappings.size() + " mappings were supplied");
        }
        for (SVNExternal external : externals) {
            File workingCopy = external.getWorkingCopyPath();
            if (!workingCopy.exists()) {
                throw new IllegalArgumentException(workingCopy.getAbsolutePath() + " does not exist");
            }
        }
    }

    public void validate(MavenProject project, List<Mapping> mappings) {
        validate(project.getProperties(), mappings);
        validateModules(project.getModules(), mappings);
    }

    public void validateModules(List<String> modules, List<Mapping> mappings) {
        Collections.sort(mappings);
        Collections.sort(modules);
        if (isEmpty(modules) && isEmpty(mappings)) {
            return;
        } else if (isEmpty(modules) && !isEmpty(mappings)) {
            throw new IllegalArgumentException(
                    "No modules detected but " + mappings.size() + " mappings were supplied");
        } else if (!isEmpty(modules) && isEmpty(mappings)) {
            throw new IllegalArgumentException(
                    modules.size() + " modules were detected but no mappings were supplied");
        } else if (modules.size() != mappings.size()) {
            throw new IllegalArgumentException("Mismatch. " + modules.size() + " modules were detected. "
                    + mappings.size() + " mappings were supplied");
        }
        for (int i = 0; i < modules.size(); i++) {
            String module1 = modules.get(i);
            String module2 = mappings.get(i).getModule();
            if (!module1.equals(module2)) {
                throw new IllegalArgumentException("Mismatch. " + module1 + " <> " + module2);
            }
        }

    }

    public void validate(Properties properties, List<Mapping> mappings) {
        if (isEmpty(mappings)) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        int missingCount = 0;
        for (Mapping mapping : mappings) {
            String key = mapping.getVersionProperty();
            String value = properties.getProperty(key);
            if (StringUtils.isBlank(value)) {
                if (missingCount++ != 0) {
                    sb.append(", ");
                }
                sb.append(key);
            }
        }
        if (missingCount != 0) {
            throw new IllegalArgumentException("Missing values for [" + sb.toString() + "]");
        }
    }

    public String trimSnapshot(String version) {
        if (version.toUpperCase().endsWith(QUALIFIER_DELIMETER + MAVEN_SNAPSHOT_TOKEN)) {
            int length = MAVEN_SNAPSHOT_TOKEN.length() + 1;
            return StringUtils.left(version, version.length() - length);
        } else {
            return version;
        }
    }

    public boolean isEmpty(Collection<?> c) {
        return c == null || c.isEmpty();
    }

    protected Version parseVersion(String s) {
        boolean snapshot = s.toUpperCase().endsWith(QUALIFIER_DELIMETER + MAVEN_SNAPSHOT_TOKEN);
        Version version = new Version();
        version.setSnapshot(snapshot);
        String[] tokens = StringUtils.split(s, ".-");
        if (tokens.length > 0) {
            version.setMajor(tokens[0]);
        }
        if (tokens.length > 1) {
            version.setMinor(tokens[1]);
        }
        if (tokens.length > 2) {
            version.setIncremental(tokens[2]);
        }
        String qualifier = getQualifier(tokens);
        version.setQualifier(qualifier);
        return version;
    }

    protected String getQualifier(String[] tokens) {
        if (tokens.length <= 3) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 3; i < tokens.length; i++) {
            if (tokens[i].toUpperCase().equals(MAVEN_SNAPSHOT_TOKEN)) {
                break;
            }
            if (i != 3) {
                sb.append(QUALIFIER_DELIMETER);
            }
            sb.append(tokens[i]);
        }
        return sb.toString();
    }

    public void write(File file, String data) {
        try {
            FileUtils.write(file, data);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public void deleteDirectory(File dir) {
        try {
            FileUtils.deleteDirectory(dir);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public String read(File file) {
        try {
            return FileUtils.readFileToString(file);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }
}