001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.tasklist.impl;
043:
044: import java.util.List;
045: import org.netbeans.modules.tasklist.filter.TaskFilter;
046: import java.beans.PropertyChangeListener;
047: import java.beans.PropertyChangeSupport;
048: import java.util.ArrayList;
049: import java.util.HashSet;
050: import java.util.Iterator;
051: import java.util.Set;
052: import java.util.logging.Level;
053: import java.util.logging.Logger;
054: import org.netbeans.modules.tasklist.trampoline.TaskManager;
055: import org.netbeans.spi.tasklist.FileTaskScanner;
056: import org.netbeans.spi.tasklist.PushTaskScanner;
057: import org.netbeans.spi.tasklist.Task;
058: import org.netbeans.spi.tasklist.TaskScanningScope;
059: import org.openide.filesystems.FileAttributeEvent;
060: import org.openide.filesystems.FileChangeListener;
061: import org.openide.filesystems.FileEvent;
062: import org.openide.filesystems.FileObject;
063: import org.openide.filesystems.FileRenameEvent;
064: import org.openide.filesystems.FileStateInvalidException;
065: import org.openide.filesystems.FileSystem;
066: import org.openide.util.Exceptions;
067: import org.openide.util.RequestProcessor;
068:
069: /**
070: * Default implementation of Task Manager
071: *
072: * @author S. Aubrecht
073: */
074: public class TaskManagerImpl extends TaskManager {
075:
076: public static final String PROP_SCOPE = "taskScanningScope"; //NOI18N
077:
078: public static final String PROP_FILTER = "filter"; //NOI18N
079:
080: public static final String PROP_WORKING_STATUS = "workingStatus"; //NOI18N
081:
082: private PropertyChangeSupport propertySupport = new PropertyChangeSupport(
083: this );
084:
085: private FileScanningWorker worker;
086: private TaskCache taskCache = new TaskCache();
087: private TaskList taskList = new TaskList();
088: private TaskScanningScope scope = Accessor.getEmptyScope();
089: private TaskFilter filter = TaskFilter.EMPTY;
090:
091: private static TaskManagerImpl theInstance;
092:
093: private Set<PushTaskScanner> workingScanners = new HashSet<PushTaskScanner>(
094: 10);
095: private boolean fileScannerWorking = false;
096: private boolean workingStatus = false;
097:
098: public static TaskManagerImpl getInstance() {
099: if (null == theInstance)
100: theInstance = new TaskManagerImpl();
101: return theInstance;
102: }
103:
104: public void observe(final TaskScanningScope newScope,
105: final TaskFilter newFilter) {
106: RequestProcessor.getDefault().post(new Runnable() {
107: public void run() {
108: doObserve(newScope, newFilter);
109: }
110: });
111: }
112:
113: private void doObserve(TaskScanningScope newScope,
114: TaskFilter newFilter) {
115: TaskScanningScope oldScope = scope;
116: TaskFilter oldFilter = filter;
117: synchronized (this ) {
118: if (null == newScope
119: || Accessor.getEmptyScope().equals(newScope)) {
120: scope.attach(null);
121: //turn off
122: stopWorker();
123: //stop listening to file system events
124: listenToFileSystemChanges(scope, false);
125:
126: workingScanners.clear();
127: fileScannerWorking = false;
128:
129: //detach simple/file scanners
130: for (PushTaskScanner scanner : ScannerList
131: .getPushScannerList().getScanners()) {
132: scanner.setScope(null, null);
133: }
134: for (FileTaskScanner scanner : ScannerList
135: .getFileScannerList().getScanners()) {
136: scanner.attach(null);
137: }
138: scope = Accessor.getEmptyScope();
139: filter = TaskFilter.EMPTY;
140:
141: setWorkingStatus(false);
142: } else {
143: //turn on or switch scope/filter
144: if (null == newFilter)
145: newFilter = TaskFilter.EMPTY;
146:
147: if (!scope.equals(newScope)
148: || !filter.equals(newFilter)) {
149:
150: taskList.clear();
151:
152: if (!newScope.equals(scope)) {
153: scope.attach(null);
154: newScope.attach(Accessor.createCallback(this ,
155: newScope));
156: }
157:
158: workingScanners.clear();
159: fileScannerWorking = false;
160:
161: setWorkingStatus(false);
162:
163: scope = newScope;
164: filter = newFilter;
165:
166: attachFileScanners(newFilter, filter);
167: attachPushScanners(newScope, newFilter, filter);
168:
169: //start listening to file system events
170: listenToFileSystemChanges(scope, true);
171:
172: startWorker();
173: worker.scan(scope.iterator(), filter);
174: }
175: }
176: }
177: propertySupport.firePropertyChange(PROP_SCOPE, oldScope,
178: newScope);
179: propertySupport.firePropertyChange(PROP_FILTER, oldFilter,
180: newFilter);
181: }
182:
183: private void attachFileScanners(TaskFilter newFilter,
184: TaskFilter oldFilter) {
185: for (FileTaskScanner scanner : getFileScanners()) {
186: if (!newFilter.isEnabled(scanner))
187: scanner.attach(null);
188: else if (newFilter.isEnabled(scanner))
189: scanner.attach(Accessor.createCallback(this , scanner));
190: }
191: }
192:
193: private void attachPushScanners(TaskScanningScope newScope,
194: TaskFilter newFilter, TaskFilter oldFilter) {
195: for (PushTaskScanner scanner : getPushScanners()) {
196: if (!newFilter.isEnabled(scanner)) {
197: scanner.setScope(null, null);
198: } else if (newFilter.isEnabled(scanner)) {
199: scanner.setScope(newScope, Accessor.createCallback(
200: this , scanner));
201: }
202: }
203: }
204:
205: Iterable<? extends FileTaskScanner> getFileScanners() {
206: return ScannerList.getFileScannerList().getScanners();
207: }
208:
209: Iterable<? extends PushTaskScanner> getPushScanners() {
210: return ScannerList.getPushScannerList().getScanners();
211: }
212:
213: public void abort() {
214: RequestProcessor.getDefault().post(new Runnable() {
215: public void run() {
216: doAbort();
217: }
218: });
219: }
220:
221: private void doAbort() {
222: if (null != worker)
223: worker.abort();
224:
225: for (PushTaskScanner scanner : ScannerList.getPushScannerList()
226: .getScanners()) {
227: scanner.setScope(null, null);
228: }
229:
230: workingScanners.clear();
231: fileScannerWorking = false;
232: setWorkingStatus(false);
233: }
234:
235: boolean isObserved() {
236: return !Accessor.getEmptyScope().equals(getScope());
237: }
238:
239: public TaskScanningScope getScope() {
240: return scope;
241: }
242:
243: public TaskList getTasks() {
244: return taskList;
245: }
246:
247: public void addPropertyChangeListener(
248: PropertyChangeListener listener) {
249: propertySupport.addPropertyChangeListener(listener);
250: }
251:
252: public void addPropertyChangeListener(String propName,
253: PropertyChangeListener listener) {
254: propertySupport.addPropertyChangeListener(propName, listener);
255: }
256:
257: public void removePropertyChangeListener(
258: PropertyChangeListener listener) {
259: propertySupport.removePropertyChangeListener(listener);
260: }
261:
262: public void removePropertyChangeListener(String propName,
263: PropertyChangeListener listener) {
264: propertySupport
265: .removePropertyChangeListener(propName, listener);
266: }
267:
268: private void startWorker() {
269: if (null == worker) {
270: worker = new FileScanningWorker(taskCache, taskList,
271: filter, new FileScannerProgress());
272: RequestProcessor.getDefault().post(worker);
273: }
274: }
275:
276: private void stopWorker() {
277: if (null != worker) {
278: worker.kill();
279: worker = null;
280: }
281: }
282:
283: private void maybeScanResource(final FileObject rc,
284: final boolean clearCache) {
285: RequestProcessor.getDefault().post(new Runnable() {
286: public void run() {
287: if (isObserved() && scope.isInScope(rc)) {
288: synchronized (TaskManagerImpl.this ) {
289: if (clearCache) {
290: taskCache.clear(rc);
291: taskList.clear(rc);
292: }
293:
294: startWorker();
295: worker.priorityScan(rc);
296: }
297: }
298: }
299: });
300: }
301:
302: private FileChangeListener fileListener = null;
303:
304: private FileChangeListener getFileChangeListener() {
305: if (null == fileListener) {
306: fileListener = new FileChangeListener() {
307:
308: public void fileFolderCreated(FileEvent fe) {
309: maybeScanResource(fe.getFile(), false);
310: }
311:
312: public void fileDataCreated(FileEvent fe) {
313: maybeScanResource(fe.getFile(), false);
314: }
315:
316: public void fileChanged(FileEvent fe) {
317: FileObject rc = fe.getFile();
318:
319: maybeScanResource(rc, true);
320: }
321:
322: public void fileDeleted(final FileEvent fe) {
323: RequestProcessor.getDefault().post(new Runnable() {
324: public void run() {
325: synchronized (TaskManagerImpl.this ) {
326: FileObject rc = fe.getFile();
327: taskCache.clear(rc);
328: taskList.clear(rc);
329: }
330: }
331: });
332: }
333:
334: public void fileRenamed(FileRenameEvent fe) {
335: //TODO rename in current model and in cache instead of rescan??
336: maybeScanResource(fe.getFile(), false);
337: }
338:
339: public void fileAttributeChanged(FileAttributeEvent fe) {
340: //ignore
341: }
342: };
343: }
344: return fileListener;
345: }
346:
347: private void listenToFileSystemChanges(
348: final TaskScanningScope scanningScope,
349: final boolean addListener) {
350: FileSystem fs = getFileSystem(scanningScope);
351: if (null != fs) {
352: if (addListener) {
353: fs.addFileChangeListener(getFileChangeListener());
354: } else {
355: if (null != fileListener) {
356: fs
357: .removeFileChangeListener(getFileChangeListener());
358: fileListener = null;
359: }
360: }
361: }
362: }
363:
364: private FileSystem getFileSystem(TaskScanningScope scanningScope) {
365: if (null != scanningScope) {
366: Iterator<FileObject> resources = scanningScope.iterator();
367: if (resources.hasNext()) {
368: try {
369: FileObject rc = resources.next();
370: if (null != rc)
371: return rc.getFileSystem();
372: } catch (FileStateInvalidException fsiE) {
373: getLogger().log(Level.WARNING, fsiE.getMessage(),
374: fsiE);
375: }
376: }
377: }
378: return null;
379: }
380:
381: public TaskFilter getFilter() {
382: return filter;
383: }
384:
385: public void refresh(final FileTaskScanner scanner,
386: final FileObject... resources) {
387: synchronized (this ) {
388: taskCache.clear(scanner, resources);
389: taskList.clear(scanner, resources);
390: if (isObserved() && isEnabled(scanner)) {
391:
392: final ArrayList<FileObject> resourcesInScope = new ArrayList<FileObject>(
393: resources.length);
394: for (int i = 0; i < resources.length && null != scope; i++) {
395: if (scope.isInScope(resources[i])) {
396: resourcesInScope.add(resources[i]);
397: }
398: }
399: if (!resourcesInScope.isEmpty()) {
400:
401: Runnable r = new Runnable() {
402: public void run() {
403: startWorker();
404: worker
405: .priorityScan(
406: scanner,
407: resourcesInScope
408: .toArray(new FileObject[resourcesInScope
409: .size()]));
410: }
411: };
412: RequestProcessor.getDefault().post(r);
413: }
414: }
415: }
416: }
417:
418: private boolean isEnabled(FileTaskScanner scanner) {
419: return getFilter().isEnabled(scanner);
420: }
421:
422: public void refresh(FileTaskScanner scanner) {
423: synchronized (this ) {
424: taskCache.clear(scanner);
425: taskList.clear(scanner);
426:
427: if (isObserved() && isEnabled(scanner)) {
428:
429: Runnable r = new Runnable() {
430: public void run() {
431: startWorker();
432: worker.scan(scope.iterator(), filter);
433: }
434: };
435: RequestProcessor.getDefault().post(r);
436: }
437: }
438: }
439:
440: public void refresh(final TaskScanningScope scopeToRefresh) {
441: if (this .scope.equals(scopeToRefresh)) {
442: RequestProcessor.getDefault().post(new Runnable() {
443: public void run() {
444: doRefresh(scopeToRefresh);
445: }
446: });
447: }
448: }
449:
450: private void doRefresh(TaskScanningScope scopeToRefresh) {
451: synchronized (this ) {
452: if (this .scope.equals(scopeToRefresh)) {
453: listenToFileSystemChanges(scope, false);
454: listenToFileSystemChanges(scope, true);
455: taskList.clear();
456: if (isObserved()) {
457: for (PushTaskScanner scanner : ScannerList
458: .getPushScannerList().getScanners()) {
459: scanner.setScope(null, null);
460: if (getFilter().isEnabled(scanner))
461: scanner.setScope(scopeToRefresh, Accessor
462: .createCallback(this , scanner));
463: }
464: startWorker();
465: worker.scan(scope.iterator(), filter);
466: }
467: }
468: }
469: }
470:
471: public void started(PushTaskScanner scanner) {
472: synchronized (workingScanners) {
473: workingScanners.add(scanner);
474: setWorkingStatus(true);
475: }
476: }
477:
478: public void finished(PushTaskScanner scanner) {
479: synchronized (workingScanners) {
480: workingScanners.remove(scanner);
481: setWorkingStatus(isWorking());
482: }
483: }
484:
485: public void setTasks(PushTaskScanner scanner, FileObject resource,
486: List<? extends Task> tasks) {
487: if (isObserved() && scope.isInScope(resource))
488: taskList.setTasks(scanner, resource, tasks, filter);
489: }
490:
491: public void clearAllTasks(PushTaskScanner scanner) {
492: taskList.clear(scanner);
493: }
494:
495: private Logger getLogger() {
496: return Logger.getLogger(TaskManagerImpl.class.getName());
497: }
498:
499: private void setWorkingStatus(boolean newStatus) {
500: synchronized (workingScanners) {
501: if (newStatus != workingStatus) {
502: boolean oldStatus = workingStatus;
503: workingStatus = newStatus;
504: propertySupport.firePropertyChange(PROP_WORKING_STATUS,
505: oldStatus, newStatus);
506: //for unit testing
507: if (!workingStatus) {
508: workingScanners.notifyAll();
509: }
510: }
511: }
512: }
513:
514: private boolean isWorking() {
515: synchronized (workingScanners) {
516: return !workingScanners.isEmpty() || fileScannerWorking;
517: }
518: }
519:
520: /**
521: * For unit testing only
522: */
523: void waitFinished() {
524: synchronized (workingScanners) {
525: try {
526: workingScanners.wait();
527: } catch (InterruptedException e) {
528: Exceptions.printStackTrace(e);
529: }
530: }
531: }
532:
533: class FileScannerProgress {
534: public void started() {
535: synchronized (workingScanners) {
536: fileScannerWorking = true;
537: setWorkingStatus(true);
538: }
539: }
540:
541: public void finished() {
542: synchronized (workingScanners) {
543: fileScannerWorking = false;
544: setWorkingStatus(isWorking());
545: }
546: }
547: }
548: }
|