/******************************************************************************* * Copyright (c) 2005, 2011 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 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.ui.text.java; 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.Status; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.ui.text.java.AbstractProposalSorter; import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext; import org.eclipse.jdt.internal.ui.JavaPlugin; /** * The description of an extension to the * <code>org.eclipse.jdt.ui.javaCompletionProposalSorters</code> extension point. Instances are * immutable. * * @since 3.2 */ 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 name of the performance event used to trace extensions. */ private static final String PERFORMANCE_EVENT= JavaPlugin.getPluginId() + "/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 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 * @throws CoreException if the configuration does not contain mandatory attributes */ ProposalSorterHandle(IConfigurationElement element) throws InvalidRegistryObjectException, CoreException { 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; fClass= element.getAttribute(CLASS); checkNotNull(fClass, CLASS); } /** * Checks that the given attribute value is not <code>null</code>. * * @param value the value to check if not null * @param attribute the attribute * @throws InvalidRegistryObjectException if the registry element is no longer valid * @throws CoreException if <code>value</code> is <code>null</code> */ private void checkNotNull(Object value, String attribute) throws InvalidRegistryObjectException, CoreException { if (value == null) { Object[] args= { getId(), fElement.getContributor().getName(), attribute }; String message= Messages.format(JavaTextMessages.CompletionProposalComputerDescriptor_illegal_attribute_message, args); IStatus status= new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, message, null); throw new CoreException(status); } } /** * 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) fSorter= createSorter(); return fSorter; } /** * 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); 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); } JavaPlugin.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= JavaTextMessages.CompletionProposalComputerDescriptor_reason_invalid; return new Status(IStatus.INFO, JavaPlugin.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= JavaTextMessages.CompletionProposalComputerDescriptor_reason_instantiation; return new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$ } private Status createExceptionStatus(RuntimeException x) { // misbehaving extension - log & disable String disable= createBlameMessage(); String reason= JavaTextMessages.CompletionProposalComputerDescriptor_reason_runtime_ex; return new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$ } private Status createAPIViolationStatus(String operation) { String disable= createBlameMessage(); Object[] args= {operation}; String reason= Messages.format(JavaTextMessages.CompletionProposalComputerDescriptor_reason_API, args); return new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, disable + " " + reason, null); //$NON-NLS-1$ } private Status createPerformanceStatus(String operation) { String disable= createBlameMessage(); Object[] args= {operation}; String reason= Messages.format(JavaTextMessages.CompletionProposalComputerDescriptor_reason_performance, args); return new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, disable + " " + reason, null); //$NON-NLS-1$ } private String createBlameMessage() { Object[] args= { getName(), getId() }; String disable= Messages.format(JavaTextMessages.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; } }