org.eclipse.dltk.ui.text.completion.CompletionProposalComputerRegistry.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.dltk.ui.text.completion.CompletionProposalComputerRegistry.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation 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
 *
     
 *******************************************************************************/
package org.eclipse.dltk.ui.text.completion;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.Set;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.InvalidRegistryObjectException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.dltk.internal.corext.util.Messages;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.PreferenceConstants;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Link;
import org.eclipse.ui.dialogs.PreferencesUtil;

public final class CompletionProposalComputerRegistry {

    private static final String EXTENSION_POINT = "scriptCompletionProposalComputer"; //$NON-NLS-1$

    /** The singleton instance. */
    private static CompletionProposalComputerRegistry fgSingleton = null;

    /**
     * Returns the default computer registry.
     * <p>
     * TODO keep this or add some other singleton, e.g. JavaPlugin?
     * </p>
     * 
     * @return the singleton instance
     */
    public static synchronized CompletionProposalComputerRegistry getDefault() {
        if (fgSingleton == null) {
            fgSingleton = new CompletionProposalComputerRegistry();
        }

        return fgSingleton;
    }

    /**
     * The sets of descriptors, grouped by partition type (key type:
     * {@link String}, value type: {@linkplain List
     * List&lt;CompletionProposalComputerDescriptor&gt;}).
     */
    private final Map<String, List<CompletionProposalComputerDescriptor>> fDescriptorsByPartition = new HashMap<String, List<CompletionProposalComputerDescriptor>>();
    /**
     * Unmodifiable versions of the sets stored in
     * <code>fDescriptorsByPartition</code> (key type: {@link String}, value
     * type: {@linkplain List List&lt;CompletionProposalComputerDescriptor&gt;}
     * ).
     */
    private final Map<String, List<CompletionProposalComputerDescriptor>> fPublicDescriptorsByPartition = new HashMap<String, List<CompletionProposalComputerDescriptor>>();
    /**
     * All descriptors (element type:
     * {@link CompletionProposalComputerDescriptor}).
     */
    private final List<CompletionProposalComputerDescriptor> fDescriptors = new ArrayList<CompletionProposalComputerDescriptor>();
    /**
     * Unmodifiable view of <code>fDescriptors</code>
     */
    private final List<CompletionProposalComputerDescriptor> fPublicDescriptors = Collections
            .unmodifiableList(fDescriptors);

    private final List<CompletionProposalCategory> fCategories = new ArrayList<CompletionProposalCategory>();
    private final List<CompletionProposalCategory> fPublicCategories = Collections.unmodifiableList(fCategories);
    /**
     * <code>true</code> if this registry has been loaded.
     */
    private boolean fLoaded = false;

    /**
     * Creates a new instance.
     */
    public CompletionProposalComputerRegistry() {
    }

    /**
     * Returns the list of {@link CompletionProposalComputerDescriptor}s
     * describing all extensions to the
     * <code>javaCompletionProposalComputer</code> extension point for the given
     * partition type.
     * <p>
     * A valid partition is either one of the constants defined in
     * {@link org.eclipse.dltk.ui.text.IJavaPartitions} or
     * {@link org.eclipse.jface.text.IDocument#DEFAULT_CONTENT_TYPE}. An empty
     * list is returned if there are no extensions for the given partition.
     * </p>
     * <p>
     * The returned list is read-only and is sorted in the order that the
     * extensions were read in. There are no duplicate elements in the returned
     * list. The returned list may change if plug-ins are loaded or unloaded
     * while the application is running or if an extension violates the API
     * contract of
     * {@link org.eclipse.dltk.ui.text.java.IScriptCompletionProposalComputer}.
     * When computing proposals, it is therefore imperative to copy the returned
     * list before iterating over it.
     * </p>
     * 
     * @param partition
     *            the partition type for which to retrieve the computer
     *            descriptors
     * @return the list of extensions to the
     *         <code>javaCompletionProposalComputer</code> extension point
     *         (element type: {@link CompletionProposalComputerDescriptor})
     */
    List<CompletionProposalComputerDescriptor> getProposalComputerDescriptors(String partition) {
        ensureExtensionPointRead();
        List<CompletionProposalComputerDescriptor> result = fPublicDescriptorsByPartition.get(partition);
        return result != null ? result : Collections.<CompletionProposalComputerDescriptor>emptyList();
    }

    /**
     * Returns the list of {@link CompletionProposalComputerDescriptor}s
     * describing all extensions to the
     * <code>javaCompletionProposalComputer</code> extension point.
     * <p>
     * The returned list is read-only and is sorted in the order that the
     * extensions were read in. There are no duplicate elements in the returned
     * list. The returned list may change if plug-ins are loaded or unloaded
     * while the application is running or if an extension violates the API
     * contract of
     * {@link org.eclipse.dltk.ui.text.java.IScriptCompletionProposalComputer}.
     * When computing proposals, it is therefore imperative to copy the returned
     * list before iterating over it.
     * </p>
     * 
     * @return the list of extensions to the
     *         <code>javaCompletionProposalComputer</code> extension point
     *         (element type: {@link CompletionProposalComputerDescriptor})
     */
    List<CompletionProposalComputerDescriptor> getProposalComputerDescriptors() {
        ensureExtensionPointRead();
        return fPublicDescriptors;
    }

