001: /*******************************************************************************
002: * Copyright (c) 2005, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.ui.text.java;
011:
012: import java.util.Collections;
013: import java.util.List;
014:
015: import org.eclipse.core.runtime.Assert;
016: import org.eclipse.core.runtime.CoreException;
017: import org.eclipse.core.runtime.IConfigurationElement;
018: import org.eclipse.core.runtime.IStatus;
019: import org.eclipse.core.runtime.InvalidRegistryObjectException;
020: import org.eclipse.core.runtime.PerformanceStats;
021: import org.eclipse.core.runtime.Platform;
022: import org.eclipse.core.runtime.Status;
023:
024: import org.eclipse.jdt.internal.corext.util.Messages;
025:
026: import org.eclipse.jdt.ui.text.java.AbstractProposalSorter;
027: import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
028:
029: import org.eclipse.jdt.internal.ui.JavaPlugin;
030:
031: import org.osgi.framework.Bundle;
032:
033: /**
034: * The description of an extension to the
035: * <code>org.eclipse.jdt.ui.javaCompletionProposalSorters</code> extension point. Instances are
036: * immutable.
037: *
038: * @since 3.2
039: */
040: public final class ProposalSorterHandle {
041: /** The extension schema name of the id attribute. */
042: private static final String ID = "id"; //$NON-NLS-1$
043: /** The extension schema name of the name attribute. */
044: private static final String NAME = "name"; //$NON-NLS-1$
045: /** The extension schema name of the class attribute. */
046: private static final String CLASS = "class"; //$NON-NLS-1$
047: /** The extension schema name of the activate attribute. */
048: private static final String ACTIVATE = "activate"; //$NON-NLS-1$
049: /** The name of the performance event used to trace extensions. */
050: private static final String PERFORMANCE_EVENT = JavaPlugin
051: .getPluginId()
052: + "/perf/content_assist_sorters/extensions"; //$NON-NLS-1$
053: /**
054: * If <code>true</code>, execution time of extensions is measured and extensions may be
055: * disabled if execution takes too long.
056: */
057: private static final boolean MEASURE_PERFORMANCE = PerformanceStats
058: .isEnabled(PERFORMANCE_EVENT);
059: /** The one and only operation name. */
060: private static final String SORT = "sort"; //$NON-NLS-1$
061:
062: /** The identifier of the extension. */
063: private final String fId;
064: /** The name of the extension. */
065: private final String fName;
066: /** The class name of the provided <code>AbstractProposalSorter</code>. */
067: private final String fClass;
068: /** The activate attribute value. */
069: private final boolean fActivate;
070: /** The configuration element of this extension. */
071: private final IConfigurationElement fElement;
072: /** The computer, if instantiated, <code>null</code> otherwise. */
073: private AbstractProposalSorter fSorter;
074:
075: /**
076: * Creates a new descriptor.
077: *
078: * @param element the configuration element to read
079: * @throws InvalidRegistryObjectException if the configuration element is not valid any longer
080: * or does not contain mandatory attributes
081: */
082: ProposalSorterHandle(IConfigurationElement element)
083: throws InvalidRegistryObjectException {
084: Assert.isLegal(element != null);
085:
086: fElement = element;
087: fId = element.getAttribute(ID);
088: checkNotNull(fId, ID);
089:
090: String name = element.getAttribute(NAME);
091: if (name == null)
092: fName = fId;
093: else
094: fName = name;
095:
096: String activateAttribute = element.getAttribute(ACTIVATE);
097: fActivate = Boolean.valueOf(activateAttribute).booleanValue();
098:
099: fClass = element.getAttribute(CLASS);
100: checkNotNull(fClass, CLASS);
101: }
102:
103: /**
104: * Checks an element that must be defined according to the extension
105: * point schema. Throws an
106: * <code>InvalidRegistryObjectException</code> if <code>obj</code>
107: * is <code>null</code>.
108: *
109: * @param obj the object to check if not null
110: * @param attribute the attribute
111: * @throws InvalidRegistryObjectException
112: */
113: private void checkNotNull(Object obj, String attribute)
114: throws InvalidRegistryObjectException {
115: if (obj == null) {
116: Object[] args = { getId(),
117: fElement.getContributor().getName(), attribute };
118: String message = Messages
119: .format(
120: JavaTextMessages.CompletionProposalComputerDescriptor_illegal_attribute_message,
121: args);
122: IStatus status = new Status(IStatus.WARNING, JavaPlugin
123: .getPluginId(), IStatus.OK, message, null);
124: JavaPlugin.log(status);
125: throw new InvalidRegistryObjectException();
126: }
127: }
128:
129: /**
130: * Returns the identifier of the described extension.
131: *
132: * @return Returns the id
133: */
134: public String getId() {
135: return fId;
136: }
137:
138: /**
139: * Returns the name of the described extension.
140: *
141: * @return Returns the name
142: */
143: public String getName() {
144: return fName;
145: }
146:
147: /**
148: * Returns a cached instance of the sorter as described in the extension's xml. The sorter is
149: * {@link #createSorter() created} the first time that this method is called and then cached.
150: *
151: * @return a new instance of the proposal sorter as described by this descriptor
152: * @throws CoreException if the creation fails
153: * @throws InvalidRegistryObjectException if the extension is not valid any longer (e.g. due to
154: * plug-in unloading)
155: */
156: private synchronized AbstractProposalSorter getSorter()
157: throws CoreException, InvalidRegistryObjectException {
158: if (fSorter == null && (fActivate || isPluginLoaded()))
159: fSorter = createSorter();
160: return fSorter;
161: }
162:
163: private boolean isPluginLoaded()
164: throws InvalidRegistryObjectException {
165: Bundle bundle = getBundle();
166: return bundle != null && bundle.getState() == Bundle.ACTIVE;
167: }
168:
169: private Bundle getBundle() throws InvalidRegistryObjectException {
170: String symbolicName = fElement.getContributor().getName();
171: Bundle bundle = Platform.getBundle(symbolicName);
172: return bundle;
173: }
174:
175: /**
176: * Returns a new instance of the sorter as described in the
177: * extension's xml.
178: *
179: * @return a new instance of the completion proposal computer as
180: * described by this descriptor
181: * @throws CoreException if the creation fails
182: * @throws InvalidRegistryObjectException if the extension is not
183: * valid any longer (e.g. due to plug-in unloading)
184: */
185: private AbstractProposalSorter createSorter() throws CoreException,
186: InvalidRegistryObjectException {
187: return (AbstractProposalSorter) fElement
188: .createExecutableExtension(CLASS);
189: }
190:
191: /**
192: * Safely computes completion proposals through the described extension. If the extension throws
193: * an exception or otherwise does not adhere to the contract described in
194: * {@link AbstractProposalSorter}, the list is returned as is.
195: *
196: * @param context the invocation context passed on to the extension
197: * @param proposals the list of computed completion proposals to be sorted (element type:
198: * {@link org.eclipse.jface.text.contentassist.ICompletionProposal}), must be writable
199: */
200: public void sortProposals(ContentAssistInvocationContext context,
201: List proposals) {
202: IStatus status;
203: try {
204: AbstractProposalSorter sorter = getSorter();
205:
206: PerformanceStats stats = startMeter(SORT, sorter);
207:
208: sorter.beginSorting(context);
209: Collections.sort(proposals, sorter);
210: sorter.endSorting();
211:
212: status = stopMeter(stats, SORT);
213:
214: // valid result
215: if (status == null)
216: return;
217:
218: status = createAPIViolationStatus(SORT);
219:
220: } catch (InvalidRegistryObjectException x) {
221: status = createExceptionStatus(x);
222: } catch (CoreException x) {
223: status = createExceptionStatus(x);
224: } catch (RuntimeException x) {
225: status = createExceptionStatus(x);
226: }
227:
228: JavaPlugin.log(status);
229: return;
230: }
231:
232: private IStatus stopMeter(final PerformanceStats stats,
233: String operation) {
234: if (MEASURE_PERFORMANCE) {
235: stats.endRun();
236: if (stats.isFailure())
237: return createPerformanceStatus(operation);
238: }
239: return null;
240: }
241:
242: private PerformanceStats startMeter(String context,
243: AbstractProposalSorter sorter) {
244: final PerformanceStats stats;
245: if (MEASURE_PERFORMANCE) {
246: stats = PerformanceStats
247: .getStats(PERFORMANCE_EVENT, sorter);
248: stats.startRun(context);
249: } else {
250: stats = null;
251: }
252: return stats;
253: }
254:
255: private Status createExceptionStatus(
256: InvalidRegistryObjectException x) {
257: // extension has become invalid - log & disable
258: String disable = createBlameMessage();
259: String reason = JavaTextMessages.CompletionProposalComputerDescriptor_reason_invalid;
260: return new Status(IStatus.INFO, JavaPlugin.getPluginId(),
261: IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$
262: }
263:
264: private Status createExceptionStatus(CoreException x) {
265: // unable to instantiate the extension - log & disable
266: String disable = createBlameMessage();
267: String reason = JavaTextMessages.CompletionProposalComputerDescriptor_reason_instantiation;
268: return new Status(IStatus.ERROR, JavaPlugin.getPluginId(),
269: IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$
270: }
271:
272: private Status createExceptionStatus(RuntimeException x) {
273: // misbehaving extension - log & disable
274: String disable = createBlameMessage();
275: String reason = JavaTextMessages.CompletionProposalComputerDescriptor_reason_runtime_ex;
276: return new Status(IStatus.WARNING, JavaPlugin.getPluginId(),
277: IStatus.OK, disable + " " + reason, x); //$NON-NLS-1$
278: }
279:
280: private Status createAPIViolationStatus(String operation) {
281: String disable = createBlameMessage();
282: Object[] args = { operation };
283: String reason = Messages
284: .format(
285: JavaTextMessages.CompletionProposalComputerDescriptor_reason_API,
286: args);
287: return new Status(IStatus.WARNING, JavaPlugin.getPluginId(),
288: IStatus.OK, disable + " " + reason, null); //$NON-NLS-1$
289: }
290:
291: private Status createPerformanceStatus(String operation) {
292: String disable = createBlameMessage();
293: Object[] args = { operation };
294: String reason = Messages
295: .format(
296: JavaTextMessages.CompletionProposalComputerDescriptor_reason_performance,
297: args);
298: return new Status(IStatus.WARNING, JavaPlugin.getPluginId(),
299: IStatus.OK, disable + " " + reason, null); //$NON-NLS-1$
300: }
301:
302: private String createBlameMessage() {
303: Object[] args = { getName(), getId() };
304: String disable = Messages.format(
305: JavaTextMessages.ProposalSorterHandle_blame, args);
306: return disable;
307: }
308:
309: /**
310: * Returns the error message from the described extension, <code>null</code> for no error.
311: *
312: * @return the error message from the described extension, <code>null</code> for no error
313: */
314: public String getErrorMessage() {
315: return null;
316: }
317:
318: }
|