org.eclipse.oomph.p2.internal.ui.RepositoryExplorer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.oomph.p2.internal.ui.RepositoryExplorer.java

Source

/*
 * Copyright (c) 2014, 2015 Eike Stepper (Berlin, Germany) 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:
 *    Eike Stepper - initial API and implementation
 */
package org.eclipse.oomph.p2.internal.ui;

import org.eclipse.oomph.internal.ui.GeneralDragAdapter;
import org.eclipse.oomph.internal.ui.GeneralDropAdapter;
import org.eclipse.oomph.internal.ui.GeneralDropAdapter.DroppedObjectHandler;
import org.eclipse.oomph.internal.ui.OomphTransferDelegate;
import org.eclipse.oomph.p2.P2Exception;
import org.eclipse.oomph.p2.P2Factory;
import org.eclipse.oomph.p2.P2Package;
import org.eclipse.oomph.p2.Repository;
import org.eclipse.oomph.p2.Requirement;
import org.eclipse.oomph.p2.VersionSegment;
import org.eclipse.oomph.p2.core.P2Util;
import org.eclipse.oomph.p2.core.RepositoryProvider;
import org.eclipse.oomph.p2.impl.RequirementImpl;
import org.eclipse.oomph.p2.internal.ui.RepositoryManager.RepositoryManagerListener;
import org.eclipse.oomph.p2.provider.RequirementItemProvider;
import org.eclipse.oomph.ui.SearchField;
import org.eclipse.oomph.ui.SearchField.FilterHandler;
import org.eclipse.oomph.ui.UIUtil;
import org.eclipse.oomph.util.CollectionUtil;
import org.eclipse.oomph.util.ObjectUtil;
import org.eclipse.oomph.util.StringUtil;

import org.eclipse.emf.ecore.EObject;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IProvidedCapability;
import org.eclipse.equinox.p2.metadata.IRequirement;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.equinox.p2.metadata.expression.IMatchExpression;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.forms.FormColors;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.part.ViewPart;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Eike Stepper
 */
public class RepositoryExplorer extends ViewPart implements FilterHandler {
    public static final String ID = "org.eclipse.oomph.p2.ui.RepositoryExplorer"; //$NON-NLS-1$

    private static final IDialogSettings SETTINGS = P2UIPlugin.INSTANCE
            .getDialogSettings(RepositoryExplorer.class.getSimpleName());

    private static final int DND_OPERATIONS = DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK;

    private static final Transfer[] DND_TRANSFERS = OomphTransferDelegate
            .asTransfers(org.eclipse.oomph.internal.ui.OomphTransferDelegate.DELEGATES)
            .toArray(new Transfer[OomphTransferDelegate
                    .asTransfers(org.eclipse.oomph.internal.ui.OomphTransferDelegate.DELEGATES).size()]);

    private static final String DEFAULT_CAPABILITY_NAMESPACE = IInstallableUnit.NAMESPACE_IU_ID;

    private static final String CURRENT_NAMESPACE_KEY = "currentNamespace";

    private static final String EXPERT_MODE_KEY = "expertMode";

    private static final String CATEGORIZE_ITEMS_KEY = "categorizeItems";

    private static final String VERSION_SEGMENT_KEY = "versionSegment";

    private static final String COMPATIBLE_VERSION_KEY = "compatibleVersion";

    private static final String SOURCE_SUFFIX = ".source";

    private static final String SOURCE_FEATURE_SUFFIX = SOURCE_SUFFIX + Requirement.FEATURE_SUFFIX;

    private static final Object[] NO_ELEMENTS = new Object[0];

    private final LoadJob loadJob = new LoadJob();

    private final AnalyzeJob analyzeJob = new AnalyzeJob();

    private final Mode categoriesMode = new CategoriesMode();

    private final Mode featuresMode = new FeaturesMode();

    private final Mode capabilitiesMode = new CapabilitiesMode();

    private final RepositoryFocusListener repositoryFocusListener = new RepositoryFocusListener();

    private final RepositoryHistoryListener repositoryHistoryListener = new RepositoryHistoryListener();

    private final VersionProvider versionProvider = new VersionProvider();

    private final CollapseAllAction collapseAllAction = new CollapseAllAction();

    private final SearchAction searchAction = new SearchAction();

    private Composite container;

    private ComboViewer repositoryViewer;

    private CCombo repositoryCombo;

    private RepositoryProvider.Metadata repositoryProvider;

    private Composite selectorComposite;

    private Composite itemsComposite;

    private StructuredViewer itemsViewer;

    private CategoryItem itemsViewerInput;

    private TableViewer versionsViewer;

    private String currentNamespace;

    private boolean expertMode;

    private boolean categorizeItems;

    private boolean compatibleVersion;

    private Mode mode;

    private IQueryResult<IInstallableUnit> installableUnits;

    private String filter;

    private FormToolkit formToolkit;

    public RepositoryExplorer() {
        currentNamespace = SETTINGS.get(CURRENT_NAMESPACE_KEY);
        if (currentNamespace == null) {
            currentNamespace = DEFAULT_CAPABILITY_NAMESPACE;
        }

        expertMode = SETTINGS.getBoolean(EXPERT_MODE_KEY);

        String value = SETTINGS.get(CATEGORIZE_ITEMS_KEY);
        if (value == null || value.length() == 0) {
            categorizeItems = true;
        } else {
            categorizeItems = "true".equals(value);
        }

        compatibleVersion = SETTINGS.getBoolean(COMPATIBLE_VERSION_KEY);
    }

    @Override
    public void dispose() {
        if (formToolkit != null) {
            formToolkit.dispose();
        }

        disposeRepositoryProvider();
        super.dispose();
    }

    private void disposeRepositoryProvider() {
        if (repositoryProvider != null) {
            repositoryProvider.dispose();
            repositoryProvider = null;
        }
    }

    @Override
    public void setFocus() {
        if (RepositoryManager.INSTANCE.getActiveRepository() != null) {
            repositoryCombo.setFocus();
        }
    }

    private void updateMode() {
        Mode mode = expertMode ? capabilitiesMode : categorizeItems ? categoriesMode : featuresMode;
        if (this.mode != mode) {
            this.mode = mode;

            GridLayout selectorLayout = new GridLayout();
            selectorLayout.marginWidth = 0;
            selectorLayout.marginHeight = 0;

            selectorComposite.setLayout(selectorLayout);
            mode.fillSelector(selectorComposite);
            selectorComposite.layout();
            selectorComposite.getParent().layout();

            mode.fillItems(itemsComposite);
            itemsComposite.layout();

            itemsViewer.addSelectionChangedListener(new ISelectionChangedListener() {
                public void selectionChanged(SelectionChangedEvent event) {
                    IStructuredSelection selection = (IStructuredSelection) itemsViewer.getSelection();
                    if (selection.size() == 1) {
                        versionsViewer.setInput(selection.getFirstElement());
                    } else {
                        versionsViewer.setInput(null);
                    }
                }
            });

            analyzeJob.reschedule();
            collapseAllAction.updateEnablement();
        }
    }

