KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > tasklist > suggestions > SuggestionsScanner


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20
21 package org.netbeans.modules.tasklist.suggestions;
22
23 import org.openide.loaders.DataObject;
24 import org.openide.awt.StatusDisplayer;
25 import org.openide.util.NbBundle;
26 import org.openide.util.Lookup;
27 import org.openide.util.Cancellable;
28 import org.openide.cookies.EditorCookie;
29 import org.openide.filesystems.FileObject;
30 import org.openide.filesystems.FileUtil;
31 import org.openide.windows.WindowManager;
32 import org.openide.windows.Mode;
33 import org.openide.windows.TopComponent;
34 import org.openide.text.CloneableEditorSupport;
35 import org.openide.nodes.Node;
36 import org.openide.ErrorManager;
37 import org.netbeans.modules.tasklist.providers.SuggestionContext;
38 import org.netbeans.modules.tasklist.providers.SuggestionProvider;
39 import org.netbeans.modules.tasklist.providers.DocumentSuggestionProvider;
40 import org.netbeans.apihole.tasklist.SPIHole;
41 import org.netbeans.modules.tasklist.client.SuggestionManager;
42 import org.netbeans.api.queries.VisibilityQuery;
43
44 import javax.swing.*;
45 import java.util.*;
46 import java.lang.ref.WeakReference JavaDoc;
47 import java.lang.ref.Reference JavaDoc;
48 import java.lang.reflect.InvocationTargetException JavaDoc;
49
50 /**
51  * Scans for suggestions by delegating to
52  * plugged-in providers.
53  *
54  * @todo Should I use FileObjects instead of DataObjects when passing
55  * file identity around? It seems weird that I don't allow
56  * scanning on secondary files (although it seems right in the
57  * cases I can think of - we don't want to scan .class files,
58  * .o files, .form files, ...). Pros: DataObject layer is a classification
59  * layer that defines EditorCookies etc. Cons: Too many dependencies
60  * on possibly slow and leaking code.
61  *
62  * @author Petr Kuzel
63  */

