blob: 41ccee1ed159e10c22caf4e9d11ff5f69db0fc66 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 The Eclipse Foundation and others.
* 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:
* The Eclipse Foundation - initial API and implementation
* Yatta Solutions - bug 432803: public API
*******************************************************************************/
package org.eclipse.epp.internal.mpc.ui.wizards;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.epp.internal.mpc.ui.MarketplaceClientUi;
import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceNodeCatalogItem;
import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceNodeInstallableUnitItem;
import org.eclipse.epp.internal.mpc.ui.operations.FeatureDescriptor;
import org.eclipse.epp.mpc.ui.Operation;
import org.eclipse.equinox.internal.p2.discovery.model.CatalogItem;
import org.eclipse.osgi.util.NLS;
/**
* A model of selected items in the catalog. Provides feature-level selection fidelity, and stores the selected
* operation.
*
* @author David Green
*/
public class SelectionModel {
private final Map<CatalogItem, Operation> itemToOperation = new HashMap<CatalogItem, Operation>();
private List<CatalogItemEntry> entries;
private final InstallProfile installProfile;
public SelectionModel(InstallProfile installProfile) {
this.installProfile = installProfile;
}
/**
* Select the given item with the given operation.
*
* @param item
* the item to select
* @param operation
* the operation to perform. Providing {@link Operation#NONE} removes the selection
* @deprecated use {@link #select(CatalogItem, Operation)} instead
*/
@Deprecated
public void select(CatalogItem item, org.eclipse.epp.internal.mpc.ui.wizards.Operation operation) {
select(item, operation == null ? null : operation.getOperation());
}
/**
* Select the given item with the given operation.
*
* @param item
* the item to select
* @param operation
* the operation to perform. Providing {@link Operation#NONE} removes the selection
*/
public void select(CatalogItem item, Operation operation) {
boolean changed = false;
if (operation == null || Operation.NONE == operation) {
if (itemToOperation.remove(item) != Operation.NONE) {
changed = true;
}
if (entries != null) {
Iterator<CatalogItemEntry> it = entries.iterator();
while (it.hasNext()) {
CatalogItemEntry entry = it.next();
if (entry.getItem().equals(item)) {
it.remove();
}
}
}
} else {
Operation previous = itemToOperation.put(item, operation);
if (previous != operation) {
changed = true;
if (entries != null) {
Iterator<CatalogItemEntry> it = entries.iterator();
while (it.hasNext()) {
CatalogItemEntry entry = it.next();
if (entry.getItem().equals(item)) {
it.remove();
}
}
CatalogItemEntry itemEntry = createItemEntry(item, operation);
entries.add(itemEntry);
}
}
}
if (changed) {
selectionChanged();
}
}
public List<CatalogItemEntry> getCatalogItemEntries() {
if (entries == null) {
List<CatalogItemEntry> entries = new ArrayList<CatalogItemEntry>();
for (Entry<CatalogItem, Operation> entry : itemToOperation.entrySet()) {
CatalogItem item = entry.getKey();
Operation operation = entry.getValue();
CatalogItemEntry itemEntry = createItemEntry(item, operation);
entries.add(itemEntry);
}
this.entries = entries;
}
return entries;
}
/**
* @deprecated use {@link #createItemEntry(CatalogItem, Operation)} instead
*/
@Deprecated
public CatalogItemEntry createItemEntry(CatalogItem item,
org.eclipse.epp.internal.mpc.ui.wizards.Operation operation) {
return createItemEntry(item, operation == null ? null : operation.getOperation());
}
public CatalogItemEntry createItemEntry(CatalogItem item, Operation operation) {
CatalogItemEntry itemEntry = new CatalogItemEntry(item, operation);
computeChildren(itemEntry);
return itemEntry;
}
private void computeChildren(CatalogItemEntry itemEntry) {
List<FeatureEntry> children = new ArrayList<FeatureEntry>();
List<MarketplaceNodeInstallableUnitItem> iuItems = ((MarketplaceNodeCatalogItem) itemEntry.getItem()).getInstallableUnitItems();
for (MarketplaceNodeInstallableUnitItem iuItem : iuItems) {
FeatureEntry featureEntry = new FeatureEntry(itemEntry, iuItem);
featureEntry.setInstalled(computeInstalled(featureEntry));
featureEntry.setChecked(computeInitiallyChecked(featureEntry));
children.add(featureEntry);
}
itemEntry.children = children;
}
private Boolean computeInitiallyChecked(FeatureEntry featureEntry) {
CatalogItemEntry parent = featureEntry.getParent();
Operation selectedOperation = parent.getSelectedOperation();
switch (selectedOperation) {
case INSTALL:
if (!featureEntry.isInstalled()) {
return featureEntry.isRequiredInstall() || featureEntry.getInstallableUnitItem().isDefaultSelected();
}
return featureEntry.hasUpdateAvailable();
case UNINSTALL:
return featureEntry.isInstalled();
case UPDATE:
return featureEntry.hasUpdateAvailable() || featureEntry.isRequiredInstall();
case CHANGE:
return featureEntry.isInstalled();
}
return false;
}
private boolean computeInstalled(FeatureEntry entry) {
Set<String> installedFeatures = installProfile.getInstalledFeatures();
return installedFeatures.contains(entry.featureDescriptor.getId())
|| installedFeatures.contains(entry.featureDescriptor.getSimpleId());
}
public static class CatalogItemEntry {
private final CatalogItem item;
private final Operation operation;
private List<FeatureEntry> children;
private CatalogItemEntry(CatalogItem item, Operation operation) {
this.item = item;
this.operation = operation;
}
public CatalogItem getItem() {
return item;
}
/**
* @deprecated use {@link #getSelectedOperation()}
*/
@Deprecated
public org.eclipse.epp.internal.mpc.ui.wizards.Operation getOperation() {
return org.eclipse.epp.internal.mpc.ui.wizards.Operation.map(operation);
}
public Operation getSelectedOperation() {
return operation;
}
public List<FeatureEntry> getChildren() {
return children;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((item == null) ? 0 : item.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CatalogItemEntry other = (CatalogItemEntry) obj;
if (item == null) {
if (other.item != null) {
return false;
}
} else if (!item.equals(other.item)) {
return false;
}
return true;
}
}
public class FeatureEntry {
private final CatalogItemEntry parent;
private final MarketplaceNodeInstallableUnitItem installableUnitItem;
private FeatureDescriptor featureDescriptor;
private Boolean checked;
private FeatureEntry(CatalogItemEntry parent, MarketplaceNodeInstallableUnitItem installableUnitItem) {
this(parent, installableUnitItem, new FeatureDescriptor(installableUnitItem.getId()));
}
private FeatureEntry(CatalogItemEntry parent, MarketplaceNodeInstallableUnitItem installableUnitItem,
FeatureDescriptor featureDescriptor) {
super();
this.parent = parent;
this.installableUnitItem = installableUnitItem;
this.featureDescriptor = featureDescriptor;
}
public FeatureDescriptor getFeatureDescriptor() {
return featureDescriptor;
}
public void setFeatureDescriptor(FeatureDescriptor featureDescriptor) {
if (featureDescriptor != null && this.featureDescriptor != null
&& !this.featureDescriptor.getId().equals(featureDescriptor.getId())) {
throw new IllegalStateException();
}
this.featureDescriptor = featureDescriptor;
}
public MarketplaceNodeInstallableUnitItem getInstallableUnitItem() {
return installableUnitItem;
}
public boolean isOptional() {
return getInstallableUnitItem().isOptional();
}
public boolean isInstalled() {
return Boolean.TRUE.equals(getInstallableUnitItem().getInstalled());
}
public void setInstalled(boolean installed) {
getInstallableUnitItem().setInstalled(installed);
}
public boolean hasUpdateAvailable() {
return isInstalled() && Boolean.TRUE.equals(getInstallableUnitItem().getUpdateAvailable());
}
public boolean isRequiredInstall() {
return !isInstalled() && !getInstallableUnitItem().isOptional();
}
public CatalogItemEntry getParent() {
return parent;
}
public void setChecked(Boolean checked) {
this.checked = checked;
}
public boolean isChecked() {
return Boolean.TRUE.equals(this.checked);
}
public boolean isGrayed() {
return this.checked == null;
}
public void setGrayed() {
setChecked(null);
}
public Operation computeChangeOperation() {
return isGrayed() ? Operation.NONE : computeChangeOperation(isChecked());
}
public Operation computeChangeOperation(boolean checked) {
CatalogItemEntry parent = getParent();
switch (parent.getSelectedOperation()) {
case INSTALL:
case UPDATE:
if (checked) {
if (hasUpdateAvailable()) {
return Operation.UPDATE;
} else if (isRequiredInstall() && !isInstalled()) {
return Operation.INSTALL;
}
if (!isInstalled()) {
return Operation.INSTALL;
}
}
return Operation.NONE;
case UNINSTALL:
if (checked && isInstalled()) {
return Operation.UNINSTALL;
}
return Operation.NONE;
case CHANGE:
if (checked) {
if (isInstalled()) {
if (hasUpdateAvailable()) {
return Operation.UPDATE;
}
return Operation.NONE;
} else {
return Operation.INSTALL;
}
} else {
if (isInstalled()) {
return Operation.UNINSTALL;
} else {
return Operation.NONE;
}
}
default:
return Operation.NONE;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((featureDescriptor == null) ? 0 : featureDescriptor.hashCode());
result = prime * result + ((parent == null) ? 0 : parent.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
FeatureEntry other = (FeatureEntry) obj;
if (featureDescriptor == null) {
if (other.featureDescriptor != null) {
return false;
}
} else if (!featureDescriptor.equals(other.featureDescriptor)) {
return false;
}
if (parent == null) {
if (other.parent != null) {
return false;
}
} else if (!parent.equals(other.parent)) {
return false;
}
return true;
}
}
/**
* @deprecated use {@link #getItemToSelectedOperation()} instead
*/
@Deprecated
public Map<CatalogItem, org.eclipse.epp.internal.mpc.ui.wizards.Operation> getItemToOperation() {
Map<CatalogItem, org.eclipse.epp.internal.mpc.ui.wizards.Operation> itemToOperation = new HashMap<CatalogItem, org.eclipse.epp.internal.mpc.ui.wizards.Operation>();
Set<Entry<CatalogItem, Operation>> entrySet = this.itemToOperation.entrySet();
for (Entry<CatalogItem, Operation> entry : entrySet) {
itemToOperation.put(entry.getKey(), org.eclipse.epp.internal.mpc.ui.wizards.Operation.map(entry.getValue()));
}
return itemToOperation;
}
public Map<CatalogItem, Operation> getItemToSelectedOperation() {
return Collections.unmodifiableMap(itemToOperation);
}
public void selectionChanged() {
// ignore
}
public Map<FeatureEntry, Operation> getFeatureEntryToOperation(boolean includeNone, boolean verify) {
Map<FeatureEntry, Operation> featureEntries = new HashMap<FeatureEntry, Operation>();
for (CatalogItemEntry entry : getCatalogItemEntries()) {
for (FeatureEntry featureEntry : entry.getChildren()) {
Operation operation = featureEntry.computeChangeOperation();
if (operation != null && (includeNone || operation != Operation.NONE)) {
Operation old = featureEntries.put(featureEntry, operation);
if (old != null && old != Operation.NONE) {
if (operation == Operation.NONE) {
featureEntries.put(featureEntry, old);
old = null;
} else if (verify && !old.equals(operation)) {
IStatus error = new Status(IStatus.ERROR, MarketplaceClientUi.BUNDLE_ID, NLS.bind(
Messages.SelectionModel_Inconsistent_Actions, new Object[] {
featureEntry.getFeatureDescriptor().getName(), old, operation }));
throw new IllegalStateException(new CoreException(error));
}
}
}
}
}
return Collections.unmodifiableMap(featureEntries);
}
public Set<FeatureEntry> getSelectedFeatureEntries() {
return getFeatureEntryToOperation(false, false).keySet();
}
/**
* @deprecated use {@link #getSelectedFeatureEntries()} instead
* @return the descriptors for all selected features
*/
@Deprecated
public Set<FeatureDescriptor> getSelectedFeatureDescriptors() {
Set<FeatureDescriptor> featureDescriptors = new HashSet<FeatureDescriptor>();
Set<FeatureEntry> selectedFeatureEntries = getSelectedFeatureEntries();
for (FeatureEntry featureEntry : selectedFeatureEntries) {
featureDescriptors.add(featureEntry.getFeatureDescriptor());
}
return Collections.unmodifiableSet(featureDescriptors);
}
/**
* Get all catalog items that have at least one feature selected
*/
public Set<CatalogItem> getSelectedCatalogItems() {
Set<CatalogItem> items = new HashSet<CatalogItem>();
for (CatalogItemEntry entry : getCatalogItemEntries()) {
if (entry.getSelectedOperation() == Operation.NONE) {
continue;
}
for (FeatureEntry featureEntry : entry.getChildren()) {
Operation operation = featureEntry.computeChangeOperation();
if (operation != null && operation != Operation.NONE) {
items.add(entry.item);
break;
}
}
if (entry.getSelectedOperation() == Operation.CHANGE) {
items.add(entry.item);
}
}
return Collections.unmodifiableSet(items);
}
/**
* @deprecated use {@link #getSelectedOperation(CatalogItem)} instead
*/
@Deprecated
public org.eclipse.epp.internal.mpc.ui.wizards.Operation getOperation(CatalogItem item) {
Operation operation = getSelectedOperation(item);
return org.eclipse.epp.internal.mpc.ui.wizards.Operation.map(operation);
}
public Operation getSelectedOperation(CatalogItem item) {
Operation operation = itemToOperation.get(item);
return operation == null ? Operation.NONE : operation;
}
public boolean computeProvisioningOperationViableForFeatureSelection() {
IStatus status = computeFeatureOperationViability();
if (status == null) {
//no operation
//this is okay for a CHANGE
Map<Operation, List<CatalogItem>> operationToItem = computeOperationToItem();
if (operationToItem.size() == 1 && operationToItem.containsKey(Operation.CHANGE)) {
return true;
}
return false;
}
return computeProvisioningOperationViable();
}
public boolean computeProvisioningOperationViable() {
IStatus status = computeProvisioningOperationViability();
if (status != null) {
switch (status.getSeverity()) {
case IStatus.INFO:
case IStatus.OK:
case IStatus.WARNING:
return true;
}
return false;
}
return false;//no operations
}
/**
* Determine what message related to finishing the wizard should correspond to the current selection.
*
* @return the message, or null if there should be no message.
*/
public IStatus computeProvisioningOperationViability() {
IStatus featureStatus = computeFeatureOperationViability();
if (featureStatus == null || !featureStatus.isOK()) {
return featureStatus;
}
Map<Operation, List<CatalogItem>> operationToItem = computeOperationToItem();
if (operationToItem.size() == 0) {
return new Status(IStatus.ERROR, MarketplaceClientUi.BUNDLE_ID, Messages.SelectionModel_Nothing_Selected);
} else if (operationToItem.size() == 1) {
Entry<Operation, List<CatalogItem>> entry = operationToItem.entrySet().iterator().next();
return new Status(IStatus.INFO, MarketplaceClientUi.BUNDLE_ID,
NLS.bind(
Messages.SelectionModel_count_selectedFor_operation,
entry.getValue().size() == 1 ? Messages.SelectionModel_oneSolution : NLS.bind(
Messages.SelectionModel_countSolutions, entry.getValue().size()), entry.getKey()
.getLabel()));
} else if (operationToItem.size() == 2 && operationToItem.containsKey(Operation.INSTALL)
&& operationToItem.containsKey(Operation.UPDATE)) {
int count = 0;
for (List<CatalogItem> items : operationToItem.values()) {
count += items.size();
}
return new Status(IStatus.INFO, MarketplaceClientUi.BUNDLE_ID, NLS.bind(
Messages.SelectionModel_countSolutionsSelectedForInstallUpdate, count));
} else {
return new Status(IStatus.ERROR, MarketplaceClientUi.BUNDLE_ID,
Messages.SelectionModel_cannotInstallRemoveConcurrently);
}
}
private IStatus computeFeatureOperationViability() {
Map<FeatureEntry, Operation> selectedFeatureEntries;
try {
selectedFeatureEntries = getFeatureEntryToOperation(false, true);
} catch (IllegalStateException ex) {
CoreException cause = (CoreException) ex.getCause();
return cause.getStatus();
}
if (selectedFeatureEntries.isEmpty()) {
return null;
}
return Status.OK_STATUS;
}
private Map<Operation, List<CatalogItem>> computeOperationToItem() {
Map<CatalogItem, Operation> itemToOperation = getItemToSelectedOperation();
Map<Operation, List<CatalogItem>> catalogItemByOperation = new HashMap<Operation, List<CatalogItem>>();
for (Map.Entry<CatalogItem, Operation> entry : itemToOperation.entrySet()) {
if (entry.getValue() == Operation.NONE) {
continue;
}
List<CatalogItem> list = catalogItemByOperation.get(entry.getValue());
if (list == null) {
list = new ArrayList<CatalogItem>();
catalogItemByOperation.put(entry.getValue(), list);
}
list.add(entry.getKey());
}
return catalogItemByOperation;
}
public void clear() {
itemToOperation.clear();
entries = null;
}
}