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.BufferedReader;
045: import java.io.File;
046: import java.io.FileInputStream;
047: import java.io.FileOutputStream;
048: import java.io.FilenameFilter;
049: import java.io.IOException;
050: import java.io.InputStreamReader;
051: import java.io.OutputStreamWriter;
052: import java.io.PrintWriter;
053: import java.net.URI;
054: import java.net.URISyntaxException;
055: import java.net.URL;
056: import java.util.Collections;
057: import java.util.HashSet;
058: import java.util.LinkedList;
059: import java.util.List;
060: import java.util.Queue;
061: import java.util.Set;
062: import java.util.logging.Level;
063: import java.util.logging.Logger;
064: import javax.tools.Diagnostic;
065: import javax.tools.Diagnostic.Kind;
066: import org.netbeans.api.java.classpath.ClassPath;
067: import org.netbeans.api.project.FileOwnerQuery;
068: import org.netbeans.api.project.Project;
069: import org.netbeans.modules.java.source.usages.Index;
070: import org.netbeans.spi.tasklist.Task;
071: import org.openide.filesystems.FileObject;
072: import org.openide.filesystems.FileUtil;
073: import org.openide.filesystems.URLMapper;
074:
075: /**
076: *
077: * @author Jan Lahoda, Stanislav Aubrecht
078: */
079: public class TaskCache {
080:
081: private static final String ERR_EXT = "err";
082: private static final String WARN_EXT = "warn";
083:
084: private static final Logger LOG = Logger.getLogger(TaskCache.class
085: .getName());
086:
087: static {
088: // LOG.setLevel(Level.FINEST);
089: }
090:
091: private static TaskCache theInstance;
092:
093: private TaskCache() {
094: }
095:
096: public static TaskCache getDefault() {
097: if (null == theInstance) {
098: theInstance = new TaskCache();
099: }
100: return theInstance;
101: }
102:
103: private String getTaskType(Kind k) {
104: switch (k) {
105: case ERROR:
106: return "nb-tasklist-error"; //NOI18N
107: case WARNING:
108: case MANDATORY_WARNING:
109: return "nb-tasklist-warning"; //NOI18N
110: }
111: return null;
112: }
113:
114: public List<Task> getErrors(FileObject file) {
115: return getErrors(file, false);
116: }
117:
118: private List<Task> getErrors(FileObject file, boolean onlyErrors) {
119: LOG.log(Level.FINE, "getErrors, file={0}", FileUtil
120: .getFileDisplayName(file));
121:
122: try {
123: File input = computePersistentFile(file);
124:
125: LOG.log(Level.FINE, "getErrors, error file={0}",
126: input == null ? "null" : input.getAbsolutePath());
127:
128: if (input == null || !input.canRead())
129: return Collections.<Task> emptyList();
130:
131: input.getParentFile().mkdirs();
132:
133: return loadErrors(input, file, onlyErrors);
134: } catch (IOException e) {
135: e.printStackTrace();
136: }
137:
138: return Collections.<Task> emptyList();
139: }
140:
141: private boolean dumpErrors(File output,
142: List<? extends Diagnostic> errors,
143: boolean interestedInReturnValue) throws IOException {
144: if (!errors.isEmpty()) {
145: boolean existed = interestedInReturnValue
146: && output.exists();
147: output.getParentFile().mkdirs();
148: PrintWriter pw = new PrintWriter(new OutputStreamWriter(
149: new FileOutputStream(output), "UTF-8"));
150:
151: for (Diagnostic d : errors) {
152: pw.print(d.getKind());
153: pw.print(':');
154: pw.print(d.getLineNumber());
155: pw.print(':');
156:
157: String description = d.getMessage(null);
158:
159: description = description
160: .replaceAll("\\\\", "\\\\\\\\");
161: description = description.replaceAll("\n", "\\\\n");
162: description = description.replaceAll(":", "\\\\d");
163:
164: pw.println(description);
165: }
166:
167: pw.close();
168:
169: return !existed;
170: } else {
171: return output.delete();
172: }
173: }
174:
175: private void separate(List<? extends Diagnostic> input,
176: List<Diagnostic> errors, List<Diagnostic> notErrors) {
177: for (Diagnostic d : input) {
178: if (d.getKind() == Kind.ERROR) {
179: errors.add(d);
180: } else {
181: notErrors.add(d);
182: }
183: }
184: }
185:
186: public Set<URL> dumpErrors(URL root, URL file, File fileFile,
187: List<? extends Diagnostic> errors) throws IOException {
188: if (!fileFile.canRead()) {
189: //if the file is not readable anymore, ignore the errors:
190: errors = Collections.emptyList();
191: }
192:
193: File[] output = computePersistentFile(root, file);
194:
195: List<Diagnostic> trueErrors = new LinkedList<Diagnostic>();
196: List<Diagnostic> notErrors = new LinkedList<Diagnostic>();
197:
198: separate(errors, trueErrors, notErrors);
199:
200: boolean modified = dumpErrors(output[1], trueErrors, true);
201:
202: dumpErrors(output[2], notErrors, false);
203:
204: Set<URL> toRefresh = new HashSet<URL>();
205:
206: toRefresh.add(file);
207:
208: File currentFile = fileFile.getParentFile();
209:
210: toRefresh.add(currentFile.toURI().toURL());
211:
212: if (modified) {
213: File current = output[1].getParentFile();
214:
215: while (!output[0].equals(current)) {
216: current = current.getParentFile();
217: currentFile = currentFile.getParentFile();
218: toRefresh.add(currentFile.toURI().toURL());
219: }
220:
221: FileObject rootFO = URLMapper.findFileObject(root);
222:
223: //XXX:
224: if (rootFO != null) {
225: Project p = FileOwnerQuery.getOwner(rootFO);
226:
227: if (p != null) {
228: FileObject currentFO = rootFO;
229: FileObject projectDirectory = p
230: .getProjectDirectory();
231:
232: if (FileUtil.isParentOf(projectDirectory, rootFO)) {
233: while (currentFO != null
234: && currentFO != projectDirectory) {
235: toRefresh.add(currentFO.getURL());
236: currentFO = currentFO.getParent();
237: }
238: }
239:
240: toRefresh.add(projectDirectory.getURL());
241: }
242: }
243: }
244:
245: return toRefresh;
246: }
247:
248: private List<Task> loadErrors(File input, FileObject file,
249: boolean onlyErrors) throws IOException {
250: List<Task> result = new LinkedList<Task>();
251: BufferedReader pw = new BufferedReader(new InputStreamReader(
252: new FileInputStream(input), "UTF-8"));
253: String line;
254:
255: while ((line = pw.readLine()) != null) {
256: String[] parts = line.split(":");
257:
258: Kind kind = Kind.valueOf(parts[0]);
259:
260: if (kind == null) {
261: continue;
262: }
263:
264: int lineNumber = Integer.parseInt(parts[1]);
265: String message = parts[2];
266:
267: message = message.replaceAll("\\\\d", ":");
268: message = message.replaceAll("\\\\n", " ");
269: message = message.replaceAll("\\\\\\\\", "\\\\");
270:
271: String severity = getTaskType(kind);
272:
273: if (null != severity && (!onlyErrors || kind == Kind.ERROR)) {
274: Task err = Task.create(file, severity, message,
275: lineNumber);
276: result.add(err);
277: }
278: }
279:
280: pw.close();
281:
282: return result;
283: }
284:
285: public List<URL> getAllFilesWithRecord(URL root) throws IOException {
286: return getAllFilesWithRecord(root, false);
287: }
288:
289: private List<URL> getAllFilesWithRecord(URL root, boolean onlyErrors)
290: throws IOException {
291: try {
292: List<URL> result = new LinkedList<URL>();
293: URI rootURI = root.toURI();
294: File[] cacheRoot = computePersistentFile(root, root);
295: URI cacheRootURI = cacheRoot[0].toURI();
296: Queue<File> todo = new LinkedList<File>();
297:
298: todo.add(cacheRoot[0]);
299:
300: while (!todo.isEmpty()) {
301: File f = todo.poll();
302:
303: assert f != null;
304:
305: if (f.isFile()) {
306: if (f.getName().endsWith(ERR_EXT)) {
307: String relative = cacheRootURI.relativize(
308: f.toURI()).getRawPath();
309:
310: relative = relative.replaceAll(ERR_EXT + "$",
311: "java");
312: result.add(rootURI.resolve(relative).toURL());
313: }
314: if (!onlyErrors && f.getName().endsWith(WARN_EXT)) {
315: String relative = cacheRootURI.relativize(
316: f.toURI()).getRawPath();
317:
318: relative = relative.replaceAll(WARN_EXT + "$",
319: "java");
320: result.add(rootURI.resolve(relative).toURL());
321: }
322: } else {
323: File[] files = f.listFiles();
324:
325: if (files != null) {
326: for (File children : files)
327: todo.offer(children);
328: }
329: }
330: }
331:
332: return result;
333: } catch (URISyntaxException e) {
334: throw (IOException) new IOException().initCause(e);
335: }
336: }
337:
338: public List<URL> getAllFilesInError(URL root) throws IOException {
339: return getAllFilesWithRecord(root, true);
340: }
341:
342: public boolean isInError(FileObject file, boolean recursive) {
343: LOG.log(Level.FINE, "file={0}, recursive={1}", new Object[] {
344: file, Boolean.valueOf(recursive) });
345:
346: if (file.isData()) {
347: return !getErrors(file, true).isEmpty();
348: } else {
349: try {
350: ClassPath cp = ClassPath.getClassPath(file,
351: ClassPath.SOURCE);
352:
353: if (cp == null) {
354: return false;
355: }
356:
357: FileObject root = cp.findOwnerRoot(file);
358:
359: if (root == null) {
360: LOG
361: .log(
362: Level.FINE,
363: "file={0} does not have a root on its own source classpath",
364: file);
365: return false;
366: }
367:
368: String resourceName = cp.getResourceName(file,
369: File.separatorChar, false);
370: File cacheRoot = Index.getClassFolder(root.getURL(),
371: true);
372:
373: if (cacheRoot == null) {
374: //index does not exist:
375: return false;
376: }
377:
378: final File folder = new File(new File(cacheRoot
379: .getParentFile(), "errors"), resourceName);
380:
381: return folderContainsErrors(folder, recursive);
382: } catch (IOException e) {
383: Logger.getLogger("global").log(Level.WARNING, null, e);
384: return false;
385: }
386: }
387: }
388:
389: private boolean folderContainsErrors(File folder,
390: boolean recursively) throws IOException {
391: File[] errors = folder.listFiles(new FilenameFilter() {
392: public boolean accept(File dir, String name) {
393: return name.endsWith(".err");
394: }
395: });
396:
397: if (errors == null)
398: return false;
399:
400: if (errors.length > 0) {
401: return true;
402: }
403:
404: if (!recursively)
405: return false;
406:
407: File[] children = folder.listFiles();
408:
409: if (children == null)
410: return false;
411:
412: for (File c : children) {
413: if (c.isDirectory() && folderContainsErrors(c, recursively)) {
414: return true;
415: }
416: }
417:
418: return false;
419: }
420:
421: private File[] computePersistentFile(URL root, URL file)
422: throws IOException {
423: try {
424: URI fileURI = file.toURI();
425: URI u = root.toURI();
426: String resourceName = u.relativize(fileURI).getPath();
427: int lastDot = resourceName.lastIndexOf('.');
428: if (lastDot != (-1)) {
429: resourceName = resourceName.substring(0, lastDot);
430: }
431: File cacheRoot = Index.getClassFolder(root);
432: File errorsRoot = new File(cacheRoot.getParentFile(),
433: "errors");
434: File errorCacheFile = new File(errorsRoot, resourceName
435: + "." + ERR_EXT);
436: File warningCacheFile = new File(errorsRoot, resourceName
437: + "." + WARN_EXT);
438:
439: return new File[] { errorsRoot, errorCacheFile,
440: warningCacheFile };
441: } catch (URISyntaxException e) {
442: throw (IOException) new IOException().initCause(e);
443: }
444: }
445:
446: private File computePersistentFile(FileObject file)
447: throws IOException {
448: ClassPath cp = ClassPath.getClassPath(file, ClassPath.SOURCE);
449:
450: if (cp == null)
451: return null;
452:
453: FileObject root = cp.findOwnerRoot(file);
454:
455: if (root == null) {
456: LOG
457: .log(
458: Level.FINE,
459: "file={0} does not have a root on its own source classpath",
460: file);
461: return null;
462: }
463:
464: String resourceName = cp.getResourceName(file,
465: File.separatorChar, false);
466: File cacheRoot = Index.getClassFolder(root.getURL());
467: File cacheFile = new File(new File(cacheRoot.getParentFile(),
468: "errors"), resourceName + ".err");
469:
470: return cacheFile;
471: }
472:
473: }
|