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.core;
043:
044: import java.io.OutputStreamWriter;
045: import java.io.PrintStream;
046: import java.io.PrintWriter;
047: import java.text.MessageFormat;
048: import java.util.ArrayList;
049: import java.util.Arrays;
050: import java.util.Date;
051: import java.util.HashSet;
052: import java.util.List;
053: import java.util.ResourceBundle;
054: import java.util.Set;
055: import java.util.concurrent.Callable;
056: import java.util.logging.Handler;
057: import java.util.logging.Level;
058: import java.util.logging.LogRecord;
059: import java.util.logging.Logger;
060: import org.netbeans.core.startup.TopLogging;
061:
062: /** Wraps errormanager with logger.
063: *
064: * @author Jaroslav Tulach, Jesse Glick
065: */
066: public final class NbErrorManager extends Handler {
067:
068: static Exc createExc(Throwable t, Level severity, LogRecord add) {
069: LogRecord[] ann = findAnnotations(t, add);
070: return new Exc(t, severity, ann, findAnnotations0(t, add, true,
071: new HashSet<Throwable>()));
072: }
073:
074: public void publish(LogRecord record) {
075: if (record.getThrown() != null) {
076: Level level = record.getLevel();
077: if (level.intValue() == Level.WARNING.intValue() + 1) {
078: // unknown level
079: level = null;
080: }
081: if (level != null
082: && level.intValue() == Level.SEVERE.intValue() + 1) {
083: // unknown level
084: level = null;
085: }
086: Exc ex = createExc(record.getThrown(), level, record
087: .getLevel().intValue() == 1973 ? record : null);
088: NotifyExcPanel.notify(ex);
089: }
090: }
091:
092: public void flush() {
093: //logWriter.flush();
094: }
095:
096: public void close() throws SecurityException {
097: // nothing needed
098: }
099:
100: /** Extracts localized message from a LogRecord */
101: private static final String getLocalizedMessage(LogRecord rec) {
102: ResourceBundle rb = rec.getResourceBundle();
103: if (rb == null) {
104: return null;
105: }
106:
107: String msg = rec.getMessage();
108: if (msg == null) {
109: return null;
110: }
111:
112: String format = rb.getString(msg);
113:
114: Object[] arr = rec.getParameters();
115: if (arr == null) {
116: return format;
117: }
118:
119: return MessageFormat.format(format, arr);
120: }
121:
122: /** Finds annotations associated with given exception.
123: * @param t the exception
124: * @return array of annotations or null
125: */
126: private static LogRecord[] findAnnotations(Throwable t,
127: LogRecord add) {
128: return findAnnotations0(t, add, false, new HashSet<Throwable>());
129: }
130:
131: /** If recursively is true it is not adviced to print all annotations
132: * because a lot of warnings will be printed. But while searching for
133: * localized message we should scan all the annotations (even recursively).
134: */
135: private static LogRecord[] findAnnotations0(Throwable t,
136: LogRecord add, boolean recursively,
137: Set<Throwable> alreadyVisited) {
138: List<LogRecord> l = new ArrayList<LogRecord>();
139: Throwable collect = t;
140: while (collect != null) {
141: if (collect instanceof Callable) {
142: Object res = null;
143: try {
144: res = ((Callable) collect).call();
145: } catch (Exception ex) {
146: ex.printStackTrace();
147: }
148: if (res instanceof LogRecord[]) {
149: LogRecord[] arr = (LogRecord[]) res;
150: l.addAll(Arrays.asList(arr));
151: }
152: }
153: collect = collect.getCause();
154: }
155:
156: if (add != null) {
157: l.add(add);
158: }
159:
160: if (recursively) {
161: ArrayList<LogRecord> al = new ArrayList<LogRecord>();
162: for (LogRecord ano : l) {
163: Throwable t1 = ano.getThrown();
164: if ((t1 != null) && (!alreadyVisited.contains(t1))) {
165: alreadyVisited.add(t1);
166: LogRecord[] tmpAnnoArray = findAnnotations0(t1,
167: null, true, alreadyVisited);
168: if ((tmpAnnoArray != null)
169: && (tmpAnnoArray.length > 0)) {
170: al.addAll(Arrays.asList(tmpAnnoArray));
171: }
172: }
173: }
174: l.addAll(al);
175: }
176:
177: Throwable cause = t.getCause();
178: if (cause != null) {
179: LogRecord[] extras = findAnnotations0(cause, null, true,
180: alreadyVisited);
181: if (extras != null && extras.length > 0) {
182: l.addAll(Arrays.asList(extras));
183: }
184: }
185:
186: LogRecord[] arr;
187: arr = new LogRecord[l.size()];
188: l.toArray(arr);
189:
190: return arr;
191: }
192:
193: /**
194: * Another final class that is used to communicate with
195: * NotifyExcPanel and provides enough information to the dialog.
196: */
197: static final class Exc {
198: /** the original throwable */
199: private Throwable t;
200: private Date d;
201: private LogRecord[] arr;
202: private LogRecord[] arrAll; // all - recursively
203: private Level severity;
204:
205: /** @param severity if -1 then we will compute the
206: * severity from annotations
207: */
208: Exc(Throwable t, Level severity, LogRecord[] arr,
209: LogRecord[] arrAll) {
210: this .t = t;
211: this .severity = severity;
212: this .arr = arr == null ? new LogRecord[0] : arr;
213: this .arrAll = arrAll == null ? new LogRecord[0] : arrAll;
214: }
215:
216: /** @return message */
217: String getMessage() {
218: String m = t.getMessage();
219: if (m != null) {
220: return m;
221: }
222: return (String) find(1);
223: }
224:
225: String getFirstStacktraceLine() {
226: StackTraceElement[] elems = t.getStackTrace();
227: if ((elems == null) || (elems.length == 0)) {
228: return null;
229: }
230: StackTraceElement elem = elems[0];
231: return elem.getClassName() + "." + elem.getMethodName(); // NOI18N
232: }
233:
234: /** @return localized message */
235: String getLocalizedMessage() {
236: String m = t.getLocalizedMessage();
237: if (m != null && !m.equals(t.getMessage())) {
238: return m;
239: }
240: if (arrAll == null) {
241: // arrAll not filled --> use the old non recursive variant
242: return (String) find(2);
243: }
244: for (int i = 0; i < arrAll.length; i++) {
245: String s = NbErrorManager
246: .getLocalizedMessage(arrAll[i]);
247: if (s != null) {
248: return s;
249: }
250: }
251: return m;
252: }
253:
254: boolean isLocalized() {
255: String m = t.getLocalizedMessage();
256: if (m != null && !m.equals(t.getMessage())) {
257: return true;
258: }
259: if (arrAll == null) {
260: // arrAll not filled --> use the old non recursive variant
261: return (String) find(2) != null;
262: }
263: for (int i = 0; i < arrAll.length; i++) {
264: String s = NbErrorManager
265: .getLocalizedMessage(arrAll[i]);
266: if (s != null) {
267: return true;
268: }
269: }
270: return false;
271: }
272:
273: /** @return class name of the exception */
274: String getClassName() {
275: return (String) find(3);
276: }
277:
278: /** @return the severity of the exception */
279: Level getSeverity() {
280: if (severity != null) {
281: return severity;
282: }
283:
284: LogRecord[] anns = (arrAll != null) ? arrAll : arr;
285: for (int i = 0; i < anns.length; i++) {
286: Level s = anns[i].getLevel();
287: if (severity == null
288: || s.intValue() > severity.intValue()) {
289: severity = s;
290: }
291: }
292:
293: if (severity == null || severity == Level.ALL) {
294: // no severity specified, assume this is an error
295: severity = t instanceof Error ? Level.SEVERE
296: : Level.WARNING;
297: }
298:
299: return severity;
300: }
301:
302: /** @return date assigned to the exception */
303: Date getDate() {
304: if (d == null) {
305: d = (Date) find(4);
306: }
307: return d;
308: }
309:
310: void printStackTrace(PrintStream ps) {
311: printStackTrace(new PrintWriter(new OutputStreamWriter(ps)));
312: }
313:
314: /** Prints stack trace of all annotations and if
315: * there is no annotation trace then of the exception
316: */
317: void printStackTrace(PrintWriter pw) {
318: // #19487: don't go into an endless loop here
319: printStackTrace(pw, new HashSet<Throwable>(10));
320: }
321:
322: private void printStackTrace(PrintWriter pw,
323: Set<Throwable> nestingCheck) {
324: if (t != null && !nestingCheck.add(t)) {
325: // Unlocalized log message - this is for developers of NB, not users
326: Logger l = Logger.getAnonymousLogger();
327: l
328: .warning("WARNING - ErrorManager detected cyclic exception nesting:"); // NOI18N
329: for (Throwable thrw : nestingCheck) {
330: l.warning("\t" + thrw); // NOI18N
331: LogRecord[] anns = findAnnotations(thrw, null);
332: if (anns != null) {
333: for (int i = 0; i < anns.length; i++) {
334: Throwable t2 = anns[i].getThrown();
335: if (t2 != null) {
336: l.warning("\t=> " + t2); // NOI18N
337: }
338: }
339: }
340: }
341: l
342: .warning("Be sure not to annotate an exception with itself, directly or indirectly."); // NOI18N
343: return;
344: }
345: /*Heaeder
346: pw.print (getDate ());
347: pw.print (": "); // NOI18N
348: pw.print (getClassName ());
349: pw.print (": "); // NOI18N
350: String theMessage = getMessage();
351: if (theMessage != null) {
352: pw.print(theMessage);
353: } else {
354: pw.print("<no message>"); // NOI18N
355: }
356: pw.println ();
357: */
358: /*Annotations */
359: for (LogRecord rec : arr) {
360: if (rec == null) {
361: continue;
362: }
363:
364: Throwable thr = rec.getThrown();
365: String annotation = NbErrorManager
366: .getLocalizedMessage(rec);
367:
368: if (annotation == null) {
369: annotation = rec.getMessage();
370: }
371: /*
372: if (annotation == null && thr != null) annotation = thr.getLocalizedMessage();
373: if (annotation == null && thr != null) annotation = thr.getMessage();
374: */
375:
376: if (annotation != null) {
377: if (thr == null) {
378: pw.println("Annotation: " + annotation);// NOI18N
379: }
380: //else pw.println ("Nested annotation: "+annotation);// NOI18N
381: }
382: }
383:
384: // ok, print trace of the original exception too
385: // Attempt to show an annotation indicating where the exception
386: // was caught. Not 100% reliable but often helpful.
387: if (t instanceof VirtualMachineError) {
388: // Decomposition may not work here, e.g. for StackOverflowError.
389: // Play it safe.
390: t.printStackTrace(pw);
391: } else {
392: TopLogging.printStackTrace(t, pw);
393: }
394: /*Nested annotations */
395: for (int i = 0; i < arr.length; i++) {
396: if (arr[i] == null) {
397: continue;
398: }
399:
400: Throwable thr = arr[i].getThrown();
401: if (thr != null) {
402: LogRecord[] ans = findAnnotations(thr, null);
403: Exc ex = new Exc(thr, null, ans, null);
404: pw.println("==>"); // NOI18N
405: ex.printStackTrace(pw, nestingCheck);
406: }
407: }
408: }
409:
410: /**
411: * Method that iterates over annotations to find out
412: * the first annotation that brings the requested value.
413: *
414: * @param kind what to look for (1, 2, 3, 4, ...);
415: * @return the found object
416: */
417: private Object find(int kind) {
418: return find(kind, true);
419: }
420:
421: /**
422: * Method that iterates over annotations to find out
423: * the first annotation that brings the requested value.
424: *
425: * @param kind what to look for (1, 2, 3, 4, ...);
426: * @return the found object
427: */
428: private Object find(int kind, boolean def) {
429: for (int i = 0; i < arr.length; i++) {
430: LogRecord a = arr[i];
431:
432: Object o = null;
433: switch (kind) {
434: case 1: // message
435: o = a.getMessage();
436: break;
437: case 2: // localized
438: o = NbErrorManager.getLocalizedMessage(a);
439: break;
440: case 3: // class name
441: {
442: Throwable t = a.getThrown();
443: o = t == null ? null : t.getClass().getName();
444: break;
445: }
446: case 4: // date
447: o = new Date(a.getMillis());
448: break;
449: }
450:
451: if (o != null) {
452: return o;
453: }
454: }
455:
456: if (!def) {
457: return null;
458: }
459: switch (kind) {
460: case 1: // message
461: return t.getMessage();
462: case 2: // loc.msg.
463: return t.getLocalizedMessage();
464: case 3: // class name
465: return t.getClass().getName();
466: case 4: // date
467: return new Date();
468: default:
469: throw new IllegalArgumentException("Unknown "
470: + Integer.valueOf(kind) // NOI18N
471: );
472: }
473: }
474: }
475:
476: }
|