/*******************************************************************************
* Copyright (c) 2012 Pivotal Software, Inc.
* 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:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.commons.frameworks.ui.internal.plugins;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreePathContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.ui.dialogs.SearchPattern;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.BasePluginData;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.Plugin;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.PluginVersion;
import org.springsource.ide.eclipse.commons.frameworks.ui.FrameworkUIActivator;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.icons.IconManager;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt.IProjectSelectionHandler;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt.IProjectSelector;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt.ProjectSelectionPart;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt.ProjectSelectorFactory;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt.SWTFactory;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt.ViewerSearchPart;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.tasks.IUIRunnable;
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.tasks.TaskManager;
/**
* The manager allows plugins to be selected for install, uninstall or update
* <p>
* The actual operations on the selected plugins are executed only after the
* dialogue closes.
* </p>
* <p>
* The install and uninstall operations can be used to undo each other. So if a
* plugin is marked as selected for install, pressing the uninstall operation
* undos the selection, restoring the plugin state to its original state.
* </p>
* <p>
* IMPORTANT: to understand the behaviour of this manager, it is important to
* distinguish between the underlying plugin model and the tree elements in a
* viewer that represent the plugin model. They are separate, although a tree
* element always holds a reference to its corresponding plugin model entity.
* </p>
* <p>
* When the manager first opens it will populate the list of plugins based on
* the local list of available plugins as well as will mark those plugins on
* that list that are installed in a given project context. This list can be
* refreshed by requesting Grails for an updated list, or it can be reset to the
* state the list was in when the manager first opened (i.e. the state based on
* the local list of plugins).
* </p>
* Plugins and their versions are represented in a tree viewer as tree elements
* that hold a reference to a plugin version model entity. In addition, these
* tree elements also hold selection and install state, indicating whether the
* plugin is currently installed, or the selection status while the manager is
* still open (i.e. whether a plugin has been selected for upgrade, uninstall,
* etc..
* <p>
* A tree element is therefore the UI representation of a plugin version model
* entity, with additional information specific to the operations that can be
* performed with this manager.
* </p>
* <p>
* The tree viewer itself displays two types of elements, a root element and a
* version element. BOTH of these represent a plugin version model entity,
* except that the root element indicates which version is either available for
* install OR which version is currently installed. The version element simply
* indicates a particular version model entity for a plugin. The list of child
* version elements is IMMUTABLE, but the root element version is NOT. The
* reason is that the root element always reflects which version of a plugin the
* user has selected for a particular operation, and therefore may change as the
* user changes selections, and performs different operations.
* </p>
* @author Nieraj Singh
* @author Andy Clement
* @author Christian Dupuis
* @author Andrew Eisenberg
* @author Kris De Volder
*/
public abstract class PluginManagerDialog extends CommandDialog {
public PluginManagerDialog(Shell parentShell,
List<IProject> projects) {
super(parentShell, projects);
}
public static final String INITIAL_REFRESH_DEPENDENCIES_DIALOGUE_TITLE = "No local plugin list found";
private GrailsBrowserViewer pluginViewer;
private IProject selectedProject;
private Text descriptionLabel;
private Label authorLabel;
private Link documentationLink;
private IconManager iconManager;
private static final int MIN_DESCRIPTION_HEIGHT = 65;
private static final int MIN_DESCRIPTION_WIDTH = SWT.DEFAULT;
/**
* Read/write only through appropriate getter method. Do not access directly
*/
private Map<Plugin, RootTreeElement> rootElementMap;
private Map<PluginVersion, VersionTreeElement> childElementMap;
/**
* Local cache of all the plugins. Used for resetting only.
*/
private Collection<? extends Plugin> pluginList;
private static final String DATA_NOT_AVAILABLE = "n/a";
private LinkedHashSet<PluginVersion> selectedToInstall = new LinkedHashSet<PluginVersion>();
private LinkedHashSet<PluginVersion> selectedToUninstall = new LinkedHashSet<PluginVersion>();
private List<Button> selectionBasedOperationButtons;
private ViewerSearchPart searchPart;
public enum PluginColumnType implements IPluginListColumn {
PLUGIN_STATE("", 12), PLUGIN_NAME("Name", 20), TITLE("Title", 80), VERSION(
"Version", 10);
private String name;
private int weight;
private PluginColumnType(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public int getWidth() {
return weight;
}
}
/**
* Plugins can be filtered based on theis state (if installed, selected for
* change, or having updates)
*
* @author nisingh
*
*/
public enum GrailsFilterType {
SHOW_ALL("Show all", true), SHOW_INSTALLED_ONLY("Show installed only",
false), SHOW_AVAILABLE_UPDATES_ONLY(
"Show available updates only", false), SHOW_CHANGES_ONLY(
"Show pending changes only", false);
private String filterName;
private boolean defaultSelection;
private GrailsFilterType(String filterName, boolean defaultSelection) {
this.filterName = filterName;
this.defaultSelection = defaultSelection;
}
public String getFilterName() {
return filterName;
}
public boolean getDefaultSelection() {
return defaultSelection;
}
}
/**
* Search area that allows users to filter the list of plugins based on text
* patterns.
*
* @param parent
*/
protected void createSearchArea(Composite parent) {
searchPart = new ViewerSearchPart(parent) {
protected boolean matches(Object element, Object parentElement,
String pattern) {
if (element instanceof ITreeElement) {
ITreeElement treeElement = (ITreeElement) element;
PluginVersion version = treeElement.getVersionModel();
if (PluginManagerDialog.this.matches(version,
pattern)) {
return true;
} else {
List<PluginVersion> versions = version.getParent()
.getVersions();
if (versions != null) {
for (PluginVersion ver : versions) {
if (PluginManagerDialog.this.matches(
ver, pattern)) {
return true;
}
}
}
}
}
return false;
}
};
}
protected void initIconManager() {
iconManager = new IconManager();
}
/**
* Determines if data contained in the plugin model element matches a given
* pattern. This data could be the model element name, title, or
* description. True if it matches, false otherwise
*
* @param version
* plugin model element whose data needs to be compared against a
* pattern
* @param pattern
* @return true if plugin model element data matches the given pattern.
*/
protected boolean matches(PluginVersion version, String pattern) {
if (version == null || pattern == null) {
return false;
}
String lowerCasePattern = pattern.toLowerCase();
String name = version.getName();
SearchPattern filter = new SearchPattern();
filter.setPattern(lowerCasePattern);
if (name != null && filter.matches(name)) {
return true;
}
String title = version.getTitle();
if (title != null && filter.matches(title)) {
return true;
}
return false;
}
/**
* The main UI area of the dialogue containing all the controls of the
* manager
*/
protected void createCommandArea(Composite parent) {
createProjectArea(parent);
createPluginViewerArea(parent);
// Populate the viewer for the first time
resynchPluginsList(false);
pluginViewer.getTreeViewer().getTree().setFocus();
}
/**
* The project selection area.
*
* @param parent
*/
protected void createProjectArea(Composite parent) {
// Set the default selection based on the first selected project
Collection<IProject> selectedProjects = getProjects();
LinkedHashSet<IProject> availableProjects = new LinkedHashSet<IProject>();
if (selectedProjects != null && !selectedProjects.isEmpty()) {
availableProjects.addAll(selectedProjects);
}
// Get all workspace projects
Collection<IProject> allProjects = updateProjects();
if (!allProjects.isEmpty()) {
availableProjects.addAll(allProjects);
}
IProjectSelectionHandler handler = new IProjectSelectionHandler() {
public void handleProjectSelectionChange(IProject project) {
PluginManagerDialog.this.selectedProject = project;
resynchPluginsList(false);
}
};
IProjectSelector selector = new ProjectSelectorFactory(getShell(),
parent, availableProjects, handler).getProjectSelector();
if (selector != null) {
selector.createProjectArea();
if (selector instanceof ProjectSelectionPart) {
((ProjectSelectionPart) selector)
.showProjectSwitchDialogue(true);
}
// Initial selection
if (!availableProjects.isEmpty()) {
selectedProject = availableProjects.iterator().next();
selector.setProject(selectedProject);
}
}
}
protected abstract Collection<IProject> updateProjects();
protected void clearAllPluginChanges() {
if (selectedToInstall != null) {
selectedToInstall.clear();
}
if (selectedToUninstall != null) {
selectedToUninstall.clear();
}
}
public boolean hasPluginChanges() {
return (selectedToInstall != null && !selectedToInstall.isEmpty())
|| (selectedToUninstall != null && !selectedToUninstall
.isEmpty());
}
protected void createFilterOptionsArea(Composite parent) {
Group optionsGroup = new Group(parent, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, false).applyTo(optionsGroup);
GridLayoutFactory
.fillDefaults()
.numColumns(1)
.margins(getDefaultCompositeHMargin(),
getDefaultCompositeVMargin()).applyTo(optionsGroup);
optionsGroup.setText("Filters");
Composite filterOptionsArea = new Composite(optionsGroup, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2)
.applyTo(filterOptionsArea);
GridDataFactory.fillDefaults().grab(true, false)
.applyTo(filterOptionsArea);
if (optionsGroup != null) {
GrailsFilterType[] types = GrailsFilterType.values();
for (GrailsFilterType type : types) {
String filterName = type.getFilterName();
if (filterName != null) {
Button button = SWTFactory.createRadialButton(
filterOptionsArea, filterName,
type.getDefaultSelection());
if (button != null) {
button.addSelectionListener(new ViewerFilterButtonListener());
button.setData(type);
}
}
}
}
}
protected void createDescriptionArea(Composite parent) {
Composite descriptionArea = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(descriptionArea);
GridDataFactory.fillDefaults().grab(true, true)
.applyTo(descriptionArea);
Group descriptionGroup = new Group(descriptionArea, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true)
.applyTo(descriptionGroup);
GridLayoutFactory
.fillDefaults()
.numColumns(1)
.margins(getDefaultCompositeHMargin(),
getDefaultCompositeVMargin()).applyTo(descriptionGroup);
descriptionGroup.setText("Published Plugin Information");
Label desLabel = new Label(descriptionGroup, SWT.LEFT);
desLabel.setText("Description:");
descriptionLabel = new Text(descriptionGroup, SWT.V_SCROLL | SWT.BORDER
| SWT.READ_ONLY | SWT.WRAP);
GridDataFactory.fillDefaults().grab(true, true)
.hint(MIN_DESCRIPTION_WIDTH, MIN_DESCRIPTION_HEIGHT)
.applyTo(descriptionLabel);
Composite authorArea = new Composite(descriptionGroup, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(authorArea);
GridDataFactory.fillDefaults().grab(true, false).applyTo(authorArea);
Label authLab = new Label(authorArea, SWT.LEFT);
authLab.setText("Author:");
authorLabel = new Label(authorArea, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, false).applyTo(authorLabel);
Composite linkArea = new Composite(descriptionGroup, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(linkArea);
GridDataFactory.fillDefaults().grab(true, false).applyTo(linkArea);
Label descLinkLabel = new Label(linkArea, SWT.LEFT);
descLinkLabel.setText("Documentation:");
documentationLink = new Link(linkArea, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, false)
.applyTo(documentationLink);
documentationLink.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
Object dataObj = documentationLink.getData();
if (dataObj instanceof BasePluginData) {
String urlExpression = ((BasePluginData) dataObj)
.getDocumentation();
handleNavigation(urlExpression);
}
}
});
refreshDescriptionArea();
}
/**
* Handles navigation from a hyperlink in the description area.
*
* @param urlExpression
*/
protected void handleNavigation(String urlExpression) {
String errorMessage = null;
try {
URL url = new URL(urlExpression);
boolean launched = Program.launch(url.toString());
if (!launched) {
IWorkbenchBrowserSupport support = PlatformUI.getWorkbench()
.getBrowserSupport();
try {
if (support != null) {
IWebBrowser browser = support.getExternalBrowser();
if (browser != null) {
browser.openURL(url);
return;
} else {
errorMessage = "Unable to find browser support to navigate to URL. Check default browser support in Eclipse or OS";
}
}
} catch (PartInitException e) {
errorMessage = "Unable to navigate to URL: "
+ e.getLocalizedMessage();
}
}
} catch (MalformedURLException e) {
errorMessage = "Unable to navigate to URL: "
+ e.getLocalizedMessage();
}
if (errorMessage != null) {
InternalMessageDialogue dialog = new InternalMessageDialogue(
"Problems navigating to URL", errorMessage + ": "
+ urlExpression, MessageDialog.ERROR, false);
dialog.open();
}
}
/**
* This gets invokes any time a selection changes in the tree viewer.
* Updates the description controls with the selected plugin information. If
* more than one plugin is selected, a "n/a" is displayed for each
* description field.
*/
protected void refreshDescriptionArea() {
PluginVersion version = getSingleSelectedPluginVersion();
if (version != null) {
String description = version.getDescription();
if (description != null && description.length() > 0) {
descriptionLabel.setText(description);
} else {
descriptionLabel.setText(DATA_NOT_AVAILABLE);
}
String author = version.getAuthor();
if (author != null && author.length() > 0) {
authorLabel.setText(author);
} else {
authorLabel.setText(DATA_NOT_AVAILABLE);
}
String documentation = version.getDocumentation();
if (documentation != null && documentation.length() > 0) {
documentation = "<a href=\"" + documentation + "\">"
+ documentation + "</a>";
documentationLink.setText(documentation);
} else {
documentationLink.setText(DATA_NOT_AVAILABLE);
}
documentationLink.setData(version);
return;
}
descriptionLabel.setText(DATA_NOT_AVAILABLE);
authorLabel.setText(DATA_NOT_AVAILABLE);
documentationLink.setText(DATA_NOT_AVAILABLE);
documentationLink.setData(null);
}
/**
* Will return a selected Plugin IF and ONLY IF there is one selection.
* Returns null if there are no plugins selected or multiple plugins
* selected
*
* @return one selected plugin, or null in any other case
*/
protected PluginVersion getSingleSelectedPluginVersion() {
IStructuredSelection selection = (IStructuredSelection) pluginViewer
.getTreeViewer().getSelection();
if (selection != null && selection.size() == 1) {
Object element = selection.getFirstElement();
if (element instanceof ITreeElement) {
return ((ITreeElement) element).getVersionModel();
}
}
return null;
}
/**
* Creates the main tree viewer area that contains the tree viewer that
* displays the list of plugins as well as the button selection controls
*
* @param parent
*/
protected void createPluginViewerArea(Composite parent) {
createSearchArea(parent);
Composite viewerArea = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false)
.applyTo(viewerArea);
GridDataFactory.fillDefaults().grab(true, true).applyTo(viewerArea);
Composite treeArea = new Composite(viewerArea, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(treeArea);
GridDataFactory.fillDefaults().grab(true, true).applyTo(treeArea);
pluginViewer = new GrailsBrowserViewer(getColumns());
pluginViewer.createControls(treeArea);
TreeViewer viewer = pluginViewer.getTreeViewer();
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
refreshDescriptionArea();
validateOperationButtons();
}
});
viewer.addFilter(new PluginStatusFilter());
viewer.setContentProvider(new GrailsViewerContentProvider());
viewer.setLabelProvider(new GrailsViewerLabelProvider());
viewer.setSorter(new GrailsViewerColumnSorter());
createDescriptionArea(treeArea);
createFilterOptionsArea(treeArea);
createOperationButtonArea(viewerArea);
searchPart.connectViewer(pluginViewer.getTreeViewer());
initIconManager();
}
/**
* Determine which selection based operation buttons to enable/disable based
* on the current plugin selection
*/
protected void validateOperationButtons() {
// Determine which operation buttons to enable based on the
// current selection
List<ITreeElement> selectedElements = getSelectedTreeElements();
List<Button> operationButtons = getPluginOperationButtons();
boolean disableAll = selectedElements.isEmpty();
for (Button button : operationButtons) {
if (disableAll) {
button.setEnabled(false);
} else {
// Allow the selection to determine if the operation associated
// with the button should enable the button
for (ITreeElement element : selectedElements) {
// In place always disable all buttons
/* if (element.getVersionModel().getParent().isInPlace()) {
button.setEnabled(false);
break;
} else */ if (!enableOperationButtonState(button, element)) {
// If at least one element cannot handle the given
// operation
// the button is disabled and there is no further need
// to
// check the remaining elements.
break;
}
}
}
}
}
/**
* Change the state of an operation button, based on the state of a given
* tree element If the button is not a selection-based operation button, no
* changes are peformed and the current state of the button is retained and
* returned.
*
* @param button
* to change state, IF it is a selection-based operation button
* @param element
* to test if button enable state needs to change
* @return new enable state of the button, or the original state if the
* button state is not changed
*/
protected boolean enableOperationButtonState(Button button,
ITreeElement element) {
Object data = button.getData();
boolean isEnabled = button.getEnabled();
if (data instanceof PluginOperation) {
PluginOperation operation = (PluginOperation) data;
isEnabled = element.isValidOperation(operation);
button.setEnabled(isEnabled);
}
return isEnabled;
}
/**
* Creates a button for each available plugin operation.
*
* @param parent
*/
protected void createOperationButtonArea(Composite parent) {
Composite buttons = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults()
.align(GridData.CENTER, GridData.BEGINNING).applyTo(buttons);
GridLayoutFactory.fillDefaults().applyTo(buttons);
PluginOperation[] types = PluginOperation.values();
selectionBasedOperationButtons = new ArrayList<Button>(types.length);
for (PluginOperation type : types) {
Button button = createSelectionButton(buttons, type);
if (button != null && isSelectionBasedOperation(type)) {
selectionBasedOperationButtons.add(button);
}
}
}
protected boolean isSelectionBasedOperation(PluginOperation operation) {
if (operation == null) {
return false;
}
switch (operation) {
case INSTALL:
case UNINSTALL:
case UPDATE:
return true;
}
return false;
}
/**
* Buttons that operate on selections in the tree viewer. May be null if no
* buttons have been initiliased.
*
* @return list of buttons or null if not initialised
*/
protected List<Button> getPluginOperationButtons() {
return selectionBasedOperationButtons;
}
/**
* Creates a button and adds appropriate listeners for a given operation
*
* @param parent
* @param type
* @return
*/
protected Button createSelectionButton(Composite parent,
PluginOperation type) {
if (type == null) {
return null;
}
Button button = new Button(parent, SWT.PUSH);
button.setText(type.getName());
Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
GridDataFactory.fillDefaults()
.hint(Math.max(widthHint, minSize.x), SWT.DEFAULT)
.applyTo(button);
button.setData(type);
button.addSelectionListener(new PluginSelectionButtonListener());
return button;
}
protected PluginColumnType getColumnType(String name) {
if (name == null) {
return null;
}
PluginColumnType[] types = PluginColumnType.values();
for (PluginColumnType type : types) {
if (type.getName().equals(name)) {
return type;
}
}
return null;
}
protected IPluginListColumn[] getColumns() {
PluginColumnType[] type = PluginColumnType.values();
IPluginListColumn[] columns = new IPluginListColumn[type.length];
for (int i = 0; i < columns.length && i < type.length; i++) {
columns[i] = type[i];
}
return columns;
}
/**
* Resynch the plugin list. If the 'aggressive' flag is set, then a more forcefull refresh
* will be performed, clearing both in-memory and on-disk caches. The agressive flag will
* be set, typically, when the resynch is triggered by the user explicitly clicking on the
* refresh button. In other cases it won't typically be set.
*/
protected void resynchPluginsList(final boolean aggressive) {
IUIRunnable pluginListCommand = new IUIRunnable() {
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
pluginList = updatePlugins(aggressive, monitor);
}
public Shell getShell() {
return PluginManagerDialog.this.getShell();
}
};
TaskManager.runSynch(pluginListCommand,
"Resolving list of available plugins.");
setViewerInput();
}
/**
* If the 'aggressive' flag is set, then a more forceful refresh
* will be performed, clearing both in-memory and on-disk caches. The aggressive flag will
* be set, typically, only when triggered by the user explicitly clicking on the
* refresh button.
*/
protected abstract Collection<? extends Plugin> updatePlugins(boolean aggressive, IProgressMonitor monitor);
/**
* Given a plugin model, returns the corresponding root tree viewer element
* <p>
* if a root tree viewer element cannot be found, it will attempt to create
* one and return it
* </p>
* Return null if it cannot create a root element, or it cannot find an
* existing root element
*
* @param plugin
* @return
*/
protected RootTreeElement getRootElement(Plugin plugin) {
if (plugin == null || rootElementMap == null) {
return null;
}
RootTreeElement element = rootElementMap.get(plugin);
if (element == null) {
element = new RootTreeElement(plugin.getLatestReleasedVersion());
rootElementMap.put(plugin, element);
}
return element;
}
/**
* Returns a child version tree element for the corresponding plugin version
* model.
*
* @param version
* @return
*/
protected VersionTreeElement getVersionElement(PluginVersion version) {
if (version == null || childElementMap == null) {
return null;
}
VersionTreeElement element = childElementMap.get(version);
if (element == null) {
// No orphan children. Add only if a parent is found.
// Must have a Parent
RootTreeElement rootElement = getRootElement(version.getParent());
if (rootElement != null) {
element = new VersionTreeElement(version, rootElement);
childElementMap.put(version, element);
} else {
FrameworkUIActivator.log(new Status(IStatus.ERROR, FrameworkUIActivator.PLUGIN_ID,
"Orphaned version: "
+ version.getName()
+ " "
+ version.getVersion()
+ ". Cannot find a published plugin. Omitting from manager plugin's list"
));
}
}
return element;
}
/**
* Get the plugins that are selected for install. It is never null, but can
* be empty.
*
* @return non-null list of selected plugins for install. Can be empty.
*/
public Collection<PluginVersion> getSelectedToInstall() {
return selectedToInstall;
}
/**
* Get the plugins that are selected for uninstall. It is never null, but
* can be empty.
*
* @return non-null list of selected plugins for install. Can be empty.
*/
public Collection<PluginVersion> getSelectedtoUninstall() {
return selectedToUninstall;
}
/**
* Get currently selected project for which plugin management should be
* performed. Should never be null.
*
* @return non-null project for which plugin management should be performed.
*/
public IProject getSelectedProject() {
return selectedProject;
}
/**
* Given a root element, it will either UNDO a select-install operation OR
* mark the root element for Uninstall. The root element's corresponding
* child version element will also be updated.
*
* @param root
*/
protected void uninstallRoot(RootTreeElement root) {
if (root == null) {
return;
}
PluginState currentState = root.getPluginState();
if (currentState == PluginState.SELECT_INSTALL) {
// Restore the original state.
restoreOriginalState(root);
} else if (currentState == PluginState.INSTALLED
|| currentState == PluginState.UPDATE_AVAILABLE) {
// select corresponding version and mark both the root and version
// for
// uninstall
VersionTreeElement associatedVersion = root
.getSelectedVersionTreeElement();
uninstallVersion(associatedVersion);
}
}
/**
* Marks the given version state and its corresponding root to Install, but
* does NOT validate to make sure it is possible to mark it to install.
* Callers must validate before calling this method. When marked for
* install, all other version states are cleared for that plugin.
*
* @param selectedVersionElement
*/
protected void installVersion(VersionTreeElement selectedVersionElement) {
if (selectedVersionElement == null) {
return;
}
RootTreeElement root = selectedVersionElement.getParent();
if (root != null) {
restoreOriginalState(root);
selectedVersionElement.setState(PluginState.SELECT_INSTALL);
addToInstallSelection(selectedVersionElement.getVersionModel());
root.setSelectedVersionTreeElement(selectedVersionElement);
root.setState(PluginState.SELECT_INSTALL);
}
}
/**
* Marks the given version state and its corresponding root state to
* Uninstall. Note that this does not check for validity. It simply clears
* state for all other versions of the plugin, and marks the specified
* version and its corresponding root with an Uninstall state.
*
* @param selectedVersionElement
* which needs to be set to "Uninstall" along with its root.
*/
protected void uninstallVersion(VersionTreeElement selectedVersionElement) {
if (selectedVersionElement == null) {
return;
}
RootTreeElement root = selectedVersionElement.getParent();
if (root != null) {
restoreOriginalState(root);
selectedVersionElement.setState(PluginState.SELECT_UNINSTALL);
addToUninstallSelection(selectedVersionElement.getVersionModel());
root.setSelectedVersionTreeElement(selectedVersionElement);
root.setState(PluginState.SELECT_UNINSTALL);
}
}
/**
* Plugin models added to this list get installed after the wizard
* completes. THis list also includes plugin models selected for upgrade.
* This may change if Grails adds a distinct "upgrade" plugin command. As of
* 1.3.4, the "install-plugin" handles both cases.
*
* @param version
*/
protected void addToInstallSelection(PluginVersion version) {
if (version != null && !selectedToInstall.contains(version)) {
selectedToInstall.add(version);
removeFromUninstallSelection(version);
}
}
/**
* Plugin models added to this list get uninstalled after the wizard
* completes.
*
* @param version
*/
protected void addToUninstallSelection(PluginVersion version) {
if (version != null && !selectedToUninstall.contains(version)) {
selectedToUninstall.add(version);
removeFromInstallSelection(version);
}
}
protected void removeFromInstallSelection(PluginVersion version) {
selectedToInstall.remove(version);
}
protected void removeFromUninstallSelection(PluginVersion version) {
selectedToUninstall.remove(version);
}
/**
* Restores a root element to its pre-selection state.
* <p>
* For plugins that are not installed but marked for install, this means
* clearing the plugin state (no state) For plugins that are installed but
* marked for uninstall, this means restoring the plugin state to that of
* installed (Or update available).
* </p>
*
* @param element
*/
protected void restoreOriginalState(RootTreeElement element) {
if (element == null) {
return;
}
// Clear the current selection first
VersionTreeElement currentSelection = element
.getSelectedVersionTreeElement();
if (currentSelection != null) {
PluginVersion currentSelectionVersion = currentSelection
.getVersionModel();
removeFromInstallSelection(currentSelectionVersion);
removeFromUninstallSelection(currentSelectionVersion);
currentSelection.setState(null);
element.setSelectedVersionTreeElement(null);
}
// Reset original state for both root and corresponding child
PluginState rootState = null;
Plugin plugin = element.getVersionModel().getParent();
// Each Root element MUST have an associated child version
// Determine the child version based on whether the plugin is installed
// (if so, the associated child should be the installed version) or
// if the plugin is not installed, the associated child should be the
// latest
// released version.
// If the plugin is installed, get the version that is installed
PluginVersion childVersion = plugin.getInstalled();
// Unless it is installed, the child version state is usually null
PluginState childState = null;
// Determine the state of the root based on whether the installed
// version is the latest or if there is an update available.
// Regardless of the state of the root, the actual child version is
// always marked as Installed.
if (childVersion != null) {
rootState = getOriginalStateOfRootPlugin(childVersion);
// Corresponding child version is always marked as installed
// regardless if the plugin has an update available or not
childState = PluginState.INSTALLED;
} else {
// If no version is installed, link the root with the latest
// released version
// as this is the version that will be installed if a user performs
// a "Install"
// operation on the root.
childVersion = plugin.getLatestReleasedVersion();
}
// Link the child and the root, and set the states for both
VersionTreeElement associatedElement = getVersionElement(childVersion);
associatedElement.setState(childState);
element.setSelectedVersionTreeElement(associatedElement);
element.setState(rootState);
}
/**
* Determines if the associated plugin for this version is installed and
* what the original state is (either installed to the latest version or has
* an update available. The original state is the state prior to any deltas
* that were performed on the plugin during the manager session ).
* <p>
* Not to get confused, this does NOT check whether the specific version is
* the one that is installed, but rather if the plugin this version is
* associated with is installed, and whether that plugin is installed with
* the latest version or it has an update available.
* </p>
*
* @param version
* whose plugin should be checked if it is installed or not.
* @return state of the plugin in the project (installed or update
* available), or null if it is not installed or install state
* cannot be determined.
*/
protected PluginState getOriginalStateOfRootPlugin(PluginVersion version) {
if (version == null) {
return null;
}
if (version.getParent().isInstalled()) {
return hasUpdate(version) ? PluginState.UPDATE_AVAILABLE
: PluginState.INSTALLED;
}
return null;
}
/**
* Updates a root element to the latest version. Note that NO validations
* are performed on whether an update is possible or not. Validation must be
* performed prior to calling this method
*
* @param selectedElement
*/
protected void updateRootElement(RootTreeElement selectedElement) {
// Update all only works on Root Elements
if (selectedElement == null) {
return;
}
VersionTreeElement latestVersionElement = getVersionElement(selectedElement
.getVersionModel().getParent().getLatestReleasedVersion());
installVersion(latestVersionElement);
}
/**
* Iterates through all root elements in the tree and selects any plugin
* that is marked as having an update for update.
*/
protected void handleUpdateAll() {
@SuppressWarnings("unchecked")
List<ITreeElement> rootElements = (List<ITreeElement>) pluginViewer.getTreeViewer()
.getInput();
for (ITreeElement rootElement : rootElements) {
if (rootElement.getPluginState() == PluginState.UPDATE_AVAILABLE
&& rootElement instanceof RootTreeElement) {
updateRootElement((RootTreeElement) rootElement);
}
}
validateOperationButtons();
refreshViewer(true);
}
/**
* Determines if an update exists for the given plugin version. Note that
* certain plugins are NOT marked as having update availables even if newer
* versions appear, as their version is directly tied to the Grails version
* being used by the project. Such plugins are typically plugins that are
* preinstalled when a Grails project is created (e.g., hibernate and
* tomcat).
*
* @param version
* to check if a newer version exists
* @return true if a newer version exists, although in some cases with
* preinstalled plugins false is returned even if newer versions
* exist
*
*/
protected boolean hasUpdate(PluginVersion version) {
boolean isPreinstalledPlugin = isPreinstalled(version);
if (!isPreinstalledPlugin && version.getParent().hasUpdate()) {
return true;
}
return false;
}
protected abstract boolean isPreinstalled(PluginVersion version);
/**
* Checks if the Viewer containing the list of plugins is disposed. It is
* disposed (returns true) if the viewer is null, the corresponding tree
* widget is null, or the tree widget is disposed. Otherwise it returns
* false.
* <p/>
* In most cases, the state of the viewer does NOT have to be checked as
* most viewer access operations occur from the UI thread.
* <p/>
* However, in some cases where the viewer should react to events from a
* non-UI thread, the viewer dispose state should be checked, as in the case
* of a viewer refresh from a Grails command executed in another process.
*/
protected boolean isViewerDisposed() {
if (pluginViewer == null) {
return true;
}
Tree tree = pluginViewer.getTreeViewer().getTree();
if (tree == null) {
return true;
}
return tree.isDisposed();
}
/**
* Resets the input in the plugins list viewer and clears all selections.
*
* @param pluginList
* new input to reset. if viewer is disposed nothing happens. If
* list is null, all entries in the tree are cleared.
*/
protected void setViewerInput() {
clearAllPluginChanges();
rootElementMap = new HashMap<Plugin, RootTreeElement>();
childElementMap = new HashMap<PluginVersion, VersionTreeElement>();
List<RootTreeElement> input = new ArrayList<RootTreeElement>();
if (pluginList != null && !pluginList.isEmpty()) {
// refresh list of installed plugins
for (Plugin plugin : pluginList) {
RootTreeElement treeElement = getRootElement(plugin);
if (treeElement != null) {
restoreOriginalState(treeElement);
input.add(treeElement);
}
}
}
if (!isViewerDisposed()) {
pluginViewer.getTreeViewer().setInput(input);
refreshViewer(true);
}
}
/**
* Refreshes the plugins list viewer in the UI with its current input. Does
* not reset the input
*
* @param updateLabel
* if all labels and icons should be regenerated. false otherwise
*/
protected void refreshViewer(boolean updateLabel) {
if (!isViewerDisposed()) {
pluginViewer.getTreeViewer().refresh(updateLabel);
}
}
/**
* The tree viewer that displays the list of plugins and allows operations
* to be performed on plugin selections. The tree viewer is populated by
* tree elements that correspond to plugin models. A root tree element
* corresponds to a plugin version that is either available to install or is
* currently installed, while the child of the root tree element corresponds
* to specific versions of that plugin.
*
* @author nisingh
*
*/
protected class GrailsBrowserViewer extends TreeViewerComposite {
public GrailsBrowserViewer(IPluginListColumn[] columns) {
super(columns, columns[PluginColumnType.PLUGIN_NAME.ordinal()]);
}
public void refreshFilter(GrailsFilterType type) {
PluginStatusFilter filter = getPluginStatusFilter();
if (filter != null) {
filter.setFilterType(type);
refreshViewer(false);
}
}
protected PluginStatusFilter getPluginStatusFilter() {
ViewerFilter[] filters = GrailsBrowserViewer.this.getTreeViewer()
.getFilters();
if (filters != null) {
for (ViewerFilter filter : filters) {
if (filter instanceof PluginStatusFilter) {
return (PluginStatusFilter) filter;
}
}
}
return null;
}
}
protected class GrailsViewerLabelProvider extends ColumnLabelProvider {
public void update(ViewerCell cell) {
Object element = cell.getElement();
int index = cell.getColumnIndex();
cell.setText(getColumnText(element, index));
cell.setImage(getColumnImage(element, index));
cell.setFont(getFont(element));
}
public Image getColumnImage(Object element, int index) {
if (element instanceof ITreeElement) {
PluginColumnType[] values = PluginColumnType.values();
if (index < values.length) {
PluginColumnType type = values[index];
if (type == PluginColumnType.PLUGIN_STATE) {
PluginState state = ((ITreeElement) element)
.getPluginState();
if (state != null && iconManager != null) {
return iconManager.getIcon(state);
}
}
}
}
return null;
}
public Font getFont(Object element) {
if (element instanceof ITreeElement) {
Plugin pluginModel = ((ITreeElement) element)
.getVersionModel().getParent();
// Use different font to indicate inplace plugin
if (pluginModel.isInPlace()) {
return JFaceResources.getFontRegistry().getBold(
JFaceResources.DIALOG_FONT);
}
}
return super.getFont(element);
}
public String getColumnText(Object element, int index) {
if (element instanceof ITreeElement) {
PluginColumnType[] values = PluginColumnType.values();
if (index < values.length) {
PluginColumnType type = values[index];
PluginVersion version = ((ITreeElement) element)
.getVersionModel();
String text = null;
switch (type) {
case PLUGIN_NAME:
text = version.getName();
break;
case TITLE:
text = version.getTitle();
break;
case VERSION:
text = version.getVersion();
break;
}
if (text != null) {
return text;
}
}
}
return null;
}
}
protected class GrailsViewerColumnSorter extends TreeViewerColumnComparator {
protected String getCompareString(TreeColumn column, Object rowItem) {
if (rowItem instanceof ITreeElement) {
ITreeElement element = (ITreeElement) rowItem;
PluginColumnType type = getColumnType(column.getText());
PluginVersion version = element.getVersionModel();
if (type != null) {
switch (type) {
// if sorting by plugin state, it means
// two equal states are being compared therefore
// sort plugins with the same state by name
case PLUGIN_STATE:
case PLUGIN_NAME:
return version.getName();
case TITLE:
return version.getTitle();
case VERSION:
return version.getVersion();
}
}
}
return null;
}
public int compare(Viewer viewer, Object e1, Object e2) {
if (viewer instanceof TreeViewer) {
Tree tree = ((TreeViewer) viewer).getTree();
if (e1 instanceof ITreeElement && e2 instanceof ITreeElement) {
ITreeElement treeElement1 = (ITreeElement) e1;
ITreeElement treeElement2 = (ITreeElement) e2;
PluginVersion pluginVersion1 = treeElement1
.getVersionModel();
PluginVersion pluginVersion2 = treeElement2
.getVersionModel();
PluginState state1 = treeElement1.getPluginState();
PluginState state2 = treeElement2.getPluginState();
// These types always are compared with each other, not with
// any other type
if (e1 instanceof RootTreeElement
&& e2 instanceof RootTreeElement) {
int sortOrder = -1;
int sortDirection = tree.getSortDirection();
// Regardless of the sort column, always place in place
// plugins at top
boolean isInPlaceElement1 = treeElement1
.getVersionModel().getParent().isInPlace();
boolean isInPlaceElement2 = treeElement2
.getVersionModel().getParent().isInPlace();
if (isInPlaceElement1) {
if (isInPlaceElement2) {
// if both are inplace, sort alphabetically
return super.compare(viewer, e1, e2);
} else {
// the first inplace has higher priority
sortOrder = -1;
}
} else if (isInPlaceElement2) {
// the second inplace has higher priority
sortOrder = 1;
} else {
// neither are in-place therefore sort based on the
// type of column that
// is selected
TreeColumn sortColumn = tree.getSortColumn();
// Don't sort if there is no sort column specified
if (sortColumn == null) {
return 0;
}
PluginColumnType type = getColumnType(sortColumn
.getText());
// If the sort column is the state column, sort by
// state
if (type == PluginColumnType.PLUGIN_STATE) {
if (state1 != null) {
// Compare the two non-null states first
if (state2 != null) {
// Use the state definition to determine
// which comes first
int stateComparison = state1.ordinal()
- state2.ordinal();
if (stateComparison == 0) {
// if sorting by plugin state, and
// two equal states are being
// compared
// sort plugins with the same state
// by
// name
return super
.compare(viewer, e1, e2);
} else {
sortOrder = stateComparison;
}
} else {
// state 1 has higher order
sortOrder = -1;
}
} else if (state2 != null) {
// state 2 has higher order
sortOrder = 1;
} else {
// both states are null so use some other
// criteria to determine sorting order
return super.compare(viewer, e1, e2);
}
} else {
// if other columns sort alphabetically
return super.compare(viewer, e1, e2);
}
}
return sortDirection == SWT.UP ? sortOrder : -sortOrder;
} else if (e1 instanceof VersionTreeElement
&& e2 instanceof VersionTreeElement) {
// These types always are compared with each other, not
// with
// any other type
// Versions are sorted by version number regardless of
// sort column
String versionID1 = pluginVersion1.getVersion();
String versionID2 = pluginVersion2.getVersion();
if (versionID1 != null) {
if (versionID2 != null) {
// Highest version has priority
// thus the reason for the reversed comparison
return versionID2.compareTo(versionID1);
} else {
return -1;
}
} else if (versionID2 != null) {
return 1;
}
}
}
}
return super.compare(viewer, e1, e2);
}
}
/**
* Represents the top level root tree element in the tree viewer. The root
* element represents the particular version of that plugin that has been
* marked for selection, or is available for install. A tree element
* therefore ALWAYS holds a reference to a particular plugin version model
* entity, AND has an associated tree child version element, whose state is
* reflected by the root element state (example, if a child version "1.3" of
* a particular plugin is marked for uninstall, the root element of for that
* plugin will display a "1.3" version and will also be marked with the same
* uninstall icon.
* <p>
* The purpose of the root element is to indicate to the user what current
* version of a plugin is selected or installed or available for install ,
* and what the state of that version is.
* </p>
* In all cases, the root element version must match a corresponding version
* element. In most cases, the root element state matches the corresponding
* version element state, except in the case of a root element marked as
* having updates available. In this latter case the corresponding child is
* marked with an Installed state while the root is marked with an
* "Update available" state
*
* @author nisingh
*
*/
public class RootTreeElement extends TreeElement {
private VersionTreeElement associatedVersion;
/**
* A root tree element has two elements associated with it. One is model
* version that is either the latest released version for plugins that
* are not installed, or the currently installed version for plugins
* that are installed. This value is IMMUTABLE for the duration of the
* manager session, and it is never null.
* <p>
* The other is a child version tree element, which reflects the current
* tree element selection the user has made. This value is MUTABLE.
* </p>
*
* @param version
* must not be null.
*/
public RootTreeElement(PluginVersion version) {
super(version);
}
/**
* Each Root tree element is associated with a selected child version
* tree element that reflects the current selection and operation of a
* user. This need not be the lastest version of the plugin, but rather
* the current selection based on a particular operation. For example,
* if a user selected to update to a newer version, or downgrade to an
* older version, the root element will reflect this operation, and the
* associated version is therefore the newer or older version of the
* plugin that the user selected for upgrade or downgrade, respectively.
*
* <p>
* Note that this is a tree element, NOT a model element. This value is
* MUTABLE.
* </p>
*
* @param associatedVersion
*/
public void setSelectedVersionTreeElement(
VersionTreeElement associatedVersion) {
this.associatedVersion = associatedVersion;
}
/**
* Each Root tree element is associated with a selected child version
* tree element that reflects the current selection and operation of a
* user. This need not be the lastest version of the plugin, but rather
* the current selection based on a particular operation. For example,
* if a user selected to update to a newer version, or downgrade to an
* older version, the root element will reflect this operation, and the
* associated version is therefore the newer or older version of the
* plugin that the user selected for upgrade or downgrade, respectively.
*
* <p>
* Note that this is a tree element, NOT a model element. This value is
* MUTABLE.
* </p>
*
* @return associatedVersion
*/
public VersionTreeElement getSelectedVersionTreeElement() {
return associatedVersion;
}
/**
*
* This is a modified version of getVersionModel. For root elements, the
* associated model element is either:
* <p>
* 1. The original model element that was passed when the root element
* was created
* </p>
* <p>
* 2. The model element associated with the current selection, IF a
* current selection exists
* </p>
*
* The current selection has priority over the original model version.
* (non-Javadoc)
*
* @see org.springsource.ide.eclipse.commons.frameworks.ui.internal.plugins.AbstractTreeElement
* #getVersionModel()
*/
public PluginVersion getVersionModel() {
return getSelectedVersionTreeElement() != null ? getSelectedVersionTreeElement()
.getVersionModel() : super.getVersionModel();
}
protected boolean canHandleInstall(PluginState state) {
// If it is in uninstall state, then install operation can undo the
// uninstall. If the state is null, it means the plugin can be
// installed
if (state == PluginState.SELECT_UNINSTALL || state == null) {
return true;
}
return false;
}
protected boolean canHandleUninstall(PluginState state) {
// Cannot uninstall something that is already marked for uninstall
// OR something that was never installed in the first place
if (state == PluginState.SELECT_UNINSTALL || state == null) {
return false;
}
return true;
}
protected boolean canHandleUpdate(PluginState currentState) {
// can't update something that is not currently installed or
// that is already installed to the latest version
if (currentState == null || currentState == PluginState.INSTALLED) {
return false;
} else if (currentState == PluginState.UPDATE_AVAILABLE) {
return true;
} else {
// Note that the current state of the root may be in a
// delta-change
// state, therefore to verify if an update can be performed
// the ORIGINAL state of the plugin must be checked
PluginState originalState = getOriginalStateOfRootPlugin(this
.getVersionModel());
// Can ONLY enable the update functionality IF the original
// state was Update available AND the user hasn't yet updated
// to the latest version. It is possible that a user manually
// selected an older version to upgrade, which changes the root
// state to "Select Install", but this should still enable the
// update button on the root to automatically select the latest
// version
if (originalState == PluginState.UPDATE_AVAILABLE) {
if (currentState == PluginState.SELECT_INSTALL) {
// if the latest version as stated by the model matches
// the
// child tree element version
// associated with the root, then the root has been
// updated
// already
PluginVersion latestVersion = getVersionModel()
.getParent().getLatestReleasedVersion();
PluginVersion associatedModelVersion = getSelectedVersionTreeElement()
.getVersionModel();
if (Plugin.isVersionHigher(latestVersion,
associatedModelVersion)) {
return true;
}
} else if (currentState == PluginState.SELECT_UNINSTALL) {
// Allow users to jump from SELECT UINSTALL state
// straight
// to an updated state
return true;
}
}
return false;
}
}
protected void handleInstall(PluginState state) {
// If it is in an select uninstall state, install operation can undo
// the uninstall
if (state == PluginState.SELECT_UNINSTALL) {
restoreOriginalState(this);
} else if (state == null) {
VersionTreeElement associatedVersion = getSelectedVersionTreeElement();
installVersion(associatedVersion);
}
}
protected void handleUninstall(PluginState state) {
uninstallRoot(this);
}
protected void handleUpdate(PluginState state) {
updateRootElement(this);
}
}
/**
* Further specialisation of a tree element that refreshes the viewer when
* an operation completes.
*
* @author nisingh
*
*/
public abstract class TreeElement extends AbstractTreeElement {
public TreeElement(PluginVersion version) {
super(version);
}
public void performOperation(PluginOperation type) {
super.performOperation(type);
refreshViewer(true);
}
}
/**
* A tree version element represents a particular version of a plugin.
*
* @author nisingh
*
*/
public class VersionTreeElement extends TreeElement {
private RootTreeElement parent;
public VersionTreeElement(PluginVersion version, RootTreeElement parent) {
super(version);
this.parent = parent;
}
public RootTreeElement getParent() {
return this.parent;
}
protected boolean canHandleInstall(PluginState state) {
// If the state is null, install operation can only
// be performed if the root state is also null,
// meaning that the plugin is not installed
if (state == null) {
PluginState rootState = getParent().getPluginState();
if (rootState == null) {
return true;
} else {
// it may be in a delta-change state, so determing
// the ORIGINAL state of the plugin
PluginState originalState = getOriginalStateOfRootPlugin(this
.getVersionModel());
if (originalState == null) {
return true;
}
}
} else if (state == PluginState.SELECT_UNINSTALL) {
// Install can be used to Undo uninstall
return true;
}
return false;
}
protected boolean canHandleUninstall(PluginState state) {
if (state == PluginState.SELECT_INSTALL
|| state == PluginState.INSTALLED) {
return true;
}
return false;
}
protected boolean canHandleUpdate(PluginState state) {
// can only update to something that is not currently marked
if (state == null) {
// can only update if the plugin is marked as installed but
// having updates available
PluginState currentRootState = getParent().getPluginState();
if (currentRootState == PluginState.UPDATE_AVAILABLE
|| currentRootState == PluginState.INSTALLED) {
return true;
} else {
PluginState originalPluginState = getOriginalStateOfRootPlugin(this
.getVersionModel());
if (originalPluginState == PluginState.UPDATE_AVAILABLE
|| originalPluginState == PluginState.INSTALLED) {
return true;
}
}
}
return false;
}
protected void handleInstall(PluginState state) {
RootTreeElement rootElement = getParent();
// Undo uninstall
if (state == PluginState.SELECT_UNINSTALL) {
restoreOriginalState(rootElement);
} else {
installVersion(this);
}
}
protected void handleUninstall(PluginState state) {
if (state == PluginState.INSTALLED) {
uninstallVersion(this);
} else {
// Its delta selected, so restore
RootTreeElement root = getParent();
restoreOriginalState(root);
}
}
protected void handleUpdate(PluginState state) {
installVersion(this);
}
}
protected class GrailsViewerContentProvider implements
ITreePathContentProvider {
public Object[] getElements(Object inputElement) {
if (inputElement instanceof Collection<?>) {
List<Object> publishedPlugins = new ArrayList<Object>();
Collection<?> topLevel = (Collection<?>) inputElement;
for (Object possibleTreeElement : topLevel) {
if (possibleTreeElement instanceof RootTreeElement) {
publishedPlugins.add(possibleTreeElement);
}
}
return publishedPlugins.toArray();
}
return null;
}
public Object[] getChildren(TreePath path) {
Object lastElement = path.getLastSegment();
if (lastElement instanceof RootTreeElement) {
RootTreeElement treeElement = (RootTreeElement) lastElement;
// One of the children will be the latest
// version, so no need to add the
// top level elements.
PluginVersion topLevelPlugin = treeElement.getVersionModel();
List<PluginVersion> versions = topLevelPlugin.getParent()
.getVersions();
if (versions != null) {
List<Object> children = new ArrayList<Object>();
for (PluginVersion version : versions) {
VersionTreeElement versionTreeElement = getVersionElement(version);
if (versionTreeElement != null) {
children.add(versionTreeElement);
}
}
return children.toArray();
}
}
return null;
}
public TreePath[] getParents(Object element) {
if (element instanceof VersionTreeElement) {
VersionTreeElement pluginElement = (VersionTreeElement) element;
RootTreeElement parentElementElement = pluginElement
.getParent();
if (parentElementElement != null) {
TreePath path = new TreePath(
new Object[] { parentElementElement });
return new TreePath[] { path };
}
}
return new TreePath[] {};
}
public boolean hasChildren(TreePath path) {
return getChildren(path) != null;
}
public void dispose() {
// nothing for now
}
public void inputChanged(Viewer viewer, Object e1, Object e2) {
// nothing for now
}
}
/**
* Never null. Returns non-null list of selected elements. May be empty
*
* @return non-null list of selected elements. May be empty
*/
protected List<ITreeElement> getSelectedTreeElements() {
IStructuredSelection selection = (IStructuredSelection) pluginViewer
.getTreeViewer().getSelection();
List<ITreeElement> selectedElements = new ArrayList<ITreeElement>();
if (selection != null) {
for (Iterator<?> it = selection.iterator(); it.hasNext();) {
Object selectedObj = it.next();
if (selectedObj instanceof ITreeElement) {
selectedElements.add((ITreeElement) selectedObj);
}
}
}
return selectedElements;
}
protected class PluginSelectionButtonListener extends SelectionAdapter {
public void widgetSelected(SelectionEvent e) {
Widget widget = e.widget;
if (widget instanceof Button) {
Object dataObj = ((Button) widget).getData();
if (dataObj instanceof PluginOperation) {
PluginOperation type = (PluginOperation) dataObj;
// Handle non-selection based operations first
switch (type) {
case REFRESH:
resynchPluginsList(true);
break;
case UPDATE_ALL:
handleUpdateAll();
break;
case RESET:
setViewerInput();
break;
case COLLAPSE_ALL:
collapseAll();
break;
default:
handleSelectionBasedOperations(type);
}
}
}
}
}
/**
* Handles button operations that require a selection, like Install,
* Uninstall, and Update.
*
* @param type
*/
protected void handleSelectionBasedOperations(PluginOperation type) {
if (type == null) {
return;
}
List<ITreeElement> selectedElements = getSelectedTreeElements();
for (ITreeElement element : selectedElements) {
if (element.isValidOperation(type)) {
element.performOperation(type);
}
}
// Once operation performed, refresh the button states
validateOperationButtons();
}
protected void collapseAll() {
pluginViewer.getTreeViewer().collapseAll();
}
protected class ViewerFilterButtonListener extends SelectionAdapter {
public void widgetSelected(SelectionEvent e) {
Widget widget = e.widget;
if (widget instanceof Button) {
Button button = (Button) widget;
if (button.getSelection()) {
Object dataObj = button.getData();
if (dataObj instanceof GrailsFilterType) {
pluginViewer.refreshFilter((GrailsFilterType) dataObj);
}
}
}
}
}
/**
* Filters the tree viewer content based on a filter selection.
*
* @author nisingh
*
*/
protected class PluginStatusFilter extends ViewerFilter {
private GrailsFilterType option = GrailsFilterType.SHOW_ALL;
public void setFilterType(GrailsFilterType option) {
this.option = option;
}
public boolean select(Viewer viewer, Object parentElement,
Object element) {
// Only filter by RootElement. Always show all child version
// elements.
if (element instanceof RootTreeElement) {
RootTreeElement treeElement = (RootTreeElement) element;
PluginState state = treeElement.getPluginState();
// Only filter out top-level items
if (option != null) {
switch (option) {
case SHOW_AVAILABLE_UPDATES_ONLY:
if (state != PluginState.UPDATE_AVAILABLE) {
return hasUpdate(treeElement.getVersionModel());
}
break;
case SHOW_CHANGES_ONLY:
if (state != PluginState.SELECT_INSTALL
&& state != PluginState.SELECT_UNINSTALL) {
return false;
}
break;
case SHOW_INSTALLED_ONLY:
if (state == null) {
return false;
} else {
return treeElement.getVersionModel().getParent()
.isInstalled();
}
}
}
}
return true;
}
}
protected class InternalMessageDialogue extends MessageDialog {
private boolean closeManager;
public InternalMessageDialogue(String title, String dialogMessage,
int dialogueType, boolean closeManager) {
super(PluginManagerDialog.this.getShell(), title, null,
dialogMessage, dialogueType, new String[] { "OK" }, 0);
this.closeManager = closeManager;
}
public boolean close() {
boolean messageClose = super.close();
if (closeManager) {
PluginManagerDialog.this.cancelPressed();
}
return messageClose;
}
}
}