64 public final class SuggestionsScanner implements Cancellable {
65
66     /** Optional progress monitor */
67     private ScanProgress progressMonitor;
68
69     /**
70      * Contains already scanned dataobjects.
71      * It'd be very memory intensive so only preferred
72      * are stored.
73      * Other duplicities (unlikely) can cause
74      * suggestion duplicities.
75      */

76     private final Set scanned = new HashSet();
77
78     /** Target suggestion list. */
79     private SuggestionList list;
80
81     private ProviderAcceptor typeFilter;
82
83     // target manager impl
84
private final SuggestionManagerImpl manager;
85
86     private final SuggestionProviders registry;
87
88     // keep default instance (only if a client exists)
89
private static Reference JavaDoc instance;
90
91     private volatile boolean interrupted;
92     private int suggestionsCounter;
93     private int usabilityLimit = 503;
94     private boolean workaround38476;
95
96     // list that replaces direct manager regiltration
97
private List cummulateInList;
98
99     private SuggestionsScanner() {
100         manager = (SuggestionManagerImpl) Lookup.getDefault().lookup(SuggestionManager.class);
101         registry = SuggestionProviders.getDefault();
102     }
103
104     public static SuggestionsScanner getDefault() {
105         if (instance == null) {
106             return createDefault();
107         }
108         SuggestionsScanner scanner = (SuggestionsScanner) instance.get();
109         if (scanner == null) {
110             return createDefault();
111         } else {
112             return scanner;
113         }
114     }
115
116     private static SuggestionsScanner createDefault() {
117         SuggestionsScanner scanner = new SuggestionsScanner();
118         instance = new WeakReference JavaDoc(scanner);
119         return scanner;
120     }
121
122     /**
123      * Scans recursively for suggestions notifing given progress monitor.
124      * @param folders containers to be scanned. It must be DataObject subclasses!
125      * @param list
126      * @param monitor
127      */

128     public final synchronized void scan(DataObject.Container[] folders, SuggestionList list, ScanProgress monitor) {
129         scan(folders, list, monitor, ProviderAcceptor.ALL);
130     }
131
132     /**
133      * Scans recursively for suggestions notifing given progress monitor.
134      * @param folders containers to be scanned. It must be DataObject subclasses!
135      * @param list
136      * @param monitor
137      * @param filter suggestion provider filter
138      */

139     public final synchronized void scan(DataObject.Container[] folders, SuggestionList list, ScanProgress monitor, ProviderAcceptor filter) {
140         try {
141             typeFilter = filter;
142             progressMonitor = monitor;
143             scan(folders, list, true);
144         } finally {
145             typeFilter = null;
146             progressMonitor = null;
147             if (monitor != null) monitor.scanFinished();
148         }
149     }
150
151     /**
152      * Iterate over the folder recursively (optional) and scan all files.
153      * We skip CVS and SCCS folders intentionally. Would be nice if
154      * the filesystem hid these things from us.
155      *
156      * @param folders containers to be scanned. It must be DataObject subclasses!
157      * @param list target suggestions list
158      * @param recursive use descent policy
159      */

160     private final synchronized void scan(DataObject.Container[] folders, SuggestionList list,
161                            boolean recursive) {
162
163         lowMemoryWarning = false;
164         lowMemoryWarningCount = 0;
165         interrupted = false;
166         assureMemory(REQUIRED_PER_ITERATION, true); // guard low memory condition
167
suggestionsCounter = 0;
168
169
170         try {
171             this.list = list;
172
173             // scan opened files first these are most specifics
174
// it should also improve perceived performance
175
workaround38476 = true;
176             scanPreferred(folders, recursive);
177             workaround38476 = false;
178
179             if (progressMonitor != null) {
180                 int estimate = -1;
181                 progressMonitor.estimate(estimate);
182                 for (int i = 0; i < folders.length; i++) {
183                     // it's faster to check at FS level however we can miss some links (.shadow)
184
FileObject fo = ((DataObject)folders[i]).getPrimaryFile();
185                     estimate += countFolders(fo);
186                 }
187                 progressMonitor.estimate(estimate);
188                 progressMonitor.scanStarted();
189             }
190
191             for (int i = 0; i < folders.length; i++) {
192                 if (shouldStop()) return;
193                 DataObject.Container folder = folders[i];
194                 scanFolder(folder, recursive);
195             }
196         } finally {
197             scanned.clear();
198         }
199     }
200
201
202     /**
203      * Return all opened top components in editor mode.
204      * @return never null
205      */

206     static TopComponent[] openedTopComponents() {
207         final Object JavaDoc[] wsResult = new Object JavaDoc[1];
208         try {
209
210             if (SwingUtilities.isEventDispatchThread()) {
211                 Mode editorMode = WindowManager.getDefault().findMode(CloneableEditorSupport.EDITOR_MODE);
212                 if (editorMode == null) {
213                     return new TopComponent[0];
214                 } else {
215                     return editorMode.getTopComponents();
216                 }
217             } else {
218                 // I just hope that we are not called from non-AWT thread
219
// still holding AWTTreeLock otherwise deadlock
220
SwingUtilities.invokeAndWait(new Runnable JavaDoc() {
221                     public void run() {
222                         Mode editorMode = WindowManager.getDefault().findMode(CloneableEditorSupport.EDITOR_MODE);
223                         if (editorMode == null) {
224                             wsResult[0] = new TopComponent[0];
225                         } else {
226                             wsResult[0] = editorMode.getTopComponents();
227                         }
228                     }
229                 });
230                 return (TopComponent[]) wsResult[0];
231             }
232         } catch (InterruptedException JavaDoc e) {
233             return new TopComponent[0];
234         } catch (InvocationTargetException JavaDoc e) {
235             return new TopComponent[0];
236         }
237     }
238
239     /**
240      * Determines if given dataobject lies in scan context
241      * and scans it.
242      *
243      * @param folders scan context
244      * @param recursive iff true scan context is scanned recusively
245      */

246     private void scanPreferred(DataObject.Container[] folders, boolean recursive) {
247
248         TopComponent[] views = openedTopComponents();
249         DataObject[] roots = null;
250         for (int i = 0; i<views.length; i++) {
251             Node[] nodes = views[i].getActivatedNodes();
252             if (nodes == null) continue; // XXX issue #38383
253
for (int n = 0; n<nodes.length; n++) {
254                 DataObject dobj = (DataObject) nodes[n].getCookie(DataObject.class);
255                 if (dobj != null) {
256                     if (roots == null) {
257                         Set allRoots = new HashSet();
258                         for (int r = 0; r<folders.length; r++) {
259                             DataObject[] droots = folders[r].getChildren();
260                             for (int d = 0; d<droots.length; d++) {
261                                 allRoots.add(droots[d]);
262                             }
263                         }
264                         roots = (DataObject[]) allRoots.toArray(new DataObject[allRoots.size()]);
265                     }
266                     scanPreferred(dobj, roots, recursive);
267                     break; // one DataObject per TC is enough :-)
268
}
269             }
270         }
271     }
272
273     private void scanPreferred(DataObject dobj, DataObject[] roots, boolean recursive) {
274         FileObject fo = dobj.getPrimaryFile();
275         for (int i=0; i<roots.length; i++) {
276             FileObject root = roots[i].getPrimaryFile();
277             if (root.equals(fo) || (recursive ? FileUtil.isParentOf(root,fo) : fo.getParent().equals(root))) {
278                 scanLeaf(dobj);
279                 scanned.add(dobj);
280                 break; // certainly it could be under more roots
281
// but it would create duplicates and slow down the test
282
}
283         }
284     }
285
286     private void scanFolder(DataObject.Container folder, boolean recursive) {
287         DataObject[] children = folder.getChildren();
288         for (int i = 0; i < children.length; i++) {
289
290             if (shouldStop()) return;
291
292             DataObject f = children[i];
293             if (f instanceof DataObject.Container) {
294                 if (!recursive) {
295                     continue;
296                 }
297
298                 //XXX Skip CVS and SCCS folders
299
String JavaDoc name = f.getPrimaryFile().getNameExt();
300                 if ("CVS".equals(name) || "SCCS".equals(name) || ".svn".equals(name)) { // NOI18N
301
continue;
302                 }
303
304                 if (progressMonitor == null) {
305                     // XXX stil strange that possibly backgournd process writes directly to UI
306
StatusDisplayer.getDefault().setStatusText(
307                             NbBundle.getMessage(SuggestionsScanner.class,
308                                     "ScanningFolder", // NOI18N
309
f.getPrimaryFile().getNameExt()));
310                 } else {
311                     progressMonitor.folderEntered(f.getPrimaryFile());
312                 }
313
314                 scanFolder((DataObject.Container) f, true); // recurse!
315
if (progressMonitor != null) {
316                     progressMonitor.folderScanned(f.getPrimaryFile());
317                 }
318
319             } else {
320                 scanLeaf(f);
321             }
322         }
323     }
324
325
326     /**
327      * Scan content of selected top component and return results
328      * as list instead of direct registering with manager.
329      *
330      * @return list (possibly empty)
331      */

332     final synchronized List scanTopComponent(TopComponent topComponent, ProviderAcceptor acceptor) {
333         List ret = Collections.EMPTY_LIST;
334         try {
335
336             // init
337

338             cummulateInList = new LinkedList();
339             progressMonitor = null;
340             scanned.clear();
341             workaround38476 = topComponent.isOpened();
342             suggestionsCounter = 0;
343             interrupted = false;
344             typeFilter = acceptor;
345
346             // perform
347

348             Node[] nodes = topComponent.getActivatedNodes();
349             if (nodes == null) return ret;
350             for (int n = 0; n<nodes.length; n++) {
351                 DataObject dobj = (DataObject) nodes[n].getCookie(DataObject.class);
352                 if (dobj == null) return ret;
353                 scanLeaf(dobj);
354                 break; // one node is enough
355
}
356             ret = cummulateInList;
357         } finally {
358             cummulateInList = null;
359             typeFilter = null;
360         }
361         return ret;
362     }
363
364     /**
365      * Scans given data object. Converts it to scanning context.
366      */

367     private void scanLeaf(DataObject dobj) {
368         // Get document, and I do mean now!
369

370         if (!dobj.isValid()) return;
371
372         if (scanned.contains(dobj)) return;
373
374         final EditorCookie edit =
375                 (EditorCookie) dobj.getCookie(EditorCookie.class);
376         if (edit == null) return;
377
378         FileObject fo = dobj.getPrimaryFile();
379         if (VisibilityQuery.getDefault().isVisible(fo) == false) return; // ignore backups etc
380
String JavaDoc extension = fo.getExt();
381         boolean directAccess = "java".equals(extension) || "properties".equals(extension); // #38476
382
final boolean isPrimed = edit.getDocument() == null && directAccess == false;
383
384         SuggestionContext env = SPIHole.createSuggestionContext(dobj);
385
386         scanLeaf(env);
387
388         if (false) {
389             try {
390                 Thread.sleep(1000); // simulate long document processing
391
// to see what timeout based tasks are triggered
392
// (e.g. background java.parser.ParsingSupport.parse)
393
} catch (InterruptedException JavaDoc e) {
394                 // ignore
395
}
396         }
397
398
399         // XXX default editor cookie implementation (CloneableEditorSupport)
400
// does not release documents on unless one explicitly
401
// call close() that as side effect closes all components.
402
// So call close() is we are likely only document users
403
Runnable JavaDoc r = new Runnable JavaDoc() {
404                 public void run() {
405                     if (isPrimed && edit.getOpenedPanes() == null && workaround38476 == false) {
406                         edit.close();
407                     }
408                 }
409             };
410         if( SwingUtilities.isEventDispatchThread() ) {
411             r.run();
412         } else {
413             try {
414                 SwingUtilities.invokeAndWait( r );
415             } catch( InvocationTargetException JavaDoc itE ) {
416                 //ignore
417
} catch( InterruptedException JavaDoc iE ) {
418                 //ignore
419
}
420         }
421
422         if (progressMonitor != null) {
423             progressMonitor.fileScanned(dobj.getPrimaryFile());
424         }
425
426     }
427
428     private void scanLeaf(SuggestionContext env) {
429         List providers = registry.getProviders();
430         ListIterator it = providers.listIterator();
431         while (it.hasNext()) {
432             if (interrupted) return;
433             interrupted = Thread.interrupted();
434             SuggestionProvider provider = (SuggestionProvider) it.next();
435
436             assert typeFilter != null;
437             if (typeFilter.accept(provider) == false) continue;
438
439             // FIXME no initialization events possibly fired
440
// I guess that reponsibility for recovering from missing
441
// lifecycle events should be moved to providers
442
if (provider instanceof DocumentSuggestionProvider) {
443                 List l = null;
444                 String JavaDoc type = null;
445                 try {
446                     type = provider.getType();
447                     l = ((DocumentSuggestionProvider) provider).scan(env);
448                 } catch (RuntimeException JavaDoc e) {
449                     ErrorManager.getDefault().annotate(e, "Skipping faulty provider (" + provider + ")."); // NOI18N
450
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
451                 } catch (ThreadDeath JavaDoc e) {
452                     throw e;
453                 } catch (Error JavaDoc e) {
454                     ErrorManager.getDefault().annotate(e, "Skipping faulty provider (" + provider + ")."); // NOI18N
455
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
456                 }
457                 if (l != null && l.size() > 0) {
458                     // XXX ensure that scan returns a homogeneous list of tasks
459
suggestionsCounter += l.size();
460                     if (cummulateInList == null) {
461                         manager.register(type, l, null, list, true);
462                     } else {
463                         cummulateInList.addAll(l);
464                     }
465                 }
466             }
467         }
468     }
469
470     // low memory condition detection logic ~~~~~~~~
471

472     private static boolean lowMemoryWarning = false;
473     private static int lowMemoryWarningCount = 0;
474     private static int MB = 1024 * 1024;
475     private static int REQUIRED_PER_ITERATION = 2 * MB;
476     private static int REQUIRED_PER_FULL_GC = 7 * MB;
477
478     /**
479      * throws RuntimeException if low memory condition happens
480      * @param estimate etimated memory requirements before next check
481      * @param tryGC on true use potentionally very slow test that is more accurate (cooperates with GC)
482      */

483     private void assureMemory(int estimate, boolean tryGC) {
484         Runtime JavaDoc rt = Runtime.getRuntime();
485         long total = rt.totalMemory();
486         long max = rt.maxMemory(); // XXX on some 1.4.1 returns heap&native instead of -Xmx
487
long required = Math.max(total/13, estimate + REQUIRED_PER_FULL_GC);
488         if (total == max && rt.freeMemory() < required) {
489             // System.err.println("MEM " + max + " " + total + " " + rt.freeMemory());
490
if (tryGC) {
491                 try {
492                     byte[] gcProvocation = new byte[(int)required];
493                     gcProvocation[0] = 75;
494                     gcProvocation = null;
495                     return;
496                 } catch (OutOfMemoryError JavaDoc e) {
497                     handleNoMemory();
498                 }
499             } else {
500                 lowMemoryWarning = true;
501             }
502         } else if (lowMemoryWarning) {
503             lowMemoryWarning = false;
504             lowMemoryWarningCount ++;
505         }
506         // gc is getting into corner
507
if (lowMemoryWarningCount > 7 || (total == max && rt.freeMemory() < REQUIRED_PER_FULL_GC)) {
508             handleNoMemory();
509         }
510
511     }
512
513     private void handleNoMemory() {
514         interrupted = true;
515         if (progressMonitor != null) {
516             progressMonitor.scanTerminated(-1);
517         }
518     }
519
520     /** Test stop condition (thread interrupted or low memory) */
521     private boolean shouldStop() {
522         if (interrupted) return true;
523         interrupted = Thread.interrupted();
524         if (interrupted) return true;
525         assureMemory(REQUIRED_PER_ITERATION, false);
526         if (interrupted) return true;
527         if (suggestionsCounter > getCountLimit()) {
528             interrupted = true;
529             if (progressMonitor != null) {
530                 progressMonitor.scanTerminated(-3);
531             }
532         }
533         return interrupted;
534
535     }
536
537 // *** Low memory condition listener
538
// // heuristically detect overload
539
// private static Reference memoryReference;
540
//
541
// // Allocate some extra memory and keep soft reference to it
542
// // once it gets collected JVM tries to eliminate unnecesary
543
// // memory alocation from system resources
544
// private class MemoryReference extends SoftReference implements Runnable {
545
//
546
// MemoryReference(Object ref) {
547
// super(ref, Utilities.activeReferenceQueue());
548
// }
549
//
550
// // gets called by Utilities.activeReferenceQueue
551
// public void run() {
552
// memoryReleased();
553
// }
554
// }
555
//
556
// private void memoryReleased() {
557
// long total = Runtime.getRuntime().totalMemory();
558
// long free = Runtime.getRuntime().freeMemory();
559
// allocateMemory();
560
// if (total == Runtime.getRuntime().totalMemory()) {
561
// // no new system memory allocated
562
// // there were many soft references or we are getting to maxMemory limit
563
// if (!interrupted) {
564
// // XXX on most implementations it's maxMemory limit
565
// if (Runtime.getRuntime().freeMemory() < 10000 ) {
566
// interrupted = true;
567
// if (progressMonitor != null) {
568
// progressMonitor.scanTerminated(-1);
569
// }
570
// }
571
// }
572
// }
573
// }
574
//
575
// private void allocateMemory() {
576
// if (interrupted) return;
577
// if (memoryReference != null && memoryReference.get() != null) return;
578
// try {
579
// byte[] memory = new byte[3*1024*1024];
580
// memoryReference = new MemoryReference(memory);
581
// } catch (OutOfMemoryError err) {
582
// interrupted = true;
583
// }
584
// }
585
// *** Low memory condition listener
586

587     /** Stop scannig after discovering limit suggestions */
588     private int getCountLimit() {
589         return usabilityLimit;
590     }
591
592     private static int countFolders(FileObject projectFolder) {
593         int count = 0;
594         if (Thread.currentThread().isInterrupted()) return count;
595         Enumeration en = projectFolder.getFolders(false);
596         while (en.hasMoreElements()) {
597             FileObject next = (FileObject) en.nextElement();
598             String JavaDoc name = next.getNameExt();
599             if ("CVS".equals(name) || "SCCS".equals(name) || ".svn".equals(name)) { // NOI18N
600
continue;
601             }
602             count++;
603             count += countFolders(next); // recursion
604
}
605         return count;
606     }
607
608     public boolean cancel() {
609         interrupted = true;
610         if (progressMonitor != null) {
611             progressMonitor.scanTerminated(-2);
612         }
613         return true;
614     }
615
616     /** Set treshold meaning to stop the scanner. */
617     public void setUsabilityLimit(int usabilityLimit) {
618         this.usabilityLimit = usabilityLimit;
619     }
620
621     /**
622      * Handles scan method emmited progress callbacks.
623      * Implementation can interrupt scanning thread
624      * (current thread) by standard thread interruption
625      * methods.
626      */

627     public interface ScanProgress {
628         /**
629          * Predics how many folders will be scanned.
630          * @param estimatedFolders estimate (-1 for not yet know).
631          */

632         void estimate(int estimatedFolders);
633
634         void scanStarted();
635
636         void folderEntered(FileObject folder);
637
638         void fileScanned(FileObject file);
639
640         void folderScanned(FileObject folder);
641
642         void scanFinished();
643
644         /**
645          * Scan was terminated unfinished
646          * @param reason -1 out of memory, -2 user interrupt, -3 count limit
647          */

648         void scanTerminated(int reason);
649     }
650
651
652 }
653
Popular Tags