blob: f126c761fa449d7d622dcaeb4641e6f5f588b3b4 [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
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.core.ui.internal.components;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.m2e.core.internal.IMavenConstants;
import org.eclipse.m2e.core.ui.internal.MavenImages;
import org.eclipse.m2e.core.ui.internal.Messages;
@SuppressWarnings({"restriction", "synthetic-access"})
public class NestedProjectsComposite extends Composite implements IMenuListener {
private static final Logger log = LoggerFactory.getLogger(NestedProjectsComposite.class);
private static final String SEPARATOR = System.getProperty("file.separator"); //$NON-NLS-1$
CheckboxTreeViewer codebaseViewer;
Map<String, IProject> projectPaths;
Collection<IProject> projects;
IProject[] selectedProjects;
private Link includeOutDateProjectslink;
private Composite warningArea;
private Button addOutOfDateBtn;
private boolean showOutOfDateUI;
public NestedProjectsComposite(Composite parent, int style, IProject[] initialSelection, boolean showOutOfDateWarning) {
super(parent, style);
this.showOutOfDateUI = showOutOfDateWarning;
setLayout(new GridLayout(2, false));
Label lblAvailable = new Label(this, SWT.NONE);
lblAvailable.setText(Messages.UpdateDepenciesDialog_availableCodebasesLabel);
lblAvailable.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1));
codebaseViewer = new CheckboxTreeViewer(this, SWT.BORDER);
codebaseViewer.setContentProvider(new ITreeContentProvider() {
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
public Object[] getElements(Object element) {
if(element instanceof Collection) {
return ((Collection<?>) element).toArray();
}
return null;
}
public Object[] getChildren(Object parentElement) {
if(parentElement instanceof IProject) {
String elePath = getElePath(parentElement);
String prevPath = null;
List<IProject> children = new ArrayList<IProject>();
for(String path : projectPaths.keySet()) {
if(path.length() != elePath.length() && path.startsWith(elePath)) {
if(prevPath == null || !path.startsWith(prevPath)) {
prevPath = path;
children.add(getProject(path));
}
}
}
return children.toArray();
} else if(parentElement instanceof Collection) {
return ((Collection<?>) parentElement).toArray();
}
return null;
}
public Object getParent(Object element) {
String elePath = getElePath(element);
String prevPath = null;
for(String path : projectPaths.keySet()) {
if(elePath.length() != path.length() && elePath.startsWith(path)
&& (prevPath == null || prevPath.length() < path.length())) {
prevPath = path;
}
}
return prevPath == null ? projects : getProject(prevPath);
}
public boolean hasChildren(Object element) {
if(element instanceof IProject) {
String elePath = getElePath(element);
for(String path : projectPaths.keySet()) {
if(elePath.length() != path.length() && path.startsWith(elePath)) {
return true;
}
}
} else if(element instanceof Collection) {
return !((Collection<?>) element).isEmpty();
}
return false;
}
});
codebaseViewer.setLabelProvider(new MavenProjectLabelProvider() {
public Image getImage(Object element) {
Image img = super.getImage(element);
if(showOutOfDateUI && requiresUpdate((IProject) element)) {
img = MavenImages.createOverlayImage(MavenImages.OOD_MVN_PROJECT, img, MavenImages.OUT_OF_DATE_OVERLAY,
IDecoration.BOTTOM_RIGHT);
}
return img;
}
});
codebaseViewer.setComparator(new ViewerComparator());
projects = getMavenCodebases();
// prevent flicker
codebaseViewer.getTree().setRedraw(false);
try {
codebaseViewer.setInput(projects);
codebaseViewer.expandAll();
if(initialSelection != null) { // windowbuilder compat
for(IProject project : initialSelection) {
setSubtreeChecked(project, true);
}
// Reveal the first element
if(initialSelection.length > 0) {
codebaseViewer.reveal(initialSelection[0]);
}
}
} finally {
codebaseViewer.getTree().setRedraw(true);
}
Tree tree = codebaseViewer.getTree();
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
gd.heightHint = 300;
gd.widthHint = 300;
tree.setLayoutData(gd);
GridLayout layout = new GridLayout(2, false);
layout.marginLeft = 10;
Composite selectionActionComposite = new Composite(this, SWT.NONE);
selectionActionComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1));
GridLayout gl_selectionActionComposite = new GridLayout(1, false);
gl_selectionActionComposite.marginWidth = 0;
gl_selectionActionComposite.marginHeight = 0;
selectionActionComposite.setLayout(gl_selectionActionComposite);
createButtons(selectionActionComposite);
createOutOfDateProjectsWarning(parent);
createMenu();
codebaseViewer.addSelectionChangedListener(event -> updateSelectedProjects());
updateSelectedProjects();
}
private void createOutOfDateProjectsWarning(Composite composite) {
if(!showOutOfDateUI) {
return;
}
warningArea = new Composite(composite, SWT.NONE);
warningArea.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1));
warningArea.setLayout(new RowLayout(SWT.HORIZONTAL));
Label warningImg = new Label(warningArea, SWT.NONE);
warningImg.setImage(JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING));
includeOutDateProjectslink = new Link(warningArea, SWT.NONE);
includeOutDateProjectslink.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
includeOutOfDateProjects();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
includeOutOfDateProjects();
}
});
}
private void updateIncludeOutDateProjectsLink(int outOfDateProjectsCount) {
boolean visibility = true;
String text = ""; //$NON-NLS-1$
String btnTooltip;
if(outOfDateProjectsCount == 0) {
visibility = false;
btnTooltip = Messages.NestedProjectsComposite_OutOfDateProjectBtn_Generic_Tooltip;
} else if(outOfDateProjectsCount > 1) {
text = NLS.bind(Messages.NestedProjectsComposite_Multiple_OOD_Projects_Link, outOfDateProjectsCount);
btnTooltip = NLS.bind(Messages.NestedProjectsComposite_OutOfDateProjectBtn_AddProjects_Tooltip,
outOfDateProjectsCount);
} else {
text = Messages.NestedProjectsComposite_Single_OOD_Project_Link;
btnTooltip = Messages.NestedProjectsComposite_OutOfDateProjectBtn_AddOneProject_Tooltip;
}
if(includeOutDateProjectslink != null && addOutOfDateBtn != null && warningArea != null) {
includeOutDateProjectslink.setText(text);
addOutOfDateBtn.setToolTipText(btnTooltip);
warningArea.setVisible(visibility);
warningArea.getParent().layout(new Control[] {warningArea});
}
}
private int computeOutOfDateProjectsCount() {
int outOfDateProjectsCount = 0;
for(IProject p : projectPaths.values()) {
if(requiresUpdate(p) && !codebaseViewer.getChecked(p)) {
outOfDateProjectsCount++ ;
}
}
return outOfDateProjectsCount;
}
private void includeOutOfDateProjects() {
for(IProject project : projectPaths.values()) {
if(requiresUpdate(project)) {
codebaseViewer.setChecked(project, true);
}
}
updateSelectedProjects();
}
private void updateSelectedProjects() {
selectedProjects = internalGetSelectedProjects();
updateIncludeOutDateProjectsLink(computeOutOfDateProjectsCount());
}
private void setSubtreeChecked(Object obj, boolean checked) {
// CheckBoxTreeViewer#setSubtreeChecked is severely inefficient
codebaseViewer.setChecked(obj, checked);
Object[] children = ((ITreeContentProvider) codebaseViewer.getContentProvider()).getChildren(obj);
if(children != null) {
for(Object child : children) {
setSubtreeChecked(child, checked);
}
}
}
protected void createButtons(Composite selectionActionComposite) {
Button selectAllBtn = new Button(selectionActionComposite, SWT.NONE);
selectAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
selectAllBtn.setText(Messages.UpdateDepenciesDialog_selectAll);
selectAllBtn.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
for(IProject project : projects) {
setSubtreeChecked(project, true);
}
updateSelectedProjects();
}
});
if(showOutOfDateUI) {
addOutOfDateBtn = new Button(selectionActionComposite, SWT.NONE);
addOutOfDateBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
addOutOfDateBtn.setText(Messages.NestedProjectsComposite_Add_OutOfDate);
addOutOfDateBtn.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
includeOutOfDateProjects();
}
});
}
Button deselectAllBtn = new Button(selectionActionComposite, SWT.NONE);
deselectAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 1, 1));
deselectAllBtn.setText(Messages.UpdateDepenciesDialog_deselectAll);
deselectAllBtn.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
codebaseViewer.setCheckedElements(new Object[0]);
updateSelectedProjects();
}
});
Button expandAllBtn = new Button(selectionActionComposite, SWT.NONE);
expandAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, false, 1, 1));
expandAllBtn.setText(Messages.UpdateDepenciesDialog_expandAll);
expandAllBtn.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
codebaseViewer.expandAll();
}
});
Button collapseAllBtn = new Button(selectionActionComposite, SWT.NONE);
collapseAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, false, 1, 1));
collapseAllBtn.setText(Messages.UpdateDepenciesDialog_collapseAll);
collapseAllBtn.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
codebaseViewer.collapseAll();
}
});
}
String getElePath(Object element) {
if(element instanceof IProject) {
IProject project = (IProject) element;
URI locationURI = project.getLocationURI();
try {
IFileStore store = EFS.getStore(locationURI);
File file = store.toLocalFile(0, null);
return file.toString() + SEPARATOR;
} catch(CoreException ex) {
log.error(ex.getMessage(), ex);
}
}
return null;
}
IProject getProject(String path) {
return projectPaths.get(path);
}
private Collection<IProject> getMavenCodebases() {
projectPaths = new TreeMap<String, IProject>();
for(IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
try {
if(isInteresting(project)) {
if(project.getLocationURI() != null) {
String path = getElePath(project);
if(path != null) {
projectPaths.put(path, project);
}
}
}
} catch(CoreException ex) {
log.error(ex.getMessage(), ex);
}
}
if(projectPaths.isEmpty()) {
return Collections.<IProject> emptyList();
}
List<IProject> projects = new ArrayList<IProject>();
String previous = projectPaths.keySet().iterator().next();
addProject(projects, previous);
for(String path : projectPaths.keySet()) {
if(!path.startsWith(previous)) {
previous = path;
IProject project = getProject(path);
if(project != null) {
projects.add(project);
}
}
}
return projects;
}
protected boolean isInteresting(IProject project) throws CoreException {
return project.isAccessible() && project.hasNature(IMavenConstants.NATURE_ID);
}
private static void addProject(Collection<IProject> projects, String location) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
for(IContainer container : root.findContainersForLocationURI(new File(location).toURI())) {
if(container instanceof IProject) {
projects.add((IProject) container);
break;
}
}
}
private void createMenu() {
MenuManager menuMgr = new MenuManager();
Menu contextMenu = menuMgr.createContextMenu(codebaseViewer.getControl());
menuMgr.addMenuListener(this);
codebaseViewer.getControl().setMenu(contextMenu);
menuMgr.setRemoveAllWhenShown(true);
}
public void menuAboutToShow(IMenuManager manager) {
if(codebaseViewer.getSelection().isEmpty()) {
return;
}
if(codebaseViewer.getSelection() instanceof IStructuredSelection) {
manager.add(selectTree);
manager.add(deselectTree);
}
}
private final Action selectTree = new Action(Messages.UpdateDepenciesDialog_selectTree) {
public void run() {
setSubtreeChecked(getSelection(), true);
updateSelectedProjects();
}
};
private final Action deselectTree = new Action(Messages.UpdateDepenciesDialog_deselectTree) {
public void run() {
setSubtreeChecked(getSelection(), false);
updateSelectedProjects();
}
};
public IProject getSelection() {
ISelection selection = codebaseViewer.getSelection();
if(selection instanceof IStructuredSelection) {
return (IProject) ((IStructuredSelection) selection).getFirstElement();
}
return null;
}
public IProject[] getSelectedProjects() {
return selectedProjects;
}
IProject[] internalGetSelectedProjects() {
Object[] obj = codebaseViewer.getCheckedElements();
IProject[] projects = new IProject[obj.length];
for(int i = 0; i < obj.length; i++ ) {
projects[i] = (IProject) obj[i];
}
return projects;
}
public void refresh() {
projects = getMavenCodebases();
codebaseViewer.setInput(projects);
codebaseViewer.expandAll();
}
public void reset() {
projects = getMavenCodebases();
codebaseViewer.setInput(projects);
codebaseViewer.expandAll();
codebaseViewer.setCheckedElements(new Object[0]);
updateSelectedProjects();
}
public void addSelectionChangeListener(ISelectionChangedListener listener) {
codebaseViewer.addSelectionChangedListener(listener);
}
//XXX probably move to a utility class
private boolean requiresUpdate(IProject project) {
try {
IMarker[] markers = project.findMarkers(IMavenConstants.MARKER_CONFIGURATION_ID, true, IResource.DEPTH_ZERO);
for(IMarker marker : markers) {
String message = (String) marker.getAttribute(IMarker.MESSAGE);
//XXX need a better way to identify these than rely on the marker message
if(org.eclipse.m2e.core.internal.Messages.ProjectConfigurationUpdateRequired.equals(message)) {
return true;
}
}
} catch(CoreException ex) {
log.error(ex.getMessage(), ex);
}
return false;
}
}