blob: 52c8f558143716ae97169032c521c62905588e10 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.refactoring.exclude;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.osgi.util.NLS;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.project.MavenProject;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.ui.internal.editing.AddDependencyOperation;
import org.eclipse.m2e.core.ui.internal.editing.AddExclusionOperation;
import org.eclipse.m2e.core.ui.internal.editing.PomEdits.CompoundOperation;
import org.eclipse.m2e.core.ui.internal.editing.PomEdits.Operation;
import org.eclipse.m2e.core.ui.internal.editing.PomHelper;
import org.eclipse.m2e.core.ui.internal.editing.RemoveDependencyOperation;
import org.eclipse.m2e.core.ui.internal.util.ParentHierarchyEntry;
import org.eclipse.m2e.refactoring.Messages;
@SuppressWarnings("restriction")
public class ExcludeArtifactRefactoring extends Refactoring {
private static final String PLUGIN_ID = "org.eclipse.m2e.refactoring"; //$NON-NLS-1$
/**
* Dependencies to exclude
*/
final ArtifactKey[] excludes;
// private IFile pomFile;
/**
* Workspace Model to exclude dependencies from
*/
private ParentHierarchyEntry exclusionPoint;
private List<ParentHierarchyEntry> hierarchy;
public ExcludeArtifactRefactoring(ArtifactKey[] keys) {
this.excludes = keys;
}
public void setExclusionPoint(ParentHierarchyEntry exclusionPoint) {
this.exclusionPoint = exclusionPoint;
}
public void setHierarchy(List<ParentHierarchyEntry> hierarchy) {
this.hierarchy = hierarchy;
this.exclusionPoint = hierarchy != null ? hierarchy.get(0) : null;
}
@Override
public String getName() {
StringBuilder sb = new StringBuilder();
for(ArtifactKey key : excludes) {
sb.append(key.toString()).append(',');
}
sb.deleteCharAt(sb.length() - 1);
return NLS.bind(Messages.MavenExcludeWizard_title, sb.toString());
}
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws OperationCanceledException {
return new RefactoringStatus();
}
private List<Change> changes;
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
return MavenPlugin.getMaven().execute((context, monitor) -> checkFinalConditions0(monitor), pm);
}
RefactoringStatus checkFinalConditions0(IProgressMonitor pm) throws CoreException, OperationCanceledException {
if(hierarchy == null || exclusionPoint == null) {
return RefactoringStatus.createFatalErrorStatus(Messages.ExcludeArtifactRefactoring_unableToLocateProject);
}
changes = new ArrayList<>();
Set<ArtifactKey> locatedKeys = new HashSet<>();
List<IStatus> statuses = new ArrayList<>();
SubMonitor monitor = SubMonitor.convert(pm, 3);
List<Operation> exclusionOp = new ArrayList<>();
// Exclusion point
for(Entry<Dependency, Set<ArtifactKey>> entry : getDependencyExcludes(exclusionPoint, monitor.newChild(1))
.entrySet()) {
locatedKeys.addAll(entry.getValue());
Dependency dependency = entry.getKey();
if(contains(entry.getValue(), dependency)) {
exclusionOp.add(new RemoveDependencyOperation(dependency));
} else {
for(ArtifactKey key : entry.getValue()) {
if(!hasExclusion(exclusionPoint, dependency, key)) {
exclusionOp.add(new AddExclusionOperation(dependency, key));
}
}
}
}
// Below exclusion point - pull up dependency to exclusion point
for(ParentHierarchyEntry project : getWorkspaceDescendants()) {
List<Operation> operations = new ArrayList<>();
for(Entry<Dependency, Set<ArtifactKey>> entry : getDependencyExcludes(project, monitor.newChild(1)).entrySet()) {
locatedKeys.addAll(entry.getValue());
Dependency dependency = entry.getKey();
operations.add(new RemoveDependencyOperation(dependency));
if(!contains(entry.getValue(), dependency)) {
if(!hasDependency(exclusionPoint, dependency)) {
exclusionOp.add(new AddDependencyOperation(dependency));
}
for(ArtifactKey key : entry.getValue()) {
if(!hasExclusion(exclusionPoint, dependency, key)) {
exclusionOp.add(new AddExclusionOperation(dependency, key));
}
}
}
}
if(operations.size() > 0) {
IFile pom = project.getResource();
changes.add(PomHelper.createChange(pom,
new CompoundOperation(operations.toArray(new Operation[operations.size()])), getName(pom)));
}
}
// Above exclusion - Add dep to exclusionPoint
for(ParentHierarchyEntry project : getWorkspaceAncestors()) {
for(Entry<Dependency, Set<ArtifactKey>> entry : getDependencyExcludes(project, monitor.newChild(1)).entrySet()) {
locatedKeys.addAll(entry.getValue());
Dependency dependency = entry.getKey();
if(contains(entry.getValue(), dependency)) {
IFile pom = project.getResource();
if(pom != null) {
statuses.add(new Status(IStatus.INFO, PLUGIN_ID, NLS.bind(
Messages.ExcludeArtifactRefactoring_removeDependencyFrom, toString(dependency), pom.getFullPath())));
changes.add(PomHelper.createChange(pom, new RemoveDependencyOperation(dependency), getName(pom)));
}
} else {
exclusionOp.add(new AddDependencyOperation(dependency));
for(ArtifactKey key : entry.getValue()) {
if(!hasExclusion(exclusionPoint, dependency, key)) {
exclusionOp.add(new AddExclusionOperation(dependency, key));
}
}
}
}
}
if(!exclusionOp.isEmpty()) {
IFile pom = exclusionPoint.getResource();
changes.add(PomHelper.createChange(pom,
new CompoundOperation(exclusionOp.toArray(new Operation[exclusionOp.size()])), getName(pom)));
}
if(statuses.size() == 1) {
return RefactoringStatus.create(statuses.get(0));
} else if(statuses.size() > 1) {
return RefactoringStatus.create(new MultiStatus(PLUGIN_ID, 0, statuses.toArray(new IStatus[statuses.size()]),
Messages.ExcludeArtifactRefactoring_errorCreatingRefactoring, null));
} else if(locatedKeys.isEmpty()) {
return RefactoringStatus.createFatalErrorStatus(Messages.ExcludeArtifactRefactoring_noTargets);
} else if(locatedKeys.size() != excludes.length) {
StringBuilder sb = new StringBuilder();
for(ArtifactKey key : excludes) {
if(!locatedKeys.contains(key)) {
sb.append(key.toString()).append(',');
}
}
sb.deleteCharAt(sb.length() - 1);
return RefactoringStatus
.createErrorStatus(NLS.bind(Messages.ExcludeArtifactRefactoring_failedToLocateArtifact, sb.toString()));
}
return new RefactoringStatus();
}
private String getName(IFile file) {
return new StringBuilder().append(file.getName()).append(" - ").append(file.getProject().getName()).toString(); //$NON-NLS-1$
}
/**
* Map key is one of <dependency> element of specified (workspace) model. Map value is set of <excludes> element keys
* to be added to the <dependency>.
*/
private Map<Dependency, Set<ArtifactKey>> getDependencyExcludes(ParentHierarchyEntry model, IProgressMonitor monitor)
throws CoreException {
IMavenProjectFacade facade = model.getFacade();
MavenProject project = model.getProject();
DependencyNode root = MavenPlugin.getMavenModelManager().readDependencyTree(facade, project, JavaScopes.TEST,
monitor);
Visitor visitor = new Visitor(model);
root.accept(visitor);
return visitor.getSourceMap();
}
@Override
public Change createChange(IProgressMonitor pm) throws OperationCanceledException {
CompositeChange change = new CompositeChange(Messages.ExcludeArtifactRefactoring_changeTitle);
change.addAll(changes.toArray(new Change[changes.size()]));
return change;
}
private static boolean matches(Dependency d, ArtifactKey a) {
return d.getArtifactId().equals(a.getArtifactId()) && d.getGroupId().equals(a.getGroupId());
}
private static boolean contains(Set<ArtifactKey> keys, Dependency d) {
for(ArtifactKey key : keys) {
if(matches(d, key)) {
return true;
}
}
return false;
}
private Collection<ParentHierarchyEntry> getHierarchy() {
return hierarchy;
}
private Collection<ParentHierarchyEntry> getWorkspaceDescendants() {
List<ParentHierarchyEntry> descendants = new ArrayList<>();
for(ParentHierarchyEntry project : getHierarchy()) {
if(project == exclusionPoint) {
break;
}
if(project.getFacade() != null) {
descendants.add(project);
}
}
return descendants;
}
private Collection<ParentHierarchyEntry> getWorkspaceAncestors() {
List<ParentHierarchyEntry> ancestors = new ArrayList<>();
boolean add = false;
for(ParentHierarchyEntry project : getHierarchy()) {
if(project == exclusionPoint) {
add = !add;
} else if(add) {
if(project.getFacade() != null) {
ancestors.add(project);
}
}
}
return ancestors;
}
private static String toString(Dependency dependency) {
return NLS.bind("{0}:{1}:{2}", //$NON-NLS-1$
new String[] {dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()});
}
private boolean hasDependency(ParentHierarchyEntry project, Dependency dependency) {
List<Dependency> dependencies = project.getProject().getOriginalModel().getDependencies();
if(dependencies == null) {
return false;
}
for(Dependency dep : dependencies) {
if(dep.getArtifactId().equals(dependency.getArtifactId()) && dep.getGroupId().equals(dependency.getGroupId())
&& (dep.getVersion() == null || dep.getVersion().equals(dependency.getVersion()))) {
return true;
}
}
return false;
}
private static boolean hasExclusion(ParentHierarchyEntry project, Dependency d, ArtifactKey exclusion) {
List<Dependency> dependencies = project.getProject().getOriginalModel().getDependencies();
if(dependencies == null) {
return false;
}
Dependency dependency = null;
for(Dependency dep : dependencies) {
if(dep.getArtifactId().equals(d.getArtifactId()) && dep.getGroupId().equals(d.getGroupId())
&& (dep.getVersion() == null || dep.getVersion().equals(d.getVersion()))) {
dependency = dep;
break;
}
}
if(dependency == null || dependency.getExclusions() == null) {
return false;
}
for(Exclusion ex : dependency.getExclusions()) {
if(ex.getArtifactId().equals(exclusion.getArtifactId()) && ex.getGroupId().equals(exclusion.getGroupId())) {
return true;
}
}
return false;
}
private class Visitor implements DependencyVisitor {
private List<Dependency> dependencies;
private Map<Dependency, Set<ArtifactKey>> sourceMap = new HashMap<>();
Visitor(ParentHierarchyEntry project) {
dependencies = new ArrayList<>();
dependencies.addAll(project.getProject().getOriginalModel().getDependencies());
// for(Profile profile : project.getActiveProfiles()) {
// dependencies.addAll(profile.getDependencies());
// }
}
Map<Dependency, Set<ArtifactKey>> getSourceMap() {
return sourceMap;
}
private int depth;
private DependencyNode topLevel;
@Override
public boolean visitLeave(DependencyNode node) {
depth-- ;
return true;
}
@Override
public boolean visitEnter(DependencyNode node) {
if(depth == 1) {
topLevel = node;
}
depth++ ;
if(node.getDependency() != null) {
Artifact a = node.getDependency().getArtifact();
for(ArtifactKey exclude : excludes) {
if(a.getGroupId().equals(exclude.getGroupId()) && a.getArtifactId().equals(exclude.getArtifactId())) {
if(topLevel != null) {
// need to add exclusion to top-level dependency
Dependency dependency = findDependency(topLevel);
if(dependency != null) {
put(dependency, exclude);
}
}
return true;
}
}
}
return true;
}
private void put(Dependency dep, ArtifactKey key) {
Set<ArtifactKey> keys = sourceMap.get(dep);
if(keys == null) {
keys = new HashSet<>();
sourceMap.put(dep, keys);
}
keys.add(key);
}
private Dependency findDependency(String groupId, String artifactId) {
for(Dependency d : dependencies) {
if(d.getGroupId().equals(groupId) && d.getArtifactId().equals(artifactId)) {
return d;
}
}
return null;
}
private Dependency findDependency(DependencyNode node) {
Artifact artifact;
if(node.getRelocations().isEmpty()) {
artifact = node.getDependency().getArtifact();
} else {
artifact = node.getRelocations().get(0);
}
return findDependency(artifact.getGroupId(), artifact.getArtifactId());
}
}
}