/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.maven.tools;
import org.eclipse.che.commons.xml.Element;
import org.eclipse.che.commons.xml.ElementMapper;
import org.eclipse.che.commons.xml.NewElement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import static org.eclipse.che.commons.xml.NewElement.createElement;
import static org.eclipse.che.commons.xml.XMLTreeLocation.after;
import static org.eclipse.che.commons.xml.XMLTreeLocation.afterAnyOf;
import static org.eclipse.che.commons.xml.XMLTreeLocation.before;
import static org.eclipse.che.commons.xml.XMLTreeLocation.inTheBegin;
import static org.eclipse.che.commons.xml.XMLTreeLocation.inTheEnd;
import static java.lang.Boolean.parseBoolean;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
/**
* The {@code <dependency>} element contains information about project's dependency.
* <p/>
* Supported next data:
* <ul>
* <li>artifactId</li>
* <li>groupId</li>
* <li>version</li>
* <li>scope</li>
* <li>classifier</li>
* <li>type</li>
* <li>optional</li>
* <li>exclusions</li>
* </ul>
*
* @author Eugene Voevodin
*/
public class Dependency {
private static final ToExclusionMapper TO_EXCLUSION_MAPPER = new ToExclusionMapper();
private String groupId;
private String artifactId;
private String version;
private String type;
private String classifier;
private String scope;
private String optional;
private List<Exclusion> exclusions;
Element dependencyElement;
public Dependency(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
public Dependency() {
}
Dependency(Element element) {
dependencyElement = element;
artifactId = element.getChildText("artifactId");
groupId = element.getChildText("groupId");
version = element.getChildText("version");
classifier = element.getChildText("classifier");
optional = element.getChildText("optional");
scope = element.getChildText("scope");
type = element.getChildText("type");
if (element.hasSingleChild("exclusions")) {
exclusions = element.getSingleChild("exclusions").getChildren(TO_EXCLUSION_MAPPER);
}
}
/**
* Returns the unique id for an artifact produced by
* the project group, e.g. {@code maven-artifact}.
*/
public String getArtifactId() {
return artifactId;
}
/**
* Returns the classifier of the dependency.
* <p/>
* This allows distinguishing two artifacts
* that belong to the same POM but were built
* differently, and is appended to the filename after the version.
* For example, {@code jdk14} and {@code jdk15}.
*/
public String getClassifier() {
return classifier;
}
/**
* Returns dependency exclusions if dependency has it or empty set if doesn't
* <p/>
* <b>Note: update methods should not be used on returned list</b>
*/
public List<Exclusion> getExclusions() {
if (exclusions == null) {
return emptyList();
}
return new ArrayList<>(exclusions);
}
/**
* Returns the project group that produced the dependency, e.g. {@code org.apache.maven}.
*/
public String getGroupId() {
return groupId;
}
/**
* Returns the dependency is optional or not.
* If it is optional then {@code true} will be returned otherwise {@code false}
*/
public String getOptional() {
return optional;
}
/**
* Returns the scope of the dependency:
* <ul>
* <li>compile</li>
* <li>runtime</li>
* <li>test</li>
* <li>system</li>
* <li>provided</li>
* </ul>
* Used to calculate the various classpath used for
* compilation, testing, and so on.
* It also assists in determining which artifacts
* to include in a distribution of
* this project. For more information, see
* <a href="http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html">the
* dependency mechanism</a>.
*/
public String getScope() {
return scope == null ? "compile" : scope;
}
/**
* Returns the type of dependency.
* <p/>
* This defaults to <code>jar</code>.
* While it usually represents the extension on
* the filename of the dependency,
* that is not always the case. A type can be
* mapped to a different
* extension and a classifier.
* The type often corresponds to the packaging
* used, though this is also
* not always the case.
* Some examples are {@code jar, war, ejb-client}.
* New types can be defined by plugins that set
* {@code extensions} to {@code true}, so
* this is not a complete list.
*/
public String getType() {
return type == null ? "jar" : type;
}
/**
* Returns the version of the dependency
*/
public String getVersion() {
return version;
}
/**
* Adds new exclusion to the list of dependency exclusions.
* <p/>
* If dependency doesn't have exclusions then it will be added to xml.
*
* @param exclusion
* new exclusion which will be added
* @return this dependency instance
* @throws NullPointerException
* when {@code exclusion} is {@code null}
*/
public Dependency addExclusion(Exclusion exclusion) {
requireNonNull(exclusion, "Required not null exclusion");
exclusions().add(exclusion);
//add exclusion to xml tree
if (!isNew()) {
if (dependencyElement.hasSingleChild("exclusions")) {
dependencyElement.getSingleChild("exclusions").appendChild(exclusion.asXMLElement());
} else {
dependencyElement.appendChild(createElement("exclusions", exclusion.asXMLElement()));
}
exclusion.exclusionElement = dependencyElement.getSingleChild("exclusions").getLastChild();
}
return this;
}
/**
* Removes exclusion from the dependency exclusions.
* <p/>
* If last exclusion has been removed removes exclusions element as well.
*
* @param exclusion
* exclusion which should be removed
* @return this dependency instance
* @throws NullPointerException
* when {@code exclusion} is {@code null}
*/
public Dependency removeExclusion(Exclusion exclusion) {
requireNonNull(exclusion, "Required not null exclusion");
exclusions().remove(exclusion);
//remove dependency from xml
if (!isNew() && exclusions.isEmpty()) {
dependencyElement.removeChild("exclusions");
exclusion.exclusionElement = null;
} else {
exclusion.remove();
}
return this;
}
/**
* Sets list of artifacts that should be excluded from this dependency's
* artifact list when it comes to calculating transitive dependencies.
* <p/>
* If {@code exclusions} is {@code null} or <i>empty</i> and this dependency instance is associated with
* xml element then {@code exclusions} be removed from model as well as from xml.
*
* @param exclusions
* new dependency exclusions
* @return this dependency instance
*/
public Dependency setExclusions(Collection<? extends Exclusion> exclusions) {
if (exclusions == null || exclusions.isEmpty()) {
removeExclusions();
} else if (isNew()) {
this.exclusions = new ArrayList<>(exclusions);
} else {
setExclusions0(exclusions);
}
return this;
}
/**
* Sets the unique id for an artifact produced by the project group, e.g. {@code maven-artifact}.
* <p/>
* If {@code artifactId} is {@code null} and this dependency instance is associated with
* xml element then {@code artifactId} will be removed from model as well as from xml.
*/
public Dependency setArtifactId(String artifactId) {
this.artifactId = artifactId;
if (!isNew()) {
if (artifactId == null) {
dependencyElement.removeChild("artifactId");
} else if (dependencyElement.hasSingleChild("artifactId")) {
dependencyElement.getSingleChild("artifactId").setText(artifactId);
} else {
dependencyElement.insertChild(createElement("artifactId", artifactId), after("groupId").or(inTheBegin()));
}
}
return this;
}
/**
* Sets the classifier of the dependency.
* <p/>
* If {@code classifier} is {@code null} and this dependency instance is associated with
* xml element then {@code classifier} will be removed from model as well as from xml.
*
* @param classifier
* new dependency classifier
* @return this dependency instance
*/
public Dependency setClassifier(String classifier) {
this.classifier = classifier;
if (!isNew()) {
if (classifier == null) {
dependencyElement.removeChild("classifier");
} else if (dependencyElement.hasSingleChild("classifier")) {
dependencyElement.getSingleChild("classifier").setText(classifier);
} else {
dependencyElement.insertChild(createElement("classifier", classifier), before("exclusions").or(inTheEnd()));
}
}
return this;
}
/**
* Sets the project group that produced the dependency, e.g. <i>org.apache.maven</i>.
* <p/>
* If {@code groupId} is {@code null} and this dependency instance is associated with
* xml element then {@code groupId} will be removed from model as well as from xml.
*
* @param groupId
* new dependency groupId
* @return this dependency instance
*/
public Dependency setGroupId(String groupId) {
this.groupId = groupId;
if (!isNew()) {
if (groupId == null) {
dependencyElement.removeChild("groupId");
} else if (dependencyElement.hasSingleChild("groupId")) {
dependencyElement.getSingleChild("groupId").setText(groupId);
} else {
dependencyElement.insertChild(createElement("groupId", groupId), inTheBegin());
}
}
return this;
}
/**
* Sets indicates the dependency is optional for use of this library.
* <p/>
* If {@code optional} is {@code null} and this dependency instance is associated with
* xml element then {@code optional} will be removed from model as well as from xml.
*
* @param optional
* new dependency optional parameter
* @return this dependency instance
* @see #setOptional(boolean)
*/
public Dependency setOptional(String optional) {
this.optional = optional;
if (!isNew()) {
if (optional == null) {
dependencyElement.removeChild("optional");
} else if (dependencyElement.hasSingleChild("optional")) {
dependencyElement.getSingleChild("optional").setText(optional);
} else {
dependencyElement.insertChild(createElement("optional", optional), inTheBegin());
}
}
return this;
}
/**
* Sets the scope of the dependency:
* <ul>
* <li>compile</li>
* <li>runtime</li>
* <li>test</li>
* <li>system</li>
* <li>provided</li>
* </ul>
* Used to calculate the various classpath used for compilation, testing, and so on.
* It also assists in determining which artifacts to include in a distribution of this project.
* For more information, see
* <a href="http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html">the dependency mechanism</a>.
* <p/>
* If {@code scope} is {@code null} and this dependency instance is associated with
* xml element then {@code scope} will be removed from model as well as from xml.
*
* @param scope
* new dependency scope
* @return this dependency instance
*/
public Dependency setScope(String scope) {
this.scope = scope;
if (!isNew()) {
if (scope == null) {
dependencyElement.removeChild("scope");
} else if (dependencyElement.hasSingleChild("scope")) {
dependencyElement.getSingleChild("scope").setText(scope);
} else {
dependencyElement.appendChild(createElement("scope", scope));
}
}
return this;
}
/**
* Sets the type of dependency.
* <p/>
* This defaults to <code>jar</code>.
* While it usually represents the extension on
* the filename of the dependency,
* that is not always the case. A type can be
* mapped to a different
* extension and a classifier.
* The type often corresponds to the packaging
* used, though this is also
* not always the case.
* Some examples are {@code jar, war, ejb-client}.
* New types can be defined by plugins that set
* {@code extensions} to {@code true}, so
* this is not a complete list.
* <p/>
* If {@code type} is {@code null} and this dependency instance is associated with
* xml element then {@code type} will be removed from model as well as from xml.
*
* @param type
* new dependency type
* @return this dependency instance
*/
public Dependency setType(String type) {
this.type = type;
if (!isNew()) {
if (type == null) {
dependencyElement.removeChild("type");
} else if (dependencyElement.hasSingleChild("type")) {
dependencyElement.getSingleChild("type").setText(type);
} else {
dependencyElement.appendChild(createElement("type", type));
}
}
return this;
}
/**
* Set the version of the dependency, e.g. <i>3.2.1</i>.
* In Maven 2, this can also be specified as a range of versions.
* <p/>
* If {@code version} is {@code null} and this dependency instance is associated with
* xml element then {@code version} will be removed from model as well as from xml.
*
* @param version
* new dependency version
* @return this dependency instance
*/
public Dependency setVersion(String version) {
this.version = version;
if (!isNew()) {
if (version == null) {
dependencyElement.removeChild("version");
} else if (dependencyElement.hasChild("version")) {
dependencyElement.getSingleChild("version").setText(version);
} else {
dependencyElement.insertChild(createElement("version", version), afterAnyOf("artifactId",
"groupId").or(inTheBegin()));
}
}
return this;
}
/**
* Returns {@code true} if dependency is optional otherwise returns {@code false}
*/
public boolean isOptional() {
return parseBoolean(optional);
}
/**
* Sets indicates the dependency is optional for use of this library.
*
* @see #setOptional(String)
*/
public Dependency setOptional(boolean optional) {
return setOptional(String.valueOf(optional));
}
@Override
public String toString() {
return "Dependency {groupId=" + groupId + ", artifactId=" + artifactId + ", version=" + version + ", type=" + type + "}";
}
public void remove() {
if (!isNew()) {
dependencyElement.remove();
dependencyElement = null;
}
}
NewElement asXMLElement() {
final NewElement newElement = createElement("dependency");
newElement.appendChild(createElement("groupId", groupId));
newElement.appendChild(createElement("artifactId", artifactId));
newElement.appendChild(createElement("version", version));
if (scope != null && !scope.equals("compile")) {
newElement.appendChild(createElement("scope", scope));
}
if (type != null && !type.equals("jar")) {
newElement.appendChild(createElement("type", type));
}
if (classifier != null) {
newElement.appendChild(createElement("classifier", classifier));
}
if (optional != null) {
newElement.appendChild(createElement("optional", optional));
}
if (exclusions != null) {
final NewElement exclusionsEl = createElement("exclusions");
for (Exclusion exclusion : exclusions) {
exclusionsEl.appendChild(exclusion.asXMLElement());
}
exclusionsEl.appendChild(exclusionsEl);
}
return newElement;
}
private void setExclusions0(Collection<? extends Exclusion> exclusions) {
for (Exclusion exclusion : exclusions()) {
exclusion.remove();
}
//use addExclusion to add and associate each new exclusion with element
this.exclusions = new ArrayList<>(exclusions.size());
for (Exclusion exclusion : exclusions) {
addExclusion(exclusion);
}
}
private void removeExclusions() {
if (!isNew()) {
dependencyElement.removeChild("exclusions");
}
this.exclusions = null;
}
private List<Exclusion> exclusions() {
return exclusions == null ? exclusions = new LinkedList<>() : exclusions;
}
private boolean isNew() {
return dependencyElement == null;
}
private static class ToExclusionMapper implements ElementMapper<Exclusion> {
@Override
public Exclusion map(Element element) {
return new Exclusion(element);
}
}
}