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-2007 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.java.source.tasklist;
043:
044: import java.io.IOException;
045: import java.net.URL;
046: import java.util.Collections;
047: import java.util.HashSet;
048: import java.util.List;
049: import java.util.Map;
050: import java.util.Set;
051: import java.util.WeakHashMap;
052: import java.util.concurrent.atomic.AtomicBoolean;
053: import java.util.logging.Level;
054: import java.util.logging.Logger;
055: import org.netbeans.api.java.classpath.ClassPath;
056: import org.netbeans.api.project.Project;
057: import org.netbeans.api.project.ProjectUtils;
058: import org.netbeans.api.project.SourceGroup;
059: import org.netbeans.spi.tasklist.PushTaskScanner;
060: import org.netbeans.spi.tasklist.Task;
061: import org.netbeans.spi.tasklist.TaskScanningScope;
062: import org.openide.filesystems.FileObject;
063: import org.openide.filesystems.FileUtil;
064: import org.openide.filesystems.URLMapper;
065: import org.openide.util.Exceptions;
066: import org.openide.util.RequestProcessor;
067: import org.openide.util.TaskListener;
068: import org.openide.util.WeakSet;
069:
070: /**
071: *
072: * @author Stanislav Aubrecht, Jan Lahoda
073: */
074: public final class JavaTaskProvider extends PushTaskScanner {
075:
076: private static final Logger LOG = Logger
077: .getLogger(JavaTaskProvider.class.getName());
078:
079: private static JavaTaskProvider INSTANCE;
080:
081: private TaskScanningScope scope;
082: private Callback callback;
083:
084: public JavaTaskProvider() {
085: super ("Java Errors", "Java compiler errors and warnings", null);
086: INSTANCE = this ;
087: }
088:
089: private synchronized void refreshImpl(FileObject file) {
090: LOG.log(Level.FINE, "refresh: {0}", file);
091:
092: if (scope == null || callback == null)
093: return; //nothing to refresh
094: if (!scope.isInScope(file)) {
095: if (!file.isFolder())
096: return;
097:
098: //the given file may be a parent of some file that is in the scope:
099: for (FileObject inScope : scope.getLookup().lookupAll(
100: FileObject.class)) {
101: if (FileUtil.isParentOf(file, inScope)) {
102: enqueue(new Work(inScope, callback));
103: }
104: }
105:
106: return;
107: }
108:
109: LOG.log(Level.FINE, "enqueing work for: {0}", file);
110: enqueue(new Work(file, callback));
111: }
112:
113: public static void refresh(FileObject file) {
114: if (INSTANCE != null) {
115: INSTANCE.refreshImpl(file);
116: }
117: }
118:
119: public static void refreshAll() {
120: if (INSTANCE != null) {
121: synchronized (INSTANCE) {
122: INSTANCE.setScope(INSTANCE.scope, INSTANCE.callback);
123: }
124: }
125: }
126:
127: @Override
128: public synchronized void setScope(TaskScanningScope scope,
129: Callback callback) {
130: //cancel all current operations:
131: cancelAllCurrent();
132:
133: this .scope = scope;
134: this .callback = callback;
135:
136: if (scope == null || callback == null)
137: return;
138:
139: for (FileObject file : scope.getLookup().lookupAll(
140: FileObject.class)) {
141: enqueue(new Work(file, callback));
142: }
143:
144: for (Project p : scope.getLookup().lookupAll(Project.class)) {
145: for (SourceGroup sg : ProjectUtils.getSources(p)
146: .getSourceGroups("java")) {
147: enqueue(new Work(sg.getRootFolder(), callback));
148: }
149: }
150: }
151:
152: private static final Set<RequestProcessor.Task> TASKS = new HashSet<RequestProcessor.Task>();
153: private static boolean clearing;
154: private static final RequestProcessor WORKER = new RequestProcessor(
155: "Java Task Provider");
156: private static Map<FileObject, Set<FileObject>> root2FilesWithAttachedErrors = new WeakHashMap<FileObject, Set<FileObject>>();
157:
158: private static void enqueue(Work w) {
159: synchronized (TASKS) {
160: final RequestProcessor.Task task = WORKER.post(w);
161:
162: TASKS.add(task);
163: task.addTaskListener(new TaskListener() {
164: public void taskFinished(org.openide.util.Task task) {
165: synchronized (TASKS) {
166: if (!clearing) {
167: TASKS.remove(task);
168: }
169: }
170: }
171: });
172: if (task.isFinished()) {
173: TASKS.remove(task);
174: }
175: }
176: }
177:
178: private static void cancelAllCurrent() {
179: synchronized (TASKS) {
180: clearing = true;
181: try {
182: for (RequestProcessor.Task t : TASKS) {
183: t.cancel();
184: }
185: TASKS.clear();
186: } finally {
187: clearing = false;
188: }
189: }
190:
191: synchronized (JavaTaskProvider.class) {
192: root2FilesWithAttachedErrors.clear();
193: }
194:
195: }
196:
197: //only for tests:
198: static void waitWorkFinished() throws Exception {
199: while (true) {
200: RequestProcessor.Task t = null;
201: synchronized (TASKS) {
202: if (TASKS.isEmpty())
203: return;
204: t = TASKS.iterator().next();
205: }
206:
207: t.waitFinished();
208: }
209: }
210:
211: private static Set<FileObject> getFilesWithAttachedErrors(
212: FileObject root) {
213: Set<FileObject> result = root2FilesWithAttachedErrors.get(root);
214:
215: if (result == null) {
216: root2FilesWithAttachedErrors.put(root,
217: result = new WeakSet<FileObject>());
218: }
219:
220: return result;
221: }
222:
223: private static synchronized void updateErrorsInRoot(
224: Callback callback, FileObject root) {
225: Set<FileObject> filesWithErrors = getFilesWithAttachedErrors(root);
226: Set<FileObject> fixedFiles = new HashSet<FileObject>(
227: filesWithErrors);
228: Set<FileObject> nueFilesWithErrors = new HashSet<FileObject>();
229:
230: try {
231: if (TasklistSettings.isTasklistEnabled()) {
232: for (URL u : TaskCache.getDefault()
233: .getAllFilesWithRecord(root.getURL())) {
234: FileObject file = URLMapper.findFileObject(u);
235:
236: if (file != null) {
237: List<Task> result = TaskCache.getDefault()
238: .getErrors(file);
239:
240: LOG.log(Level.FINE, "Setting {1} for {0}\n",
241: new Object[] { file, result });
242:
243: callback.setTasks(file, result);
244:
245: if (!fixedFiles.remove(file)) {
246: nueFilesWithErrors.add(file);
247: }
248: }
249: }
250: }
251: } catch (IOException e) {
252: Exceptions.printStackTrace(e);
253: }
254:
255: for (FileObject f : fixedFiles) {
256: LOG.log(Level.FINE, "Clearing errors for {0}", f);
257: callback.setTasks(f, Collections.<Task> emptyList());
258: }
259:
260: filesWithErrors.addAll(nueFilesWithErrors);
261: }
262:
263: private static final class Work implements Runnable {
264: private FileObject fileOrRoot;
265: private Callback callback;
266:
267: public Work(FileObject fileOrRoot, Callback callback) {
268: this .fileOrRoot = fileOrRoot;
269: this .callback = callback;
270: }
271:
272: public FileObject getFileOrRoot() {
273: return fileOrRoot;
274: }
275:
276: public Callback getCallback() {
277: return callback;
278: }
279:
280: public void run() {
281: FileObject file = getFileOrRoot();
282:
283: LOG.log(Level.FINE, "dequeued work for: {0}", file);
284:
285: ClassPath cp = ClassPath.getClassPath(file,
286: ClassPath.SOURCE);
287:
288: if (cp == null) {
289: LOG.log(Level.FINE, "cp == null");
290: return;
291: }
292:
293: FileObject root = cp.findOwnerRoot(file);
294:
295: if (file.isData()) {
296: List<? extends Task> tasks = TaskCache.getDefault()
297: .getErrors(file);
298: Set<FileObject> filesWithErrors = getFilesWithAttachedErrors(root);
299:
300: if (tasks.isEmpty()) {
301: filesWithErrors.remove(file);
302: } else {
303: filesWithErrors.add(file);
304: }
305:
306: LOG.log(Level.FINE, "setting {1} for {0}",
307: new Object[] { file, tasks });
308: getCallback().setTasks(file, tasks);
309: } else {
310: updateErrorsInRoot(getCallback(), root);
311: }
312: }
313: }
314: }
|