package org.springframework.roo.project; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.roo.support.util.CollectionUtils; import org.springframework.roo.support.util.DomUtils; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Simplified immutable representation of a maven build plugin. * <p> * Structured after the model used by Maven. * * @author Alan Stewart * @since 1.1 */ public class Plugin implements Comparable<Plugin> { /** * The Maven groupId that will be assigned to a plugin if one is not * provided */ public static final String DEFAULT_GROUP_ID = "org.apache.maven.plugins"; /** * Parses the given plugin XML element for the plugin's Maven artifactId * * @param plugin the XML element to parse (required) * @return a non-blank id */ private static String getArtifactId(final Element plugin) { return plugin.getElementsByTagName("artifactId").item(0).getTextContent(); } /** * Parses the configuration of the given plugin (global, not * execution-scoped) * * @param plugin the XML element to parse (required) * @return <code>null</code> if there isn't one */ private static Configuration getConfiguration(final Element plugin) { return Configuration.getInstance(XmlUtils.findFirstElement("configuration", plugin)); } /** * Parses the given XML plugin element for the plugin's dependencies * * @param plugin the XML element to parse (required) * @return a non-<code>null</code> list */ private static List<Dependency> getDependencies(final Element plugin) { final List<Dependency> dependencies = new ArrayList<Dependency>(); for (final Element dependencyElement : XmlUtils.findElements("dependencies/dependency", plugin)) { // groupId final Element groupIdElement = XmlUtils.findFirstElement("groupId", dependencyElement); final String groupId = DomUtils.getTextContent(groupIdElement, ""); // artifactId final Element artifactIdElement = XmlUtils.findFirstElement("artifactId", dependencyElement); final String artifactId = DomUtils.getTextContent(artifactIdElement, ""); // version final Element versionElement = XmlUtils.findFirstElement("version", dependencyElement); final String version = DomUtils.getTextContent(versionElement, ""); final Dependency dependency = new Dependency(groupId, artifactId, version); // Parse any exclusions for (final Element exclusion : XmlUtils.findElements("exclusions/exclusion", dependencyElement)) { // groupId final Element exclusionGroupIdElement = XmlUtils.findFirstElement("groupId", exclusion); final String exclusionGroupId = DomUtils.getTextContent(exclusionGroupIdElement, ""); // artifactId final Element exclusionArtifactIdElement = XmlUtils.findFirstElement("artifactId", exclusion); final String exclusionArtifactId = DomUtils.getTextContent(exclusionArtifactIdElement, ""); if (StringUtils.isNotBlank(exclusionGroupId) && StringUtils.isNotBlank(exclusionArtifactId)) { dependency.addExclusion(exclusionGroupId, exclusionArtifactId); } } dependencies.add(dependency); } return dependencies; } /** * Parses the given XML plugin element for the plugin's executions * * @param plugin the XML element to parse (required) * @return a non-<code>null</code> list */ private static List<Execution> getExecutions(final Element plugin) { final List<Execution> executions = new ArrayList<Execution>(); // Loop through the "execution" elements in the plugin element for (final Element execution : XmlUtils.findElements("executions/execution", plugin)) { final Element idElement = XmlUtils.findFirstElement("id", execution); final String id = DomUtils.getTextContent(idElement, ""); final Element phaseElement = XmlUtils.findFirstElement("phase", execution); final String phase = DomUtils.getTextContent(phaseElement, ""); final List<String> goals = new ArrayList<String>(); for (final Element goalElement : XmlUtils.findElements("goals/goal", execution)) { goals.add(goalElement.getTextContent()); } final Configuration configuration = Configuration.getInstance(XmlUtils.findFirstElement("configuration", execution)); executions.add(new Execution(id, phase, configuration, goals.toArray(new String[goals.size()]))); } return executions; } /** * Parses the plugin's Maven groupId from the given element * * @param plugin the element to parse (required) * @return a non-blank groupId */ public static String getGroupId(final Element plugin) { if (plugin.getElementsByTagName("groupId").getLength() > 0) { return plugin.getElementsByTagName("groupId").item(0).getTextContent(); } return DEFAULT_GROUP_ID; } /** * Parses the plugin's version number from the given XML element * * @param plugin the element to parse (required) * @return a non-<code>null</code> version number (might be empty) */ private static String getVersion(final Element plugin) { final List<Element> versionElements = XmlUtils.findElements("./version", plugin); if (!versionElements.isEmpty()) { return versionElements.get(0).getTextContent(); } return ""; } private final Configuration configuration; private final List<Dependency> dependencies = new ArrayList<Dependency>(); private final List<Execution> executions = new ArrayList<Execution>(); private final GAV gav; /** * Constructor from a POM-style XML element that defines a Maven <plugin>. * * @param plugin the XML element to parse (required) */ public Plugin(final Element plugin) { this(getGroupId(plugin), getArtifactId(plugin), getVersion(plugin), getConfiguration(plugin), getDependencies(plugin), getExecutions(plugin)); } /** * Constructor that takes the minimal Maven artifact coordinates. * * @param groupId the group ID (required) * @param artifactId the artifact ID (required) * @param version the version (required) */ public Plugin(final String groupId, final String artifactId, final String version) { this(groupId, artifactId, version, null, null, null); } /** * Constructor that allows all fields to be set. * * @param groupId the group ID (required) * @param artifactId the artifact ID (required) * @param version the version (required) * @param configuration the configuration for this plugin (optional) * @param dependencies the dependencies for this plugin (can be * <code>null</code>) * @param executions the executions for this plugin (can be * <code>null</code>) */ public Plugin(final String groupId, final String artifactId, final String version, final Configuration configuration, final Collection<? extends Dependency> dependencies, final Collection<? extends Execution> executions) { Validate.notNull(groupId, "Group ID required"); Validate.notNull(artifactId, "Artifact ID required"); //Validate.notNull(version, "Version required"); if (version == null || version == "") { gav = new GAV(groupId, artifactId, "-"); } else { gav = new GAV(groupId, artifactId, version); } this.configuration = configuration; // Defensively copy the given nullable collections CollectionUtils.populate(this.dependencies, dependencies); CollectionUtils.populate(this.executions, executions); } public int compareTo(final Plugin o) { if (o == null) { throw new NullPointerException(); } int result = gav.compareTo(o.getGAV()); if (result == 0 && configuration != null && o.configuration != null) { result = configuration.compareTo(o.configuration); } return result; } @Override public boolean equals(final Object obj) { return obj instanceof Plugin && compareTo((Plugin) obj) == 0; } public String getArtifactId() { return gav.getArtifactId(); } /** * Returns the top-level configuration of this plugin, if any. Note that * individual {@link Execution}s may have their own {@link Configuration}s * instead of or in addition to this configuration. * * @return <code>null</code> if none exists */ public Configuration getConfiguration() { return configuration; } public List<Dependency> getDependencies() { return dependencies; } /** * Returns the {@link Element} to add to the given POM {@link Document} for * this plugin * * @param plugin the plugin for which to create an XML Element (required) * @param document the document to which the element will belong (required) * @return a non-<code>null</code> element * @since 1.2.0 */ public Element getElement(final Document document) { final Element pluginElement = document.createElement("plugin"); // Basic coordinates pluginElement.appendChild(XmlUtils.createTextElement(document, "groupId", getGroupId())); pluginElement.appendChild(XmlUtils.createTextElement(document, "artifactId", getArtifactId())); pluginElement.appendChild(XmlUtils.createTextElement(document, "version", getVersion())); // Configuration if (configuration != null) { final Node configuration = document.importNode(this.configuration.getConfiguration(), true); pluginElement.appendChild(configuration); } // Executions if (!executions.isEmpty()) { final Element executionsElement = DomUtils.createChildElement("executions", pluginElement, document); for (final Execution execution : executions) { executionsElement.appendChild(execution.getElement(document)); } } // Dependencies if (!dependencies.isEmpty()) { final Element dependenciesElement = DomUtils.createChildElement("dependencies", pluginElement, document); for (final Dependency dependency : dependencies) { dependenciesElement.appendChild(dependency.getElement(document)); } } return pluginElement; } public List<Execution> getExecutions() { return executions; } /** * Returns the Maven-style coordinates of this plugin * * @return a non-<code>null</code> set of coordinates */ public GAV getGAV() { return gav; } /** * Returns this plugin's groupId. * * @return */ public String getGroupId() { return gav.getGroupId(); } /** * @return a simple description, as would be used for console output */ public String getSimpleDescription() { return gav.toString(); } public String getVersion() { return gav.getVersion(); } @Override public int hashCode() { final int prime = 31; final int result = prime * 1 + gav.hashCode(); return prime * result + (configuration == null ? 0 : configuration.hashCode()); } /** * Indicates whether the given {@link Plugin} has the same Maven coordinates * as this one; this is not necessarily the same as calling * {@link #equals(Object)}, which may compare more fields beyond the basic * coordinates. * * @param plugin the plugin to check (can be <code>null</code>) * @return <code>false</code> if any coordinates are different */ public boolean hasSameCoordinates(final Plugin dependency) { return dependency != null && compareCoordinates(dependency) == 0; } /** * Compares this plugin's identifying coordinates (i.e. not the version) to * those of the given plugin * * @param other the plugin being compared to (required) * @return see {@link Comparable#compareTo(Object)} */ private int compareCoordinates(final Plugin other) { Validate.notNull(other, "Plugin being compared to cannot be null"); int result = getGroupId().compareTo(other.getGroupId()); if (result == 0) { result = getArtifactId().compareTo(other.getArtifactId()); } return result; } @Override public String toString() { final ToStringBuilder builder = new ToStringBuilder(this); builder.append("gav", gav); if (configuration != null) { builder.append("configuration", configuration); } return builder.toString(); } }