    /**
     * Returns the list of proposal categories contributed to the
     * <code>javaCompletionProposalComputer</code> extension point.
     * <p>
     * <p>
     * The returned list is read-only and is sorted in the order that the
     * extensions were read in. There are no duplicate elements in the returned
     * list. The returned list may change if plug-ins are loaded or unloaded
     * while the application is running.
     * </p>
     * 
     * @return list of proposal categories contributed to the
     *         <code>javaCompletionProposalComputer</code> extension point
     *         (element type: {@link CompletionProposalCategory})
     */
    public List<CompletionProposalCategory> getProposalCategories() {
        ensureExtensionPointRead();
        return fPublicCategories;
    }

    /**
     * Ensures that the extensions are read and stored in
     * <code>fDescriptorsByPartition</code>.
     */
    private void ensureExtensionPointRead() {
        boolean reload;
        synchronized (this) {
            reload = !fLoaded;
            fLoaded = true;
        }
        if (reload)
            reload();
    }

    /**
     * Reloads the extensions to the extension point.
     * <p>
     * This method can be called more than once in order to reload from a
     * changed extension registry.
     * </p>
     */
    public void reload() {
        IExtensionRegistry registry = Platform.getExtensionRegistry();
        List<IConfigurationElement> elements = new ArrayList<IConfigurationElement>(
                Arrays.asList(registry.getConfigurationElementsFor(DLTKUIPlugin.getPluginId(), EXTENSION_POINT)));

        Map<String, List<CompletionProposalComputerDescriptor>> map = new HashMap<String, List<CompletionProposalComputerDescriptor>>();
        List<CompletionProposalComputerDescriptor> all = new ArrayList<CompletionProposalComputerDescriptor>();

        List<CompletionProposalCategory> categories = getCategories(elements);
        for (Iterator<IConfigurationElement> iter = elements.iterator(); iter.hasNext();) {
            IConfigurationElement element = iter.next();
            try {
                CompletionProposalComputerDescriptor desc = new CompletionProposalComputerDescriptor(element, this,
                        categories);
                final Set<String> partitions = desc.getPartitions();
                for (String partition : partitions) {
                    List<CompletionProposalComputerDescriptor> list = map.get(partition);
                    if (list == null) {
                        list = new ArrayList<CompletionProposalComputerDescriptor>();
                        map.put(partition, list);
                    }
                    list.add(desc);
                }
                all.add(desc);

            } catch (InvalidRegistryObjectException x) {
                /*
                 * Element is not valid any longer as the contributing plug-in
                 * was unloaded or for some other reason. Do not include the
                 * extension in the list and inform the user about it.
                 */
                Object[] args = { element.toString() };
                String message = Messages
                        .format(ScriptTextMessages.CompletionProposalComputerRegistry_invalid_message, args);
                IStatus status = new Status(IStatus.WARNING, DLTKUIPlugin.getPluginId(), IStatus.OK, message, x);
                informUser(status);
            }
        }

        synchronized (this) {
            fCategories.clear();
            fCategories.addAll(categories);

            Set<String> partitions = map.keySet();
            fDescriptorsByPartition.keySet().retainAll(partitions);
            fPublicDescriptorsByPartition.keySet().retainAll(partitions);
            for (Iterator<String> it = partitions.iterator(); it.hasNext();) {
                String partition = it.next();
                List<CompletionProposalComputerDescriptor> old = fDescriptorsByPartition.get(partition);
                List<CompletionProposalComputerDescriptor> current = map.get(partition);
                if (old != null) {
                    old.clear();
                    old.addAll(current);
                } else {
                    fDescriptorsByPartition.put(partition, current);
                    fPublicDescriptorsByPartition.put(partition, Collections.unmodifiableList(current));
                }
            }

            fDescriptors.clear();
            fDescriptors.addAll(all);
        }
    }

