KickJava   Java API By Example, From Geeks To Geeks.

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


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.windows.TopComponent;
24 import org.openide.windows.Workspace;
25 import org.openide.windows.WindowManager;
26 import org.openide.windows.Mode;
27 import org.openide.loaders.DataObject;
28 import org.openide.text.*;
29 import org.openide.nodes.Node;
30 import org.openide.cookies.EditorCookie;
31 import org.openide.util.RequestProcessor;
32 import org.openide.filesystems.FileObject;
33 import org.openide.ErrorManager;
34 import org.netbeans.modules.tasklist.suggestions.settings.ManagerSettings;
35 import org.netbeans.modules.tasklist.core.TLUtils;
36 import org.netbeans.modules.tasklist.core.Task;
37 import org.netbeans.modules.tasklist.providers.DocumentSuggestionProvider;
38 import org.netbeans.modules.tasklist.providers.SuggestionProvider;
39
40 import javax.swing.*;
41 import javax.swing.Timer JavaDoc;
42 import javax.swing.event.*;
43 import javax.swing.text.Document JavaDoc;
44 import javax.swing.text.StyledDocument JavaDoc;
45 import java.beans.PropertyChangeEvent JavaDoc;
46 import java.beans.PropertyChangeListener JavaDoc;
47 import java.awt.event.*;
48 import java.awt.*;
49 import java.util.*;
50 import java.util.List JavaDoc;
51 import java.util.logging.Logger JavaDoc;
52 import java.lang.reflect.InvocationTargetException JavaDoc;
53
54 /**
55  * Broker actively monitors environment and provides
56  * suggestion lists (jobs) for:
57  * <ul>
58  * <li>{@link #startBroker}, currently opened document (list managed by SuggestionsManager, see getListByRequest)
59  * <li>{@link #startAllOpenedBroker}, all opened documents (list managed by this class)
60  * XXX it does not catch changes/suggestions made by providers on their own
61  * </ul>
62  *
63  * @author Petr Kuzel
64  * @author Tor Norbye (transitive from sacked SuggestionManagerImpl)
65  */

