| /******************************************************************************* |
| * Copyright (c) 2005, 2007 IBM Corporation and others. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| |
| *******************************************************************************/ |
| package org.eclipse.dltk.ui.text.completion; |
| |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.InvalidRegistryObjectException; |
| import org.eclipse.core.runtime.PerformanceStats; |
| 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.jface.text.contentassist.ICompletionProposal; |
| import org.osgi.framework.Bundle; |
| |
| public final class ProposalSorterHandle { |
| /** The extension schema name of the id attribute. */ |
| private static final String ID= "id"; //$NON-NLS-1$ |
| /** The extension schema name of the name attribute. */ |
| private static final String NAME= "name"; //$NON-NLS-1$ |
| /** The extension schema name of the class attribute. */ |
| private static final String CLASS= "class"; //$NON-NLS-1$ |
| /** The extension schema name of the activate attribute. */ |
| private static final String ACTIVATE= "activate"; //$NON-NLS-1$ |
| /** The name of the performance event used to trace extensions. */ |
| private static final String PERFORMANCE_EVENT= DLTKUIPlugin.PLUGIN_ID + "/perf/content_assist_sorters/extensions"; //$NON-NLS-1$ |
| /** |
| * If <code>true</code>, execution time of extensions is measured and extensions may be |
| * disabled if execution takes too long. |
| */ |
| private static final boolean MEASURE_PERFORMANCE= PerformanceStats.isEnabled(PERFORMANCE_EVENT); |
| /** The one and only operation name. */ |
| private static final String SORT= "sort"; //$NON-NLS-1$ |
| |
| /** The identifier of the extension. */ |
| private final String fId; |
| /** The name of the extension. */ |
| private final String fName; |
| /** The class name of the provided <code>AbstractProposalSorter</code>. */ |
| private final String fClass; |
| /** The activate attribute value. */ |
| private final boolean fActivate; |
| /** The configuration element of this extension. */ |
| private final IConfigurationElement fElement; |
| /** The computer, if instantiated, <code>null</code> otherwise. */ |
| private AbstractProposalSorter fSorter; |
| |
| /** |
| * Creates a new descriptor. |
| * |
| * @param element the configuration element to read |
| * @throws InvalidRegistryObjectException if the configuration element is not valid any longer |
| * or does not contain mandatory attributes |
| */ |
| ProposalSorterHandle(IConfigurationElement element) throws InvalidRegistryObjectException { |
| Assert.isLegal(element != null); |
| |
| fElement= element; |
| fId= element.getAttribute(ID); |
| checkNotNull(fId, ID); |
| |
| String name= element.getAttribute(NAME); |
| if (name == null) |
| fName= fId; |
| else |
| fName= name; |
| |
| String activateAttribute= element.getAttribute(ACTIVATE); |
| fActivate= Boolean.valueOf(activateAttribute).booleanValue(); |
| |
| fClass= element.getAttribute(CLASS); |
| checkNotNull(fClass, CLASS); |
| } |
| |
| /** |
| * Checks an element that must be defined according to the extension |
| * point schema. Throws an |
| * <code>InvalidRegistryObjectException</code> if <code>obj</code> |
| * is <code>null</code>. |
| */ |
| private void checkNotNull(Object obj, String attribute) throws InvalidRegistryObjectException { |
| if (obj == null) { |
| Object[] args= { getId(), fElement.getContributor().getName(), attribute }; |
| String message= Messages.format(ScriptTextMessages.CompletionProposalComputerDescriptor_illegal_attribute_message, args); |
| IStatus status= new Status(IStatus.WARNING, DLTKUIPlugin.PLUGIN_ID, IStatus.OK, message, null); |
| DLTKUIPlugin.log(status); |
| throw new InvalidRegistryObjectException(); |
| } |
| } |
| |
| /** |
| * Returns the identifier of the described extension. |
| * |
| * @return Returns the id |
| */ |
| public String getId() { |
| return fId; |
| } |
| |
| /** |
| * Returns the name of the described extension. |
| * |
| * @return Returns the name |
| */ |
| public String getName() { |
| return fName; |
| } |
| |
| /** |
| * Returns a cached instance of the sorter as described in the extension's xml. The sorter is |
| * {@link #createSorter() created} the first time that this method is called and then cached. |
| * |
| * @return a new instance of the proposal sorter as described by this descriptor |
| * @throws CoreException if the creation fails |
| * @throws InvalidRegistryObjectException if the extension is not valid any longer (e.g. due to |
| * plug-in unloading) |
| */ |
| private synchronized AbstractProposalSorter getSorter() throws CoreException, InvalidRegistryObjectException { |
| if (fSorter == null && (fActivate || isPluginLoaded())) |
| fSorter= createSorter(); |
| return fSorter; |
| } |
| |
| private boolean isPluginLoaded() throws InvalidRegistryObjectException { |
| Bundle bundle= getBundle(); |
| return bundle != null && bundle.getState() == Bundle.ACTIVE; |
| } |
| |
| private Bundle getBundle() throws InvalidRegistryObjectException { |
| String symbolicName= fElement.getContributor().getName(); |
| Bundle bundle= Platform.getBundle(symbolicName); |
| return bundle; |
| } |
| |
| /** |
| * Returns a new instance of the sorter as described in the |
| * extension's xml. |
| * |
| * @return a new instance of the completion proposal computer as |
| * described by this descriptor |
| * @throws CoreException if the creation fails |
| * @throws InvalidRegistryObjectException if the extension is not |
| * valid any longer (e.g. due to plug-in unloading) |
| */ |
| private AbstractProposalSorter createSorter() throws CoreException, InvalidRegistryObjectException { |
| return (AbstractProposalSorter) fElement.createExecutableExtension(CLASS); |
| } |
| |
| /** |
| * Safely computes completion proposals through the described extension. If the extension throws |
| * an exception or otherwise does not adhere to the contract described in |
| * {@link AbstractProposalSorter}, the list is returned as is. |
| * |
| * @param context the invocation context passed on to the extension |
| * @param proposals the list of computed completion proposals to be sorted (element type: |
| * {@link org.eclipse.jface.text.contentassist.ICompletionProposal}), must be writable |
| */ |
| public void sortProposals(ContentAssistInvocationContext context, |
| List<ICompletionProposal> proposals) { |
| IStatus status; |
| try { |
| AbstractProposalSorter sorter= getSorter(); |
| |
| PerformanceStats stats= startMeter(SORT, sorter); |
| |
| sorter.beginSorting(context, proposals); |
| Collections.sort(proposals, sorter); |
| sorter.endSorting(); |
| |
| status= stopMeter(stats, SORT); |
| |
| // valid result |
| if (status == null) |
| return; |
| |
| status= createAPIViolationStatus(SORT); |
| |
| } catch (InvalidRegistryObjectException x) { |
| status= createExceptionStatus(x); |
| } catch (CoreException x) { |
| status= createExceptionStatus(x); |
| } catch (RuntimeException x) { |
| status= createExceptionStatus(x); |
| } |
| |
| DLTKUIPlugin.log(status); |
| return; |
| } |
| |
| private IStatus stopMeter(final PerformanceStats stats, String operation) { |
| if (MEASURE_PERFORMANCE) { |
| stats.endRun(); |
| if (stats.isFailure()) |
| return createPerformanceStatus(operation); |
| } |
| return null; |
| } |
| |
| private PerformanceStats startMeter(String context, AbstractProposalSorter sorter) { |
| final PerformanceStats stats; |
| if (MEASURE_PERFORMANCE) { |
| stats= PerformanceStats.getStats(PERFORMANCE_EVENT, sorter); |
| stats.startRun(context); |
| } else { |
| stats= null; |
| } |
| return stats; |
| } |
| |
| private Status createExceptionStatus(InvalidRegistryObjectException x) { |
| // extension has become invalid - log & disable |
| String disable= createBlameMessage(); |
| String reason= ScriptTextMessages.CompletionProposalComputerDescriptor_reason_invalid; |
| return new Status(IStatus.INFO, DLTKUIPlugin.getPluginId(), IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$ |
| } |
| |
| private Status createExceptionStatus(CoreException x) { |
| // unable to instantiate the extension - log & disable |
| String disable= createBlameMessage(); |
| String reason= ScriptTextMessages.CompletionProposalComputerDescriptor_reason_instantiation; |
| return new Status(IStatus.ERROR, DLTKUIPlugin.getPluginId(), IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$ |
| } |
| |
| private Status createExceptionStatus(RuntimeException x) { |
| // misbehaving extension - log & disable |
| String disable= createBlameMessage(); |
| String reason= ScriptTextMessages.CompletionProposalComputerDescriptor_reason_runtime_ex; |
| return new Status(IStatus.WARNING, DLTKUIPlugin.getPluginId(), IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$ |
| } |
| |
| private Status createAPIViolationStatus(String operation) { |
| String disable= createBlameMessage(); |
| Object[] args= {operation}; |
| String reason= Messages.format(ScriptTextMessages.CompletionProposalComputerDescriptor_reason_API, args); |
| return new Status(IStatus.WARNING, DLTKUIPlugin.getPluginId(), IStatus.OK, disable + " " + reason, null); //$NON-NLS-1$ |
| } |
| |
| private Status createPerformanceStatus(String operation) { |
| String disable= createBlameMessage(); |
| Object[] args= {operation}; |
| String reason= Messages.format(ScriptTextMessages.CompletionProposalComputerDescriptor_reason_performance, args); |
| return new Status(IStatus.WARNING, DLTKUIPlugin.getPluginId(), IStatus.OK, disable + " " + reason, null); //$NON-NLS-1$ |
| } |
| |
| private String createBlameMessage() { |
| Object[] args= { getName(), getId() }; |
| String disable= Messages.format(ScriptTextMessages.ProposalSorterHandle_blame, args); |
| return disable; |
| } |
| |
| /** |
| * Returns the error message from the described extension, <code>null</code> for no error. |
| * |
| * @return the error message from the described extension, <code>null</code> for no error |
| */ |
| public String getErrorMessage() { |
| return null; |
| } |
| |
| } |