    private List<CompletionProposalCategory> getCategories(List<IConfigurationElement> elements) {
        IPreferenceStore store = DLTKUIPlugin.getDefault().getPreferenceStore();
        String preference = store.getString(PreferenceConstants.CODEASSIST_EXCLUDED_CATEGORIES);
        Set<String> disabled = new HashSet<String>();
        StringTokenizer tok = new StringTokenizer(preference, "\0"); //$NON-NLS-1$
        while (tok.hasMoreTokens())
            disabled.add(tok.nextToken());
        Map<String, Integer> ordered = new HashMap<String, Integer>();
        preference = store.getString(PreferenceConstants.CODEASSIST_CATEGORY_ORDER);
        tok = new StringTokenizer(preference, "\0"); //$NON-NLS-1$
        while (tok.hasMoreTokens()) {
            StringTokenizer inner = new StringTokenizer(tok.nextToken(), ":"); //$NON-NLS-1$
            String id = inner.nextToken();
            int rank = Integer.parseInt(inner.nextToken());
            ordered.put(id, new Integer(rank));
        }

        List<CompletionProposalCategory> categories = new ArrayList<CompletionProposalCategory>();
        for (Iterator<IConfigurationElement> iter = elements.iterator(); iter.hasNext();) {
            IConfigurationElement element = iter.next();
            try {
                if (element.getName().equals("proposalCategory")) { //$NON-NLS-1$
                    iter.remove(); // remove from list to leave only computers

                    CompletionProposalCategory category = new CompletionProposalCategory(element, this);
                    categories.add(category);
                    category.setIncluded(!disabled.contains(category.getId()));
                    Integer rank = ordered.get(category.getId());
                    if (rank != null) {
                        int r = rank.intValue();
                        boolean separate = r < 0xffff;
                        category.setSeparateCommand(separate);
                        category.setSortOrder(r);
                    }
                }
            } catch (InvalidRegistryObjectException x) {
                /*
                 * Element is not valid any longer as the contributing plug-in
                 * was unloaded or for some other reason. Do not include the
                 * extension in the list and inform the user about it.
                 */
                Object[] args = { element.toString() };
                String message = Messages
                        .format(ScriptTextMessages.CompletionProposalComputerRegistry_invalid_message, args);
                IStatus status = new Status(IStatus.WARNING, DLTKUIPlugin.getPluginId(), IStatus.OK, message, x);
                informUser(status);
            }
        }
        return categories;
    }

    /**
     * Log the status and inform the user about a misbehaving extension.
     * 
     * @param descriptor
     *            the descriptor of the misbehaving extension
     * @param status
     *            a status object that will be logged
     */
    void informUser(CompletionProposalComputerDescriptor descriptor, IStatus status) {
        DLTKUIPlugin.log(status);
        String title = ScriptTextMessages.CompletionProposalComputerRegistry_error_dialog_title;
        CompletionProposalCategory category = descriptor.getCategory();
        IContributor culprit = descriptor.getContributor();
        Set<String> affectedPlugins = getAffectedContributors(category, culprit);

        final String avoidHint;
        final String culpritName = culprit == null ? null : culprit.getName();
        if (affectedPlugins.isEmpty())
            avoidHint = Messages.format(ScriptTextMessages.CompletionProposalComputerRegistry_messageAvoidanceHint,
                    new Object[] { culpritName, category.getDisplayName() });
        else
            avoidHint = Messages.format(
                    ScriptTextMessages.CompletionProposalComputerRegistry_messageAvoidanceHintWithWarning,
                    new Object[] { culpritName, category.getDisplayName(), toString(affectedPlugins) });

        String message = status.getMessage();
        // inlined from MessageDialog.openError
        MessageDialog dialog = new MessageDialog(DLTKUIPlugin.getActiveWorkbenchShell(), title,
                null /* default image */, message, MessageDialog.ERROR, new String[] { IDialogConstants.OK_LABEL },
                0) {
            protected Control createCustomArea(Composite parent) {
                Link link = new Link(parent, SWT.NONE);
                link.setText(avoidHint);
                link.addSelectionListener(new SelectionAdapter() {
                    public void widgetSelected(SelectionEvent e) {
                        PreferencesUtil
                                .createPreferenceDialogOn(getShell(),
                                        "org.eclipse.dltk.ui.preferences.CodeAssistPreferenceAdvanced", null, null) //$NON-NLS-1$
                                .open();
                    }
                });
                GridData gridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
                gridData.widthHint = this.getMinimumMessageWidth();
                link.setLayoutData(gridData);
                return link;
            }
        };
        dialog.open();
    }

    /**
     * Returns the names of contributors affected by disabling a category.
     * 
     * @param category
     *            the category that would be disabled
     * @param culprit
     *            the cuprit plug-in, which is not included in the returned list
     * @return the names of the contributors other than <code>culprit</code>
     *         that contribute to <code>category</code> (element type:
     *         {@link String})
     */
    private Set<String> getAffectedContributors(CompletionProposalCategory category, IContributor culprit) {
        Set<String> affectedPlugins = new HashSet<String>();
        for (Iterator<CompletionProposalComputerDescriptor> it = getProposalComputerDescriptors().iterator(); it
                .hasNext();) {
            CompletionProposalComputerDescriptor desc = it.next();
            CompletionProposalCategory cat = desc.getCategory();
            if (cat.equals(category)) {
                IContributor contributor = desc.getContributor();
                if (contributor != null && !culprit.equals(contributor))
                    affectedPlugins.add(contributor.getName());
            }
        }
        return affectedPlugins;
    }

    private Object toString(Collection<String> collection) {
        // strip brackets off AbstractCollection.toString()
        String string = collection.toString();
        return string.substring(1, string.length() - 1);
    }

    private void informUser(IStatus status) {
        DLTKUIPlugin.log(status);
        String title = ScriptTextMessages.CompletionProposalComputerRegistry_error_dialog_title;
        String message = status.getMessage();
        MessageDialog.openError(DLTKUIPlugin.getActiveWorkbenchShell(), title, message);
    }
}