66 public final class SuggestionsBroker {
67
68     private static SuggestionsBroker instance;
69
70     private SuggestionList list;
71
72     private int clientCount = 0;
73
74     private SuggestionManagerImpl manager = (SuggestionManagerImpl) SuggestionManagerImpl.getDefault();
75
76     // hook for unit tests
77
Env env = new Env();
78
79     // FileObject, Set<Suggestion>
80
private Map openedFilesSuggestionsMap = new HashMap();
81
82     private int allOpenedClientsCount = 0;
83
84     /** all opened mode is a client of currently opened job (this field). */
85     private Job allOpenedJob;
86
87     private SuggestionList allOpenedList;
88
89     private static final Logger JavaDoc LOGGER = TLUtils.getLogger(SuggestionsBroker.class);
90
91     // It's list as it need to support duplicates, see overlayed add/remove in start/stop methods
92
private List JavaDoc acceptors = new ArrayList(5);
93
94     private final ProviderAcceptor compound = new ProviderAcceptor() {
95         public boolean accept(SuggestionProvider provider) {
96             Iterator it = acceptors.iterator();
97             while (it.hasNext()) {
98                 ProviderAcceptor acceptor = (ProviderAcceptor) it.next();
99                 if (acceptor.accept(provider)) return true;
100             }
101             return false;
102         }
103     };
104
105     private SuggestionsBroker() {
106     }
107
108     public static SuggestionsBroker getDefault() {
109         if (instance == null) {
110             instance = new SuggestionsBroker();
111         }
112         return instance;
113     }
114
115
116     public Job startBroker(ProviderAcceptor acceptor) {
117         clientCount++;
118         if (clientCount == 1) {
119             manager.dispatchRun();
120             startActiveSuggestionFetching();
121         }
122         return new Job(acceptor);
123     }
124
125     /** Handle for suggestions foe active document. */
126     public class Job {
127
128         private boolean stopped = false;
129         private final ProviderAcceptor acceptor;
130
131         private Job(ProviderAcceptor acceptor) {
132             this.acceptor = acceptor;
133             acceptors.add(acceptor);
134         }
135
136         public void stopBroker() {
137             if (stopped) return;
138             stopped = true;
139             acceptors.remove(acceptor);
140
141             clientCount--;
142             if (clientCount == 0) {
143                 stopActiveSuggestionFetching();
144                 // Get rid of suggestion cache, we cannot invalidate its
145
// entries properly without keeping a listener
146
if (cache != null) {
147                     cache.flush();
148                 }
149                 list = null;
150                 instance = null;
151             }
152         }
153
154         /**
155          * Returns live list containing current suggestions.
156          * List is made live by invoking {@link SuggestionsBroker#startBroker} and
157          * is abandoned ance last client calls {@link Job#stopBroker}.
158          * <p>
159          * It's global list so listeners must be carefully unregistered
160          * unfortunatelly it's rather complex because list
161          * is typically passed to other clasess (TaskChildren).
162          * Hopefully you can WeakListener.
163          */

164         public SuggestionList getSuggestionsList() {
165             return getCurrentSuggestionsList();
166         }
167
168     }
169
170     /** Starts monitoring all opened files */
171     public AllOpenedJob startAllOpenedBroker(ProviderAcceptor acceptor) {
172         allOpenedClientsCount++;
173         if (allOpenedClientsCount == 1) {
174             acceptors.add(acceptor);
175             TopComponent[] documents = SuggestionsScanner.openedTopComponents();
176             SuggestionsScanner scanner = SuggestionsScanner.getDefault();
177             List JavaDoc allSuggestions = new LinkedList();
178             for (int i = 0; i<documents.length; i++) {
179                 DataObject dobj = extractDataObject(documents[i]);
180                 if (dobj == null) continue;
181                 FileObject fileObject = dobj.getPrimaryFile();
182
183                 // do not scan form files (any file with more topcomponents (editors)) twice
184
if (openedFilesSuggestionsMap.keySet().contains(fileObject) == false) {
185                     List JavaDoc suggestions = scanner.scanTopComponent(documents[i], compound);
186                     openedFilesSuggestionsMap.put(fileObject, suggestions);
187                     allSuggestions.addAll(suggestions);
188                 }
189             }
190             getAllOpenedSuggestionList().addRemove(allSuggestions, null, true, null, null);
191
192             allOpenedJob = startBroker(acceptor);
193         }
194         return new AllOpenedJob(acceptor);
195     }
196
197     /** Handle to suggestions for all opened files request. */
198     public class AllOpenedJob {
199
200         private boolean stopped = false;
201         private final ProviderAcceptor acceptor;
202
203         private AllOpenedJob(ProviderAcceptor acceptor) {
204             this.acceptor = acceptor;
205             acceptors.add(acceptor);
206         }
207
208         public SuggestionList getSuggestionList() {
209             return getAllOpenedSuggestionList();
210         }
211
212         public void stopBroker() {
213
214             if (stopped) return;
215             stopped = true;
216             acceptors.remove(acceptor);
217
218             allOpenedClientsCount--;
219             if (allOpenedClientsCount == 0) {
220                 allOpenedJob.stopBroker();
221                 openedFilesSuggestionsMap.clear();
222             }
223         }
224     }
225
226     private SuggestionList getCurrentSuggestionsList() {
227         if (list == null) {
228             list = new SuggestionList();
229         }
230         return list;
231     }
232
233     private SuggestionList getAllOpenedSuggestionList() {
234         if (allOpenedList == null) {
235             allOpenedList = new SuggestionList();
236         }
237         return allOpenedList;
238     }
239
240     /*
241      * Code related to Document scanning. It listens to the source editor and
242      * tracks document opens and closes, as well as "current document" changes.
243      * <p>
244      * For lightweight document analysis, you can redo the scanning
245      * whenever the editor is shown and hidden; for more expensive analysis,
246      * you may only want to do it when the document is opened (after a timeout).
247      * <p>
248      * The API does not define which thread these methods are called on,
249      * so don't make any assumptions. If you want to post something on
250      * the AWT event dispatching thread for example use SwingUtilities.
251      * <p>
252      * Note that changes in document attributes only are "ignored" (in
253      * the sense that they do not cause document edit notification.)
254      *
255      * @todo Document threading behavior
256      * @todo Document timer behavior (some of the methods are called after
257      * a delay, others are called immediately.)
258      *
259      */

260
261
262     /** Current request reference. Used to correlate register()
263      * calls with requests sent to rescan()/clear()
264      */

265     private volatile Long JavaDoc currRequest = new Long JavaDoc(0);
266
267     /** Points to the last completed request. Set to currRequest
268      * when rescan() is done.
269      */

270     private volatile Comparable JavaDoc finishedRequest = null;
271
272     final Object JavaDoc getCurrRequest() {
273         return currRequest;
274     }
275
276     /**
277      * Start scanning for source items.
278      * Attaches top component registry and data object
279      * registry listeners to monitor currently edited file.
280      */

281     private void startActiveSuggestionFetching() {
282
283         LOGGER.info("Starting active suggestions fetching...."); // NOI18N
284

285         // must be removed in docStop
286
WindowSystemMonitor monitor = getWindowSystemMonitor();
287         monitor.enableOpenCloseEvents();
288         env.addTCRegistryListener(monitor);
289         env.addDORegistryListener(getDataSystemMonitor());
290
291         /* OLD:
292         org.openide.windows.TopComponent.getRegistry().
293             addPropertyChangeListener(this);
294
295         // Also scan the current node right away: pretend source listener was
296         // notified of the change to the current node (which has already occurred)
297         // ... unfortunately this is not as easy as just calling getActivatedNodes
298         // on the registry -- because that node may not be the last EDITORvisible
299         // node... So resort to some hacks.
300         Node[] nodes = NewTaskAction.getEditorNodes();
301         if (nodes != null) {
302             scanner.propertyChange(new PropertyChangeEvent(
303               this,
304               TopComponent.Registry.PROP_ACTIVATED_NODES,
305               null,
306               nodes));
307         } else {
308             // Most likely you're not looking at a panel that has an
309             // associated node, e.g. the welcome screen, or the editor isn't
310             // open
311 if (err.isLoggable(ErrorManager.INFORMATIONAL)) {
312 err.log("Couldn't find current nodes...");
313 }
314         }
315         */

316
317         // NEW:
318
/** HACK: We need to always know what the current source file
319          in the editor is - and even when there isn't a source file
320          there, we need to know: if you for example switch to the
321          Welcome screen we should remove the tasks for the formerly
322          shown source file.
323
324          I've tried listening to the global node, since we should
325          always get notified when the current node changes. However,
326          this has a couple of problems. First, we may get notified
327          of the node change BEFORE the source file is done editing;
328          in that case we can't find the node in the editor (we need
329          to check that a node is in the editor since we don't want
330          the task window to for example show the tasks for the
331          current selection in the explorer). Another problem is
332          a scenario I just ran into where if you open A, B from
333          explorer, then select A in the explorer, then select B in
334          the editor: when you now double click A in the explorer
335          there's no rescan. (I may have to debug this).
336
337          So instead I will go to a more reliable scheme, which
338          unfortunately smells more like a hack from a NetBeans
339          perspective. The basic idea is this: I can find the
340          source editor, and which top component is showing in
341          the source editor. I can get notified of when this
342          changes - by listening for componentHidden of the
343          top most pane. Then I just have to go and see which
344          component is now showing, and switch my component listener
345          to this new component. (From the component I can discover
346          which source file it's editing). This has the benefit
347          that I'll know precisely when a new file has been loaded
348          in, etc. It may have the disadvantage that if you open
349          source files in other modes (by docking and undocking
350          away from the standard configuration) things get
351          broken. Perhaps I can keep my old activated-node-listener
352          scheme in place as a backup solution when locating the
353          source editor mode etc. fails.
354
355          It gets more complicated. What if you open the task window
356          when the editor is not visible? Then you can't attach a
357          listener to the current window - so you don't get notified
358          when a new file is opened. For that reason we also need to
359          listen to the workspace's property change notification, which
360          will tell us when the set of modes changes in the workspace.
361
362          ...and of course the workspace itself can change. So we need
363          to listen to the workspace change notification in the window
364          manager as well...
365          */

366
367         /*
368         WindowManager manager = WindowManager.getDefault();
369         manager.addPropertyChangeListener(this);
370         Workspace workspace = WindowManager.getDefault().
371             getCurrentWorkspace();
372         workspace.addPropertyChangeListener(this);
373         */

374
375         prepareRescanInAWT(false);
376     }
377
378     /** Cache tracking suggestions in recently visited files */
379     private SuggestionCache cache = null;
380
381     /** List of suggestions restored from the cache that we must delete
382      when leaving this document */

383     private List JavaDoc docSuggestions = null;
384
385     /**
386      * Prepares current environment. Monitors
387      * actual document modification state using DocumentListener
388      * and CaretListener. Actual TopComponent is guarded
389      * by attached ComponentListener.
390      * <p>
391      * Must be called from <b>AWT thread only</b>.
392      */

393     private boolean prepareCurrent() {
394
395         assert SwingUtilities.isEventDispatchThread() : "This method must be run in the AWT thread"; // NOI18N
396

397         // Unregister previous listeners
398
if (currentTC != null) {
399             currentTC.removeComponentListener(getWindowSystemMonitor());
400             currentTC = null;
401         }
402         if (currentDocument != null) {
403             currentDocument.removeDocumentListener(getEditorMonitor());
404             handleDocHidden(currentDocument, currentDO);
405         }
406         removeCurrentCaretListeners();
407
408         // Find which component is showing in it
409
// Add my own component listener to it
410
// When componentHidden, unregister my own component listener
411
// Redo above
412

413         // Locate source editor
414
TopComponent tc = env.findActiveEditor();
415         if (tc == null) {
416             // The last editor-support window in the editor was probably
417
// just closed - or was not on top
418

419             // remove suggestions
420
List JavaDoc previous = new ArrayList(getCurrentSuggestionsList().getTasks());
421             getCurrentSuggestionsList().addRemove(null, previous, false, null, null);
422             // for opened files list it's done ahandeTopComponentCloased
423

424             LOGGER.fine("Cannot find active source editor!"); // during startup
425
return false;
426         }
427
428
429         DataObject dao = extractDataObject(tc);
430         if (dao == null) {
431             // here we have tc found by findActiveEditor() that uses classification logic to detect real editors
432
LOGGER.warning("Suspicious editor without dataobject: " + tc); // NOI18N
433
return false;
434         }
435
436         /*
437         if (dao == lastDao) {
438             // We've been asked to scan the same dataobject as last time;
439             // don't do that.
440             // Most likely you've temporarily switched to another (non-editor)
441             // node, and switched back (for example, double clicking on a node
442             // in the task window) and we're still on the same file so there's
443             // no reason to rescan. We track changes to the currently scanned
444             // object differently (through a document listener).
445             err.log("Same dao as last time - not doing anything");
446             return; // Don't scan again
447         }
448         lastDao = dao;
449         */

450
451         final EditorCookie edit = (EditorCookie) dao.getCookie(EditorCookie.class);
452         if (edit == null) {
453             //err.log("No editor cookie - not doing anything");
454
return false;
455         }
456
457         final Document JavaDoc doc = edit.getDocument(); // Does not block
458

459         /* This comment applies to the old implementation, where
460            we're listening on activated node changes. Now that we're
461            listening for tab changes, the document should already
462            have been read in by the time the tab changes and we're
463            notified of it:
464
465         // We might have a race condition here... you open the
466         // document, and our property change listener gets notified -
467         // but the document hasn't completed loading yet despite our
468         // 1 second timer. Thus we might not get a document... However
469         // since we continue listening for changes, eventually we WILL
470         // discover the document
471         */

472         if (doc == null) {
473             LOGGER.fine("No document is loaded in editor!"); // can happen during startup
474
return false;
475         }
476
477         currentDocument = doc;
478         currentDocument.addDocumentListener(getEditorMonitor());
479
480         // Listen for changes on this component so we know when
481
// it's replaced by something else XXX looks like PROP_ACTIVATED duplication
482
currentTC = tc;
483         currentTC.addComponentListener(getWindowSystemMonitor());
484
485         currentDO = dao;
486         currentModified = dao.isModified();
487
488         addCurrentCaretListeners();
489
490         return true;
491
492     }
493
494     /** If served by cache returns true. */
495     private boolean serveByCache() {
496         if (cache != null) {
497             // TODO check scanOnShow too! (when we have scanOnOpen
498
// as default instead of scanOnShow as is the case now.
499
// The semantics of the flag need to change before we
500
// check it here; it's always true. Make it user selectable.)
501
docSuggestions = cache.lookup(currentDocument);
502             if (docSuggestions != null) {
503                 manager.register(null, docSuggestions, null, getCurrentSuggestionsList(), true);
504                 // TODO Consider putting the above on a runtimer - but
505
// a much shorter runtimer (0.1 seconds or something like
506
// that) such that the editor gets a chance to draw itself
507
// etc.
508

509                 // Also wipe out the cache items since we will replace them
510
// when docHidden is called, or when docEdited is called,
511
// etc.
512
//cache.remove(document);
513

514                 // Remember that we're done "scanning"
515
finishedRequest = currRequest;
516                 return true;
517             }
518         }
519         return false;
520     }
521
522     private static DataObject extractDataObject(TopComponent topComponent) {
523         DataObject dobj = (DataObject) topComponent.getLookup().lookup(DataObject.class);
524         if (dobj != null && dobj.isValid()) {
525             return dobj;
526         } else {
527             return null;
528         }
529     }
530
531     /**
532      * The given document has been edited or saved, and a time interval
533      * (by default around 2 seconds I think) has passed without any
534      * further edits or saves.
535      * <p>
536      * Update your Suggestions as necessary. This may mean removing
537      * previously registered Suggestions, or editing existing ones,
538      * or adding new ones, depending on the current contents of the
539      * document.
540      * <p>
541      * Spawns <b>Suggestions Broker thread</b> that finishes actula work
542      * asynchronously.
543      *
544      * @param tc
545      * @param dataobject The Data Object for the file being opened
546      * @param delay postpone the action by delay miliseconds
547      *
548      * @return parametrized task that rescans given dataobject in delay miliseconds
549      */

550     private RequestProcessor.Task performRescanInRP(final TopComponent tc, final DataObject dataobject, int delay) {
551
552         /* Scan requests are run in a separate "background" thread.
553            However, what happens if the user switches to a different
554            tab -while- a scan job is running? If the scan hasn't
555            started, the timer is removed, but if the scan is in
556            progress, we have to know to discard registered results.
557            For that reason, we have a "current request" reference that
558            we pass with scan requests, and that scanners will hand
559            back with scan results. The reference is an integer.
560            When we switch to a new tab, we increment the integer.
561            So if we get a registration, with an "old" integer (not the
562            current one), we know the results are obsolete.
563            We also need to know if the current scan is done (to know
564            whether or not we should flush these results into the cache,
565            or if scanning must begin from the beginning when we return
566            to this file.) For that reason, we also have a "finished
567            request" integer which points to the most recent finished
568            request; we only stuff the cache if finished == current.
569            We can also use the request flag to bail in the middle of
570            iterating over providers in case a new request has arrived.
571         */

572
573         // Is MAX_VALUE even feasible here? There's no greater/lessthan
574
// comparison, so wrapping around will work just fine, but I may
575
// have to check manually and do it myself in case some kind
576
// of overflow exception is thrown
577
// Wait, I'm doing a comparison now - look for currRequest.longValue
578
assert currRequest.longValue() != Long.MAX_VALUE : "Wrap around logic needed!"; // NOI18N
579
currRequest = new Long JavaDoc(currRequest.longValue() + 1);
580         final Object JavaDoc origRequest = currRequest;
581
582         // free AWT && Timer threads
583
return serializeOnBackground(new Runnable JavaDoc() {
584             public void run() {
585
586                 scheduledRescan = null;
587
588                 // Stale request If so, just drop this one
589
//if (origRequest != currRequest) return;
590

591                 // code is fixing (modifing) document
592
if (wait) {
593                     waitingEvent = true;
594                     return;
595                 }
596
597                 // reassure that Tc was not meanwhile closed. Both current file job
598
// and all opened files job monitors just files opened in editor pane
599

600                 if (isTopComponentOpened(tc) == false) {
601                     return;
602                 }
603
604                 LOGGER.fine("Dispatching rescan() request to providers...");
605
606                 setScanning(true);
607
608                 List JavaDoc scannedSuggestions = manager.dispatchScan(dataobject, compound);
609
610                 // once again reassure that Tc was not meanwhile closed. Both current file job
611
// and all opened files job monitors just files opened in editor pane
612

613                 if (isTopComponentOpened(tc) == false) {
614                     return;
615                 }
616
617                 // update "allOpened" suggestion list
618

619                 if (allOpenedClientsCount > 0) {
620                     FileObject fo = dataobject.getPrimaryFile();
621                     List JavaDoc previous = (List JavaDoc) openedFilesSuggestionsMap.remove(fo);
622                     openedFilesSuggestionsMap.put(fo, scannedSuggestions);
623
624                     // fast check if anything have changed, avoid firing events from tasklist
625
if (previous == null || previous.size() != scannedSuggestions.size() || previous.containsAll(scannedSuggestions) == false) {
626                         getAllOpenedSuggestionList().addRemove(scannedSuggestions, previous, false, null, null);
627                     }
628                 }
629
630                 if (clientCount > 0) {
631                     List JavaDoc previous = new ArrayList(getCurrentSuggestionsList().getTasks());
632
633                     // fast check if anything have changed, avoid firing events from tasklist
634
if (previous == null || previous.size() != scannedSuggestions.size() || previous.containsAll(scannedSuggestions) == false) {
635                         getCurrentSuggestionsList().addRemove(scannedSuggestions, previous, false, null, null);
636                     }
637                 }
638
639                 // enforce comparable requests, works only for single request source
640
if ((finishedRequest == null) ||
641                         ((Comparable JavaDoc)origRequest).compareTo(finishedRequest) > 0) {
642                     finishedRequest = (Comparable JavaDoc) origRequest;
643                 }
644                 if (currRequest == finishedRequest) {
645                     setScanning(false); // XXX global state, works only for single request source
646
LOGGER.fine("It was last pending request.");
647                 }
648             }
649         }, delay);
650     }
651
652     /**
653      * Tests if given topcomponent is opend, Thread safe alternatiove
654      * to TopComponent.isOpened().
655      */

656     private static boolean isTopComponentOpened(final TopComponent tc) {
657         if (tc == null) return false;
658         final boolean[] isOpened = new boolean[1];
659         int attempt = 0;
660         while (attempt < 57) { // reattempt on interrupt that I got from unknow thread
661
try {
662                 SwingUtilities.invokeAndWait(new Runnable JavaDoc() {
663                     public void run() {
664                         isOpened[0] = tc.isOpened(); // must be called from AWT
665
}
666                 });
667                 return isOpened[0];
668             } catch (InterruptedException JavaDoc e) {
669                 ErrorManager err = ErrorManager.getDefault();
670                 err.annotate(e, "[TODO] while get " + tc.getDisplayName() + " state, interrupted, ignoring..."); // NOI18N
671
err.notify(ErrorManager.INFORMATIONAL, e);
672                 attempt++;
673             } catch (InvocationTargetException JavaDoc e) {
674                 ErrorManager err = ErrorManager.getDefault();
675                 err.annotate(e, "[TODO] cannot get " + tc.getDisplayName() + " state."); // NOI18N
676
err.notify(e);
677                 return false;
678             } finally {
679                 if (attempt != 0) Thread.currentThread().interrupt(); // retain the interrupted flag
680
}
681         }
682         return false;
683     }
684
685     private RequestProcessor rp = new RequestProcessor("Suggestions Broker"); // NOI18N
686

687     /** Enqueue request and perform it on background later on. */
688     private RequestProcessor.Task serializeOnBackground(Runnable JavaDoc request, int delay) {
689         return rp.post(request, delay , Thread.MIN_PRIORITY);
690     }
691
692     /**
693      * Grab all the suggestions associated with this document/dataobject
694      * and push it into the suggestion cache.
695      */

696     private void stuffCache(Document JavaDoc document, DataObject dataobject,
697                             boolean unregisterOnly) {
698
699         boolean filteredTaskListFixed = false; //XXX register bellow
700
if (filteredTaskListFixed == false) return;
701
702         // XXX Performance: if docSuggestions != null, we should be able
703
// to just reuse it, since the document must not have been edited!
704

705         SuggestionList tasklist = getCurrentSuggestionsList();
706         if (tasklist.getTasks().size() == 0) {
707             return;
708         }
709         Iterator it = tasklist.getTasks().iterator();
710         List JavaDoc sgs = new ArrayList(tasklist.getTasks().size());
711         while (it.hasNext()) {
712             SuggestionImpl s = (SuggestionImpl) it.next();
713             Object JavaDoc seed = s.getSeed();
714             // Make sure we don't pick up category nodes here!!!
715
if (seed != SuggestionList.CATEGORY_NODE_SEED) {
716                 sgs.add(s);
717             }
718
719             Iterator sit = s.subtasksIterator();
720             while (sit.hasNext()) {
721                 s = (SuggestionImpl) sit.next();
722                 seed = s.getSeed();
723                 if (seed != SuggestionList.CATEGORY_NODE_SEED) {
724                     sgs.add(s);
725                 }
726             }
727         }
728         if (!unregisterOnly) {
729             if (cache == null) {
730                 cache = new SuggestionCache();
731             }
732             cache.add(document, dataobject, sgs);
733         }
734
735         // Get rid of tasks from list
736
// XXX is not it already done by providers, it causes problems
737
if (sgs.size() > 0) {
738             manager.register(null, null, sgs, tasklist, true);
739         }
740     }
741
742     /** The top-component we're currently tracking (active one) */
743     private TopComponent currentTC = null;
744
745     /** The document we're currently tracking (active one) */
746     private Document JavaDoc currentDocument = null;
747
748     /** The data-object we're currently tracking (active one) */
749     private DataObject currentDO = null;
750
751     /** The panes we're currently tracking (active one) */ //XXX first element should be enough
752
private JEditorPane[] editorsWithCaretListener = null;
753
754     /** The modification status sampled on tracing start and save operation */
755     private boolean currentModified = false;
756
757     /** Add caret listener to dataobject's editor panes. */
758     private void addCurrentCaretListeners() {
759
760         assert editorsWithCaretListener == null : "addCaretListeners() must not be called twice without removeCaretListeners() => memory leak"; // NOI18N
761

762         EditorCookie edit = (EditorCookie) currentDO.getCookie(EditorCookie.class);
763         if (edit != null) {
764             JEditorPane panes[] = edit.getOpenedPanes();
765             if ((panes != null) && (panes.length > 0)) {
766                 // We want to know about cursor changes in ALL panes
767
editorsWithCaretListener = panes;
768                 for (int i = 0; i < editorsWithCaretListener.length; i++) {
769                     editorsWithCaretListener[i].addCaretListener(getEditorMonitor());
770                 }
771             }
772         }
773     }
774
775     /** Unregister previously added caret listeners. */
776     private void removeCurrentCaretListeners() {
777         if (editorsWithCaretListener != null) {
778             for (int i = 0; i < editorsWithCaretListener.length; i++) {
779                 editorsWithCaretListener[i].removeCaretListener(getEditorMonitor());
780             }
781         }
782         editorsWithCaretListener = null;
783     }
784
785     boolean pendingScan = false;
786
787     /** Timed task which keeps track of outstanding scan requests; we don't
788      scan briefly selected files */

789     private volatile RequestProcessor.Task scheduledRescan;
790
791     /**
792      * Plan a rescan (meaning: put delayed task into RP). In whole
793      * broker there is only one scheduled task (and at maximum one
794      * running concurrenly if delay is smaller than execution time).
795      *
796      * @param delay If true, don't create a rescan if one isn't already
797      * pending, but if one is, delay it.
798      * @param scanDelay actual delay value in ms
799      *
800      */

801     private void scheduleRescan(boolean delay, final int scanDelay) {
802
803         // This is just a delayer (e.g. for caret motion) - if there isn't
804
// already a pending timeout, we're done. Caret motion shouldn't
805
// -cause- a rescan, but if one is already planned, we want to delay
806
// it.
807
if (delay && (scheduledRescan == null)) {
808             return;
809         }
810
811         // Stop our current timer; the previous node has not
812
// yet been scanned; too brief an interval
813
if (scheduledRescan != null) {
814             scheduledRescan.cancel();
815             scheduledRescan = null;
816             LOGGER.fine("Scheduled rescan task delayed by " + scanDelay + " ms."); // NOI18N
817
}
818
819         // prepare environment in AWT and post to RP
820

821         Runnable JavaDoc task = new Runnable JavaDoc() {
822             public void run() {
823                 if (prepareCurrent()) {
824                     // trap, randomly triggered by multiview
825
assert currentDO.equals(extractDataObject(currentTC)) : "DO=" + currentDO + " TC=" + currentTC;
826                     scheduledRescan = performRescanInRP(currentTC, currentDO, scanDelay);
827                 }
828             }
829         };
830
831         if (SwingUtilities.isEventDispatchThread()) {
832             task.run();
833         } else {
834             SwingUtilities.invokeLater(task);
835         }
836     }
837
838     /** An event ocurred during quiet fix period. */
839     private boolean waitingEvent = false;
840     private boolean wait = false;
841
842     /**
843      * Set fix mode (quiet period) in which self initialized modifications are expected.
844      * @param wait <ul> <li> true postpone all listeners until ...
845      * <li> false ressurect listeners activity
846      */

847     final void setFixing(boolean wait) {
848         boolean wasWaiting = this.wait;
849         this.wait = wait;
850         if (!wait && wasWaiting && (waitingEvent)) {
851             scheduleRescan(false, ManagerSettings.getDefault().getEditScanDelay());
852             waitingEvent = false;
853         }
854     }
855
856
857     /** The set of visible top components changed */
858     private void componentsChanged() {
859         // We may receive "changed events" from different sources:
860
// componentHidden (which is the only source which tells us
861
// when you've switched between two open tabs) and
862
// TopComponent.registry's propertyChange on PROP_OPENED
863
// (which is the only source telling us about tabs closing).
864

865         // However, there is some overlap - when you open a new
866
// tab, we get notified by both. So coalesce these events by
867
// enquing a change lookup on the next iteration through the
868
// event loop; if a second notification comes in during the
869
// same event processing iterationh it's simply discarded.
870

871         prepareRescanInAWT(true);
872
873     }
874
875     /**
876      * It sends asynchronously to AWT thread (selected editor TC must be grabbed in AWT).
877      * Once prepared it sends request to a background thread.
878      * @param delay if true schedule later acording to user settings otherwise do immediatelly
879      */

880     private void prepareRescanInAWT(final boolean delay) {
881
882         // XXX unify with scheduleRescan
883

884         Runnable JavaDoc performer = new Runnable JavaDoc() {
885             public void run() {
886                 if (clientCount > 0) {
887                     prepareCurrent();
888                     if (serveByCache() == false) {
889                         if (ManagerSettings.getDefault().isScanOnShow()) {
890                             if (delay) {
891                                 performRescanInRP(currentTC, currentDO, ManagerSettings.getDefault().getShowScanDelay());
892                             } else {
893                                 performRescanInRP(currentTC, currentDO, 0);
894                             }
895                         }
896                     }
897                 }
898             }
899         };
900
901         if (SwingUtilities.isEventDispatchThread()) {
902             performer.run();
903         } else {
904             // docStop() might have happen
905
// in the mean time - make sure we don't do a
906
// delay=true when we're not supposed to
907
// be processing views
908
SwingUtilities.invokeLater(performer);
909         }
910     }
911
912     /**
913      * Stop scanning for source items, deregistering
914      * environment listeners.
915      */

916     private void stopActiveSuggestionFetching() {
917
918         LOGGER.info("Stopping active suggestions fetching...."); // NOI18N
919

920         if (scheduledRescan != null) {
921             scheduledRescan.cancel();
922             scheduledRescan = null;
923         }
924
925         env.removeTCRegistryListener(getWindowSystemMonitor());
926         env.removeDORegistryListener(getDataSystemMonitor());
927
928         // Unregister previous listeners
929
if (currentTC != null) {
930             currentTC.removeComponentListener(getWindowSystemMonitor());
931             currentTC = null;
932         }
933         if (currentDocument != null) {
934             currentDocument.removeDocumentListener(getEditorMonitor());
935             // NOTE: we do NOT null it out since we still need to
936
// see if the document is unchanged
937
}
938         removeCurrentCaretListeners();
939
940         handleDocHidden(currentDocument, currentDO);
941         currentDocument = null;
942     }
943
944
945     private void setScanning(boolean scanning) {
946         // XXX fishy direct access to view assuming 1:1 relation with list
947
// SuggestionList tasklist = getList();
948
// TaskListView v = tasklist.getView();
949
// if (v instanceof SuggestionsView) {
950
// SuggestionsView view = (SuggestionsView) v;
951
// view.setScanning(scanning);
952
// }
953
}
954
955     private void handleDocHidden(Document JavaDoc document, DataObject dataobject) {
956         // This is not right - runTimer is telling us whether we have
957
// a request pending - (and we should indeed kill the timer
958
// if we do) - but we need to know if a RequestProcessor is
959
// actually running.
960
if (currRequest != finishedRequest) {
961             if (cache != null) {
962                 cache.remove(document);
963             }
964             // Remove the items we've registered so far... (partial
965
// registration) since we're in the middle of a request
966
stuffCache(document, dataobject, true);
967         } else {
968             stuffCache(document, dataobject, false);
969         }
970
971         docSuggestions = null;
972     }
973
974     private void handleTopComponentClosed(TopComponent tc) {
975
976         //System.err.println("[TODO] closing: " + tc.getDisplayName());
977

978         componentsChanged();
979
980         DataObject dobj = extractDataObject(tc);
981         if (dobj == null) {
982             //System.err.println("[TODO] has no DO: " + tc.getDisplayName());
983
return;
984         }
985
986         List JavaDoc previous = (List JavaDoc) openedFilesSuggestionsMap.remove(dobj.getPrimaryFile());
987         if (previous != null) {
988             //System.err.println("[TODO] removing TODOs: " + tc.getDisplayName() + " :" + previous);
989
getAllOpenedSuggestionList().addRemove(null, previous, false, null, null);
990         } else {
991             //System.err.println("[TODO] has no TODOs: " + tc.getDisplayName());
992
}
993     }
994
995     private void handleTopComponentOpened(TopComponent tc) {
996         //System.err.println("[TODO] opened: " + tc.getDisplayName());
997
if (tc.isShowing()) {
998             // currently selected one
999
componentsChanged();
1000        } else {
1001            // it is not selected anymore, it was opened in burst
1002
DataObject dao = extractDataObject(tc);
1003            if (dao == null) return;
1004            performRescanInRP(tc, dao, ManagerSettings.getDefault().getShowScanDelay());
1005        }
1006    }
1007
1008    private WindowSystemMonitor windowSystemMonitor;
1009
1010    /** See note on {@link WindowSystemMonitor#enableOpenCloseEvents} */
1011    private WindowSystemMonitor getWindowSystemMonitor() {
1012        if (windowSystemMonitor == null) {
1013            windowSystemMonitor = new WindowSystemMonitor();
1014        }
1015        return windowSystemMonitor;
1016    }
1017
1018    // The code is unnecesary comples there is pending issue #48937
1019
private class WindowSystemMonitor implements PropertyChangeListener JavaDoc, ComponentListener {
1020
1021        /** Previous Set&lt;TopComponent> */
1022        private Set openedSoFar = Collections.EMPTY_SET;
1023
1024        /**
1025         * Must be called before adding this listener to environment if in hope that
1026         * it will provide (initial) open/close events.
1027         */

1028        private void enableOpenCloseEvents() {
1029            List JavaDoc list = Arrays.asList(SuggestionsScanner.openedTopComponents());
1030            openedSoFar = new HashSet(list);
1031
1032            Iterator it = list.iterator();
1033            while (it.hasNext()) {
1034                TopComponent tc = (TopComponent) it.next();
1035                tc.addComponentListener(new ComponentAdapter() {
1036                                          public void componentShown(ComponentEvent e) {
1037                                           TopComponent tcomp = (TopComponent) e.getComponent();
1038                                           tcomp.removeComponentListener(this);
1039                                           handleTopComponentOpened(tcomp);
1040                                          }
1041                                        });
1042
1043            }
1044        }
1045
1046        /** Reacts to changes */
1047        public void propertyChange(PropertyChangeEvent JavaDoc ev) {
1048
1049            String JavaDoc prop = ev.getPropertyName();
1050            if (prop.equals(TopComponent.Registry.PROP_OPENED)) {
1051
1052                LOGGER.fine("EVENT opened top-components changed");
1053
1054// if (allOpenedClientsCount > 0) {
1055
// determine what components have been closed, window system does not
1056
// provide any other listener to do it in more smart way
1057

1058                    List JavaDoc list = Arrays.asList(SuggestionsScanner.openedTopComponents());
1059                    Set actual = new HashSet(list);
1060
1061                    if (openedSoFar != null) {
1062                        Iterator it = openedSoFar.iterator();
1063                        while(it.hasNext()) {
1064                            TopComponent tc = (TopComponent) it.next();
1065                            if (actual.contains(tc) ) continue;
1066                            handleTopComponentClosed(tc);
1067                        }
1068
1069                        Iterator ita = actual.iterator();
1070                        while(ita.hasNext()) {
1071                            TopComponent tc = (TopComponent) ita.next();
1072                            if (openedSoFar.contains(tc)) continue;
1073                            // defer actual action to componentShown, We need to assure opened TC is
1074
// selected one. At this moment previous one is still selected.
1075
tc.addComponentListener(new ComponentAdapter() {
1076                                public void componentShown(ComponentEvent e) {
1077                                    TopComponent tcomp = (TopComponent) e.getComponent();
1078                                    tcomp.removeComponentListener(this);
1079                                    handleTopComponentOpened(tcomp);
1080                                }
1081                            });
1082                        }
1083                    }
1084
1085                    openedSoFar = actual;
1086  // } else {
1087
// componentsChanged();
1088
// openedSoFar = null;
1089
// }
1090
} else if (TopComponent.Registry.PROP_ACTIVATED.equals(prop)) {
1091                LOGGER.fine("EVENT top-component activated");
1092
1093                TopComponent activated = WindowManager.getDefault().getRegistry().getActivated();
1094                if (clientCount > 0 && isSelectedEditor(activated) && currentTC == null) {
1095                    prepareRescanInAWT(false);
1096                }
1097            }
1098        }
1099
1100        public void componentShown(ComponentEvent e) {
1101            // Don't care
1102
}
1103
1104        public void componentHidden(ComponentEvent e) {
1105
1106            LOGGER.fine("EVENT " + e.getComponent() + " has been hidden");
1107
1108            //XXX it does not support both "current file" and "all opened" clients at same time
1109
if (allOpenedClientsCount == 0) {
1110                componentsChanged();
1111            }
1112        }
1113
1114        public void componentResized(ComponentEvent e) {
1115            // Don't care
1116
}
1117
1118        public void componentMoved(ComponentEvent e) {
1119            // Don't care
1120
}
1121
1122        private boolean isSelectedEditor(Component tc) {
1123            Mode mode = WindowManager.getDefault().findMode(CloneableEditorSupport.EDITOR_MODE);
1124            TopComponent selected = null;
1125            if (mode != null) {
1126                selected = mode.getSelectedTopComponent();
1127            }
1128            return selected == tc;
1129        }
1130    }
1131
1132
1133    private DataSystemMonitor dataSystemMonitor;
1134
1135    private DataSystemMonitor getDataSystemMonitor() {
1136        if (dataSystemMonitor == null) {
1137            dataSystemMonitor = new DataSystemMonitor();
1138        }
1139        return dataSystemMonitor;
1140    }
1141
1142    /**
1143     * Listener for DataObject.Registry changes.
1144     *
1145     * This class listens for modify-changes of dataobjects such that
1146     * it can notify files of Save operations.
1147     */

1148    private class DataSystemMonitor implements ChangeListener JavaDoc {
1149        public void stateChanged(ChangeEvent JavaDoc e) {
1150            /* Not sure what the source is, but it isn't dataobject
1151                 and the javadoc doesn't say anything specific, so
1152                 I guess I can't rely on that as a filter
1153            if (e.getSource() != dataobject) {
1154                // If you reinstate this in some way, make sure it
1155                // works for Save ALL as well!!!
1156                return;
1157            }
1158            */

1159
1160            LOGGER.fine("EVENT " + e.getSource() + " changed.");
1161
1162            Set mods = DataObject.getRegistry().getModifiedSet();
1163            boolean wasModified = currentModified;
1164            currentModified = mods.contains(currentDO);
1165            if (currentModified != wasModified) {
1166                if (!currentModified) {
1167                    if (ManagerSettings.getDefault().isScanOnSave()) {
1168                        scheduleRescan(false, ManagerSettings.getDefault().getSaveScanDelay());
1169                    }
1170                }
1171            }
1172        }
1173    }
1174
1175    private EditorMonitor editorMonitor;
1176
1177    private EditorMonitor getEditorMonitor() {
1178        if (editorMonitor == null) {
1179            editorMonitor = new EditorMonitor();
1180        }
1181        return editorMonitor;
1182    }
1183
1184    private class EditorMonitor implements DocumentListener, CaretListener {
1185
1186        //XXX missing reset logic
1187
private int prevLineNo = -1;
1188
1189        public void changedUpdate(DocumentEvent e) {
1190            // Do nothing.
1191
// Changed update is only called for ATTRIBUTE changes in the
1192
// document, which I define as not relevant to the Document
1193
// Suggestion Providers.
1194
}
1195
1196        public void insertUpdate(DocumentEvent e) {
1197
1198            LOGGER.fine("EVENT document changed");
1199
1200            if (ManagerSettings.getDefault().isScanOnEdit()) {
1201                scheduleRescan(false, ManagerSettings.getDefault().getEditScanDelay());
1202            }
1203        }
1204
1205        public void removeUpdate(DocumentEvent e) {
1206
1207            LOGGER.fine("EVENT document changed");
1208
1209            if (ManagerSettings.getDefault().isScanOnEdit()) {
1210                scheduleRescan(false, ManagerSettings.getDefault().getEditScanDelay());
1211            }
1212        }
1213
1214        /** Moving the cursor position should cause a delay in document scanning,
1215         * but not trigger a new update */

1216        public void caretUpdate(CaretEvent caretEvent) {
1217
1218            LOGGER.fine("EVENT caret moved");
1219
1220            scheduleRescan(true, ManagerSettings.getDefault().getEditScanDelay());
1221
1222            // Check to see if I have any existing errors on this line - and if so,
1223
// highlight them.
1224
if (currentDocument instanceof StyledDocument JavaDoc) {
1225                int offset = caretEvent.getDot();
1226                int lineno = NbDocument.findLineNumber((StyledDocument JavaDoc) currentDocument, offset);
1227                if (lineno == prevLineNo) {
1228                    // Just caret motion on the same line as the previous one -- ignore
1229
return;
1230                }
1231                prevLineNo = lineno;
1232
1233                // Here we could add 1 to the line number, since findLineNumber
1234
// returns a 0-based line number, and most APIs return a 1-based
1235
// line number; however, Line.Set.getOriginal also expects
1236
// something zero based, so instead of doing the usual bit
1237
// of subtracting there, we drop the add and subtract altogether
1238

1239                // Go to the given line
1240
Line line = TLUtils.getLineByNumber(currentDO, lineno + 1);
1241                /*
1242                try {
1243                    LineCookie lc = (LineCookie)dataobject.getCookie(LineCookie.class);
1244                    if (lc != null) {
1245                        Line.Set ls = lc.getLineSet();
1246                        if (ls != null) {
1247                            line = ls.getCurrent(lineno);
1248                        }
1249                    }
1250                } catch (Exception e) {
1251                    ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
1252                }
1253                */

1254                if (line != null) {
1255                    // XXX badge editor suggestion in tasklist
1256
//[SuggestionsView]setCursorLine(line);
1257
}
1258            }
1259        }
1260
1261    }
1262
1263
1264
1265    /**
1266     * Binding to outer world that can be changed by unit tests
1267     */

1268    static class Env {
1269
1270        void addTCRegistryListener(PropertyChangeListener JavaDoc pcl) {
1271            TopComponent.getRegistry().addPropertyChangeListener(pcl);
1272        }
1273
1274        void removeTCRegistryListener(PropertyChangeListener JavaDoc pcl) {
1275            TopComponent.getRegistry().removePropertyChangeListener(pcl);
1276        }
1277
1278        void addDORegistryListener(ChangeListener JavaDoc cl) {
1279            DataObject.getRegistry().addChangeListener(cl);
1280
1281        }
1282
1283        void removeDORegistryListener(ChangeListener JavaDoc cl) {
1284            DataObject.getRegistry().removeChangeListener(cl);
1285        }
1286
1287        /**
1288         * Locates active editor topComponent. Must be run in AWT
1289         * thread. Eliminates Welcome screen, JavaDoc
1290         * and orher non-editor stuff in EDITOR_MODE.
1291         * @return tc or <code>null</code> for non-editor selected topcomponent
1292         */

1293        private TopComponent findActiveEditor() {
1294            Mode mode = WindowManager.getDefault().findMode(CloneableEditorSupport.EDITOR_MODE);
1295            if (mode == null) {
1296                // The editor window was probablyjust closed
1297
return null;
1298            }
1299            TopComponent tc = mode.getSelectedTopComponent();
1300
1301            // form files within MultiViewCloneableTopComponent contantly returns null
1302
// so I got suggestion to use instanceof CloneableEditorSupport.Pane workaround
1303
// if (tc != null && tc.getLookup().lookup(EditorCookie.class) != null) {
1304
if (tc instanceof CloneableEditorSupport.Pane) {
1305                // Found the source editor...
1306
// if (tc.isShowing()) { // FIXME it returns false for components I can positivelly see
1307
// hopefully mode does not return hidden TC as selected one.
1308
// It happens right after startup
1309
return tc;
1310// }
1311
}
1312            return null;
1313        }
1314    }
1315
1316}
1317
Popular Tags