    private void setItems(Item... items) {
        if (!container.isDisposed()) {
            versionsViewer.setInput(null);

            itemsViewerInput = new CategoryItem();
            itemsViewerInput.setChildren(items);
            itemsViewer.setInput(itemsViewerInput);

            if (itemsViewer instanceof TreeViewer && filter != null) {
                TreeViewer treeViewer = (TreeViewer) itemsViewer;
                treeViewer.expandAll();
            }
        }
    }

    private boolean isFiltered(String string) {
        return filter == null || string == null || string.toLowerCase().contains(filter);
    }

    public void handleFilter(String filter) {
        if (filter == null || filter.length() == 0) {
            this.filter = null;
        } else {
            this.filter = filter.toLowerCase();
        }

        analyzeJob.reschedule();
    }

    @Override
    public void createPartControl(Composite parent) {
        final Display display = parent.getDisplay();

        formToolkit = new FormToolkit(display);
        FormColors colors = formToolkit.getColors();
        colors.createColor("initial_repository",
                FormColors.blend(colors.getForeground().getRGB(), colors.getBackground().getRGB(), 75));

        Form form = formToolkit.createForm(parent);
        container = form.getBody();
        container.setLayout(new GridLayout(1, false));

        createRepositoriesArea(container);
        createItemsArea(container);
        createVersionsArea(container);

        updateMode();

        String activeRepository = RepositoryManager.INSTANCE.getActiveRepository();
        if (activeRepository == null) {
            // Force hint to be shown.
            repositoryFocusListener.focusLost(null);
        } else {
            repositoryCombo.setText(activeRepository);
            triggerLoad(activeRepository);
        }

        hookActions();
    }

    private static CCombo createCombo(Composite parent, int style, boolean grabExcessHorizontalSpace) {
        CCombo combo = new CCombo(parent, style);
        GridData layoutData = new GridData(SWT.FILL, SWT.FILL, grabExcessHorizontalSpace, false);

        int increaseHeight = 0;
        String ws = Platform.getWS();
        if (Platform.WS_COCOA.equals(ws)) {
            increaseHeight = 7;
        } else if (Platform.WS_GTK.equals(ws)) {
            increaseHeight = 9;
        }

        if (increaseHeight != 0) {
            FontData[] fontData = combo.getFont().getFontData();
            layoutData.heightHint = fontData[0].getHeight() + increaseHeight;
        }

        combo.setLayoutData(layoutData);
        return combo;
    }

    private void createRepositoriesArea(Composite container) {
        repositoryCombo = createCombo(container, SWT.BORDER, true);
        repositoryCombo.setToolTipText(
                "Repository location (type a URL, drop a repository or pick from the drop down history)");
        repositoryCombo.addFocusListener(repositoryFocusListener);
        repositoryCombo.addKeyListener(repositoryHistoryListener);

        repositoryViewer = new ComboViewer(repositoryCombo);
        repositoryViewer.setContentProvider(new RepositoryContentProvider());
        repositoryViewer.setLabelProvider(new LabelProvider());
        repositoryViewer.setInput(RepositoryManager.INSTANCE);
        repositoryViewer.addSelectionChangedListener(repositoryHistoryListener);

        repositoryViewer.addDropSupport(DND_OPERATIONS, DND_TRANSFERS,
                new GeneralDropAdapter(repositoryViewer, P2Factory.eINSTANCE.createRepositoryList(),
                        P2Package.Literals.REPOSITORY_LIST__REPOSITORIES, new DroppedObjectHandler() {
                            public void handleDroppedObject(Object object) throws Exception {
                                if (object instanceof Repository) {
                                    Repository repository = (Repository) object;
                                    String url = repository.getURL();
                                    if (!StringUtil.isEmpty(url)) {
                                        activateAndLoadRepository(url);
                                    }
                                }
                            }
                        }));
    }

