]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-before/ui/org/eclipse/jdt/internal/ui/text/java/CompletionProposalComputerDescriptor.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-before / ui / org / eclipse / jdt / internal / ui / text / java / CompletionProposalComputerDescriptor.java
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
7  *
8  * Contributors:
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;
13
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Set;
19
20 import org.osgi.framework.Bundle;
21
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;
33
34 import org.eclipse.jface.text.IDocument;
35 import org.eclipse.jface.text.contentassist.ICompletionProposal;
36 import org.eclipse.jface.text.contentassist.IContextInformation;
37
38 import org.eclipse.jdt.internal.corext.util.Messages;
39
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;
43
44 import org.eclipse.jdt.internal.ui.JavaPlugin;
45
46 /**
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}.
50  *
51  * @see CompletionProposalComputerRegistry
52  * @since 3.2
53  */
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$
73         /**
74          * If <code>true</code>, execution time of extensions is measured and the data forwarded to
75          * core's {@link PerformanceStats} service.
76          */
77         private static final boolean MEASURE_PERFORMANCE= PerformanceStats.isEnabled(PERFORMANCE_EVENT);
78         /**
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}.
83          */
84         private static final long MAX_DELAY= 5000;
85
86         /* log constants */
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$
91
92         static {
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);
100
101                 PARTITION_SET= Collections.unmodifiableSet(partitions);
102         }
103
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;
124         /**
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.
128          */
129         private boolean fIsReportingDelay= false;
130         /** The start of the last operation. */
131         private long fStart;
132         /**
133          * Tells whether we tried to load the computer.
134          * @since 3.4
135          */
136         boolean fTriedLoadingComputer= false;
137         
138         /**
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.
142          * 
143          * @since 3.8
144          */
145         private boolean fNeedsSortingAfterFiltering;
146
147
148         /**
149          * Creates a new descriptor.
150          *
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
156          */
157         CompletionProposalComputerDescriptor(IConfigurationElement element, CompletionProposalComputerRegistry registry, List<CompletionProposalCategory> categories) throws InvalidRegistryObjectException, CoreException {
158                 Assert.isLegal(registry != null);
159                 Assert.isLegal(element != null);
160
161                 fRegistry= registry;
162                 fElement= element;
163                 IExtension extension= element.getDeclaringExtension();
164                 fId= extension.getUniqueIdentifier();
165                 checkNotNull(fId, "id"); //$NON-NLS-1$
166
167                 String name= extension.getLabel();
168                 if (name.length() == 0)
169                         fName= fId;
170                 else
171                         fName= name;
172
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
177                 } else {
178                         for (int i= 0; i < children.length; i++) {
179                                 String type= children[i].getAttribute(TYPE);
180                                 checkNotNull(type, TYPE);
181                                 partitions.add(type);
182                         }
183                         fPartitions= Collections.unmodifiableSet(partitions);
184                 }
185
186                 String activateAttribute= element.getAttribute(ACTIVATE);
187                 fActivate= Boolean.valueOf(activateAttribute).booleanValue();
188
189                 String needsSortingAfterFilteringAttribute= element.getAttribute(NEEDS_SORTING_AFTER_FILTERING);
190                 fNeedsSortingAfterFiltering= Boolean.valueOf(needsSortingAfterFilteringAttribute).booleanValue();
191
192                 fClass= element.getAttribute(CLASS);
193                 checkNotNull(fClass, CLASS);
194
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)) {
202                                 category= cat;
203                                 break;
204                         }
205                 }
206                 if (category == null) {
207                         // create a category if it does not exist
208                         fCategory= new CompletionProposalCategory(categoryId, fName, registry);
209                         categories.add(fCategory);
210                 } else {
211                         fCategory= category;
212                 }
213         }
214
215         /**
216          * Checks that the given attribute value is not <code>null</code>.
217          *
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>
222          */
223         private void checkNotNull(Object value, String attribute) throws InvalidRegistryObjectException, CoreException {
224                 if (value == null) {
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);
229                 }
230         }
231
232         /**
233          * Returns the identifier of the described extension.
234          *
235          * @return Returns the id
236          */
237         public String getId() {
238                 return fId;
239         }
240
241         /**
242          * Returns the name of the described extension.
243          *
244          * @return Returns the name
245          */
246         public String getName() {
247                 return fName;
248         }
249
250         /**
251          * Returns the partition types of the described extension.
252          *
253          * @return the set of partition types (element type: {@link String})
254          */
255         public Set<String> getPartitions() {
256                 return fPartitions;
257         }
258
259         /**
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.
264          *
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)
271          */
272         private synchronized IJavaCompletionProposalComputer getComputer(boolean canCreate) throws CoreException, InvalidRegistryObjectException {
273                 if (fComputer == null && canCreate && !fTriedLoadingComputer && (fActivate || isPluginLoaded())) {
274                         fTriedLoadingComputer= true;
275                         fComputer= createComputer();
276                 }
277                 return fComputer;
278         }
279
280         private boolean isPluginLoaded() {
281                 Bundle bundle= getBundle();
282                 return bundle != null && bundle.getState() == Bundle.ACTIVE;
283         }
284
285         private Bundle getBundle() {
286                 String namespace= fElement.getDeclaringExtension().getContributor().getName();
287                 Bundle bundle= Platform.getBundle(namespace);
288                 return bundle;
289         }
290
291         /**
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
294          * is by using the
295          * {@linkplain #computeCompletionProposals(ContentAssistInvocationContext, IProgressMonitor) computeCompletionProposals}
296          * and
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.
300          *
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)
306          */
307         public IJavaCompletionProposalComputer createComputer() throws CoreException, InvalidRegistryObjectException {
308                 return (IJavaCompletionProposalComputer) fElement.createExecutableExtension(CLASS);
309         }
310
311         /**
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.
315          *
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})
320          */
321         public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {
322                 if (!isEnabled())
323                         return Collections.emptyList();
324
325                 IStatus status;
326                 try {
327                         IJavaCompletionProposalComputer computer= getComputer(true);
328                         if (computer == null) // not active yet
329                                 return Collections.emptyList();
330
331                         try {
332                                 PerformanceStats stats= startMeter(context, computer);
333                                 List<ICompletionProposal> proposals= computer.computeCompletionProposals(context, monitor);
334                                 stopMeter(stats, COMPUTE_COMPLETION_PROPOSALS);
335
336                                 if (proposals != null) {
337                                         fLastError= computer.getErrorMessage();
338                                         return proposals;
339                                 }
340                         } finally {
341                                 fIsReportingDelay= true;
342                         }
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);
350                 } finally {
351                         monitor.done();
352                 }
353
354                 fRegistry.informUser(this, status);
355
356                 return Collections.emptyList();
357         }
358
359         /**
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.
363          *
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})
368          */
369         public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
370                 if (!isEnabled())
371                         return Collections.emptyList();
372
373                 IStatus status;
374                 try {
375                         IJavaCompletionProposalComputer computer= getComputer(true);
376                         if (computer == null) // not active yet
377                                 return Collections.emptyList();
378
379                         PerformanceStats stats= startMeter(context, computer);
380                         List<IContextInformation> proposals= computer.computeContextInformation(context, monitor);
381                         stopMeter(stats, COMPUTE_CONTEXT_INFORMATION);
382
383                         if (proposals != null) {
384                                 fLastError= computer.getErrorMessage();
385                                 return proposals;
386                         }
387
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);
395                 } finally {
396                         monitor.done();
397                 }
398
399                 fRegistry.informUser(this, status);
400
401                 return Collections.emptyList();
402         }
403
404
405         /**
406          * Notifies the described extension of a proposal computation session start.
407          * <p><em>
408          * Note: This method is called every time code assist is invoked and
409          * is <strong>not</strong> filtered by partition type.
410          * </em></p>
411          */
412         public void sessionStarted() {
413                 if (!isEnabled())
414                         return;
415
416                 IStatus status;
417                 try {
418                         IJavaCompletionProposalComputer computer= getComputer(true);
419                         if (computer == null) // not active yet
420                                 return;
421
422                         PerformanceStats stats= startMeter(SESSION_STARTED, computer);
423                         computer.sessionStarted();
424                         stopMeter(stats, SESSION_ENDED);
425
426                         return;
427                 } catch (InvalidRegistryObjectException x) {
428                         status= createExceptionStatus(x);
429                 } catch (CoreException x) {
430                         status= createExceptionStatus(x);
431                 } catch (RuntimeException x) {
432                         status= createExceptionStatus(x);
433                 }
434
435                 fRegistry.informUser(this, status);
436         }
437
438         /**
439          * Notifies the described extension of a proposal computation session end.
440          * <p><em>
441          * Note: This method is called every time code assist is invoked and
442          * is <strong>not</strong> filtered by partition type.
443          * </em></p>
444          */
445         public void sessionEnded() {
446                 if (!isEnabled())
447                         return;
448
449                 IStatus status;
450                 try {
451                         IJavaCompletionProposalComputer computer= getComputer(false);
452                         if (computer == null) // not active yet
453                                 return;
454
455                         PerformanceStats stats= startMeter(SESSION_ENDED, computer);
456                         computer.sessionEnded();
457                         stopMeter(stats, SESSION_ENDED);
458
459                         return;
460                 } catch (InvalidRegistryObjectException x) {
461                         status= createExceptionStatus(x);
462                 } catch (CoreException x) {
463                         status= createExceptionStatus(x);
464                 } catch (RuntimeException x) {
465                         status= createExceptionStatus(x);
466                 }
467
468                 fRegistry.informUser(this, status);
469         }
470
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());
476                 } else {
477                         stats= null;
478                 }
479
480                 if (fIsReportingDelay) {
481                         fStart= System.currentTimeMillis();
482                 }
483
484                 return stats;
485         }
486
487         private void stopMeter(final PerformanceStats stats, 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(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$
511         }
512
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$
518         }
519
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$
525         }
526
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$
532         }
533
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$
539         }
540
541         private String createBlameMessage() {
542                 Object[] args= { getName(), fElement.getDeclaringExtension().getContributor().getName() };
543                 String disable= Messages.format( JavaTextMessages.CompletionProposalComputerDescriptor_blame_message, args);
544                 return disable;
545         }
546
547         /**
548          * Returns the enablement state of the described extension.
549          *
550          * @return the enablement state of the described extension
551          */
552         private boolean isEnabled() {
553                 return fCategory.isEnabled();
554         }
555
556         CompletionProposalCategory getCategory() {
557                 return fCategory;
558         }
559
560         /**
561          * Returns the error message from the described extension.
562          *
563          * @return the error message from the described extension
564          */
565         public String getErrorMessage() {
566                 return fLastError;
567         }
568
569         /**
570          * Returns the contributor of the described extension.
571          *
572          * @return the contributor of the described extension
573          */
574     IContributor getContributor()  {
575         try {
576                 return fElement.getContributor();
577         } catch (InvalidRegistryObjectException e) {
578                 return null;
579         }
580     }
581
582         /**
583          * Returns the <code>needsSortingAfterFiltering</code> flag of the described extension.
584          * 
585          * @return the needsSortingAfterFiltering flag of the described extension
586          * @since 3.8
587          */
588         public boolean isSortingAfterFilteringNeeded() {
589                 return fNeedsSortingAfterFiltering;
590         }
591 }