001: /*******************************************************************************
002: * Copyright (c) 2005, 2007 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.HashSet;
014: import java.util.Iterator;
015: import java.util.List;
016: import java.util.Set;
017:
018: import org.eclipse.core.runtime.Assert;
019: import org.eclipse.core.runtime.CoreException;
020: import org.eclipse.core.runtime.IConfigurationElement;
021: import org.eclipse.core.runtime.IContributor;
022: import org.eclipse.core.runtime.IExtension;
023: import org.eclipse.core.runtime.IProgressMonitor;
024: import org.eclipse.core.runtime.IStatus;
025: import org.eclipse.core.runtime.InvalidRegistryObjectException;
026: import org.eclipse.core.runtime.PerformanceStats;
027: import org.eclipse.core.runtime.Platform;
028: import org.eclipse.core.runtime.Status;
029:
030: import org.eclipse.jface.text.IDocument;
031:
032: import org.eclipse.jdt.internal.corext.util.Messages;
033:
034: import org.eclipse.jdt.ui.text.IJavaPartitions;
035: import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
036: import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer;
037:
038: import org.eclipse.jdt.internal.ui.JavaPlugin;
039:
040: import org.osgi.framework.Bundle;
041:
042: /**
043: * The description of an extension to the
044: * <code>org.eclipse.jdt.ui.javaCompletionProposalComputer</code> extension point. Instances are
045: * immutable. Instances can be obtained from a {@link CompletionProposalComputerRegistry}.
046: *
047: * @see CompletionProposalComputerRegistry
048: * @since 3.2
049: */
050: final class CompletionProposalComputerDescriptor {
051: /** The default category id. */
052: private static final String DEFAULT_CATEGORY_ID = "org.eclipse.jdt.ui.defaultProposalCategory"; //$NON-NLS-1$
053: /** The extension schema name of the category id attribute. */
054: private static final String CATEGORY_ID = "categoryId"; //$NON-NLS-1$
055: /** The extension schema name of the partition type attribute. */
056: private static final String TYPE = "type"; //$NON-NLS-1$
057: /** The extension schema name of the class attribute. */
058: private static final String CLASS = "class"; //$NON-NLS-1$
059: /** The extension schema name of the activate attribute. */
060: private static final String ACTIVATE = "activate"; //$NON-NLS-1$
061: /** The extension schema name of the partition child elements. */
062: private static final String PARTITION = "partition"; //$NON-NLS-1$
063: /** Set of Java partition types. */
064: private static final Set PARTITION_SET;
065: /** The name of the performance event used to trace extensions. */
066: private static final String PERFORMANCE_EVENT = JavaPlugin
067: .getPluginId()
068: + "/perf/content_assist/extensions"; //$NON-NLS-1$
069: /**
070: * If <code>true</code>, execution time of extensions is measured and the data forwarded to
071: * core's {@link PerformanceStats} service.
072: */
073: private static final boolean MEASURE_PERFORMANCE = PerformanceStats
074: .isEnabled(PERFORMANCE_EVENT);
075: /**
076: * Independently of the {@link PerformanceStats} service, any operation that takes longer than
077: * {@value} milliseconds will be flagged as an violation. This timeout does not apply to the
078: * first invocation, as it may take longer due to plug-in initialization etc. See also
079: * {@link #fIsReportingDelay}.
080: */
081: private static final long MAX_DELAY = 5000;
082:
083: /* log constants */
084: private static final String COMPUTE_COMPLETION_PROPOSALS = "computeCompletionProposals()"; //$NON-NLS-1$
085: private static final String COMPUTE_CONTEXT_INFORMATION = "computeContextInformation()"; //$NON-NLS-1$
086: private static final String SESSION_STARTED = "sessionStarted()"; //$NON-NLS-1$
087: private static final String SESSION_ENDED = "sessionEnded()"; //$NON-NLS-1$
088:
089: static {
090: Set partitions = new HashSet();
091: partitions.add(IDocument.DEFAULT_CONTENT_TYPE);
092: partitions.add(IJavaPartitions.JAVA_DOC);
093: partitions.add(IJavaPartitions.JAVA_MULTI_LINE_COMMENT);
094: partitions.add(IJavaPartitions.JAVA_SINGLE_LINE_COMMENT);
095: partitions.add(IJavaPartitions.JAVA_STRING);
096: partitions.add(IJavaPartitions.JAVA_CHARACTER);
097:
098: PARTITION_SET = Collections.unmodifiableSet(partitions);
099: }
100:
101: /** The identifier of the extension. */
102: private final String fId;
103: /** The name of the extension. */
104: private final String fName;
105: /** The class name of the provided <code>IJavaCompletionProposalComputer</code>. */
106: private final String fClass;
107: /** The activate attribute value. */
108: private final boolean fActivate;
109: /** The partition of the extension (element type: {@link String}). */
110: private final Set fPartitions;
111: /** The configuration element of this extension. */
112: private final IConfigurationElement fElement;
113: /** The registry we are registered with. */
114: private final CompletionProposalComputerRegistry fRegistry;
115: /** The computer, if instantiated, <code>null</code> otherwise. */
116: private IJavaCompletionProposalComputer fComputer;
117: /** The ui category. */
118: private final CompletionProposalCategory fCategory;
119: /** The first error message in the most recent operation, or <code>null</code>. */
120: private String fLastError;
121: /**
122: * Tells whether to inform the user when <code>MAX_DELAY</code> has been exceeded.
123: * We start timing execution after the first session because the first may take
124: * longer due to plug-in activation and initialization.
125: */
126: private boolean fIsReportingDelay = false;
127: /** The start of the last operation. */
128: private long fStart;
129:
130: /**
131: * Creates a new descriptor.
132: *
133: * @param element the configuration element to read
134: * @param registry the computer registry creating this descriptor
135: * @param categories the categories
136: * @throws InvalidRegistryObjectException if this extension is no longer valid
137: */
138: CompletionProposalComputerDescriptor(IConfigurationElement element,
139: CompletionProposalComputerRegistry registry, List categories)
140: throws InvalidRegistryObjectException {
141: Assert.isLegal(registry != null);
142: Assert.isLegal(element != null);
143:
144: fRegistry = registry;
145: fElement = element;
146: IExtension extension = element.getDeclaringExtension();
147: fId = extension.getUniqueIdentifier();
148: checkNotNull(fId, "id"); //$NON-NLS-1$
149:
150: String name = extension.getLabel();
151: if (name.length() == 0)
152: fName = fId;
153: else
154: fName = name;
155:
156: Set partitions = new HashSet();
157: IConfigurationElement[] children = element
158: .getChildren(PARTITION);
159: if (children.length == 0) {
160: fPartitions = PARTITION_SET; // add to all partition types if no partition is configured
161: } else {
162: for (int i = 0; i < children.length; i++) {
163: String type = children[i].getAttribute(TYPE);
164: checkNotNull(type, TYPE);
165: partitions.add(type);
166: }
167: fPartitions = Collections.unmodifiableSet(partitions);
168: }
169:
170: String activateAttribute = element.getAttribute(ACTIVATE);
171: fActivate = Boolean.valueOf(activateAttribute).booleanValue();
172:
173: fClass = element.getAttribute(CLASS);
174: checkNotNull(fClass, CLASS);
175:
176: String categoryId = element.getAttribute(CATEGORY_ID);
177: if (categoryId == null)
178: categoryId = DEFAULT_CATEGORY_ID;
179: CompletionProposalCategory category = null;
180: for (Iterator it = categories.iterator(); it.hasNext();) {
181: CompletionProposalCategory cat = (CompletionProposalCategory) it
182: .next();
183: if (cat.getId().equals(categoryId)) {
184: category = cat;
185: break;
186: }
187: }
188: if (category == null) {
189: // create a category if it does not exist
190: fCategory = new CompletionProposalCategory(categoryId,
191: fName, registry);
192: categories.add(fCategory);
193: } else {
194: fCategory = category;
195: }
196: }
197:
198: /**
199: * Checks an element that must be defined according to the extension
200: * point schema.
201: *
202: * @param obj the element to be checked
203: * @param attribute the attribute
204: * @throws InvalidRegistryObjectException if <code>obj</code> is <code>null</code>
205: */
206: private void checkNotNull(Object obj, String attribute)
207: throws InvalidRegistryObjectException {
208: if (obj == null) {
209: Object[] args = { getId(),
210: fElement.getContributor().getName(), attribute };
211: String message = Messages
212: .format(
213: JavaTextMessages.CompletionProposalComputerDescriptor_illegal_attribute_message,
214: args);
215: IStatus status = new Status(IStatus.WARNING, JavaPlugin
216: .getPluginId(), IStatus.OK, message, null);
217: JavaPlugin.log(status);
218: throw new InvalidRegistryObjectException();
219: }
220: }
221:
222: /**
223: * Returns the identifier of the described extension.
224: *
225: * @return Returns the id
226: */
227: public String getId() {
228: return fId;
229: }
230:
231: /**
232: * Returns the name of the described extension.
233: *
234: * @return Returns the name
235: */
236: public String getName() {
237: return fName;
238: }
239:
240: /**
241: * Returns the partition types of the described extension.
242: *
243: * @return the set of partition types (element type: {@link String})
244: */
245: public Set getPartitions() {
246: return fPartitions;
247: }
248:
249: /**
250: * Returns a cached instance of the computer as described in the
251: * extension's xml. The computer is
252: * {@link #createComputer() created} the first time that this method
253: * is called and then cached.
254: *
255: * @return a new instance of the completion proposal computer as
256: * described by this descriptor
257: * @throws CoreException if the creation fails
258: * @throws InvalidRegistryObjectException if the extension is not
259: * valid any longer (e.g. due to plug-in unloading)
260: */
261: private synchronized IJavaCompletionProposalComputer getComputer()
262: throws CoreException, InvalidRegistryObjectException {
263: if (fComputer == null && (fActivate || isPluginLoaded()))
264: fComputer = createComputer();
265: return fComputer;
266: }
267:
268: private boolean isPluginLoaded() {
269: Bundle bundle = getBundle();
270: return bundle != null && bundle.getState() == Bundle.ACTIVE;
271: }
272:
273: private Bundle getBundle() {
274: String namespace = fElement.getDeclaringExtension()
275: .getContributor().getName();
276: Bundle bundle = Platform.getBundle(namespace);
277: return bundle;
278: }
279:
280: /**
281: * Returns a new instance of the computer as described in the
282: * extension's xml. Note that the safest way to access the computer
283: * is by using the
284: * {@linkplain #computeCompletionProposals(ContentAssistInvocationContext, IProgressMonitor) computeCompletionProposals}
285: * and
286: * {@linkplain #computeContextInformation(ContentAssistInvocationContext, IProgressMonitor) computeContextInformation}
287: * methods. These delegate the functionality to the contributed
288: * computer, but handle instance creation and any exceptions thrown.
289: *
290: * @return a new instance of the completion proposal computer as
291: * described by this descriptor
292: * @throws CoreException if the creation fails
293: * @throws InvalidRegistryObjectException if the extension is not
294: * valid any longer (e.g. due to plug-in unloading)
295: */
296: public IJavaCompletionProposalComputer createComputer()
297: throws CoreException, InvalidRegistryObjectException {
298: return (IJavaCompletionProposalComputer) fElement
299: .createExecutableExtension(CLASS);
300: }
301:
302: /**
303: * Safely computes completion proposals through the described extension. If the extension
304: * is disabled, throws an exception or otherwise does not adhere to the contract described in
305: * {@link IJavaCompletionProposalComputer}, an empty list is returned.
306: *
307: * @param context the invocation context passed on to the extension
308: * @param monitor the progress monitor passed on to the extension
309: * @return the list of computed completion proposals (element type:
310: * {@link org.eclipse.jface.text.contentassist.ICompletionProposal})
311: */
312: public List computeCompletionProposals(
313: ContentAssistInvocationContext context,
314: IProgressMonitor monitor) {
315: if (!isEnabled())
316: return Collections.EMPTY_LIST;
317:
318: IStatus status;
319: try {
320: IJavaCompletionProposalComputer computer = getComputer();
321: if (computer == null) // not active yet
322: return Collections.EMPTY_LIST;
323:
324: try {
325: PerformanceStats stats = startMeter(context, computer);
326: List proposals = computer.computeCompletionProposals(
327: context, monitor);
328: stopMeter(stats, COMPUTE_COMPLETION_PROPOSALS);
329:
330: if (proposals != null) {
331: fLastError = computer.getErrorMessage();
332: return proposals;
333: }
334: } finally {
335: fIsReportingDelay = true;
336: }
337: status = createAPIViolationStatus(COMPUTE_COMPLETION_PROPOSALS);
338: } catch (InvalidRegistryObjectException x) {
339: status = createExceptionStatus(x);
340: } catch (CoreException x) {
341: status = createExceptionStatus(x);
342: } catch (RuntimeException x) {
343: status = createExceptionStatus(x);
344: } finally {
345: monitor.done();
346: }
347:
348: fRegistry.informUser(this , status);
349:
350: return Collections.EMPTY_LIST;
351: }
352:
353: /**
354: * Safely computes context information objects through the described extension. If the extension
355: * is disabled, throws an exception or otherwise does not adhere to the contract described in
356: * {@link IJavaCompletionProposalComputer}, an empty list is returned.
357: *
358: * @param context the invocation context passed on to the extension
359: * @param monitor the progress monitor passed on to the extension
360: * @return the list of computed context information objects (element type:
361: * {@link org.eclipse.jface.text.contentassist.IContextInformation})
362: */
363: public List computeContextInformation(
364: ContentAssistInvocationContext context,
365: IProgressMonitor monitor) {
366: if (!isEnabled())
367: return Collections.EMPTY_LIST;
368:
369: IStatus status;
370: try {
371: IJavaCompletionProposalComputer computer = getComputer();
372: if (computer == null) // not active yet
373: return Collections.EMPTY_LIST;
374:
375: PerformanceStats stats = startMeter(context, computer);
376: List proposals = computer.computeContextInformation(
377: context, monitor);
378: stopMeter(stats, COMPUTE_CONTEXT_INFORMATION);
379:
380: if (proposals != null) {
381: fLastError = computer.getErrorMessage();
382: return proposals;
383: }
384:
385: status = createAPIViolationStatus(COMPUTE_CONTEXT_INFORMATION);
386: } catch (InvalidRegistryObjectException x) {
387: status = createExceptionStatus(x);
388: } catch (CoreException x) {
389: status = createExceptionStatus(x);
390: } catch (RuntimeException x) {
391: status = createExceptionStatus(x);
392: } finally {
393: monitor.done();
394: }
395:
396: fRegistry.informUser(this , status);
397:
398: return Collections.EMPTY_LIST;
399: }
400:
401: /**
402: * Notifies the described extension of a proposal computation session start.
403: * <p><em>
404: * Note: This method is called every time code assist is invoked and
405: * is <strong>not</strong> filtered by partition type.
406: * </em></p>
407: */
408: public void sessionStarted() {
409: if (!isEnabled())
410: return;
411:
412: IStatus status;
413: try {
414: IJavaCompletionProposalComputer computer = getComputer();
415: if (computer == null) // not active yet
416: return;
417:
418: PerformanceStats stats = startMeter(SESSION_STARTED,
419: computer);
420: computer.sessionStarted();
421: stopMeter(stats, SESSION_ENDED);
422:
423: return;
424: } catch (InvalidRegistryObjectException x) {
425: status = createExceptionStatus(x);
426: } catch (CoreException x) {
427: status = createExceptionStatus(x);
428: } catch (RuntimeException x) {
429: status = createExceptionStatus(x);
430: }
431:
432: fRegistry.informUser(this , status);
433: }
434:
435: /**
436: * Notifies the described extension of a proposal computation session end.
437: * <p><em>
438: * Note: This method is called every time code assist is invoked and
439: * is <strong>not</strong> filtered by partition type.
440: * </em></p>
441: */
442: public void sessionEnded() {
443: if (!isEnabled())
444: return;
445:
446: IStatus status;
447: try {
448: IJavaCompletionProposalComputer computer = getComputer();
449: if (computer == null) // not active yet
450: return;
451:
452: PerformanceStats stats = startMeter(SESSION_ENDED, computer);
453: computer.sessionEnded();
454: stopMeter(stats, SESSION_ENDED);
455:
456: return;
457: } catch (InvalidRegistryObjectException x) {
458: status = createExceptionStatus(x);
459: } catch (CoreException x) {
460: status = createExceptionStatus(x);
461: } catch (RuntimeException x) {
462: status = createExceptionStatus(x);
463: }
464:
465: fRegistry.informUser(this , status);
466: }
467:
468: private PerformanceStats startMeter(Object context,
469: IJavaCompletionProposalComputer computer) {
470: final PerformanceStats stats;
471: if (MEASURE_PERFORMANCE) {
472: stats = PerformanceStats.getStats(PERFORMANCE_EVENT,
473: computer);
474: stats.startRun(context.toString());
475: } else {
476: stats = null;
477: }
478:
479: if (fIsReportingDelay) {
480: fStart = System.currentTimeMillis();
481: }
482:
483: return stats;
484: }
485:
486: private void stopMeter(final PerformanceStats stats,
487: String operation) {
488: if (MEASURE_PERFORMANCE) {
489: stats.endRun();
490: if (stats.isFailure()) {
491: IStatus status = createPerformanceStatus(operation);
492: fRegistry.informUser(this , status);
493: return;
494: }
495: }
496:
497: if (fIsReportingDelay) {
498: long current = System.currentTimeMillis();
499: if (current - fStart > MAX_DELAY) {
500: IStatus status = createPerformanceStatus(operation);
501: fRegistry.informUser(this , status);
502: }
503: }
504: }
505:
506: private IStatus createExceptionStatus(
507: InvalidRegistryObjectException x) {
508: // extension has become invalid - log & disable
509: String blame = createBlameMessage();
510: String reason = JavaTextMessages.CompletionProposalComputerDescriptor_reason_invalid;
511: return new Status(IStatus.INFO, JavaPlugin.getPluginId(),
512: IStatus.OK, blame + " " + reason, x); //$NON-NLS-1$
513: }
514:
515: private IStatus createExceptionStatus(CoreException x) {
516: // unable to instantiate the extension - log & disable
517: String blame = createBlameMessage();
518: String reason = JavaTextMessages.CompletionProposalComputerDescriptor_reason_instantiation;
519: return new Status(IStatus.ERROR, JavaPlugin.getPluginId(),
520: IStatus.OK, blame + " " + reason, x); //$NON-NLS-1$
521: }
522:
523: private IStatus createExceptionStatus(RuntimeException x) {
524: // misbehaving extension - log & disable
525: String blame = createBlameMessage();
526: String reason = JavaTextMessages.CompletionProposalComputerDescriptor_reason_runtime_ex;
527: return new Status(IStatus.WARNING, JavaPlugin.getPluginId(),
528: IStatus.OK, blame + " " + reason, x); //$NON-NLS-1$
529: }
530:
531: private IStatus createAPIViolationStatus(String operation) {
532: String blame = createBlameMessage();
533: Object[] args = { operation };
534: String reason = Messages
535: .format(
536: JavaTextMessages.CompletionProposalComputerDescriptor_reason_API,
537: args);
538: return new Status(IStatus.WARNING, JavaPlugin.getPluginId(),
539: IStatus.OK, blame + " " + reason, null); //$NON-NLS-1$
540: }
541:
542: private IStatus createPerformanceStatus(String operation) {
543: String blame = createBlameMessage();
544: Object[] args = { operation };
545: String reason = Messages
546: .format(
547: JavaTextMessages.CompletionProposalComputerDescriptor_reason_performance,
548: args);
549: return new Status(IStatus.WARNING, JavaPlugin.getPluginId(),
550: IStatus.OK, blame + " " + reason, null); //$NON-NLS-1$
551: }
552:
553: private String createBlameMessage() {
554: Object[] args = {
555: getName(),
556: fElement.getDeclaringExtension().getContributor()
557: .getName() };
558: String disable = Messages
559: .format(
560: JavaTextMessages.CompletionProposalComputerDescriptor_blame_message,
561: args);
562: return disable;
563: }
564:
565: /**
566: * Returns the enablement state of the described extension.
567: *
568: * @return the enablement state of the described extension
569: */
570: private boolean isEnabled() {
571: return fCategory.isEnabled();
572: }
573:
574: CompletionProposalCategory getCategory() {
575: return fCategory;
576: }
577:
578: /**
579: * Returns the error message from the described extension.
580: *
581: * @return the error message from the described extension
582: */
583: public String getErrorMessage() {
584: return fLastError;
585: }
586:
587: /**
588: * Returns the contributor of the described extension.
589: *
590: * @return the contributor of the described extension
591: */
592: IContributor getContributor() {
593: try {
594: return fElement.getContributor();
595: } catch (InvalidRegistryObjectException e) {
596: return null;
597: }
598: }
599:
600: }
|