blob: 736379e0d41d01e779d72b860a5d84ffb1fa9c25 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 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
*******************************************************************************/
package org.eclipse.m2e.editor.dialogs;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.ARTIFACT_ID;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.DEPENDENCIES;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.DEPENDENCY;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.DEPENDENCY_MANAGEMENT;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.GROUP_ID;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.VERSION;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.findChild;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.findChilds;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.getChild;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.getTextValue;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.performOnDOMDocument;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.removeChild;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.apache.maven.model.Dependency;
import org.apache.maven.project.MavenProject;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.ui.internal.components.PomHierarchyComposite;
import org.eclipse.m2e.core.ui.internal.dialogs.AbstractMavenDialog;
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.PomEdits.OperationTuple;
import org.eclipse.m2e.core.ui.internal.editing.PomHelper;
import org.eclipse.m2e.core.ui.internal.util.ParentHierarchyEntry;
import org.eclipse.m2e.editor.MavenEditorPlugin;
import org.eclipse.m2e.editor.composites.DependencyLabelProvider;
import org.eclipse.m2e.editor.composites.ListEditorContentProvider;
import org.eclipse.m2e.editor.pom.ValueProvider;
/**
* This dialog is used to present the user with a list of dialogs that they can move to being managed under
* "dependencyManagement". It allows them to pick the destination POM where the dependencies will be managed.
*
* @author rgould
*/
public class ManageDependenciesDialog extends AbstractMavenDialog {
private static final Logger LOG = LoggerFactory.getLogger(ManageDependenciesDialog.class);
private static final String DIALOG_SETTINGS = ManageDependenciesDialog.class.getName();
private TableViewer dependenciesViewer;
private final List<ParentHierarchyEntry> projectHierarchy;
private PomHierarchyComposite pomHierarchy;
private IStatus status;
private List<Object> originalSelection;
private ValueProvider<List<Dependency>> modelVProvider;
/**
* Hierarchy is a LinkedList representing the hierarchy relationship between POM represented by model and its parents.
* The head of the list should be the child, while the tail should be the root parent, with the others in between.
*/
public ManageDependenciesDialog(Shell parent, ValueProvider<List<Dependency>> modelVProvider,
List<ParentHierarchyEntry> hierarchy) {
this(parent, modelVProvider, hierarchy, null);
}
public ManageDependenciesDialog(Shell parent, ValueProvider<List<Dependency>> modelVProvider,
List<ParentHierarchyEntry> hierarchy, List<Object> selection) {
super(parent, DIALOG_SETTINGS);
setShellStyle(getShellStyle() | SWT.RESIZE);
setTitle(Messages.ManageDependenciesDialog_dialogTitle);
this.projectHierarchy = hierarchy;
this.originalSelection = selection;
this.modelVProvider = modelVProvider;
}
/* (non-Javadoc)
* @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
*/
protected Control createDialogArea(Composite parent) {
readSettings();
Composite composite = (Composite) super.createDialogArea(parent);
Label infoLabel = new Label(composite, SWT.WRAP);
infoLabel.setText(Messages.ManageDependenciesDialog_dialogInfo);
Label horizontalBar = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
SashForm sashForm = new SashForm(composite, SWT.SMOOTH | SWT.HORIZONTAL);
Composite dependenciesComposite = new Composite(sashForm, SWT.NONE);
Label selectDependenciesLabel = new Label(dependenciesComposite, SWT.NONE);
selectDependenciesLabel.setText(Messages.ManageDependenciesDialog_selectDependenciesLabel);
final Table dependenciesTable = new Table(dependenciesComposite, SWT.FLAT | SWT.MULTI | SWT.BORDER);
final TableColumn column = new TableColumn(dependenciesTable, SWT.NONE);
dependenciesTable.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
column.setWidth(dependenciesTable.getClientArea().width);
}
});
Composite pomComposite = new Composite(sashForm, SWT.NONE);
Label selectPomLabel = new Label(pomComposite, SWT.NONE);
selectPomLabel.setText(Messages.ManageDependenciesDialog_selectPOMLabel);
pomHierarchy = new PomHierarchyComposite(pomComposite, SWT.BORDER);
pomHierarchy.setHierarchy(getProjectHierarchy());
// pomsViewer = new TreeViewer(pomComposite, SWT.BORDER);
/*
* Configure layouts
*/
GridLayout layout = new GridLayout(1, false);
composite.setLayout(layout);
GridData gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
gridData.widthHint = 300;
infoLabel.setLayoutData(gridData);
gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
horizontalBar.setLayoutData(gridData);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
sashForm.setLayoutData(gridData);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
dependenciesComposite.setLayoutData(gridData);
layout = new GridLayout(1, false);
dependenciesComposite.setLayout(layout);
gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
selectDependenciesLabel.setLayoutData(gridData);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
dependenciesTable.setLayoutData(gridData);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
pomComposite.setLayoutData(gridData);
layout = new GridLayout(1, false);
pomComposite.setLayout(layout);
gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
selectPomLabel.setLayoutData(gridData);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
pomHierarchy.setLayoutData(gridData);
//pomsViewer.getTree().setLayoutData(gridData);
/*
* Set up list/tree viewers
*/
dependenciesViewer = new TableViewer(dependenciesTable);
dependenciesViewer.setLabelProvider(new DependencyLabelProvider());
dependenciesViewer.setContentProvider(new ListEditorContentProvider<Dependency>());
//MNGECLIPSE-2675 only show the dependencies not already managed (decide just by absence of the version element
List<Dependency> deps = modelVProvider.getValue();
List<Dependency> nonManaged = new ArrayList<Dependency>();
if(deps != null) {
for(Dependency d : deps) {
if(d.getVersion() != null) {
nonManaged.add(d);
}
}
}
dependenciesViewer.setInput(nonManaged);
dependenciesViewer.addSelectionChangedListener(new DependenciesViewerSelectionListener());
pomHierarchy.addSelectionChangedListener(new PomViewerSelectionChangedListener());
if(getProjectHierarchy().size() > 0) {
pomHierarchy.setSelection(new StructuredSelection(pomHierarchy.getProject()));
}
if(originalSelection != null && originalSelection.size() > 0) {
dependenciesViewer.setSelection(new StructuredSelection(originalSelection));
}
return composite;
}
@Override
protected void computeResult() {
final ParentHierarchyEntry currentPOM = getCurrentPOM();
final ParentHierarchyEntry targetPOM = getTargetPOM();
final IFile current = currentPOM.getResource();
final IFile target = targetPOM.getResource();
if(target == null || current == null) {
return;
}
final boolean same = targetPOM.equals(currentPOM);
final LinkedList<Dependency> modelDeps = getDependenciesList();
/*
* 1) Remove version values from the dependencies from the current POM
* 2) Add dependencies to dependencyManagement of targetPOM
*/
//First we remove the version from the original dependency
Job perform = new Job("Updating POM file(s)") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
if(same) {
performOnDOMDocument(new OperationTuple(current, new CompoundOperation(createManageOperation(modelDeps),
createRemoveVersionOperation(modelDeps))));
} else {
performOnDOMDocument(new OperationTuple(target, createManageOperation(modelDeps)), new OperationTuple(
current, createRemoveVersionOperation(modelDeps)));
}
} catch(Exception e) {
LOG.error("Error updating managed dependencies", e);
return new Status(IStatus.ERROR, MavenEditorPlugin.PLUGIN_ID, "Error updating managed dependencies", e);
}
return Status.OK_STATUS;
}
};
perform.setUser(false);
perform.setSystem(true);
perform.schedule();
}
public static Operation createRemoveVersionOperation(final List<Dependency> modelDeps) {
return new Operation() {
public void process(Document document) {
List<Element> deps = findChilds(findChild(document.getDocumentElement(), DEPENDENCIES), DEPENDENCY);
for(Element dep : deps) {
String grid = getTextValue(findChild(dep, GROUP_ID));
String artid = getTextValue(findChild(dep, ARTIFACT_ID));
for(Dependency modelDep : modelDeps) {
if(modelDep.getGroupId() != null && modelDep.getGroupId().equals(grid) && modelDep.getArtifactId() != null
&& modelDep.getArtifactId().equals(artid)) {
removeChild(dep, findChild(dep, VERSION));
}
}
}
}
};
}
public static Operation createManageOperation(final List<Dependency> modelDeps) {
return new Operation() {
public void process(Document document) {
List<Dependency> modelDependencies = new ArrayList<Dependency>(modelDeps);
Element managedDepsElement = getChild(document.getDocumentElement(), DEPENDENCY_MANAGEMENT, DEPENDENCIES);
List<Element> existing = findChilds(managedDepsElement, DEPENDENCY);
for(Element dep : existing) {
String artifactId = getTextValue(findChild(dep, ARTIFACT_ID));
String groupId = getTextValue(findChild(dep, GROUP_ID));
//cloned list, shall not modify shared resource (used by the remove operation)
Iterator<Dependency> mdIter = modelDependencies.iterator();
while(mdIter.hasNext()) {
//TODO: here we iterate to find existing managed dependencies and decide not to overwrite them.
// but this could eventually break the current project when the versions are diametrally different
// we should have shown this information to the user in the UI in the first place (for him to decide what to do)
Dependency md = mdIter.next();
if(artifactId.equals(md.getArtifactId()) && groupId.equals(md.getGroupId())) {
mdIter.remove();
break;
}
}
}
//TODO is the version is defined by property expression, we should make sure the property is defined in the current project
for(Dependency modelDependency : modelDependencies) {
PomHelper.createDependency(managedDepsElement, modelDependency.getGroupId(), modelDependency.getArtifactId(),
modelDependency.getVersion());
}
}
};
}
protected LinkedList<Dependency> getDependenciesList() {
IStructuredSelection selection = (IStructuredSelection) dependenciesViewer.getSelection();
LinkedList<Dependency> dependencies = new LinkedList<Dependency>();
for(Object obj : selection.toArray()) {
dependencies.add((Dependency) obj);
}
return dependencies;
}
protected List<ParentHierarchyEntry> getProjectHierarchy() {
return this.projectHierarchy;
}
protected ParentHierarchyEntry getTargetPOM() {
IStructuredSelection selection = (IStructuredSelection) pomHierarchy.getSelection();
return (ParentHierarchyEntry) selection.getFirstElement();
}
protected ParentHierarchyEntry getCurrentPOM() {
return pomHierarchy.getProject();
}
/**
* Compare the list of selected dependencies against the selected targetPOM. If one of the dependencies is already
* under dependencyManagement, but has a different version than the selected dependency, warn the user about this.
* returns true if the user has been warned (but this method updates the status itself)
*
* @param model
* @param dependencies
*/
protected boolean checkDependencies(org.apache.maven.model.Model model, LinkedList<Dependency> dependencies) {
if(this.status != null && this.status.getCode() == IStatus.ERROR) {
//Don't warn the user if there is already an error
return false;
}
if(model == null || model.getDependencyManagement() == null
|| model.getDependencyManagement().getDependencies() == null
|| model.getDependencyManagement().getDependencies().isEmpty()) {
return false;
}
for(Dependency selectedDep : dependencies) {
for(org.apache.maven.model.Dependency targetDep : model.getDependencyManagement().getDependencies()) {
if(selectedDep.getGroupId().equals(targetDep.getGroupId())
&& selectedDep.getArtifactId().equals(targetDep.getArtifactId())
&& !selectedDep.getVersion().equals(targetDep.getVersion())) {
String modelID = model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion(); //$NON-NLS-1$ //$NON-NLS-2$
if(targetDep.getLocation("") != null && targetDep.getLocation("").getSource() != null) { //$NON-NLS-1$ //$NON-NLS-2$
modelID = targetDep.getLocation("").getSource().getModelId(); //$NON-NLS-1$
}
Object[] arguments = {selectedDep.getArtifactId() + "-" + selectedDep.getVersion(), //$NON-NLS-1$
targetDep.getVersion(), modelID};
String message = NLS.bind(Messages.ManageDependenciesDialog_dependencyExistsWarning, arguments);
updateStatus(new Status(IStatus.WARNING, MavenEditorPlugin.PLUGIN_ID, message));
return true;
}
}
}
return false;
}
protected void checkStatus(ParentHierarchyEntry targetProject, LinkedList<Dependency> selectedDependencies) {
if(targetProject == null || selectedDependencies.isEmpty()) {
updateStatus(new Status(IStatus.ERROR, MavenEditorPlugin.PLUGIN_ID,
Messages.ManageDependenciesDialog_emptySelectionError));
return;
}
boolean error = false;
if(targetProject.getFacade() == null) {
error = true;
updateStatus(new Status(IStatus.ERROR, MavenEditorPlugin.PLUGIN_ID,
Messages.ManageDependenciesDialog_projectNotPresentError));
} else {
error = checkDependencies(targetProject.getProject().getModel(), getDependenciesList());
}
if(!error) {
clearStatus();
}
}
protected void clearStatus() {
updateStatus(new Status(IStatus.OK, MavenEditorPlugin.PLUGIN_ID, "")); //$NON-NLS-1$
}
protected class DependenciesViewerSelectionListener implements ISelectionChangedListener {
public void selectionChanged(SelectionChangedEvent event) {
checkStatus(getTargetPOM(), getDependenciesList());
}
}
protected class PomViewerSelectionChangedListener implements ISelectionChangedListener {
public void selectionChanged(SelectionChangedEvent event) {
checkStatus(getTargetPOM(), getDependenciesList());
}
}
@Override
protected void updateStatus(IStatus status) {
this.status = status;
super.updateStatus(status);
}
public static class DepLabelProvider extends LabelProvider implements IColorProvider {
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
*/
@Override
public String getText(Object element) {
MavenProject project = null;
if(element instanceof MavenProject) {
project = (MavenProject) element;
} else if(element instanceof Object[]) {
project = (MavenProject) ((Object[]) element)[0];
} else {
return ""; //$NON-NLS-1$
}
StringBuilder buffer = new StringBuilder();
buffer.append(project.getGroupId() + " : " + project.getArtifactId() + " : " + project.getVersion()); //$NON-NLS-1$ //$NON-NLS-2$
return buffer.toString();
}
public Color getForeground(Object element) {
if(element instanceof MavenProject) {
MavenProject project = (MavenProject) element;
IMavenProjectFacade search = MavenPlugin.getMavenProjectRegistry().getMavenProject(project.getGroupId(),
project.getArtifactId(), project.getVersion());
if(search == null) {
//This project is not in the workspace so we can't really modify it.
return Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY);
}
}
return null;
}
public Color getBackground(Object element) {
return null;
}
}
public class ContentProvider implements ITreeContentProvider {
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
public void dispose() {
}
public boolean hasChildren(Object element) {
Object[] children = getChildren(element);
return children.length != 0;
}
public Object getParent(Object element) {
if(element instanceof MavenProject) {
MavenProject project = (MavenProject) element;
return project.getParent();
}
return null;
}
/*
* Return root element
* (non-Javadoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider#getElements(java.lang.Object)
*/
public Object[] getElements(Object inputElement) {
if(inputElement instanceof LinkedList) {
LinkedList<MavenProject> projects = (LinkedList<MavenProject>) inputElement;
if(projects.isEmpty()) {
return new Object[0];
}
return new Object[] {projects.getLast()};
}
return new Object[0];
}
public Object[] getChildren(Object parentElement) {
if(parentElement instanceof MavenProject) {
/*
* Walk the hierarchy list until we find the parentElement and
* return the previous element, which is the child.
*/
MavenProject parent = (MavenProject) parentElement;
if(getProjectHierarchy().size() == 1) {
//No parent exists, only one element in the tree
return new Object[0];
}
if(getProjectHierarchy().get(0).equals(parent)) {
//We are the final child
return new Object[0];
}
ListIterator<ParentHierarchyEntry> iter = getProjectHierarchy().listIterator();
while(iter.hasNext()) {
ParentHierarchyEntry next = iter.next();
if(next.equals(parent)) {
iter.previous();
ParentHierarchyEntry previous = iter.previous();
return new Object[] {previous};
}
}
}
return new Object[0];
}
}
}