2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 2004-2007 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.search;
44
import java.awt.EventQueue;
45
import java.lang.ref.Reference;
46
import java.lang.reflect.InvocationTargetException;
47
import java.lang.reflect.Method;
48
import java.util.List;
49
import org.openide.DialogDisplayer;
50
import org.openide.ErrorManager;
51
import org.openide.NotifyDescriptor;
52
import org.openide.awt.StatusDisplayer;
53
import org.openide.util.NbBundle;
54
import org.openide.util.RequestProcessor;
55
import org.openide.util.Task;
56
import org.openide.util.TaskListener;
57
import org.openide.windows.OutputWriter;
58
import org.openidex.search.SearchType;
59
import static org.netbeans.modules.search.ReplaceTask.ResultStatus.SUCCESS;
60
import static org.netbeans.modules.search.ReplaceTask.ResultStatus.PRE_CHECK_FAILED;
61
import static org.netbeans.modules.search.ReplaceTask.ResultStatus.PROBLEMS_ENCOUNTERED;
64
* Manager of the Search module's activities.
65
* It knows which tasks are running and manages the module's actions so that
66
* no two conflicting tasks are running at a moment.
68
* @see <a href="doc-files/manager-state-diagram.png">State diagram</a>
69
* @author Marian Petras
74
* timeout for cleanup in the case that the module is being uninstalled
77
private static final int CLEANUP_TIMEOUT_MILLIS = 3000;
80
static final int NO_TASK = 0;
82
static final int SEARCHING = 0x01;
84
static final int CLEANING_RESULT = 0x02;
86
static final int PRINTING_DETAILS = 0x04;
88
static final int REPLACING = 0x08;
90
static final int EVENT_SEARCH_STARTED = 1;
92
static final int EVENT_SEARCH_FINISHED = 2;
94
static final int EVENT_SEARCH_INTERRUPTED = 3;
96
static final int EVENT_SEARCH_CANCELLED = 4;
98
private static final Manager instance = new Manager();
101
private boolean moduleBeingUninstalled = false;
104
private final Object lock = new Object();
106
private int state = NO_TASK;
108
private int pendingTasks = 0;
110
private TaskListener taskListener;
112
private SearchTask currentSearchTask;
114
private SearchTask pendingSearchTask;
116
private SearchTask lastSearchTask;
118
private ReplaceTask currentReplaceTask;
120
private ReplaceTask pendingReplaceTask;
122
private PrintDetailsTask currentPrintDetailsTask;
124
private PrintDetailsTask pendingPrintDetailsTask;
126
private Task searchTask;
128
private Task replaceTask;
130
private Task cleanResultTask;
132
private Task printDetailsTask;
134
private ResultModel resultModelToClean;
136
private boolean searchWindowOpen = false;
138
private Reference<OutputWriter> outputWriterRef;
143
static Manager getInstance() {
149
private Manager() { }
153
* #1: If the Search Results window is open, its root node displays:
154
* - if the search task is in progress:
155
* - summary of results
156
* - if the Search module is inactive:
157
* - summary of current results (continuously updated)
158
* - if the search task in scheduled but another task is blocking it:
159
* - name of the current task blocking the search
160
* #2: At most one result model exists at a single moment.
165
void scheduleSearchTask(SearchTask task) {
166
assert EventQueue.isDispatchThread();
168
synchronized (lock) {
169
ResultView.getInstance().setResultModel(null);
170
if (currentSearchTask != null) {
171
currentSearchTask.stop(false);
173
if (resultModelToClean != null) {
174
pendingTasks |= CLEANING_RESULT;
176
pendingTasks |= SEARCHING;
177
pendingSearchTask = task;
178
lastSearchTask = task;
179
if (state == NO_TASK) {
180
processNextPendingTask();
182
notifySearchPending(state); //invariant #1
189
void scheduleReplaceTask(ReplaceTask task) {
190
assert EventQueue.isDispatchThread();
192
synchronized (lock) {
193
assert (state == NO_TASK) && (pendingTasks == 0);
195
pendingTasks |= REPLACING;
196
pendingReplaceTask = task;
197
processNextPendingTask();
203
void scheduleSearchTaskRerun() {
204
assert EventQueue.isDispatchThread();
206
synchronized (lock) {
207
SearchTask newSearchTask = lastSearchTask.createNewGeneration();
208
lastSearchTask = null;
209
scheduleSearchTask(newSearchTask);
215
void schedulePrintingDetails(Object[] matchingObjects,
216
BasicSearchCriteria basicCriteria,
217
List<SearchType> searchTypes) {
218
synchronized (lock) {
219
assert state == NO_TASK;
220
pendingTasks |= PRINTING_DETAILS;
222
pendingPrintDetailsTask = new PrintDetailsTask(
223
matchingObjects, basicCriteria, searchTypes);
224
processNextPendingTask();
229
* Queries whether the user should be allowed to initiate a new search.
230
* For example, the user should not be allowed to do so if the last
231
* replace action has not finished yet.
233
* @return message to the user, describing the reason why a new search
234
* cannot be started, or {@code null} if there is no such reason
235
* (i.e. if a new search may be started)
237
String mayStartSearching() {
240
synchronized (lock) {
241
replacing = (state == REPLACING);
244
String msgKey = replacing ? "MSG_Cannot_start_search__replacing"//NOI18N
246
return (msgKey != null) ? NbBundle.getMessage(getClass(), msgKey)
252
private void notifySearchStarted() {
253
notifySearchTaskStateChange(EVENT_SEARCH_STARTED);
258
private void notifySearchFinished() {
259
notifySearchTaskStateChange(EVENT_SEARCH_FINISHED);
264
private void notifySearchInterrupted() {
265
notifySearchTaskStateChange(EVENT_SEARCH_INTERRUPTED);
270
private void notifySearchCancelled() {
271
notifySearchTaskStateChange(EVENT_SEARCH_CANCELLED);
275
* Notifies the result window of a search task's state change.
277
* @param changeType constant describing what happened
278
* - one of the EVENT_xxx constants
280
private void notifySearchTaskStateChange(final int changeType) {
281
synchronized (lock) {
282
if (!searchWindowOpen) {
286
callOnWindowFromAWT("searchTaskStateChanged", //NOI18N
287
new Integer(changeType));
292
private void notifySearchPending(final int blockingTask) {
293
if (!searchWindowOpen) {
296
callOnWindowFromAWT("notifySearchPending", //NOI18N
297
new Integer(blockingTask));
302
private void notifyReplaceFinished() {
303
assert Thread.holdsLock(lock);
304
assert currentReplaceTask != null;
306
ReplaceTask.ResultStatus resultStatus
307
= currentReplaceTask.getResultStatus();
308
if (resultStatus == SUCCESS) {
309
StatusDisplayer.getDefault().setStatusText(
310
NbBundle.getMessage(getClass(), "MSG_Success")); //NOI18N
311
if (searchWindowOpen) {
312
callOnWindowFromAWT("closeAndSendFocusToEditor", false);//NOI18N
315
String msgKey = (resultStatus == PRE_CHECK_FAILED)
316
? "MSG_Issues_found_during_precheck" //NOI18N
317
: "MSG_Issues_found_during_replace"; //NOI18N
318
String title = NbBundle.getMessage(getClass(), msgKey);
319
displayIssuesFromAWT(title,
320
currentReplaceTask.getProblems(),
321
resultStatus != PRE_CHECK_FAILED);
322
if (resultStatus == PRE_CHECK_FAILED) {
323
offerRescanAfterIssuesFound();
330
private void offerRescanAfterIssuesFound() {
331
assert Thread.holdsLock(lock);
332
assert currentReplaceTask != null;
334
String msg = NbBundle.getMessage(getClass(),
335
"MSG_IssuesFound_Rescan_"); //NOI18N
336
NotifyDescriptor nd = new NotifyDescriptor.Message(
338
NotifyDescriptor.QUESTION_MESSAGE);
339
String rerunOption = NbBundle.getMessage(getClass(),
340
"LBL_Rerun"); //NOI18N
341
nd.setOptions(new Object[] {rerunOption,
342
NotifyDescriptor.CANCEL_OPTION});
343
Object dlgResult = DialogDisplayer.getDefault().notify(nd);
344
if (rerunOption.equals(dlgResult)) {
346
* The rescan method calls 'scheduleSearchTaskRerun()' on this.
347
* But it will wait until 'taskFinished()' returns, which is
348
* exactly what we need to keep consistency of the manager's fields
349
* like 'currentReplaceTask', 'replaceTask' and 'state'.
350
* Using this mechanism also requires that, when sending a method
351
* to the EventQueue thread, we use invokeLater(...) and not
352
* invokeAndWait(...).
354
callOnWindowFromAWT("rescan", false); //NOI18N
360
private void notifyPrintingDetailsFinished() {
361
if (!searchWindowOpen) {
364
callOnWindowFromAWT("showAllDetailsFinished"); //NOI18N
369
private void activateResultWindow() {
372
theMethod = ResultView.class
373
.getMethod("requestActive", new Class[0]); //NOI18N
374
} catch (NoSuchMethodException ex) {
375
throw new IllegalArgumentException();
377
callOnWindowFromAWT(theMethod, null);
382
private void displayIssuesFromAWT(String title,
387
theMethod = ResultView.class.getDeclaredMethod(
388
"displayIssuesToUser", //NOI18N
392
} catch (NoSuchMethodException ex) {
393
throw new IllegalStateException(ex);
395
callOnWindowFromAWT(theMethod,
396
new Object[] {title, issues, Boolean.valueOf(att)},
401
* Calls a given method on the Search Results window, from the AWT thread.
403
* @param methodName name of the method to be called
405
private void callOnWindowFromAWT(final String methodName) {
406
callOnWindowFromAWT(methodName, true);
411
private void callOnWindowFromAWT(final String methodName,
412
final boolean wait) {
415
theMethod = ResultView.class
416
.getDeclaredMethod(methodName, new Class[0]);
417
} catch (NoSuchMethodException ex) {
418
throw new IllegalArgumentException();
420
callOnWindowFromAWT(theMethod, null, wait);
424
* Calls a given method on the Search Results window, from the AWT thread.
426
* @param methodName name of the method to be called
427
* @param param parameter to be passed to the method
429
private void callOnWindowFromAWT(final String methodName,
430
final Object param) {
431
callOnWindowFromAWT(methodName, param, true);
436
private void callOnWindowFromAWT(final String methodName,
438
final boolean wait) {
439
Method theMethod = null;
440
Method[] methods = ResultView.class.getDeclaredMethods();
441
for (int i = 0; i < methods.length; i++) {
442
Method method = methods[i];
443
if (method.getName().equals(methodName)) {
444
Class[] parameterTypes = method.getParameterTypes();
445
if (parameterTypes.length == 1) {
446
Class paramType = parameterTypes[0];
448
&& !paramType.isPrimitive())
449
|| (paramType == Integer.TYPE)
450
&& (param instanceof Integer)
451
|| parameterTypes[0].isInstance(param)) {
457
if (theMethod == null) {
458
throw new IllegalArgumentException();
460
callOnWindowFromAWT(theMethod, new Object[] {param}, wait);
465
private void callOnWindowFromAWT(final Method method,
466
final Object[] params) {
467
callOnWindowFromAWT(method, params, true);
472
private void callOnWindowFromAWT(final Method method,
473
final Object[] params,
474
final boolean wait) {
475
Runnable runnable = new Runnable() {
477
final ResultView resultViewInstance = ResultView.getInstance();
479
method.invoke(resultViewInstance, params);
480
} catch (Exception ex) {
481
ErrorManager.getDefault().notify(ex);
485
if (EventQueue.isDispatchThread()) {
490
EventQueue.invokeAndWait(runnable);
491
} catch (InvocationTargetException ex1) {
492
ErrorManager.getDefault().notify(ex1);
493
} catch (Exception ex2) {
494
ErrorManager.getDefault().notify(ErrorManager.ERROR, ex2);
497
EventQueue.invokeLater(runnable);
504
void searchWindowOpened() {
505
synchronized (lock) {
506
searchWindowOpen = true;
512
void searchWindowClosed() {
513
assert EventQueue.isDispatchThread();
515
synchronized (lock) {
516
searchWindowOpen = false;
518
if (moduleBeingUninstalled) {
522
if (currentSearchTask != null) {
523
currentSearchTask.stop(false);
525
if (resultModelToClean != null) {
526
pendingTasks |= CLEANING_RESULT;
528
pendingTasks &= ~SEARCHING;
529
pendingSearchTask = null;
530
lastSearchTask = null;
531
if (state == NO_TASK) {
532
processNextPendingTask();
539
private void processNextPendingTask() {
540
synchronized (lock) {
541
assert state == NO_TASK;
542
if (resultModelToClean == null) {
543
pendingTasks &= ~CLEANING_RESULT;
545
if ((pendingTasks & PRINTING_DETAILS) != 0) {
546
if ((pendingTasks & SEARCHING) != 0) {
547
notifySearchPending(PRINTING_DETAILS); //invariant #1
549
startPrintingDetails();
550
} else if ((pendingTasks & CLEANING_RESULT) != 0) {
551
if ((pendingTasks & SEARCHING) != 0) {
552
notifySearchPending(CLEANING_RESULT); //invariant #1
555
} else if ((pendingTasks & SEARCHING) != 0) {
557
} else if ((pendingTasks & REPLACING) != 0) {
560
assert pendingTasks == 0;
567
private void startSearching() {
568
synchronized (lock) {
569
assert pendingSearchTask != null;
571
notifySearchStarted();
573
ResultModel resultModel = pendingSearchTask.getResultModel();
574
callOnWindowFromAWT("setResultModel", //NOI18N
576
resultModelToClean = resultModel;
578
if (outputWriterRef != null) {
579
SearchDisplayer.clearOldOutput(outputWriterRef);
580
outputWriterRef = null;
583
* The following is necessary because clearing the output window
584
* activates the output window:
586
activateResultWindow();
589
RequestProcessor.Task task;
590
task = RequestProcessor.getDefault().create(pendingSearchTask);
591
task.addTaskListener(getTaskListener());
594
currentSearchTask = pendingSearchTask;
595
pendingSearchTask = null;
598
pendingTasks &= ~SEARCHING;
605
private void startReplacing() {
606
synchronized (lock) {
607
assert pendingReplaceTask != null;
609
RequestProcessor.Task task;
610
task = RequestProcessor.getDefault().create(pendingReplaceTask);
611
task.addTaskListener(getTaskListener());
614
currentReplaceTask = pendingReplaceTask;
615
pendingReplaceTask = null;
618
pendingTasks &= ~REPLACING;
625
private void startPrintingDetails() {
626
synchronized (lock) {
627
if (outputWriterRef != null) {
628
SearchDisplayer.clearOldOutput(outputWriterRef);
629
outputWriterRef = null;
632
RequestProcessor.Task task;
633
task = RequestProcessor.getDefault()
634
.create(pendingPrintDetailsTask);
635
task.addTaskListener(getTaskListener());
638
printDetailsTask = task;
639
pendingTasks &= ~PRINTING_DETAILS;
640
currentPrintDetailsTask = pendingPrintDetailsTask;
641
pendingPrintDetailsTask = null;
643
state = PRINTING_DETAILS;
649
private void startCleaning() {
650
synchronized (lock) {
651
Runnable cleaner = new CleanTask(resultModelToClean);
652
resultModelToClean = null;
654
RequestProcessor.Task task;
655
task = RequestProcessor.getDefault().create(cleaner);
656
task.addTaskListener(getTaskListener());
659
cleanResultTask = task;
660
pendingTasks &= ~CLEANING_RESULT;
661
state = CLEANING_RESULT;
667
void stopSearching() {
668
synchronized (lock) {
669
if ((pendingTasks & SEARCHING) != 0) {
670
pendingTasks &= ~SEARCHING;
671
pendingSearchTask = null;
672
notifySearchCancelled();
673
} else if (currentSearchTask != null) {
674
currentSearchTask.stop();
681
private void taskFinished(Task task) {
682
synchronized (lock) {
683
if (moduleBeingUninstalled) {
688
if (task == searchTask) {
689
assert state == SEARCHING;
690
if (currentSearchTask.notifyWhenFinished()) {
691
if (currentSearchTask.wasInterrupted()) {
692
notifySearchInterrupted();
694
notifySearchFinished();
697
currentSearchTask = null;
700
} else if (task == replaceTask) {
701
assert state == REPLACING;
702
notifyReplaceFinished();
703
currentReplaceTask = null;
706
} else if (task == cleanResultTask) {
707
assert state == CLEANING_RESULT;
708
cleanResultTask = null;
710
} else if (task == printDetailsTask) {
711
assert state == PRINTING_DETAILS;
712
notifyPrintingDetailsFinished();
714
outputWriterRef = currentPrintDetailsTask.getOutputWriterRef();
715
currentPrintDetailsTask = null;
716
printDetailsTask = null;
721
processNextPendingTask();
726
* Called only if the module is about to be uninstalled.
727
* This method is called at the moment that there are no active tasks
728
* (searching, printing details, etc.) and the module is ready for
731
private void allTasksFinished() {
732
synchronized (lock) {
738
* Called from the <code>Installer</code> to notify that the module
739
* is being uninstalled.
740
* Calling this method sets a corresponding flag. When the flag is set,
741
* no new actions (cleaning results, printing details, etc.) are started
742
* and the behaviour is changed so that manipulation with the ResultView
743
* is reduced or eliminated. Also, if no tasks are currently active,
744
* immediatelly closes the results window; otherwise it postpones closing
745
* the window until the currently active task(s) finish.
748
synchronized (lock) {
749
moduleBeingUninstalled = true;
750
if (state != NO_TASK) {
751
if (currentSearchTask != null) {
752
currentSearchTask.stop(false);
754
if (currentPrintDetailsTask != null) {
755
currentPrintDetailsTask.stop();
758
lock.wait(CLEANUP_TIMEOUT_MILLIS);
759
} catch (InterruptedException ex) {
760
ErrorManager.getDefault().notify(
761
ErrorManager.EXCEPTION,
765
callOnWindowFromAWT("closeResults"); //NOI18N
771
private TaskListener getTaskListener() {
772
if (taskListener == null) {
773
taskListener = new MyTaskListener();
781
private class MyTaskListener implements TaskListener {
791
public void taskFinished(Task task) {
792
synchronized (lock) {
793
Manager.this.taskFinished(task);