package org.springframework.roo.project.maven;
import static org.springframework.roo.project.Path.ROOT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.DependencyScope;
import org.springframework.roo.project.DependencyType;
import org.springframework.roo.project.Filter;
import org.springframework.roo.project.GAV;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PhysicalPath;
import org.springframework.roo.project.Plugin;
import org.springframework.roo.project.Property;
import org.springframework.roo.project.Repository;
import org.springframework.roo.project.Resource;
import org.springframework.roo.support.util.CollectionUtils;
import org.springframework.roo.support.util.FileUtils;
/**
* A Maven project object model (POM).
*
* @author James Tyrrell
* @author Andrew Swan
* @author Juan Carlos GarcĂa
* @since 1.2.0
*/
public class Pom {
static final String DEFAULT_PACKAGING = "jar"; // Maven behaviour
public static final String ROOT_MODULE_SYMBOL = "~";
private final Set<Plugin> pluginsInPluginManagement = new LinkedHashSet<Plugin>();
private final Set<Plugin> buildPlugins = new LinkedHashSet<Plugin>();
private final Set<Dependency> dependenciesInDependencyManagement =
new LinkedHashSet<Dependency>();
private final Set<Dependency> dependencies = new LinkedHashSet<Dependency>();
private final Set<Filter> filters = new LinkedHashSet<Filter>();
private final GAV gav;
private final String moduleName;
private final Set<Module> modules = new LinkedHashSet<Module>();
private final String name;
private final String packaging;
private final Parent parent;
private final String path;
private final Map<Path, PhysicalPath> pathLocations = new LinkedHashMap<Path, PhysicalPath>();
private final Set<Repository> pluginRepositories = new LinkedHashSet<Repository>();
private final Set<Property> pomProperties = new LinkedHashSet<Property>();
private final Set<Repository> repositories = new LinkedHashSet<Repository>();
private final Set<Resource> resources = new LinkedHashSet<Resource>();
private final String sourceDirectory; // TODO use pathCache instead
private final String testSourceDirectory; // TODO use pathCache instead
/**
* Constructor
*
* @param groupId the Maven groupId, explicit or inherited (required)
* @param artifactId the Maven artifactId (required)
* @param version the version of the artifact being built (required)
* @param packaging the Maven packaging (required)
* @param dependenciesInDependencyManagement (can be <code>null</code> for none)
* @param dependencies (can be <code>null</code> for none)
* @param parent the POM's parent declaration (can be <code>null</code> for
* none)
* @param modules the modules defined by this POM (only applies when
* packaging is "pom"; can be <code>null</code> for none)
* @param pomProperties any properties defined in the POM (can be
* <code>null</code> for none)
* @param name the Maven name of the artifact being built (can be blank)
* @param repositories any repositories defined in the POM (can be
* <code>null</code> for none)
* @param pluginRepositories any plugin repositories defined in the POM (can
* be <code>null</code> for none)
* @param sourceDirectory the directory relative to the POM that contains
* production code (can be blank for the Maven default)
* @param testSourceDirectory the directory relative to the POM that
* contains test code (can be blank for the Maven default)
* @param filters any filters defined in the POM (can be <code>null</code>
* for none)
* @param pluginsInPluginManagement any plugin defined in pluginManagement section
* @param buildPlugins any plugins defined in the POM (can be
* <code>null</code> for none)
* @param resources any build resources defined in the POM (can be
* <code>null</code> for none)
* @param path the canonical path of this POM (required)
* @param moduleName the Maven name of this module (blank for the project's
* root or only POM)
* @param paths the {@link Path}s required for this module, in addition to
* the root (can be <code>null</code>)
*/
public Pom(final String groupId, final String artifactId, final String version,
final String packaging,
final Collection<? extends Dependency> dependenciesInDependencyManagement,
final Collection<? extends Dependency> dependencies, final Parent parent,
final Collection<? extends Module> modules,
final Collection<? extends Property> pomProperties, final String name,
final Collection<? extends Repository> repositories,
final Collection<? extends Repository> pluginRepositories, final String sourceDirectory,
final String testSourceDirectory, final Collection<? extends Filter> filters,
final Collection<? extends Plugin> pluginsInPluginManagement,
final Collection<? extends Plugin> buildPlugins,
final Collection<? extends Resource> resources, final String path, final String moduleName,
final Collection<Path> paths) {
Validate.notBlank(packaging, "Invalid packaging '%s'", packaging);
Validate.notBlank(path, "Invalid path '%s'", path);
//gav = new GAV(groupId, artifactId, version);
this.moduleName = StringUtils.stripToEmpty(moduleName);
this.name = StringUtils.stripToEmpty(name);
this.packaging = packaging;
this.parent = parent;
if (version == null && parent.getVersion() != null) {
gav = new GAV(groupId, artifactId, parent.getVersion());
} else {
gav = new GAV(groupId, artifactId, version);
}
this.path = path;
this.sourceDirectory =
StringUtils.defaultIfEmpty(sourceDirectory, Path.SRC_MAIN_JAVA.getDefaultLocation());
this.testSourceDirectory =
StringUtils.defaultIfEmpty(testSourceDirectory, Path.SRC_TEST_JAVA.getDefaultLocation());
CollectionUtils.populate(this.pluginsInPluginManagement, pluginsInPluginManagement);
CollectionUtils.populate(this.buildPlugins, buildPlugins);
CollectionUtils.populate(this.dependenciesInDependencyManagement,
dependenciesInDependencyManagement);
CollectionUtils.populate(this.dependencies, dependencies);
CollectionUtils.populate(this.filters, filters);
CollectionUtils.populate(this.modules, modules);
CollectionUtils.populate(this.pluginRepositories, pluginRepositories);
CollectionUtils.populate(this.pomProperties, pomProperties);
CollectionUtils.populate(this.repositories, repositories);
CollectionUtils.populate(this.resources, resources);
cachePhysicalPaths(paths);
}
/**
* Returns this module as a Dependency with the given scope
*
* @param scope the DependencyScope to use
* @return a non-<code>null</code> instance
*/
public Dependency asDependency(final DependencyScope scope) {
return new Dependency(gav, DependencyType.valueOfTypeCode(packaging), scope);
}
/**
* Returns this module as a Dependency with the given scope and type
*
* @param scope the DependencyScope to use
* @param type the DependencyType to use
* @return a non-<code>null</code> instance
*/
public Dependency asDependency(final DependencyScope scope, final DependencyType type) {
return new Dependency(gav, type, scope);
}
private void cachePhysicalPaths(final Collection<Path> paths) {
final Collection<Path> pathsToCache = CollectionUtils.populate(new HashSet<Path>(), paths);
if (!pathsToCache.contains(ROOT)) {
pathsToCache.add(ROOT);
}
for (final Path path : pathsToCache) {
pathLocations.put(path, path.getModulePath(this));
}
}
/**
* Indicates whether it's valid to add the given {@link Dependency} to this
* POM.
*
* @param newDependency the {@link Dependency} to check (can be
* <code>null</code>)
* @return see above
* @since 1.2.1
*/
public boolean canAddDependency(final Dependency newDependency, boolean checkVersion) {
return newDependency != null && !isDependencyRegistered(newDependency, checkVersion)
&& !Dependency.isHigherLevel(newDependency.getType().toString(), packaging);
}
/**
* Indicates whether it's valid to add the given {@link Dependency} to the dependencyManagement
* of this pom
*
* @param newDependency the {@link Dependency} to check (can be
* <code>null</code>)
* @return see above
* @since 2.0
*/
public boolean canAddDependencyToDependencyManagement(final Dependency newDependency,
boolean checkVersion) {
return newDependency != null
&& !isDependencyRegisteredInDependencyManagement(newDependency, checkVersion)
&& !Dependency.isHigherLevel(newDependency.getType().toString(), packaging);
}
/**
* Indicates whether it's valid to add the given {@link Plugin} to the pluginManagement
* of this pom
*
* @param newPlugin the {@link Plugin} to check (can be
* <code>null</code>)
* @return see above
* @since 2.0
*/
public boolean canAddPluginToPluginManagement(final Plugin newPlugin, boolean checkVersion) {
return newPlugin != null && !isPluginRegisteredInPluginManagement(newPlugin, checkVersion);
}
/**
* Returns the ID of the artifact created by this module or project
*
* @return a non-blank ID
*/
public String getArtifactId() {
return gav.getArtifactId();
}
/**
* Returns any registered build plugins
*
* @return a non-<code>null</code> collection
*/
public Set<Plugin> getBuildPlugins() {
return buildPlugins;
}
/**
* Returns any build plugins with the same groupId and artifactId as the
* given plugin. This is useful for upgrade cases.
*
* @param plugin to locate (required; note the version number is ignored in
* comparisons)
* @return any matching plugins (never returns null, but may return an empty
* {@link Set})
*/
public Set<Plugin> getBuildPluginsExcludingVersion(final Plugin plugin) {
Validate.notNull(plugin, "Plugin to locate is required");
final Set<Plugin> result = new HashSet<Plugin>();
for (final Plugin p : buildPlugins) {
if (plugin.getArtifactId().equals(p.getArtifactId())
&& plugin.getGroupId().equals(p.getGroupId())) {
result.add(p);
}
}
return result;
}
public Set<Dependency> getDependencies() {
return dependencies;
}
/**
* Locates any dependencies which match the presented dependency, excluding
* the version number. This is useful for upgrade use cases, where it is
* necessary to remove any dependencies with the same group id, artifact id,
* and type as the dependency being upgraded to.
*
* @param dependency to locate (can be <code>null</code>)
* @return any matching dependencies (never returns null, but may return an
* empty {@link Set})
*/
public Set<Dependency> getDependenciesExcludingVersion(final Dependency dependency) {
final Set<Dependency> result = new HashSet<Dependency>();
for (final Dependency d : dependencies) {
if (dependency != null && dependency.getArtifactId().equals(d.getArtifactId())
&& dependency.getGroupId().equals(d.getGroupId())
&& dependency.getType().equals(d.getType())) {
result.add(d);
}
}
return result;
}
/**
* Returns the display name of this module of the user project
*
* @return a non-blank name
*/
public String getDisplayName() {
return name;
}
public Set<Filter> getFilters() {
return filters;
}
/**
* Returns the ID of the organisation or group that owns this module or
* project
*
* @return a non-blank ID
*/
public String getGroupId() {
return gav.getGroupId();
}
/**
* Returns the programmatic name of this module of the user project
*
* @return an empty string for the root or only module
*/
public String getModuleName() {
return moduleName;
}
public Set<Module> getModules() {
return modules;
}
/**
* Returns the display name of this module of the user project
*
* @return a non-blank name
* @deprecated use {@link #getDisplayName()} instead
*/
@Deprecated
public String getName() {
return getDisplayName();
}
public String getPackaging() {
return packaging;
}
public Parent getParent() {
return parent;
}
/**
* Returns this descriptor's canonical path on the file system
*
* @return a valid canonical path
*/
public String getPath() {
return path;
}
/**
* Returns the canonical path of the given {@link Path} within this module,
* plus a trailing separator if found
*
* @param path the path for which to get the canonical location (required)
* @return <code>null</code> if this module has no such path
*/
public String getPathLocation(final Path path) {
final PhysicalPath modulePath = getPhysicalPath(path);
if (modulePath == null) {
return null;
}
return FileUtils.ensureTrailingSeparator(modulePath.getLocationPath());
}
/**
* Returns the {@link PhysicalPath} for the given {@link Path} of this
* module
*
* @param path the sub-path for which to return the {@link PhysicalPath}
* @return <code>null</code> if this module has no such sub-path
*/
public PhysicalPath getPhysicalPath(final Path path) {
return pathLocations.get(path);
}
public List<PhysicalPath> getPhysicalPaths() {
return new ArrayList<PhysicalPath>(pathLocations.values());
}
public Set<Repository> getPluginRepositories() {
return pluginRepositories;
}
public Set<Property> getPomProperties() {
return pomProperties;
}
/**
* Locates any properties which match the presented property, excluding the
* value. This is useful for upgrade use cases, where it is necessary to
* locate any properties with the name so that they can be removed.
*
* @param property to locate (required; note the value is ignored in
* comparisons)
* @return any matching properties (never returns null, but may return an
* empty {@link Set})
*/
public Set<Property> getPropertiesExcludingValue(final Property property) {
Validate.notNull(property, "Property to locate is required");
final Set<Property> result = new HashSet<Property>();
for (final Property p : pomProperties) {
if (property.getName().equals(p.getName())) {
result.add(p);
}
}
return result;
}
/**
* Locates the first occurrence of a property for a given name and returns
* it.
*
* @param name the property name (required)
* @return the property if found otherwise null
*/
public Property getProperty(final String name) {
Validate.notBlank(name, "Property name to locate is required");
for (final Property p : pomProperties) {
if (name.equals(p.getName())) {
return p;
}
}
return null;
}
public Set<Repository> getRepositories() {
return repositories;
}
public Set<Resource> getResources() {
return resources;
}
/**
* Returns the canonical path of this module's root directory, plus a
* trailing separator
*
* @return a valid canonical path
*/
public String getRoot() {
return getPathLocation(Path.ROOT);
}
public String getSourceDirectory() {
return sourceDirectory;
}
public String getTestSourceDirectory() {
return testSourceDirectory;
}
/**
* Returns the version number of this module or project
*
* @return a non-blank version number
*/
public String getVersion() {
return gav.getVersion();
}
/**
* Indicates whether this {@link Pom} has the given {@link Dependency},
* ignoring the version number.
*
* @param dependency the {@link Dependency} to check for (can be
* <code>null</code>)
* @return <code>false</code> if a <code>null</code> dependency is given
* @since 1.2.1
*/
public boolean hasDependencyExcludingVersion(final Dependency dependency) {
return !getDependenciesExcludingVersion(dependency).isEmpty();
}
/**
* Indicates whether all of the given dependencies are registered, by
* calling {@link #isDependencyRegistered(Dependency)} for each one,
* ignoring any <code>null</code> elements.
*
* @param dependencies the dependencies to check (can be <code>null</code>
* or contain <code>null</code> elements)
* @return true if a <code>null</code> or empty collection is given
*/
public boolean isAllDependenciesRegistered(final Collection<? extends Dependency> dependencies) {
if (dependencies != null) {
for (final Dependency dependency : dependencies) {
if (dependency != null && !isDependencyRegistered(dependency, false)) {
return false;
}
}
}
return true;
}
/**
* Indicates whether all the given plugin repositories are registered, by
* calling {@link #isPluginRepositoryRegistered(Repository)} for each one,
* ignoring any <code>null</code> elements.
*
* @param repositories the plugin repositories to check (can be
* <code>null</code>)
* @return <code>true</code> if a <code>null</code> collection is given
*/
public boolean isAllPluginRepositoriesRegistered(
final Collection<? extends Repository> repositories) {
if (repositories != null) {
for (final Repository repository : repositories) {
if (repository != null && !isPluginRepositoryRegistered(repository)) {
return false;
}
}
}
return true;
}
/**
* Indicates whether all of the given plugins are registered, based on their
* groupId, artifactId, and version.
*
* @param plugins the plugins to check (required)
* @return <code>false</code> if any of them are not registered
*/
public boolean isAllPluginsRegistered(final Collection<? extends Plugin> plugins) {
Validate.notNull(plugins, "Plugins to check is required");
for (final Plugin plugin : plugins) {
if (plugin != null && !isBuildPluginRegistered(plugin)) {
return false;
}
}
return true;
}
/**
* Indicates whether all the given repositories are registered. Equivalent
* to calling {@link #isRepositoryRegistered(Repository)} for each one,
* ignoring any <code>null</code> elements.
*
* @param repositories the repositories to check (can be <code>null</code>)
* @return true if a <code>null</code> collection is given
*/
public boolean isAllRepositoriesRegistered(final Collection<? extends Repository> repositories) {
if (repositories != null) {
for (final Repository repository : repositories) {
if (repository != null && !isRepositoryRegistered(repository)) {
return false;
}
}
}
return true;
}
/**
* Indicates whether any of the given dependencies are registered, by
* calling {@link #isDependencyRegistered(Dependency)} for each one.
*
* @param dependencies the dependencies to check (can be <code>null</code>)
* @return see above
*/
public boolean isAnyDependenciesRegistered(final Collection<? extends Dependency> dependencies) {
if (dependencies != null) {
for (final Dependency dependency : dependencies) {
if (isDependencyRegistered(dependency, false)) {
return true;
}
}
}
return false;
}
/**
* Indicates whether any of the given plugins are registered, by calling
* {@link #isBuildPluginRegistered(Plugin)} for each one.
*
* @param plugins the plugins to check (required)
* @return whether any of the plugins are currently registered or not
*/
public boolean isAnyPluginsRegistered(final Collection<? extends Plugin> plugins) {
Validate.notNull(plugins, "Plugins to check is required");
for (final Plugin plugin : plugins) {
if (isBuildPluginRegistered(plugin)) {
return true;
}
}
return false;
}
/**
* Indicates whether the given build plugin is registered, based on its
* groupId, artifactId, and version.
*
* @param plugin to check (required)
* @return whether the build plugin is currently registered or not
* @deprecated use {@link #isPluginRegistered(GAV)} instead
*/
@Deprecated
public boolean isBuildPluginRegistered(final Plugin plugin) {
return plugin != null && isPluginRegistered(plugin.getGAV());
}
/**
* Indicates whether the given dependency is registered without checking dependency version
* , by checking the result of {@link Dependency#equals(Object)}.
*
* @param dependency
* the dependency to check (can be <code>null</code>)
* @return <code>false</code> if a <code>null</code> dependency is given
*/
public boolean isDependencyRegistered(final Dependency dependency, boolean checkVersion) {
if (checkVersion) {
return dependency != null && dependencies.contains(dependency);
}
boolean registered = false;
Iterator<Dependency> it = dependencies.iterator();
while (it.hasNext()) {
Dependency dp = it.next();
if (dependency.getGroupId().equals(dp.getGroupId())
&& dependency.getArtifactId().equals(dp.getArtifactId())) {
registered = true;
break;
}
}
return dependency != null && registered;
}
/**
* Indicates whether the given dependency is registered in dependencyManagement without checking dependency version
* , by checking the result of {@link Dependency#equals(Object)}.
*
* @param dependency
* the dependency to check (can be <code>null</code>)
* @return <code>false</code> if a <code>null</code> dependency is given
*/
public boolean isDependencyRegisteredInDependencyManagement(final Dependency dependency,
boolean checkVersion) {
if (checkVersion) {
return dependency != null && dependenciesInDependencyManagement.contains(dependency);
}
boolean registered = false;
Iterator<Dependency> it = dependenciesInDependencyManagement.iterator();
while (it.hasNext()) {
Dependency dp = it.next();
if (dependency.getGroupId().equals(dp.getGroupId())
&& dependency.getArtifactId().equals(dp.getArtifactId())) {
registered = true;
break;
}
}
return dependency != null && registered;
}
/**
* Indicates whether the given plugin is registered in pluginManagement without checking plugin version
* , by checking the result of {@link Plugin#equals(Object)}.
*
* @param plugin
* the plugin to check (can be <code>null</code>)
* @return <code>false</code> if a <code>null</code> dependency is given
*/
public boolean isPluginRegisteredInPluginManagement(final Plugin plugin, boolean checkVersion) {
if (checkVersion) {
return plugin != null && pluginsInPluginManagement.contains(plugin);
}
boolean registered = false;
Iterator<Plugin> it = pluginsInPluginManagement.iterator();
while (it.hasNext()) {
Plugin dp = it.next();
if (plugin.getGroupId().equals(dp.getGroupId())
&& plugin.getArtifactId().equals(dp.getArtifactId())) {
registered = true;
break;
}
}
return plugin != null && registered;
}
/**
* Indicates whether the given filter is registered.
*
* @param filter to check (required)
* @return whether the filter is currently registered or not
*/
public boolean isFilterRegistered(final Filter filter) {
Validate.notNull(filter, "Filter to check is required");
return filters.contains(filter);
}
/**
* Indicates whether a plugin with the given coordinates is registered
*
* @param coordinates the coordinates to match upon; can be
* <code>null</code>
* @return false if <code>null</code> coordinates are given
*/
public boolean isPluginRegistered(final GAV gav) {
for (final Plugin existingPlugin : buildPlugins) {
if (existingPlugin.getGAV().equals(gav)) {
return true;
}
}
return false;
}
/**
* Indicates whether the given plugin repository is registered.
*
* @param repository repository to check (can be <code>null</code>)
* @return <code>false</code> if a <code>null</code> repository is given
*/
public boolean isPluginRepositoryRegistered(final Repository repository) {
return pluginRepositories.contains(repository);
}
/**
* Indicates whether the given build property is registered.
*
* @param property to check (required)
* @return whether the property is currently registered or not
*/
public boolean isPropertyRegistered(final Property property) {
Validate.notNull(property, "Property to check is required");
return pomProperties.contains(property);
}
/**
* Indicates whether the given repository is registered.
*
* @param repository to check (can be <code>null</code>)
* @return <code>false</code> if a <code>null</code> repository is given
*/
public boolean isRepositoryRegistered(final Repository repository) {
return repositories.contains(repository);
}
/**
* Indicates whether the given resource is registered.
*
* @param resource to check (required)
* @return whether the resource is currently registered or not
*/
public boolean isResourceRegistered(final Resource resource) {
Validate.notNull(resource, "Resource to check is required");
return resources.contains(resource);
}
@Override
public String toString() {
// For debugging
return gav + " at " + path;
}
}