1 /*******************************************************************************
2 * Copyright (c) 2005, 2012 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * IBM Corporation - initial API and implementation
10 * Marcel Bruch <bruch@cs.tu-darmstadt.de> - [content assist] Allow to re-sort proposals - https://bugs.eclipse.org/bugs/show_bug.cgi?id=350991
11 *******************************************************************************/
12 package org.eclipse.jdt.internal.ui.text.java;
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.List;
20 import org.osgi.framework.Bundle;
22 import org.eclipse.core.runtime.Assert;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.IConfigurationElement;
25 import org.eclipse.core.runtime.IContributor;
26 import org.eclipse.core.runtime.IExtension;
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.IStatus;
29 import org.eclipse.core.runtime.InvalidRegistryObjectException;
30 import org.eclipse.core.runtime.PerformanceStats;
31 import org.eclipse.core.runtime.Platform;
32 import org.eclipse.core.runtime.Status;
34 import org.eclipse.jface.text.IDocument;
35 import org.eclipse.jface.text.contentassist.ICompletionProposal;
36 import org.eclipse.jface.text.contentassist.IContextInformation;
38 import org.eclipse.jdt.internal.corext.util.Messages;
40 import org.eclipse.jdt.ui.text.IJavaPartitions;
41 import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
42 import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer;
44 import org.eclipse.jdt.internal.ui.JavaPlugin;
47 * The description of an extension to the
48 * <code>org.eclipse.jdt.ui.javaCompletionProposalComputer</code> extension point. Instances are
49 * immutable. Instances can be obtained from a {@link CompletionProposalComputerRegistry}.
51 * @see CompletionProposalComputerRegistry
54 final class CompletionProposalComputerDescriptor {
55 /** The default category id. */
56 private static final String DEFAULT_CATEGORY_ID= "org.eclipse.jdt.ui.defaultProposalCategory"; //$NON-NLS-1$
57 /** The extension schema name of the category id attribute. */
58 private static final String CATEGORY_ID= "categoryId"; //$NON-NLS-1$
59 /** The extension schema name of the partition type attribute. */
60 private static final String TYPE= "type"; //$NON-NLS-1$
61 /** The extension schema name of the class attribute. */
62 private static final String CLASS= "class"; //$NON-NLS-1$
63 /** The extension schema name of the activate attribute. */
64 private static final String ACTIVATE= "activate"; //$NON-NLS-1$
65 /** The extension schema name of the needsSortingAfterFiltering attribute. */
66 private static final String NEEDS_SORTING_AFTER_FILTERING= "needsSortingAfterFiltering"; //$NON-NLS-1$
67 /** The extension schema name of the partition child elements. */
68 private static final String PARTITION= "partition"; //$NON-NLS-1$
69 /** Set of Java partition types. */
70 private static final Set<String> PARTITION_SET;
71 /** The name of the performance event used to trace extensions. */
72 private static final String PERFORMANCE_EVENT= JavaPlugin.getPluginId() + "/perf/content_assist/extensions"; //$NON-NLS-1$
74 * If <code>true</code>, execution time of extensions is measured and the data forwarded to
75 * core's {@link PerformanceStats} service.
77 private static final boolean MEASURE_PERFORMANCE= PerformanceStats.isEnabled(PERFORMANCE_EVENT);
79 * Independently of the {@link PerformanceStats} service, any operation that takes longer than
80 * {@value} milliseconds will be flagged as an violation. This timeout does not apply to the
81 * first invocation, as it may take longer due to plug-in initialization etc. See also
82 * {@link #fIsReportingDelay}.
84 private static final long MAX_DELAY= 5000;
87 private static final String COMPUTE_COMPLETION_PROPOSALS= "computeCompletionProposals()"; //$NON-NLS-1$
88 private static final String COMPUTE_CONTEXT_INFORMATION= "computeContextInformation()"; //$NON-NLS-1$
89 private static final String SESSION_STARTED= "sessionStarted()"; //$NON-NLS-1$
90 private static final String SESSION_ENDED= "sessionEnded()"; //$NON-NLS-1$
93 Set<String> partitions= new HashSet<String>();
94 partitions.add(IDocument.DEFAULT_CONTENT_TYPE);
95 partitions.add(IJavaPartitions.JAVA_DOC);
96 partitions.add(IJavaPartitions.JAVA_MULTI_LINE_COMMENT);
97 partitions.add(IJavaPartitions.JAVA_SINGLE_LINE_COMMENT);
98 partitions.add(IJavaPartitions.JAVA_STRING);
99 partitions.add(IJavaPartitions.JAVA_CHARACTER);
101 PARTITION_SET= Collections.unmodifiableSet(partitions);
104 /** The identifier of the extension. */
105 private final String fId;
106 /** The name of the extension. */
107 private final String fName;
108 /** The class name of the provided <code>IJavaCompletionProposalComputer</code>. */
109 private final String fClass;
110 /** The activate attribute value. */
111 private final boolean fActivate;
112 /** The partition of the extension (element type: {@link String}). */
113 private final Set<String> fPartitions;
114 /** The configuration element of this extension. */
115 private final IConfigurationElement fElement;
116 /** The registry we are registered with. */
117 private final CompletionProposalComputerRegistry fRegistry;
118 /** The computer, if instantiated, <code>null</code> otherwise. */
119 private IJavaCompletionProposalComputer fComputer;
120 /** The ui category. */
121 private final CompletionProposalCategory fCategory;
122 /** The first error message in the most recent operation, or <code>null</code>. */
123 private String fLastError;
125 * Tells whether to inform the user when <code>MAX_DELAY</code> has been exceeded.
126 * We start timing execution after the first session because the first may take
127 * longer due to plug-in activation and initialization.
129 private boolean fIsReportingDelay= false;
130 /** The start of the last operation. */
133 * Tells whether we tried to load the computer.
136 boolean fTriedLoadingComputer= false;
139 * Tells whether this proposal engine provides dynamic content that needs to be sorted after its
140 * proposal have been filtered. Filtering happens, e.g., when a user continues typing with an
141 * open completion window.
145 private boolean fNeedsSortingAfterFiltering;
149 * Creates a new descriptor.
151 * @param element the configuration element to read
152 * @param registry the computer registry creating this descriptor
153 * @param categories the categories
154 * @throws InvalidRegistryObjectException if this extension is no longer valid
155 * @throws CoreException if the extension contains invalid values
157 CompletionProposalComputerDescriptor(IConfigurationElement element, CompletionProposalComputerRegistry registry, List<CompletionProposalCategory> categories) throws InvalidRegistryObjectException, CoreException {
158 Assert.isLegal(registry != null);
159 Assert.isLegal(element != null);
163 IExtension extension= element.getDeclaringExtension();
164 fId= extension.getUniqueIdentifier();
165 checkNotNull(fId, "id"); //$NON-NLS-1$
167 String name= extension.getLabel();
168 if (name.length() == 0)
173 Set<String> partitions= new HashSet<String>();
174 IConfigurationElement[] children= element.getChildren(PARTITION);
175 if (children.length == 0) {
176 fPartitions= PARTITION_SET; // add to all partition types if no partition is configured
178 for (int i= 0; i < children.length; i++) {
179 String type= children[i].getAttribute(TYPE);
180 checkNotNull(type, TYPE);
181 partitions.add(type);
183 fPartitions= Collections.unmodifiableSet(partitions);
186 String activateAttribute= element.getAttribute(ACTIVATE);
187 fActivate= Boolean.valueOf(activateAttribute).booleanValue();
189 String needsSortingAfterFilteringAttribute= element.getAttribute(NEEDS_SORTING_AFTER_FILTERING);
190 fNeedsSortingAfterFiltering= Boolean.valueOf(needsSortingAfterFilteringAttribute).booleanValue();
192 fClass= element.getAttribute(CLASS);
193 checkNotNull(fClass, CLASS);
195 String categoryId= element.getAttribute(CATEGORY_ID);
196 if (categoryId == null)
197 categoryId= DEFAULT_CATEGORY_ID;
198 CompletionProposalCategory category= null;
199 for (Iterator<CompletionProposalCategory> it= categories.iterator(); it.hasNext();) {
200 CompletionProposalCategory cat= it.next();
201 if (cat.getId().equals(categoryId)) {
206 if (category == null) {
207 // create a category if it does not exist
208 fCategory= new CompletionProposalCategory(categoryId, fName, registry);
209 categories.add(fCategory);
216 * Checks that the given attribute value is not <code>null</code>.
218 * @param value the object to check if not null
219 * @param attribute the attribute
220 * @throws InvalidRegistryObjectException if the registry element is no longer valid
221 * @throws CoreException if <code>value</code> is <code>null</code>
223 private void checkNotNull(Object value, String attribute) throws InvalidRegistryObjectException, CoreException {
225 Object[] args= { getId(), fElement.getContributor().getName(), attribute };
226 String message= Messages.format(JavaTextMessages.CompletionProposalComputerDescriptor_illegal_attribute_message, args);
227 IStatus status= new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, message, null);
228 throw new CoreException(status);
233 * Returns the identifier of the described extension.
235 * @return Returns the id
237 public String getId() {
242 * Returns the name of the described extension.
244 * @return Returns the name
246 public String getName() {
251 * Returns the partition types of the described extension.
253 * @return the set of partition types (element type: {@link String})
255 public Set<String> getPartitions() {
260 * Returns a cached instance of the computer as described in the
261 * extension's xml. If the computer is not yet created and
262 * <code>canCreate</code> is <code>true</code> then {@link #createComputer()}
263 * is called and the result cached.
265 * @param canCreate <code>true</code> if the proposal computer can be created
266 * @return a new instance of the completion proposal computer as
267 * described by this descriptor
268 * @throws CoreException if the creation fails
269 * @throws InvalidRegistryObjectException if the extension is not
270 * valid any longer (e.g. due to plug-in unloading)
272 private synchronized IJavaCompletionProposalComputer getComputer(boolean canCreate) throws CoreException, InvalidRegistryObjectException {
273 if (fComputer == null && canCreate && !fTriedLoadingComputer && (fActivate || isPluginLoaded())) {
274 fTriedLoadingComputer= true;
275 fComputer= createComputer();
280 private boolean isPluginLoaded() {
281 Bundle bundle= getBundle();
282 return bundle != null && bundle.getState() == Bundle.ACTIVE;
285 private Bundle getBundle() {
286 String namespace= fElement.getDeclaringExtension().getContributor().getName();
287 Bundle bundle= Platform.getBundle(namespace);
292 * Returns a new instance of the computer as described in the
293 * extension's xml. Note that the safest way to access the computer
295 * {@linkplain #computeCompletionProposals(ContentAssistInvocationContext, IProgressMonitor) computeCompletionProposals}
297 * {@linkplain #computeContextInformation(ContentAssistInvocationContext, IProgressMonitor) computeContextInformation}
298 * methods. These delegate the functionality to the contributed
299 * computer, but handle instance creation and any exceptions thrown.
301 * @return a new instance of the completion proposal computer as
302 * described by this descriptor
303 * @throws CoreException if the creation fails
304 * @throws InvalidRegistryObjectException if the extension is not
305 * valid any longer (e.g. due to plug-in unloading)
307 public IJavaCompletionProposalComputer createComputer() throws CoreException, InvalidRegistryObjectException {
308 return (IJavaCompletionProposalComputer) fElement.createExecutableExtension(CLASS);
312 * Safely computes completion proposals through the described extension. If the extension
313 * is disabled, throws an exception or otherwise does not adhere to the contract described in
314 * {@link IJavaCompletionProposalComputer}, an empty list is returned.
316 * @param context the invocation context passed on to the extension
317 * @param monitor the progress monitor passed on to the extension
318 * @return the list of computed completion proposals (element type:
319 * {@link org.eclipse.jface.text.contentassist.ICompletionProposal})
321 public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {
323 return Collections.emptyList();
327 IJavaCompletionProposalComputer computer= getComputer(true);
328 if (computer == null) // not active yet
329 return Collections.emptyList();
332 PerformanceStats stats= startMeter(context, computer);
333 List<ICompletionProposal> proposals= computer.computeCompletionProposals(context, monitor);
334 stopMeter(stats, COMPUTE_COMPLETION_PROPOSALS);
336 if (proposals != null) {
337 fLastError= computer.getErrorMessage();
341 fIsReportingDelay= true;
343 status= createAPIViolationStatus(COMPUTE_COMPLETION_PROPOSALS);
344 } catch (InvalidRegistryObjectException x) {
345 status= createExceptionStatus(x);
346 } catch (CoreException x) {
347 status= createExceptionStatus(x);
348 } catch (RuntimeException x) {
349 status= createExceptionStatus(x);
354 fRegistry.informUser(this, status);
356 return Collections.emptyList();
360 * Safely computes context information objects through the described extension. If the extension
361 * is disabled, throws an exception or otherwise does not adhere to the contract described in
362 * {@link IJavaCompletionProposalComputer}, an empty list is returned.
364 * @param context the invocation context passed on to the extension
365 * @param monitor the progress monitor passed on to the extension
366 * @return the list of computed context information objects (element type:
367 * {@link org.eclipse.jface.text.contentassist.IContextInformation})
369 public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
371 return Collections.emptyList();
375 IJavaCompletionProposalComputer computer= getComputer(true);
376 if (computer == null) // not active yet
377 return Collections.emptyList();
379 PerformanceStats stats= startMeter(context, computer);
380 List<IContextInformation> proposals= computer.computeContextInformation(context, monitor);
381 stopMeter(stats, COMPUTE_CONTEXT_INFORMATION);
383 if (proposals != null) {
384 fLastError= computer.getErrorMessage();
388 status= createAPIViolationStatus(COMPUTE_CONTEXT_INFORMATION);
389 } catch (InvalidRegistryObjectException x) {
390 status= createExceptionStatus(x);
391 } catch (CoreException x) {
392 status= createExceptionStatus(x);
393 } catch (RuntimeException x) {
394 status= createExceptionStatus(x);
399 fRegistry.informUser(this, status);
401 return Collections.emptyList();
406 * Notifies the described extension of a proposal computation session start.
408 * Note: This method is called every time code assist is invoked and
409 * is <strong>not</strong> filtered by partition type.
412 public void sessionStarted() {
418 IJavaCompletionProposalComputer computer= getComputer(true);
419 if (computer == null) // not active yet
422 PerformanceStats stats= startMeter(SESSION_STARTED, computer);
423 computer.sessionStarted();
424 stopMeter(stats, SESSION_ENDED);
427 } catch (InvalidRegistryObjectException x) {
428 status= createExceptionStatus(x);
429 } catch (CoreException x) {
430 status= createExceptionStatus(x);
431 } catch (RuntimeException x) {
432 status= createExceptionStatus(x);
435 fRegistry.informUser(this, status);
439 * Notifies the described extension of a proposal computation session end.
441 * Note: This method is called every time code assist is invoked and
442 * is <strong>not</strong> filtered by partition type.
445 public void sessionEnded() {
451 IJavaCompletionProposalComputer computer= getComputer(false);
452 if (computer == null) // not active yet
455 PerformanceStats stats= startMeter(SESSION_ENDED, computer);
456 computer.sessionEnded();
457 stopMeter(stats, SESSION_ENDED);
460 } catch (InvalidRegistryObjectException x) {
461 status= createExceptionStatus(x);
462 } catch (CoreException x) {
463 status= createExceptionStatus(x);
464 } catch (RuntimeException x) {
465 status= createExceptionStatus(x);
468 fRegistry.informUser(this, status);
471 private PerformanceStats startMeter(Object context, IJavaCompletionProposalComputer computer) {
472 final PerformanceStats stats;
473 if (MEASURE_PERFORMANCE) {
474 stats= PerformanceStats.getStats(PERFORMANCE_EVENT, computer);
475 stats.startRun(context.toString());
480 if (fIsReportingDelay) {
481 fStart= System.currentTimeMillis();
487 private void stopMeter(final PerformanceStats stats, String operation) {
488 if (MEASURE_PERFORMANCE) {
490 if (stats.isFailure()) {
491 IStatus status= createPerformanceStatus(operation);
492 fRegistry.informUser(this, status);
497 if (fIsReportingDelay) {
498 long current= System.currentTimeMillis();
499 if (current - fStart > MAX_DELAY) {
500 IStatus status= createPerformanceStatus(operation);
501 fRegistry.informUser(this, status);
506 private IStatus createExceptionStatus(InvalidRegistryObjectException x) {
507 // extension has become invalid - log & disable
508 String blame= createBlameMessage();
509 String reason= JavaTextMessages.CompletionProposalComputerDescriptor_reason_invalid;
510 return new Status(IStatus.INFO, JavaPlugin.getPluginId(), IStatus.OK, blame + " " + reason, x); //$NON-NLS-1$
513 private IStatus createExceptionStatus(CoreException x) {
514 // unable to instantiate the extension - log
515 String blame= createBlameMessage();
516 String reason= JavaTextMessages.CompletionProposalComputerDescriptor_reason_instantiation;
517 return new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, blame + " " + reason, x); //$NON-NLS-1$
520 private IStatus createExceptionStatus(RuntimeException x) {
521 // misbehaving extension - log
522 String blame= createBlameMessage();
523 String reason= JavaTextMessages.CompletionProposalComputerDescriptor_reason_runtime_ex;
524 return new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, blame + " " + reason, x); //$NON-NLS-1$
527 private IStatus createAPIViolationStatus(String operation) {
528 String blame= createBlameMessage();
529 Object[] args= {operation};
530 String reason= Messages.format(JavaTextMessages.CompletionProposalComputerDescriptor_reason_API, args);
531 return new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, blame + " " + reason, null); //$NON-NLS-1$
534 private IStatus createPerformanceStatus(String operation) {
535 String blame= createBlameMessage();
536 Object[] args= {operation};
537 String reason= Messages.format(JavaTextMessages.CompletionProposalComputerDescriptor_reason_performance, args);
538 return new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, blame + " " + reason, null); //$NON-NLS-1$
541 private String createBlameMessage() {
542 Object[] args= { getName(), fElement.getDeclaringExtension().getContributor().getName() };
543 String disable= Messages.format( JavaTextMessages.CompletionProposalComputerDescriptor_blame_message, args);
548 * Returns the enablement state of the described extension.
550 * @return the enablement state of the described extension
552 private boolean isEnabled() {
553 return fCategory.isEnabled();
556 CompletionProposalCategory getCategory() {
561 * Returns the error message from the described extension.
563 * @return the error message from the described extension
565 public String getErrorMessage() {
570 * Returns the contributor of the described extension.
572 * @return the contributor of the described extension
574 IContributor getContributor() {
576 return fElement.getContributor();
577 } catch (InvalidRegistryObjectException e) {
583 * Returns the <code>needsSortingAfterFiltering</code> flag of the described extension.
585 * @return the needsSortingAfterFiltering flag of the described extension
588 public boolean isSortingAfterFilteringNeeded() {
589 return fNeedsSortingAfterFiltering;