001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Sam
028: */
029:
030: package com.caucho.netbeans.util;
031:
032: import org.netbeans.api.java.classpath.GlobalPathRegistry;
033: import org.openide.ErrorManager;
034: import org.openide.cookies.EditorCookie;
035: import org.openide.filesystems.FileObject;
036: import org.openide.filesystems.FileUtil;
037: import org.openide.loaders.DataObject;
038: import org.openide.loaders.DataObjectNotFoundException;
039: import org.openide.text.Annotation;
040: import org.openide.text.Line;
041: import org.openide.windows.OutputEvent;
042: import org.openide.windows.OutputListener;
043:
044: import java.io.File;
045: import java.util.Collections;
046: import java.util.HashMap;
047: import java.util.Map;
048:
049: /**
050: * Logger support class for creating links in the output window.
051: */
052: public final class LogSupport {
053: private final Map/*<Link, Link>*/links = Collections
054: .synchronizedMap(new HashMap());
055: private Annotation errAnnot;
056: private String prevMessage;
057:
058: public LineInfo analyzeLine(String logLine) {
059: String path = null;
060: int line = -1;
061: String message = null;
062: boolean error = false;
063: boolean accessible = false;
064:
065: logLine = logLine.trim();
066: int lineLenght = logLine.length();
067:
068: // look for unix file links (e.g. /foo/bar.java:51: 'error msg')
069: if (logLine.startsWith("/")) { // NOI18N
070: error = true;
071: int colonIdx = logLine.indexOf(':');
072: if (colonIdx > -1) {
073: path = logLine.substring(0, colonIdx);
074: accessible = true;
075: if (lineLenght > colonIdx) {
076: int nextColonIdx = logLine.indexOf(':',
077: colonIdx + 1);
078: if (nextColonIdx > -1) {
079: String lineNum = logLine.substring(
080: colonIdx + 1, nextColonIdx);
081: try {
082: line = Integer.valueOf(lineNum).intValue();
083: } catch (NumberFormatException nfe) { // ignore it
084: }
085: if (lineLenght > nextColonIdx) {
086: message = logLine.substring(
087: nextColonIdx + 1, lineLenght);
088: }
089: }
090: }
091: }
092: }
093: // look for windows file links (e.g. c:\foo\bar.java:51: 'error msg')
094: else if (lineLenght > 3
095: && Character.isLetter(logLine.charAt(0))
096: && (logLine.charAt(1) == ':')
097: && (logLine.charAt(2) == '\\')) {
098: error = true;
099: int secondColonIdx = logLine.indexOf(':', 2);
100: if (secondColonIdx > -1) {
101: path = logLine.substring(0, secondColonIdx);
102: accessible = true;
103: if (lineLenght > secondColonIdx) {
104: int thirdColonIdx = logLine.indexOf(':',
105: secondColonIdx + 1);
106: if (thirdColonIdx > -1) {
107: String lineNum = logLine.substring(
108: secondColonIdx + 1, thirdColonIdx);
109: try {
110: line = Integer.valueOf(lineNum).intValue();
111: } catch (NumberFormatException nfe) { // ignore it
112: }
113: if (lineLenght > thirdColonIdx) {
114: message = logLine.substring(
115: thirdColonIdx + 1, lineLenght);
116: }
117: }
118: }
119: }
120: }
121: // look for stacktrace links (e.g. at java.lang.Thread.run(Thread.java:595)
122: // at t.HyperlinkTest$1.run(HyperlinkTest.java:24))
123: else if (logLine.startsWith("at ") && lineLenght > 3) {
124: error = true;
125: int parenthIdx = logLine.indexOf('(');
126: if (parenthIdx > -1) {
127: String classWithMethod = logLine.substring(3,
128: parenthIdx);
129: int lastDotIdx = classWithMethod.lastIndexOf('.');
130: if (lastDotIdx > -1) {
131: int lastParenthIdx = logLine.lastIndexOf(')');
132: int lastColonIdx = logLine.lastIndexOf(':');
133: if (lastParenthIdx > -1 && lastColonIdx > -1) {
134: String lineNum = logLine.substring(
135: lastColonIdx + 1, lastParenthIdx);
136: try {
137: line = Integer.valueOf(lineNum).intValue();
138: } catch (NumberFormatException nfe) { // ignore it
139: }
140: message = prevMessage;
141: }
142: int firstDolarIdx = classWithMethod.indexOf('$'); // > -1 for inner classes
143: String className = classWithMethod.substring(0,
144: firstDolarIdx > -1 ? firstDolarIdx
145: : lastDotIdx);
146: path = className.replace('.', '/') + ".java"; // NOI18N
147: accessible = GlobalPathRegistry.getDefault()
148: .findResource(path) != null;
149: }
150: }
151: }
152: // every other message treat as normal info message
153: else {
154: prevMessage = logLine;
155: }
156: return new LineInfo(path, line, message, error, accessible);
157: }
158:
159: /**
160: * Return a link which implements <code>OutputListener</code> interface.
161: * Link is then used to represent a link in the output window. This class
162: * also handles error annotations which are shown after a line is clicked.
163: *
164: * @return link which implements <code>OutputListener</code> interface. Link
165: * is then used to represent a link in the output window.
166: */
167: public Link getLink(String errorMsg, String path, int line) {
168: Link newLink = new Link(errorMsg, path, line);
169: Link cachedLink = (Link) links.get(newLink);
170: if (cachedLink != null) {
171: return cachedLink;
172: }
173: links.put(newLink, newLink);
174: return newLink;
175: }
176:
177: /**
178: * Detach error annotation.
179: */
180: public void detachAnnotation() {
181: if (errAnnot != null) {
182: errAnnot.detach();
183: }
184: }
185:
186: /**
187: * <code>LineInfo</code> is used to store info about the parsed line.
188: */
189: public static class LineInfo {
190: private String path;
191: private int line;
192: private String message;
193: private boolean error;
194: private boolean accessible;
195:
196: /**
197: * <code>LineInfo</code> is used to store info about the parsed line.
198: *
199: * @param path path to file
200: * @param line line number where the error occurred
201: * @param message error message
202: * @param error represents the line an error?
203: * @param accessible is the file accessible?
204: */
205: public LineInfo(String path, int line, String message,
206: boolean error, boolean accessible) {
207: this .path = path;
208: this .line = line;
209: this .message = message;
210: this .error = error;
211: this .accessible = accessible;
212: }
213:
214: public String path() {
215: return path;
216: }
217:
218: public int line() {
219: return line;
220: }
221:
222: public String message() {
223: return message;
224: }
225:
226: public boolean isError() {
227: return error;
228: }
229:
230: public boolean isAccessible() {
231: return accessible;
232: }
233:
234: public String toString() {
235: return "path=" + path + " line=" + line + " message="
236: + message
237: // NOI18N
238: + " isError=" + error + " isAccessible="
239: + accessible; // NOI18N
240: }
241: }
242:
243: /**
244: * Error annotation.
245: */
246: static class ErrorAnnotation extends Annotation {
247: private String shortDesc = null;
248:
249: public ErrorAnnotation(String desc) {
250: shortDesc = desc;
251: }
252:
253: public String getAnnotationType() {
254: return "com-caucho-netbeans-error";
255: }
256:
257: public String getShortDescription() {
258: return shortDesc;
259: }
260:
261: }
262:
263: /**
264: * <code>Link</code> is used to create a link in the output window. To
265: * create a link use the <code>getLink</code> method of the
266: * <code>LogSupport</code> class. This prevents from memory vast by
267: * returning already existing instance, if one with such values exists.
268: */
269: public class Link implements OutputListener {
270: private String msg;
271: private String path;
272: private int line;
273:
274: private int hashCode = 0;
275:
276: Link(String msg, String path, int line) {
277: this .msg = msg;
278: this .path = path;
279: this .line = line;
280: }
281:
282: public int hashCode() {
283: if (hashCode == 0) {
284: int result = 17;
285: result = 37 * result + line;
286: result = 37 * result
287: + (path != null ? path.hashCode() : 0);
288: result = 37 * result
289: + (msg != null ? msg.hashCode() : 0);
290: hashCode = result;
291: }
292: return hashCode;
293: }
294:
295: public boolean equals(Object obj) {
296: if (this == obj) {
297: return true;
298: }
299: if (obj instanceof Link) {
300: Link anotherLink = (Link) obj;
301: if ((((msg != null) && msg.equals(anotherLink.msg)) || (msg == anotherLink.msg))
302: && (((path != null) && path
303: .equals(anotherLink.path)) || (path == anotherLink.path))
304: && line == anotherLink.line) {
305: return true;
306: }
307: }
308: return false;
309: }
310:
311: /**
312: * If the link is clicked, required file is opened in the editor and an
313: * <code>ErrorAnnotation</code> is attached.
314: */
315: public void outputLineAction(OutputEvent ev) {
316: FileObject sourceFile = GlobalPathRegistry.getDefault()
317: .findResource(path);
318: if (sourceFile == null) {
319: sourceFile = FileUtil.toFileObject(new File(path));
320: }
321: DataObject dataObject = null;
322: if (sourceFile != null) {
323: try {
324: dataObject = DataObject.find(sourceFile);
325: } catch (DataObjectNotFoundException ex) {
326: ErrorManager.getDefault().notify(
327: ErrorManager.INFORMATIONAL, ex);
328: }
329: }
330: if (dataObject != null) {
331: EditorCookie editorCookie = (EditorCookie) dataObject
332: .getCookie(EditorCookie.class);
333: if (editorCookie == null) {
334: return;
335: }
336: editorCookie.open();
337: Line errorLine = null;
338: try {
339: errorLine = editorCookie.getLineSet().getCurrent(
340: line - 1);
341: } catch (IndexOutOfBoundsException iobe) {
342: return;
343: }
344: if (errAnnot != null) {
345: errAnnot.detach();
346: }
347: String errorMsg = msg;
348: if (errorMsg == null || errorMsg.equals("")) {
349: errorMsg = "Exception occured";
350: }
351: errAnnot = new ErrorAnnotation(errorMsg);
352: errAnnot.attach(errorLine);
353: errAnnot.moveToFront();
354: errorLine.show(Line.SHOW_TRY_SHOW);
355: }
356: }
357:
358: /**
359: * If a link is cleared, error annotation is detached and link cache is
360: * clared.
361: */
362: public void outputLineCleared(OutputEvent ev) {
363: if (errAnnot != null) {
364: errAnnot.detach();
365: }
366: if (!links.isEmpty()) {
367: links.clear();
368: }
369: }
370:
371: public void outputLineSelected(OutputEvent ev) {
372: }
373: }
374: }
|