    private void createItemsArea(Composite parent) {
        GridLayout containerLayout = new GridLayout(2, false);
        containerLayout.marginWidth = 0;
        containerLayout.marginHeight = 0;

        Composite container = formToolkit.createComposite(parent, SWT.NONE);
        container.setLayout(containerLayout);
        container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        SearchField searchField = new SearchField(container, this) {
            @Override
            protected void finishFilter() {
                itemsViewer.getControl().setFocus();
                selectFirstLeaf(itemsViewerInput);
            }

            private void selectFirstLeaf(CategoryItem category) {
                if (category != null) {
                    Item[] children = category.getChildren();
                    if (children != null && children.length != 0) {
                        Item firstChild = children[0];
                        if (firstChild instanceof CategoryItem) {
                            CategoryItem firstCategory = (CategoryItem) firstChild;
                            selectFirstLeaf(firstCategory);
                        } else {
                            itemsViewer.setSelection(new StructuredSelection(firstChild));
                        }
                    }
                }
            }
        };

        searchField.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));

        selectorComposite = formToolkit.createComposite(container, SWT.NONE);
        selectorComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));

        itemsComposite = formToolkit.createComposite(container, SWT.NONE);
        itemsComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
        itemsComposite.setLayout(new FillLayout());
    }

    private void createVersionsArea(Composite container) {
        Composite versionsComposite = formToolkit.createComposite(container, SWT.NONE);
        GridLayout gl_versionsComposite = new GridLayout(2, false);
        gl_versionsComposite.marginWidth = 0;
        gl_versionsComposite.marginHeight = 0;
        versionsComposite.setLayout(gl_versionsComposite);
        versionsComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        versionsViewer = new TableViewer(versionsComposite, SWT.BORDER);
        versionsViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        versionsViewer.setContentProvider(versionProvider);
        versionsViewer.setLabelProvider(versionProvider);
        addDragSupport(versionsViewer);

        formToolkit.adapt(versionsViewer.getControl(), false, false);

        Composite versionsGroup = formToolkit.createComposite(versionsComposite, SWT.NONE);
        versionsGroup.setLayout(new GridLayout(1, false));
        versionsGroup.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));

        final Button compatibleButton = new Button(versionsGroup, SWT.CHECK);
        compatibleButton.setText("Compatible");
        compatibleButton.setToolTipText("Show compatible versions");
        compatibleButton.setSelection(compatibleVersion);

        final Button majorButton = addVersionSegmentButton(versionsGroup, "Major", "Show major versions",
                VersionSegment.MAJOR);
        final Button minorButton = addVersionSegmentButton(versionsGroup, "Minor", "Show minor versions",
                VersionSegment.MINOR);
        addVersionSegmentButton(versionsGroup, "Micro", "Show micro versions", VersionSegment.MICRO);
        addVersionSegmentButton(versionsGroup, "Qualifier", "Show qualified versions", VersionSegment.QUALIFIER);

        majorButton.setEnabled(!compatibleVersion);
        compatibleButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                boolean compatible = compatibleButton.getSelection();
                if (compatibleVersion != compatible) {
                    compatibleVersion = compatible;
                    SETTINGS.put(COMPATIBLE_VERSION_KEY, compatibleVersion);

                    majorButton.setEnabled(!compatible);

                    if (compatible && versionProvider.getVersionSegment() == VersionSegment.MAJOR) {
                        majorButton.setSelection(false);
                        minorButton.setSelection(true);
                        versionProvider.setVersionSegment(VersionSegment.MINOR);
                    }
                }
            }
        });
    }

    private Button addVersionSegmentButton(Composite parent, String text, String toolTip,
            final VersionSegment versionSegment) {
        Button button = new Button(parent, SWT.RADIO);
        button.setText(text);
        button.setToolTipText(toolTip);
        button.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                versionProvider.setVersionSegment(versionSegment);
            }
        });

        if (versionSegment == versionProvider.getVersionSegment()) {
            button.setSelection(true);
        }

        return button;
    }

    private void hookActions() {
        IActionBars actionBars = getViewSite().getActionBars();

        IToolBarManager toolbarManager = actionBars.getToolBarManager();
        toolbarManager.add(new Separator("additions"));
        toolbarManager.add(collapseAllAction);

        toolbarManager.add(new Action("Refresh", P2UIPlugin.INSTANCE.getImageDescriptor("refresh")) {
            {
                setToolTipText("Reload the active repository and refresh the tree");
            }

            @Override
            public void run() {
                String activeRepository = RepositoryManager.INSTANCE.getActiveRepository();
                if (activeRepository != null) {
                    disposeRepositoryProvider();
                    triggerLoad(activeRepository);
                }
            }
        });

        toolbarManager.add(new Separator("modes"));
        toolbarManager.add(new Action("Expert Mode", IAction.AS_CHECK_BOX) {
            {
                setImageDescriptor(P2UIPlugin.INSTANCE.getImageDescriptor("obj16/capability"));
                setChecked(expertMode);
            }

            @Override
            public void run() {
                expertMode = isChecked();
                SETTINGS.put(EXPERT_MODE_KEY, expertMode);
                updateMode();
            }
        });

        toolbarManager.add(new Separator("search"));
        toolbarManager.add(searchAction);

        toolbarManager.add(new Separator("end"));
    }

    private void activateAndLoadRepository(String repository) {
        if (RepositoryManager.INSTANCE.setActiveRepository(repository)) {
            triggerLoad(repository);
        }
    }

    private void triggerLoad(String repository) {
        try {
            IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
            repository = manager.performStringSubstitution(repository);
        } catch (Exception ex) {
            //$FALL-THROUGH$
        }

        URI location = null;

        try {
            location = new URI(repository);
        } catch (URISyntaxException ex) {
            File folder = new File(repository);
            if (folder.isDirectory()) {
                location = folder.toURI();
            }
        }

        if (location != null) {
            loadJob.reschedule(location);
        }
    }

    private void addDragSupport(StructuredViewer viewer) {
        viewer.addDragSupport(DND_OPERATIONS, DND_TRANSFERS,
                new GeneralDragAdapter(viewer, new GeneralDragAdapter.DraggedObjectsFactory() {
                    public List<EObject> createDraggedObjects(ISelection selection) throws Exception {
                        List<EObject> result = new ArrayList<EObject>();

                        IStructuredSelection ssel = (IStructuredSelection) selection;
                        for (Iterator<?> it = ssel.iterator(); it.hasNext();) {
                            Object element = it.next();

                            VersionRange versionRange = VersionRange.emptyRange;
                            String filter = null;

                            if (element instanceof VersionProvider.ItemVersion) {
                                VersionProvider.ItemVersion itemVersion = (VersionProvider.ItemVersion) element;
                                Version version = itemVersion.getVersion();

                                VersionSegment versionSegment = versionProvider.getVersionSegment();
                                versionRange = P2Factory.eINSTANCE.createVersionRange(version, versionSegment,
                                        compatibleVersion);

                                filter = RequirementImpl.formatMatchExpression(itemVersion.getFilter());

                                element = ((IStructuredSelection) itemsViewer.getSelection()).getFirstElement();
                            }

                            if (element instanceof Item) {
                                Item item = (Item) element;

                                String namespace = item.getNamespace();
                                if (namespace != null) {
                                    if (filter == null && item instanceof VersionedItem) {
                                        VersionedItem versionedItem = (VersionedItem) item;

                                        for (IMatchExpression<IInstallableUnit> matchExpression : versionedItem
                                                .getVersions().values()) {
                                            String string = RequirementImpl.formatMatchExpression(matchExpression);
                                            if (filter == null || filter.equals(string)) {
                                                filter = string;
                                            } else {
                                                filter = null;
                                                break;
                                            }
                                        }
                                    }

                                    Requirement requirement = P2Factory.eINSTANCE.createRequirement();
                                    requirement.setNamespace(namespace);
                                    requirement.setName(item.getName());
                                    requirement.setVersionRange(versionRange);
                                    requirement.setFilter(filter);
                                    result.add(requirement);
                                }
                            }
                        }

                        return result;
                    }
                }));
    }

    private static String[] sortStrings(Collection<String> c) {
        String[] array = c.toArray(new String[c.size()]);
        Arrays.sort(array);
        return array;
    }

    static void minimizeNamespaces(Set<String> flavors, Set<String> namespaces) {
        String[] flavorIDs = getMinimalFlavors(flavors);
        for (Iterator<String> it = namespaces.iterator(); it.hasNext();) {
            String namespace = it.next();
            for (int i = 0; i < flavorIDs.length; i++) {
                String flavor = flavorIDs[i];
                if (namespace.startsWith(flavor)) {
                    it.remove();
                    break;
                }
            }
        }
    }

    private static String[] getMinimalFlavors(final Set<String> flavors) {
        String[] flavorIDs = sortStrings(flavors);
        int start = 0;

        while (start < flavorIDs.length) {
            boolean changed = false;
            for (int i = start + 1; i < flavorIDs.length; i++) {
                String flavorID = flavorIDs[i];
                if (flavorID.startsWith(flavorIDs[start])) {
                    flavors.remove(flavorID);
                    changed = true;
                }
            }

            if (changed) {
                flavorIDs = sortStrings(flavors);
            }

            ++start;
        }

        return flavorIDs;
    }

    private static boolean isCategory(IInstallableUnit iu) {
        return "true".equalsIgnoreCase(iu.getProperty(QueryUtil.PROP_TYPE_CATEGORY));
    }

    private static boolean isFeature(IInstallableUnit iu) {
        return iu.getId().endsWith(Requirement.FEATURE_SUFFIX);
    }

    public static boolean explore(String repository) {
        IWorkbenchWindow window = UIUtil.WORKBENCH.getActiveWorkbenchWindow();
        if (window != null) {
            IWorkbenchPage page = window.getActivePage();
            if (page != null) {
                IViewPart view = page.findView(ID);
                if (view == null) {
                    try {
                        view = page.showView(ID);
                    } catch (PartInitException ex) {
                        P2UIPlugin.INSTANCE.log(ex);
                    }
                }

                if (view instanceof RepositoryExplorer) {
                    RepositoryExplorer explorer = (RepositoryExplorer) view;
                    explorer.activateAndLoadRepository(repository);
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @author Eike Stepper
     */
    private final class CollapseAllAction extends Action {
        public CollapseAllAction() {
            super("Collapse All", P2UIPlugin.INSTANCE.getImageDescriptor("collapse-all"));
            setToolTipText("Collapse all tree items");
            updateEnablement();
        }

        public void updateEnablement() {
            setEnabled(itemsViewer instanceof TreeViewer);
        }

        @Override
        public void run() {
            if (itemsViewer instanceof TreeViewer) {
                TreeViewer treeViewer = (TreeViewer) itemsViewer;
                treeViewer.collapseAll();
            }
        }
    }

    /**
     * @author Ed Merks
     */
    private final class SearchAction extends Action {
        public SearchAction() {
            super("Search", P2UIPlugin.INSTANCE.getImageDescriptor("tool16/search"));
            setToolTipText("Search Eclipse repositories by provided capabilities");
        }

        @Override
        public void run() {
            SearchEclipseRepositoryDialog searchEclipseRepositoryDialog = new SearchEclipseRepositoryDialog(
                    UIUtil.getShell());
            if (searchEclipseRepositoryDialog.open() == Dialog.OK) {
                activateAndLoadRepository(searchEclipseRepositoryDialog.getSelectedRepository());
            }
        }
    }

    /**
     * @author Eike Stepper
     */
    private abstract class SafeJob extends Job {
        public SafeJob(String name) {
            super(name);
        }

        @Override
        protected final IStatus run(IProgressMonitor monitor) {
            try {
                doSafe(monitor);
                return Status.OK_STATUS;
            } catch (OperationCanceledException ex) {
                return Status.CANCEL_STATUS;
            } catch (Exception ex) {
                if (ex instanceof P2Exception) {
                    Throwable cause = ex.getCause();
                    if (cause instanceof CoreException) {
                        ex = (CoreException) cause;
                    }
                }

                final IStatus status = P2UIPlugin.INSTANCE.getStatus(ex);
                UIUtil.asyncExec(new Runnable() {
                    public void run() {
                        setItems(new ErrorItem(status));
                    }
                });

                return Status.OK_STATUS;
            } catch (Throwable t) {
                return P2UIPlugin.INSTANCE.getStatus(t);
            }
        }

        protected abstract void doSafe(IProgressMonitor monitor) throws Throwable;
    }

    /**
     * @author Eike Stepper
     */
    private final class LoadJob extends SafeJob {
        private URI location;

        public LoadJob() {
            super("Loading repository");
        }

        public void reschedule(URI location) {
            this.location = location;
            setItems(new LoadingItem(location));

            cancel();
            schedule();
        }

        @Override
        @SuppressWarnings("restriction")
        protected void doSafe(IProgressMonitor monitor) throws Throwable {
            analyzeJob.cancel();
            installableUnits = null;

            IMetadataRepositoryManager repositoryManager = P2Util.getAgentManager().getCurrentAgent()
                    .getMetadataRepositoryManager();
            if (repositoryProvider == null || !repositoryProvider.getLocation().equals(location)) {
                disposeRepositoryProvider();
                repositoryProvider = new RepositoryProvider.Metadata(repositoryManager, location);
            }

            SubMonitor progress = SubMonitor.convert(monitor, 101);

            IMetadataRepository repository = repositoryProvider.getRepository(progress.newChild(100));

            if (repository instanceof org.eclipse.equinox.internal.p2.metadata.repository.CompositeMetadataRepository) {
                org.eclipse.equinox.internal.p2.metadata.repository.CompositeMetadataRepository compositeRepository = (org.eclipse.equinox.internal.p2.metadata.repository.CompositeMetadataRepository) repository;
                org.eclipse.equinox.internal.p2.persistence.CompositeRepositoryState state = compositeRepository
                        .toState();
                URI[] children = state.getChildren();

                final List<Item> errors = new ArrayList<Item>();
                Set<String> messages = new HashSet<String>();

                for (URI child : children) {
                    try {
                        URI absolute = URIUtil.makeAbsolute(child, location);
                        if (repositoryManager.loadRepository(absolute, null) == null) {
                            throw new ProvisionException("No repository found at " + absolute + ".");
                        }
                    } catch (Exception ex) {
                        IStatus status = P2UIPlugin.INSTANCE.getStatus(ex);
                        if (messages.add(status.getMessage())) {
                            errors.add(new ErrorItem(status));
                        }
                    }
                }

                if (!errors.isEmpty()) {
                    UIUtil.asyncExec(new Runnable() {
                        public void run() {
                            setItems(errors.toArray(new Item[errors.size()]));
                        }
                    });

                    return;
                }
            }

            installableUnits = repository.query(QueryUtil.createIUAnyQuery(), progress.newChild(1));
            analyzeJob.reschedule();
        }
    }

    /**
     * @author Eike Stepper
     */
    private final class AnalyzeJob extends SafeJob {
        public AnalyzeJob() {
            super("Analyzing repository");
        }

        public void reschedule() {
            cancel();

            if (installableUnits != null) {
                schedule();
            }
        }

        @Override
        protected void doSafe(IProgressMonitor monitor) throws Throwable {
            mode.analyzeInstallableUnits(monitor);
        }
    }

    /**
     * @author Eike Stepper
     */
    private abstract class Mode {
        protected final void disposeChildren(Composite parent) {
            for (Control child : parent.getChildren()) {
                child.dispose();
            }
        }

        protected final void fillCategorySelector(Composite parent) {
            Control[] children = parent.getChildren();
            if (children.length == 1 && children[0] instanceof Button) {
                ((Button) children[0]).setSelection(categorizeItems);
                return;
            }

            disposeChildren(parent);

            final Button button = new Button(parent, SWT.CHECK);
            button.setText("Group items by category");
            button.setToolTipText("Whether to show items in categories or in a complete list");
            button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
            button.setSelection(categorizeItems);
            button.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    categorizeItems = button.getSelection();
                    SETTINGS.put(CATEGORIZE_ITEMS_KEY, categorizeItems);

                    updateMode();
                }
            });
        }

        public abstract void fillSelector(Composite parent);

        public abstract void fillItems(Composite parent);

        public abstract void analyzeInstallableUnits(IProgressMonitor monitor);
    }

    /**
     * @author Eike Stepper
     */
    private final class CategoriesMode extends Mode {
        @Override
        public void fillSelector(Composite parent) {
            fillCategorySelector(parent);
        }

        @Override
        public void fillItems(Composite parent) {
            disposeChildren(parent);

            TreeViewer categoriesViewer = new TreeViewer(parent, SWT.BORDER | SWT.MULTI);
            categoriesViewer.setUseHashlookup(true);
            categoriesViewer.setContentProvider(new ItemContentProvider());
            categoriesViewer.setLabelProvider(new ItemLabelProvider());
            addDragSupport(categoriesViewer);

            itemsViewer = categoriesViewer;
        }

        @SuppressWarnings("restriction")
        @Override
        public void analyzeInstallableUnits(IProgressMonitor monitor) {
            // IU.id -> value
            Map<String, String> names = new HashMap<String, String>();
            Map<String, Set<IInstallableUnit>> ius = new HashMap<String, Set<IInstallableUnit>>();
            Map<String, Set<IRequirement>> categories = new HashMap<String, Set<IRequirement>>();

            for (IInstallableUnit iu : installableUnits) {
                P2UIPlugin.checkCancelation(monitor);
                String id = iu.getId();

                names.put(id, P2Util.getName(iu));
                CollectionUtil.add(ius, id, iu);

                if (isCategory(iu)) {
                    CollectionUtil.addAll(categories, id, iu.getRequirements());
                }
            }

            Set<String> rootIDs = new HashSet<String>();

            for (String categoryID : categories.keySet()) {
                P2UIPlugin.checkCancelation(monitor);
                rootIDs.add(categoryID);
            }

            for (Set<IRequirement> requirements : categories.values()) {
                for (IRequirement requirement : requirements) {
                    P2UIPlugin.checkCancelation(monitor);

                    if (requirement instanceof org.eclipse.equinox.internal.p2.metadata.IRequiredCapability) {
                        org.eclipse.equinox.internal.p2.metadata.IRequiredCapability requiredCapability = (org.eclipse.equinox.internal.p2.metadata.IRequiredCapability) requirement;
                        if (IInstallableUnit.NAMESPACE_IU_ID.equals(requiredCapability.getNamespace())) {
                            rootIDs.remove(requiredCapability.getName());
                        }
                    }
                }
            }

            Set<CategoryItem> rootCategories = new HashSet<CategoryItem>();
            for (String rootID : rootIDs) {
                P2UIPlugin.checkCancelation(monitor);

                CategoryItem rootCategory = analyzeCategory(names, ius, categories, rootID, monitor);
                if (rootCategory != null) {
                    rootCategories.add(rootCategory);
                }
            }

            final CategoryItem[] roots = rootCategories.toArray(new CategoryItem[rootCategories.size()]);
            UIUtil.asyncExec(new Runnable() {
                public void run() {
                    setItems(roots);
                }
            });
        }

        @SuppressWarnings("restriction")
        private CategoryItem analyzeCategory(Map<String, String> names, Map<String, Set<IInstallableUnit>> ius,
                Map<String, Set<IRequirement>> categories, String categoryID, IProgressMonitor monitor) {
            Map<String, Item> children = new HashMap<String, Item>();
            Map<Item, Map<Version, IMatchExpression<IInstallableUnit>>> versions = new HashMap<Item, Map<Version, IMatchExpression<IInstallableUnit>>>();

            for (IRequirement requirement : categories.get(categoryID)) {
                P2UIPlugin.checkCancelation(monitor);

                if (requirement instanceof org.eclipse.equinox.internal.p2.metadata.IRequiredCapability) {
                    org.eclipse.equinox.internal.p2.metadata.IRequiredCapability requiredCapability = (org.eclipse.equinox.internal.p2.metadata.IRequiredCapability) requirement;
                    if (IInstallableUnit.NAMESPACE_IU_ID.equals(requiredCapability.getNamespace())) {
                        String requiredID = requiredCapability.getName();
                        if (categories.containsKey(requiredID)) {
                            CategoryItem child = analyzeCategory(names, ius, categories, requiredID, monitor);
                            if (child != null) {
                                children.put(requiredID, child);
                            }
                        } else {
                            VersionRange range = requiredCapability.getRange();
                            Item child = children.get(requiredID);

                            Set<IInstallableUnit> set = ius.get(requiredID);
                            if (set != null) {
                                for (IInstallableUnit iu : set) {
                                    P2UIPlugin.checkCancelation(monitor);
                                    Version version = iu.getVersion();

                                    if (range.isIncluded(version)) {
                                        if (child == null) {
                                            String name = names.get(requiredID);
                                            if (isFiltered(name)) {
                                                if (isFeature(iu)) {
                                                    if (requiredID.endsWith(SOURCE_FEATURE_SUFFIX)) {
                                                        String mainID = requiredID.substring(0,
                                                                requiredID.length()
                                                                        - SOURCE_FEATURE_SUFFIX.length())
                                                                + Requirement.FEATURE_SUFFIX;
                                                        String mainName = names.get(mainID);

                                                        if (ObjectUtil.equals(name, mainName)) {
                                                            name += " (Source)";
                                                        }
                                                    }

                                                    child = new FeatureItem(requiredID);
                                                } else {
                                                    if (requiredID.endsWith(SOURCE_SUFFIX)) {
                                                        String mainID = requiredID.substring(0,
                                                                requiredID.length() - SOURCE_SUFFIX.length());
                                                        String mainName = names.get(mainID);

                                                        if (ObjectUtil.equals(name, mainName)) {
                                                            name += " (Source)";
                                                        }
                                                    }

                                                    child = new PluginItem(requiredID);
                                                }

                                                child.setLabel(name);
                                                children.put(requiredID, child);
                                            }
                                        }

                                        if (child != null) {
                                            IMatchExpression<IInstallableUnit> matchExpression = iu.getFilter();

                                            Map<Version, IMatchExpression<IInstallableUnit>> map = versions
                                                    .get(child);
                                            if (map == null) {
                                                map = new HashMap<Version, IMatchExpression<IInstallableUnit>>();
                                                versions.put(child, map);
                                            }

                                            map.put(version, matchExpression);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            for (Map.Entry<Item, Map<Version, IMatchExpression<IInstallableUnit>>> entry : versions.entrySet()) {
                P2UIPlugin.checkCancelation(monitor);

                Item child = entry.getKey();
                if (child instanceof VersionedItem) {
                    VersionedItem versionedItem = (VersionedItem) child;
                    versionedItem.setVersions(entry.getValue());
                }
            }

            if (children.isEmpty()) {
                return null;
            }

            CategoryItem categoryItem = new CategoryItem();
            categoryItem.setLabel(names.get(categoryID));
            categoryItem.setChildren(children.values().toArray(new Item[children.size()]));
            return categoryItem;
        }
    }

    /**
     * @author Eike Stepper
     */
    private final class FeaturesMode extends Mode {
        @Override
        public void fillSelector(Composite parent) {
            fillCategorySelector(parent);
        }

        @Override
        public void fillItems(Composite parent) {
            disposeChildren(parent);

            TableViewer featuresViewer = new TableViewer(parent, SWT.BORDER | SWT.MULTI | SWT.VIRTUAL);
            featuresViewer.setUseHashlookup(true);
            featuresViewer.setContentProvider(new ItemContentProvider());
            featuresViewer.setLabelProvider(new ItemLabelProvider());
            addDragSupport(featuresViewer);

            itemsViewer = featuresViewer;
        }

        @Override
        public void analyzeInstallableUnits(IProgressMonitor monitor) {
            Map<String, String> names = new HashMap<String, String>();
            Map<String, Map<Version, IMatchExpression<IInstallableUnit>>> versions = new HashMap<String, Map<Version, IMatchExpression<IInstallableUnit>>>();

            for (IInstallableUnit iu : installableUnits) {
                P2UIPlugin.checkCancelation(monitor);
                String id = iu.getId();

                if (id.endsWith(Requirement.FEATURE_SUFFIX) && !id.endsWith(SOURCE_FEATURE_SUFFIX)) {
                    String name = P2Util.getName(iu);
                    if (isFiltered(name)) {
                        names.put(id, name);

                        Version version = iu.getVersion();
                        IMatchExpression<IInstallableUnit> filter = iu.getFilter();

                        Map<Version, IMatchExpression<IInstallableUnit>> map = versions.get(id);
                        if (map == null) {
                            map = new HashMap<Version, IMatchExpression<IInstallableUnit>>();
                            versions.put(id, map);
                        }

                        map.put(version, filter);
                    }
                }
            }

            final FeatureItem[] featureItems = new FeatureItem[versions.size()];
            Iterator<String> iterator = versions.keySet().iterator();

            for (int i = 0; i < featureItems.length; i++) {
                P2UIPlugin.checkCancelation(monitor);
                String id = iterator.next();
                Map<Version, IMatchExpression<IInstallableUnit>> map = versions.get(id);

                FeatureItem featureItem = new FeatureItem(id);
                featureItem.setVersions(map);
                featureItem.setLabel(names.get(id));
                featureItems[i] = featureItem;
            }

            UIUtil.asyncExec(new Runnable() {
                public void run() {
                    setItems(featureItems);
                }
            });
        }
    }

    /**
     * @author Eike Stepper
     */
    private final class CapabilitiesMode extends Mode {
        private ComboViewer namespaceViewer;

        @Override
        public void fillSelector(Composite parent) {
            disposeChildren(parent);

            CCombo namespaceCombo =
                    // new CCombo(parent, SWT.BORDER | SWT.READ_ONLY | SWT.FLAT);
                    createCombo(parent, SWT.BORDER | SWT.READ_ONLY | SWT.FLAT, false);
            namespaceCombo.setToolTipText("Select the namespace of the capabilities to show");

            namespaceViewer = new ComboViewer(namespaceCombo);
            namespaceViewer.setSorter(new ViewerSorter());
            namespaceViewer.setContentProvider(new ArrayContentProvider());
            namespaceViewer.setLabelProvider(new LabelProvider());
            namespaceViewer.setInput(new String[] { currentNamespace });
            namespaceViewer.addSelectionChangedListener(new ISelectionChangedListener() {
                public void selectionChanged(SelectionChangedEvent event) {
                    IStructuredSelection selection = (IStructuredSelection) namespaceViewer.getSelection();
                    String newNamespace = (String) selection.getFirstElement();
                    if (!ObjectUtil.equals(newNamespace, currentNamespace)) {
                        SETTINGS.put(CURRENT_NAMESPACE_KEY, newNamespace);
                        currentNamespace = newNamespace;
                        analyzeJob.reschedule();
                    }
                }
            });

            namespaceViewer.setSelection(new StructuredSelection(currentNamespace));
        }

        @Override
        public void fillItems(Composite parent) {
            disposeChildren(parent);

            TableViewer capabilitiesViewer = new TableViewer(parent, SWT.BORDER | SWT.MULTI | SWT.VIRTUAL);
            capabilitiesViewer.setUseHashlookup(true);
            capabilitiesViewer.setContentProvider(new ItemContentProvider());
            capabilitiesViewer.setLabelProvider(new ItemLabelProvider());
            addDragSupport(capabilitiesViewer);

            itemsViewer = capabilitiesViewer;
        }

        @Override
        public void analyzeInstallableUnits(IProgressMonitor monitor) {
            final Set<String> flavors = new HashSet<String>();
            final Set<String> namespaces = new HashSet<String>();
            Map<String, Set<Version>> versions = new HashMap<String, Set<Version>>();

            for (IInstallableUnit iu : installableUnits) {
                for (IProvidedCapability capability : iu.getProvidedCapabilities()) {
                    P2UIPlugin.checkCancelation(monitor);

                    String namespace = capability.getNamespace();
                    String name = capability.getName();

                    if ("org.eclipse.equinox.p2.flavor".equals(namespace)) {
                        flavors.add(name);
                    } else if (!"A.PDE.Target.Platform".equalsIgnoreCase(namespace)) {
                        namespaces.add(namespace);
                    }

                    if (ObjectUtil.equals(namespace, currentNamespace) && isFiltered(name)) {
                        Version version = capability.getVersion();
                        if (version != null && !Version.emptyVersion.equals(version)) {
                            CollectionUtil.add(versions, name, version);
                        }
                    }
                }
            }

            minimizeNamespaces(flavors, namespaces);

            if (!namespaces.contains(currentNamespace)) {
                String newCurrentNamespace = null;
                if (namespaces.contains(DEFAULT_CAPABILITY_NAMESPACE)) {
                    newCurrentNamespace = DEFAULT_CAPABILITY_NAMESPACE;
                } else if (!namespaces.isEmpty()) {
                    newCurrentNamespace = namespaces.iterator().next();
                }

                if (newCurrentNamespace != null) {
                    currentNamespace = newCurrentNamespace;
                    analyzeInstallableUnits(monitor);
                    return;
                }
            }

            final CapabilityItem[] capabilityItems = new CapabilityItem[versions.size()];
            Iterator<String> iterator = versions.keySet().iterator();

            for (int i = 0; i < capabilityItems.length; i++) {
                String id = iterator.next();

                CapabilityItem capabilityItem = new CapabilityItem();
                capabilityItem.setVersions(versions.get(id));
                capabilityItem.setNamespace(currentNamespace);
                capabilityItem.setLabel(id);
                capabilityItems[i] = capabilityItem;
            }

            UIUtil.asyncExec(new Runnable() {
                public void run() {
                    if (!container.isDisposed()) {
                        setItems(capabilityItems);

                        namespaceViewer.setInput(namespaces);
                        namespaceViewer.getCCombo().pack();
                        selectorComposite.getParent().layout();

                        UIUtil.asyncExec(new Runnable() {
                            public void run() {
                                if (!container.isDisposed() && currentNamespace != null) {
                                    namespaceViewer.setSelection(new StructuredSelection(currentNamespace));
                                }
                            }
                        });
                    }
                }
            });
        }
    }

    /**
     * @author Eike Stepper
     */
    private final class RepositoryFocusListener implements FocusListener {
        private Color originalForeground;

        public void focusGained(FocusEvent e) {
            if (originalForeground != null) {
                repositoryCombo.setText("");
                repositoryCombo.setForeground(originalForeground);
                originalForeground = null;
            }
        }

        public void focusLost(FocusEvent e) {
            String activeRepository = RepositoryManager.INSTANCE.getActiveRepository();
            if (activeRepository == null) {
                originalForeground = repositoryCombo.getForeground();
                repositoryCombo.setText("type repository url, drag and drop, or pick from list");
                Color color = formToolkit.getColors().getColor("initial_repository");
                repositoryCombo.setForeground(color);
            } else {
                if (!activeRepository.equals(repositoryCombo.getText())) {
                    repositoryCombo.setText(activeRepository);
                }
            }
        }
    }

    /**
     * @author Eike Stepper
     */
    private final class RepositoryHistoryListener extends KeyAdapter implements ISelectionChangedListener {
        private boolean listVisible;

        private String listRepository;

        @Override
        public void keyReleased(KeyEvent e) {
            boolean currentListVisible = repositoryCombo.getListVisible();
            if (currentListVisible) {
                String repository = getSelectedRepository();
                if (!StringUtil.isEmpty(repository)) {
                    listRepository = repository;
                }
            }

            if (currentListVisible && (e.keyCode == SWT.DEL || e.keyCode == SWT.BS)) {
                RepositoryManager.INSTANCE.removeRepository(listRepository);
            } else if (e.keyCode == SWT.CR && listVisible && !currentListVisible) {
                selectRepository();
            }

            listVisible = currentListVisible;
        }

        public void selectionChanged(SelectionChangedEvent event) {
            listVisible = repositoryCombo.getListVisible();
            if (!listVisible) {
                selectRepository();
            }
        }

        private void selectRepository() {
            String newRepository = getSelectedRepository();
            activateAndLoadRepository(newRepository);
        }

        private String getSelectedRepository() {
            IStructuredSelection selection = (IStructuredSelection) repositoryViewer.getSelection();
            return selection.isEmpty() ? repositoryCombo.getText() : (String) selection.getFirstElement();
        }
    }

    /**
     * @author Eike Stepper
     */
    private final class RepositoryContentProvider implements IStructuredContentProvider, RepositoryManagerListener {
        public RepositoryContentProvider() {
            RepositoryManager.INSTANCE.addListener(this);
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

        public void dispose() {
            RepositoryManager.INSTANCE.removeListener(this);
        }

        public Object[] getElements(Object element) {
            return RepositoryManager.INSTANCE.getRepositories();
        }

        public void repositoriesChanged(RepositoryManager repositoryManager) {
            UIUtil.asyncExec(new Runnable() {
                public void run() {
                    if (!container.isDisposed()) {
                        repositoryViewer.refresh();

                        UIUtil.asyncExec(new Runnable() {
                            public void run() {
                                if (!container.isDisposed()) {
                                    String activeRepository = RepositoryManager.INSTANCE.getActiveRepository();
                                    if (activeRepository == null) {
                                        repositoryViewer.setSelection(StructuredSelection.EMPTY);
                                        repositoryCombo.setText("");
                                    } else {
                                        ISelection selection = new StructuredSelection(activeRepository);
                                        repositoryViewer.setSelection(selection);
                                        repositoryCombo.setText(activeRepository);
                                        repositoryCombo.setSelection(new Point(0, activeRepository.length()));
                                    }
                                }
                            }
                        });
                    }
                }
            });
        }

        public void activeRepositoryChanged(RepositoryManager repositoryManager, String repository) {
            // Do nothing.
        }
    }

    /**
     * @author Eike Stepper
     */
    private final class ItemContentProvider implements ITreeContentProvider {
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

        public void dispose() {
        }

        public Object getParent(Object element) {
            return null;
        }

        public Object[] getElements(Object element) {
            return getChildren(element);
        }

        public Object[] getChildren(Object element) {
            Item[] children = ((Item) element).getChildren();
            if (children != null) {
                return children;
            }

            return NO_ELEMENTS;
        }

        public boolean hasChildren(Object element) {
            return ((Item) element).hasChildren();
        }
    }

    /**
    * @author Eike Stepper
    */
    private static final class ItemLabelProvider extends LabelProvider {
        @Override
        public Image getImage(Object element) {
            Item item = (Item) element;
            return item.getImage();
        }

        @Override
        public String getText(Object element) {
            Item item = (Item) element;
            return item.getLabel();
        }
    }

    /**
     * @author Eike Stepper
     */
    private static final class VersionProvider extends LabelProvider implements IStructuredContentProvider {
        private static final Image IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/version");

        private TableViewer versionsViewer;

        private VersionSegment versionSegment;

        public VersionProvider() {
            try {
                versionSegment = VersionSegment.get(SETTINGS.get(VERSION_SEGMENT_KEY));
            } catch (Exception ex) {
                //$FALL-THROUGH$
            }

            if (versionSegment == null) {
                versionSegment = VersionSegment.QUALIFIER;
            }
        }

        public VersionSegment getVersionSegment() {
            return versionSegment;
        }

        public void setVersionSegment(VersionSegment versionSegment) {
            if (this.versionSegment != versionSegment) {
                this.versionSegment = versionSegment;
                SETTINGS.put(VERSION_SEGMENT_KEY, versionSegment.getLiteral());

                if (versionsViewer != null) {
                    versionsViewer.refresh();
                }
            }
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            versionsViewer = (TableViewer) viewer;
        }

        @Override
        public void dispose() {
        }

        public Object[] getElements(Object inputElement) {
            if (inputElement instanceof VersionedItem) {
                VersionedItem versionedItem = (VersionedItem) inputElement;
                Map<Version, IMatchExpression<IInstallableUnit>> versions = versionedItem.getVersions();
                if (versions != null) {
                    Set<ItemVersion> itemVersions = new HashSet<ItemVersion>();
                    for (Map.Entry<Version, IMatchExpression<IInstallableUnit>> entry : versions.entrySet()) {
                        ItemVersion itemVersion = getItemVersion(entry.getKey(), entry.getValue());
                        itemVersions.add(itemVersion);
                    }

                    ItemVersion[] array = itemVersions.toArray(new ItemVersion[itemVersions.size()]);
                    Arrays.sort(array);
                    return array;
                }
            }

            return NO_ELEMENTS;
        }

        @Override
        public Image getImage(Object element) {
            return IMAGE;
        }

        private ItemVersion getItemVersion(Version version, IMatchExpression<IInstallableUnit> filter) {
            int segments = version.getSegmentCount();
            if (segments == 0) {
                return new ItemVersion(version, "0.0.0", filter);
            }

            segments = Math.min(segments, versionSegment.ordinal() + 1);
            StringBuilder builder = new StringBuilder();

            for (int i = 0; i < segments; i++) {
                String segment = version.getSegment(i).toString();
                if (StringUtil.isEmpty(segment)) {
                    break;
                }

                if (builder.length() != 0) {
                    builder.append('.');
                }

                builder.append(segment);
            }

            version = Version.create(builder.toString());

            if (segments < 3) {
                builder.append(".x");
            }

            return new ItemVersion(version, builder.toString(), filter);
        }

        /**
         * @author Eike Stepper
         */
        public static final class ItemVersion implements Comparable<ItemVersion> {
            private final Version version;

            private final String label;

            private final IMatchExpression<IInstallableUnit> filter;

            public ItemVersion(Version version, String label, IMatchExpression<IInstallableUnit> filter) {
                this.version = version;
                this.label = label;
                this.filter = filter;
            }

            public Version getVersion() {
                return version;
            }

            public IMatchExpression<IInstallableUnit> getFilter() {
                return filter;
            }

            public int compareTo(ItemVersion o) {
                return version.compareTo(o.version);
            }

            @Override
            public int hashCode() {
                return version.hashCode();
            }

            @Override
            public boolean equals(Object obj) {
                return version.equals(((ItemVersion) obj).version);
            }

            @Override
            public String toString() {
                return label;
            }
        }
    }

    /**
     * @author Eike Stepper
     */
    private static abstract class Item implements Comparable<Item> {
        protected static final Integer CATEGORY_ORDER = 0;

        protected static final Integer NON_CATEGORY_ORDER = 1;

        private String label;

        public Item() {
        }

        public abstract Image getImage();

        public String getNamespace() {
            return IInstallableUnit.NAMESPACE_IU_ID;
        }

        public String getName() {
            return getLabel();
        }

        public String getLabel() {
            return label;
        }

        public void setLabel(String label) {
            this.label = StringUtil.safe(label);
        }

        public Item[] getChildren() {
            return null;
        }

        public boolean hasChildren() {
            return false;
        }

        @Override
        public String toString() {
            return label;
        }

        @Override
        public final int hashCode() {
            return super.hashCode();
        }

        @Override
        public final boolean equals(Object obj) {
            return super.equals(obj);
        }

        public int compareTo(Item o) {
            Integer category1 = getCategoryOrder();
            Integer category2 = o.getCategoryOrder();

            int result = category1.compareTo(category2);
            if (result == 0) {
                String label1 = label.toLowerCase();
                String label2 = o.label.toLowerCase();
                result = label1.compareTo(label2);
            }

            return result;
        }

        protected Integer getCategoryOrder() {
            return NON_CATEGORY_ORDER;
        }
    }

    /**
     * @author Eike Stepper
     */
    private static final class LoadingItem extends Item {
        private static final Image IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/repository");

        private final URI location;

        public LoadingItem(URI location) {
            this.location = location;
        }

        @Override
        public Image getImage() {
            return IMAGE;
        }

        @Override
        public String getLabel() {
            return "Loading " + location;
        }
    }

    /**
     * @author Eike Stepper
     */
    private static final class ErrorItem extends Item {
        private final IStatus status;

        public ErrorItem(IStatus status) {
            this.status = status;
        }

        @Override
        public Image getImage() {
            return UIUtil.getStatusImage(status.getSeverity());
        }

        @Override
        public String getLabel() {
            return status.getMessage();
        }
    }

    /**
     * @author Eike Stepper
     */
    private static final class CategoryItem extends Item {
        private static final Image IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/category");

        private Item[] children;

        public CategoryItem() {
        }

        @Override
        public Image getImage() {
            return IMAGE;
        }

        @Override
        public boolean hasChildren() {
            return children != null && children.length != 0;
        }

        @Override
        public Item[] getChildren() {
            return children;
        }

        public void setChildren(Item[] children) {
            Arrays.sort(children);
            this.children = children;
        }

        @Override
        protected Integer getCategoryOrder() {
            return CATEGORY_ORDER;
        }
    }

    /**
     * @author Eike Stepper
     */
    private static abstract class VersionedItem extends Item {
        private Map<Version, IMatchExpression<IInstallableUnit>> versions;

        public VersionedItem() {
        }

        public Map<Version, IMatchExpression<IInstallableUnit>> getVersions() {
            return versions;
        }

        public void setVersions(Map<Version, IMatchExpression<IInstallableUnit>> map) {
            versions = map;
        }
    }

    /**
     * @author Eike Stepper
     */
    private static final class FeatureItem extends VersionedItem {
        private static final Image IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/artifactFeature");

        private final String id;

        public FeatureItem(String id) {
            this.id = id;
        }

        @Override
        public Image getImage() {
            return IMAGE;
        }

        @Override
        public String getName() {
            return id;
        }
    }

    /**
     * @author Eike Stepper
     */
    private static final class PluginItem extends VersionedItem {
        private static final Image IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/artifactPlugin");

        private final String id;

        public PluginItem(String id) {
            this.id = id;
        }

        @Override
        public Image getImage() {
            return IMAGE;
        }

        @Override
        public String getName() {
            return id;
        }
    }

    /**
     * @author Eike Stepper
     */
    private static final class CapabilityItem extends VersionedItem {
        private static final Image IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/capability");

        private static final Image FEATURE_IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/artifactFeature");

        private static final Image PLUGIN_IMAGE = P2UIPlugin.INSTANCE.getSWTImage("obj16/artifactPlugin");

        private static final Image PACKAGE_IMAGE = P2UIPlugin.INSTANCE
                .getSWTImage("full/obj16/Requirement_Package");

        private String namespace;

        public CapabilityItem() {
        }

        @Override
        public String getNamespace() {
            return namespace;
        }

        public void setNamespace(String namespace) {
            this.namespace = namespace;
        }

        public void setVersions(Set<Version> versions) {
            Map<Version, IMatchExpression<IInstallableUnit>> map = new HashMap<Version, IMatchExpression<IInstallableUnit>>();
            for (Version version : versions) {
                map.put(version, null);
            }

            setVersions(map);
        }

        @Override
        public Image getImage() {
            if (IInstallableUnit.NAMESPACE_IU_ID.equals(namespace)) {
                if (getLabel().endsWith(Requirement.FEATURE_SUFFIX)) {
                    return FEATURE_IMAGE;
                }

                return PLUGIN_IMAGE;
            }

            if (RequirementItemProvider.NAMESPACE_PACKAGE_ID.equals(namespace)) {
                return PACKAGE_IMAGE;
            }

            return IMAGE;
        }
    }
}