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.openide;
043:
044: import java.io.IOException;
045: import java.io.PrintStream;
046: import java.io.PrintWriter;
047: import java.io.StringWriter;
048: import java.util.ArrayList;
049: import java.util.Collection;
050: import java.util.Enumeration;
051: import java.util.HashSet;
052: import java.util.LinkedHashSet;
053: import java.util.List;
054: import java.util.Map;
055: import java.util.ResourceBundle;
056: import java.util.Set;
057: import java.util.WeakHashMap;
058: import java.util.concurrent.Callable;
059: import java.util.logging.Level;
060: import java.util.logging.LogRecord;
061: import java.util.logging.Logger;
062: import org.openide.util.Enumerations;
063: import org.openide.util.Lookup;
064: import org.openide.util.LookupEvent;
065: import org.openide.util.LookupListener;
066: import org.openide.util.WeakSet;
067:
068: /**
069: * A more or less <em>deprecated</em> system of managing, annotating, and classifying errors
070: * and log messages. Instead of <code>ErrorManager</code> use
071: * {@link Logger} as described in <a href="@TOP@/org/openide/util/doc-files/logging.html">NetBeans logging guide</a>.
072: * <p>
073: * Rather then using the {@link ErrorManager} consider using JDK's {@link Logger}
074: * for reporting log events, unwanted exceptions, etc. The methods
075: * in this class which are deprecated are annotated with a description
076: * how to use use the {@link Logger} methods to achieve the same goal.
077: * </p>
078: * <p>
079: * The levels in descending order are:
080: * <ul>
081: * <li>ERROR (highest value)
082: * <li>EXCEPTION
083: * <li>USER
084: * <li>WARNING
085: * <li>INFORMATIONAL
086: * <li>UNKNOWN (lowest value)
087: * </ul>
088: * </p>
089: *
090: * <div class="nonnormative">
091: * <p>How to...</p>
092: * <dl>
093: *
094: * <dt>Handle an exception</dt>
095: * <dd>
096: * <p>If it might be an important error (show the user):</p>
097: * <pre>
098: * try {
099: * foo.doSomething();
100: * } catch (IOException ioe) {
101: * <!--
102: * Logger.getLogger(YourClass.class.getName()).log(Level.SEVERE, "msg", ioe);
103: * // used to be
104: * -->
105: * ErrorManager.getDefault().notify(ioe);
106: * <!--
107: * }
108: * -->
109: * </pre>
110: * <p>If it is not very important but should be sent to the log file:</p>
111: * <pre>
112: * try {
113: * foo.doSomething();
114: * } catch (IOException ioe) {
115: * Logger.getLogger(YourClass.class.getName()).log(Level.CONFIG, "msg", ioe);
116: * // used to be:
117: * // ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioe);
118: * }
119: * </pre>
120: * <p>If it is the normal outcome of a user action
121: * and there is no need to show stack traces to the user:</p>
122: * <pre>
123: * try {
124: * foo.doSomething();
125: * } catch (IOException ioe) {
126: * <!--
127: * Logger.getAnonymousLogger().log(Level.INFO, "msg", ioe);
128: * // used to be:
129: * -->
130: * ErrorManager.getDefault().notify(ErrorManager.USER, ioe);
131: * }
132: * </pre>
133: * <p>You can also specify the severity when you are creating the
134: * exception (by annotating it), rather than relying on the notifier
135: * to do this. In that case, if the notifier just use the plain form
136: * of <code>notify</code> (i.e. <code>UNKNOWN</code> severity), the
137: * annotated severity is used.</p>
138: * </dd>
139: *
140: * <dt>Retain nested stacktraces / change exception type</dt>
141: * <dd>
142: * <pre>
143: * public void doSomething() throws IOException {
144: * try {
145: * doSomethingElse();
146: * } catch (IllegalArgumentException iae) {
147: * IOException ioe = new IOException("did not work: " + iae);
148: * ioe.initCause(iae);
149: * // used to be: ErrorManager.getDefault().annotate(ioe, iae);
150: * throw ioe;
151: * }
152: * }
153: * </pre>
154: * <p>You can also just use JDK 1.4 causes:</p>
155: * <pre>
156: * public void doSomething() throws IOException {
157: * try {
158: * doSomethingElse();
159: * } catch (IllegalArgumentException iae) {
160: * IOException ioe = new IOException("did not work: " + iae);
161: * ioe.initCause(iae);
162: * throw ioe;
163: * }
164: * }
165: * // ...
166: * try {
167: * foo.doSomething();
168: * } catch (IOException ioe) {
169: * // The IllegalArgumentException is still available here:
170: * ErrorManager.getDefault().notify(ioe);
171: * // or use logging
172: * Logger.getLogger(YourClass.class.getName()).log(Level.SEVERE, null, ioe);
173: * }
174: * </pre>
175: * </dd>
176: *
177: * <dt>Provide a user-visible (localized) message</dt>
178: * <dd>
179: * <pre>
180: * public void doSomething(File f) throws IOException {
181: * if (!f.isFile()) {
182: * IOException e = new IOException("Not a file: " + f); // NOI18N
183: * // For what the user actually sees:
184: * ErrorManager.getDefault().annotate(e,
185: * NbBundle.getMessage(This.class, "EXC_not_a_file", f));
186: * throw e;
187: * }
188: * }
189: * </pre>
190: * <p>You can also add the message when the exception is caught rather
191: * than when it is thrown. You could even have one piece of code throw
192: * an exception, another annotate it, and yet another notify it.
193: * </dd>
194: *
195: * <dt>Collecting several exceptions and wrapping them in one</dt>
196: * <dd>
197: * <pre>
198: * IOException all = null;
199: * for (int i = 0; i < things.length; i++) {
200: * try {
201: * things[i].process();
202: * } catch (ThingProcessingException e) {
203: * if (all == null) {
204: * all = new IOException("Could not process one or more things"); // NOI18N
205: * }
206: * ErrorManager.getDefault().annotate(all, e);
207: * }
208: * }
209: * if (all != null) {
210: * throw all;
211: * }
212: * </pre>
213: * <dd>
214: *
215: * <dt>Logging a warning message just simply uses the JDK's logging API</dt>
216: * <dd>
217: * <pre>
218: * public void doSomething(String arg) {
219: * if (arg.length() == 0) {
220: * Logger.getLogger(YourClass.class.getName()).log(Leverl.WARNING,
221: * "Warning: doSomething called on empty string");
222: * return;
223: * }
224: * // ...
225: * }
226: * </pre>
227: * </dd>
228: *
229: * <dt>Logging messages for some subcomponent can be done easily with JDK's logging API</dt>
230: * <dd>
231: * <pre>
232: * package org.netbeans.modules.foo;
233: * class FooModule {
234: * public static final Logger ERR =
235: * Logger.getLogger("org.netbeans.modules.foo");
236: * }
237: * // ...
238: * class Something {
239: * public void doSomething(String arg) {
240: * LogRecord rec = new LogRecord(Level.FINE, "MSG_Key");
241: * // where in the Bundle.properties one has:
242: * // MSG_Key=Called doSomething with arg {0}
243: * rec.setResourceBundle(NbBundle.getBundle(Something.class));
244: * rec.setParameters(new Object[] { arg });
245: * ERR.log(rec);
246: * }
247: * }
248: * </pre>
249: * </dd>
250: *
251: * </dl>
252: * </div>
253: * @author Jaroslav Tulach, Jesse Glick
254: */
255: public abstract class ErrorManager extends Object {
256: // XXX deprecate error manager after phase II and III are done. See:
257: // http://openide.netbeans.org/tutorial/reviews/opinions_35067.html
258:
259: /**
260: * Undefined severity.
261: * May be used only in {@link #notify(int, Throwable)}
262: * and {@link #annotate(Throwable, int, String, String, Throwable, Date)}.
263: */
264: public static final int UNKNOWN = 0x00000000;
265:
266: /** Message that would be useful for tracing events but which need not be a problem. */
267: public static final int INFORMATIONAL = 0x00000001;
268:
269: /** Something went wrong in the software, but it is continuing and the user need not be bothered. */
270: public static final int WARNING = 0x00000010;
271:
272: /** Something the user should be aware of. */
273: public static final int USER = 0x00000100;
274:
275: /** Something went wrong, though it can be recovered. */
276: public static final int EXCEPTION = 0x00001000;
277:
278: /** Serious problem, application may be crippled. */
279: public static final int ERROR = 0x00010000;
280:
281: /** We keep a reference to our proxy ErrorManager here. */
282: private static DelegatingErrorManager current;
283:
284: /** Getter for the default version of error manager.
285: * @return the error manager installed in the system
286: * @since 2.1
287: */
288: public static ErrorManager getDefault() {
289: synchronized (ErrorManager.class) {
290: if (current != null) {
291: return current;
292: }
293: }
294:
295: return getDefaultDelegate();
296: }
297:
298: private static DelegatingErrorManager getDefaultDelegate() {
299: DelegatingErrorManager c = new DelegatingErrorManager(""); // NOI18N
300:
301: try {
302: c.initialize();
303:
304: synchronized (ErrorManager.class) {
305: if (current == null) {
306: current = c;
307:
308: // r is not null after c.initialize();
309: current.r.addLookupListener(current);
310: }
311: }
312: } catch (RuntimeException e) {
313: // #20467
314: e.printStackTrace();
315: current = c;
316: } catch (LinkageError e) {
317: // #20467
318: e.printStackTrace();
319: current = c;
320: }
321:
322: return current;
323: }
324:
325: /** Associates annotations with an exception.
326: *
327: * @param t the exception
328: * @param arr array of annotations (or <code>null</code>)
329: * @return the same exception <code>t</code> (as a convenience)
330: */
331: public abstract Throwable attachAnnotations(Throwable t,
332: Annotation[] arr);
333:
334: /** Finds annotations associated with a given exception.
335: * @param t the exception
336: * @return array of annotations or <code>null</code>
337: */
338: public abstract Annotation[] findAnnotations(Throwable t);
339:
340: /** Annotates given exception with given values. All the
341: * previous annotations are kept and this new one is added at
342: * the top of the annotation stack (index 0 of the annotation
343: * array).
344: *
345: * @param t the exception
346: * @param severity integer describing severity, e.g. {@link #EXCEPTION}
347: * @param message message to attach to the exception or <code>null</code>
348: * @param localizedMessage localized message for the user or <code>null</code>
349: * @param stackTrace exception representing the stack trace or <code>null</code>
350: * @param date date or <code>null</code>
351: * @return the same exception <code>t</code> (as a convenience)
352: */
353: public abstract Throwable annotate(Throwable t, int severity,
354: String message, String localizedMessage,
355: Throwable stackTrace, java.util.Date date);
356:
357: /** Prints the exception to the log file and (possibly) notifies the user.
358: * Use of {@link #UNKNOWN} severity means that the error manager should automatically
359: * select an appropriate severity level, for example based on the contents of
360: * annotations in the throwable.
361: * @param severity the severity to be applied to the exception (overrides default), e.g. {@link #EXCEPTION}
362: * @param t the exception to notify
363: */
364: public abstract void notify(int severity, Throwable t);
365:
366: /** Prints the exception to the log file and (possibly) notifies the user.
367: * Guesses at the severity.
368: * @param t the exception to notify
369: * @see #UNKNOWN
370: * @see #notify(int, Throwable)
371: */
372: public final void notify(Throwable t) {
373: notify(UNKNOWN, t);
374: }
375:
376: /** Logs the message to a file and (possibly) tells the user.
377: * @param severity the severity to be applied (overrides default)
378: * @param s the log message
379: */
380: public abstract void log(int severity, String s);
381:
382: // not yet: after phase III: * @deprecated use {@link Logger#log}
383:
384: /** Logs the message to log file and (possibly) tells the user.
385: * Uses a default severity.
386: * @param s the log message
387: */
388: public final void log(String s) {
389: // not yet: after phase III: * @deprecated {@link Logger#log}
390: log(INFORMATIONAL, s);
391: }
392:
393: /** Test whether a messages with given severity will be logged in advance.
394: * Can be used to avoid the construction of complicated and expensive
395: * logging messages.
396: * <p>The default implementation just returns true. Subclasses
397: * should override to be more precise - <strong>treat this method as abstract</strong>.
398: * @param severity the severity to check, e.g. {@link #EXCEPTION}
399: * @return <code>false</code> if the next call to {@link #log(int,String)} with this severity will
400: * discard the message
401: */
402: public boolean isLoggable(int severity) {
403: // not yet: after phase III: * @deprecated Use {@link Logger#isLoggable}
404: return true;
405: }
406:
407: /**
408: * Test whether a throwable, if {@link #notify(int, Throwable) notified} at the given
409: * level, will actually be displayed in any way (even to a log file etc.).
410: * If not, there is no point in constructing it.
411: * <p>This method is distinct from {@link #isLoggable} because an error manager
412: * implementation may choose to notify stack traces at a level where it would
413: * not log messages. See issue #24056 for justification.
414: * <p>The default implementation just calls {@link #isLoggable}. Subclasses
415: * should override to be more precise - <strong>treat this method as abstract</strong>.
416: * @param severity a notification severity
417: * @return true if a throwable notified at this severity will be used; false if it will be ignored
418: * @since 3.18
419: */
420: public boolean isNotifiable(int severity) {
421: return isLoggable(severity);
422: }
423:
424: /** Returns an instance with given name.
425: * <p>By convention, you can name error managers the same as packages (or classes)
426: * they are designed to report information from.
427: * For example, <code>org.netbeans.modules.mymodule.ComplicatedParser</code>.
428: * <p>The error manager implementation should provide some way of configuring e.g.
429: * the logging level for error managers of different names. For example, in the basic
430: * NetBeans core implementation, you can define a system property with the same name
431: * as the future error manager (or a package prefix of it) whose value is the numeric
432: * logging level (e.g. <samp>-J-Dorg.netbeans.modules.mymodule.ComplicatedParser=0</samp>
433: * to log everything). Other implementations may have quite different ways of configuring
434: * the error managers.
435: * @param name the desired identifying name
436: * @return a new error manager keyed off of that name
437: */
438: public abstract ErrorManager getInstance(String name);
439:
440: //
441: // Helper methods
442: //
443:
444: /** Annotates given exception with given values. All the
445: * previous annotations are kept and this new is added at
446: * the top of the annotation stack (index 0 of the annotation
447: * array).
448: *
449: * @param t the exception
450: * @param localizedMessage localized message for the user or null
451: * @return the same exception <code>t</code> (as a convenience)
452: */
453: public final Throwable annotate(Throwable t, String localizedMessage) {
454: return annotate(t, UNKNOWN, null, localizedMessage, null, null);
455: }
456:
457: /** Annotates target exception with given exception. All the
458: * previous annotations are kept and this new is added at
459: * the top of the annotation stack (index 0 of the annotation
460: * array).
461: * <p>Consider using {@link Throwable#initCause} instead; this
462: * will be correctly reported by the NetBeans error manager, and
463: * also works properly with {@link Throwable#printStackTrace()}.
464: * @param target the exception to be annotated
465: * @param t the exception that will be added
466: * @return the same exception <code>target</code> (as a convenience)
467: */
468: public final Throwable annotate(Throwable target, Throwable t) {
469: return annotate(target, UNKNOWN, null, null, t, null);
470: }
471:
472: /** Takes annotations from one exception and associates
473: * them with another one.
474: *
475: * @param t the exception to annotate
476: * @param copyFrom exception to take annotations from
477: * @return the same exception <code>t</code> (as a convenience)
478: * @deprecated Now does the same thing as {@link #annotate(Throwable,Throwable)}
479: * except marks the annotation {@link #UNKNOWN} severity. Otherwise
480: * you used to have inadvertent data loss when <code>copyFrom</code>
481: * had annotations of its own: the subannotations were kept but the
482: * main stack trace in <code>copyFrom</code> was discarded. In practice
483: * you usually want to keep all of <code>copyFrom</code>; if for some
484: * reason you just want to keep annotations, please do so explicitly
485: * using {@link #findAnnotations} and {@link #attachAnnotations}.
486: */
487: @Deprecated
488: public final Throwable copyAnnotation(Throwable t,
489: Throwable copyFrom) {
490: // Cf. #17874 for the change in behavior.
491:
492: /*
493: Annotation[] arr = findAnnotations (copyFrom);
494:
495: if (arr != null) {
496: return attachAnnotations (
497: t, arr
498: );
499: } else {
500: */
501: return annotate(t, UNKNOWN, null, null, copyFrom, null);
502:
503: /*
504: }
505: */
506: }
507:
508: /** Annotation that can be attached to an error.
509: */
510: public static interface Annotation {
511: /** Non-localized message.
512: * @return associated message or <code>null</code>
513: */
514: public abstract String getMessage();
515:
516: /** Localized message.
517: * @return message to be presented to the user or <code>null</code>
518: */
519: public abstract String getLocalizedMessage();
520:
521: /** Stack trace. The stack trace should locate the method
522: * and position in the method where the error occurred.
523: *
524: * @return exception representing the location of the error or <code>null</code>
525: */
526: public abstract Throwable getStackTrace();
527:
528: /** Time at which the exception occurred.
529: * @return the time or <code>null</code>
530: */
531: public abstract java.util.Date getDate();
532:
533: /** Severity of the exception.
534: * {@link #UNKNOWN} serves as the default.
535: * @return number representing the severity, e.g. {@link ErrorManager#EXCEPTION}
536: */
537: public abstract int getSeverity();
538: }
539:
540: // end of Annotation
541:
542: /**
543: * Implementation of ErrorManager that delegates to the ones found by
544: * lookup.
545: */
546: private static class DelegatingErrorManager extends ErrorManager
547: implements LookupListener {
548: private String name = null;
549:
550: /**
551: * The set of instances we delegate to.
552: */
553: private Set<ErrorManager> delegates = new HashSet<ErrorManager>();
554:
555: /** fallback logger to send messages to */
556: private Logger logger;
557:
558: /**
559: * A set that has to be updated when the list of delegates
560: * changes. All instances created by getInstance are held here.
561: */
562: private WeakSet<DelegatingErrorManager> createdByMe = new WeakSet<DelegatingErrorManager>();
563:
564: /** If we are the "central" delagate this is not null and
565: * we listen on the result. On newly created delegates this
566: * is null.
567: */
568: Lookup.Result<ErrorManager> r;
569:
570: public DelegatingErrorManager(String name) {
571: this .name = name;
572: }
573:
574: /** Initializes the logger.
575: */
576: Logger logger() {
577: if (logger == null) {
578: logger = Logger.getLogger(this .name);
579: }
580: return logger;
581: }
582:
583: /** If the name is not empty creates new instance of
584: * DelegatingErrorManager. Adds it to createdByMe.
585: */
586: public ErrorManager getInstance(String name) {
587: if ((name == null) || ("".equals(name))) { // NOI18N
588:
589: return this ;
590: }
591:
592: DelegatingErrorManager dem = new DelegatingErrorManager(
593: name);
594:
595: synchronized (this ) {
596: attachNewDelegates(dem, name);
597: createdByMe.add(dem);
598: }
599:
600: return dem;
601: }
602:
603: /** Calls all delegates. */
604: public Throwable attachAnnotations(Throwable t, Annotation[] arr) {
605: for (ErrorManager em : delegates) {
606: em.attachAnnotations(t, arr);
607: }
608:
609: return t;
610: }
611:
612: /** Calls all delegates. */
613: public Annotation[] findAnnotations(Throwable t) {
614: for (ErrorManager em : delegates) {
615: Annotation[] res = em.findAnnotations(t);
616:
617: if ((res != null) && (res.length > 0)) {
618: return res;
619: }
620: }
621:
622: return new Annotation[0];
623: }
624:
625: /** Calls all delegates. */
626: public Throwable annotate(Throwable t, int severity,
627: String message, final String localizedMessage,
628: Throwable stackTrace, java.util.Date date) {
629: if (delegates.isEmpty()) {
630: LogRecord rec = new LogRecord(convertSeverity(severity,
631: true, Level.ALL), message);
632: if (stackTrace != null) {
633: rec.setThrown(stackTrace);
634: }
635: if (date != null) {
636: rec.setMillis(date.getTime());
637: }
638: if (localizedMessage != null) {
639: ResourceBundle rb = new ResourceBundle() {
640: public Object handleGetObject(String key) {
641: if ("msg".equals(key)) { // NOI18N
642: return localizedMessage;
643: } else {
644: return null;
645: }
646: }
647:
648: public Enumeration<String> getKeys() {
649: return Enumerations.singleton("msg"); // NOI18N
650: }
651: };
652: rec.setResourceBundle(rb);
653: rec.setMessage("msg"); // NOI18N
654: }
655:
656: AnnException ann = AnnException.findOrCreate(t, true);
657: ann.addRecord(rec);
658:
659: return t;
660: }
661:
662: for (ErrorManager em : delegates) {
663: em.annotate(t, severity, message, localizedMessage,
664: stackTrace, date);
665: }
666:
667: return t;
668: }
669:
670: /** Calls all delegates. */
671: public void notify(int severity, Throwable t) {
672: if (delegates.isEmpty()) {
673: if (enterLogger())
674: return;
675: try {
676: AnnException ext = AnnException.extras.get(t);
677: if (ext != null) {
678: t = ext;
679: }
680: logger().log(
681: convertSeverity(severity, true,
682: OwnLevel.UNKNOWN), t.getMessage(),
683: t);
684: } finally {
685: exitLogger();
686: }
687: return;
688: }
689:
690: try {
691: for (ErrorManager em : delegates) {
692: em.notify(severity, t);
693: }
694: } catch (RuntimeException e) {
695: // #20467
696: e.printStackTrace();
697: t.printStackTrace();
698: } catch (LinkageError e) {
699: // #20467
700: e.printStackTrace();
701: t.printStackTrace();
702: }
703: }
704:
705: /** Calls all delegates. */
706: public void log(int severity, String s) {
707: if (severity == UNKNOWN) {
708: throw new IllegalArgumentException(
709: "ErrorManager.log(UNKNOWN, ...) is not permitted"); // NOI18N
710: }
711:
712: if (delegates.isEmpty()) {
713: Level sev = convertSeverity(severity, false, Level.FINE);
714: if (enterLogger())
715: return;
716: try {
717: logger().log(sev, s);
718: } finally {
719: exitLogger();
720: }
721: return;
722: }
723:
724: for (ErrorManager em : delegates) {
725: em.log(severity, s);
726: }
727: }
728:
729: private static Level convertSeverity(final int severity,
730: boolean forException, Level def) {
731: Level sev = def;
732:
733: if (severity >= ERROR) {
734: sev = Level.SEVERE;
735: } else if (severity >= EXCEPTION) {
736: sev = Level.SEVERE;
737: } else if (severity >= USER) {
738: sev = OwnLevel.USER;
739: } else if (severity >= WARNING) {
740: sev = Level.WARNING;
741: } else if (severity >= INFORMATIONAL) {
742: sev = forException ? Level.INFO : Level.FINE;
743: }
744: return sev;
745: }
746:
747: /** Calls all delegates. */
748: public boolean isLoggable(int severity) {
749: if (severity == UNKNOWN) {
750: throw new IllegalArgumentException(
751: "ErrorManager.isLoggable(UNKNOWN) is not permitted"); // NOI18N
752: }
753:
754: if (delegates.isEmpty()) {
755: return logger().isLoggable(
756: convertSeverity(severity, false, null));
757: }
758:
759: for (ErrorManager em : delegates) {
760: if (em.isLoggable(severity)) {
761: return true;
762: }
763: }
764:
765: return false;
766: }
767:
768: /** Calls all delegates. */
769: public boolean isNotifiable(int severity) {
770: if (severity == UNKNOWN) {
771: throw new IllegalArgumentException(
772: "ErrorManager.isNotifiable(UNKNOWN) is not permitted"); // NOI18N
773: }
774:
775: if (delegates.isEmpty()) {
776: return logger().isLoggable(
777: convertSeverity(severity, true, null));
778: }
779:
780: for (ErrorManager em : delegates) {
781: if (em.isNotifiable(severity)) {
782: return true;
783: }
784: }
785:
786: return false;
787: }
788:
789: /**
790: * Updates the list of delegates. Also updates all instances created
791: * by ourselves.
792: */
793: public synchronized void setDelegates(
794: Collection<? extends ErrorManager> newDelegates) {
795: delegates = new LinkedHashSet<ErrorManager>(newDelegates);
796:
797: for (DelegatingErrorManager dem : createdByMe) {
798: attachNewDelegates(dem, dem.getName());
799: }
800: }
801:
802: private String getName() {
803: return name;
804: }
805:
806: /**
807: * Takes all our delegates, asks them for an instance identified by
808: * name and adds those results as new delegates for dem.
809: * @param String name
810: * @param DelagatingErrorManager d the instance to which we will attach
811: */
812: private void attachNewDelegates(DelegatingErrorManager dem,
813: String name) {
814: Set<ErrorManager> newDelegatesForDem = new HashSet<ErrorManager>();
815:
816: for (ErrorManager e : delegates) {
817: newDelegatesForDem.add(e.getInstance(name));
818: }
819:
820: dem.setDelegates(newDelegatesForDem);
821: }
822:
823: /** Blocks on lookup and after the lookup returns updates
824: * delegates and adds a listener.
825: */
826: public void initialize() {
827: r = Lookup.getDefault().lookupResult(ErrorManager.class);
828: setDelegates(r.allInstances());
829: }
830:
831: /** Updates the delegates.*/
832: public void resultChanged(LookupEvent ev) {
833: if (r != null) {
834: setDelegates(r.allInstances());
835: }
836: }
837:
838: private static volatile Thread lastThread;
839:
840: private static boolean enterLogger() {
841: if (lastThread == Thread.currentThread()) {
842: new Exception(
843: "using error manager from inside a logger")
844: .printStackTrace(); // NOI18N
845: return true;
846: }
847: lastThread = Thread.currentThread();
848: return false;
849: }
850:
851: private static void exitLogger() {
852: lastThread = null;
853: }
854: }
855:
856: /** An exception that has a log record associated with itself, so
857: * the NbErrorManager can extract info about the annotation.
858: */
859: private static final class AnnException extends Exception implements
860: Callable<LogRecord[]> {
861: private List<LogRecord> records;
862: /** additional mapping from throwables that refuse initCause call */
863: private static Map<Throwable, AnnException> extras = new WeakHashMap<Throwable, AnnException>();
864:
865: public String getMessage() {
866: StringBuilder sb = new StringBuilder();
867: String sep = "";
868: for (LogRecord r : records) {
869: if (r.getMessage() != null) {
870: sb.append(sep);
871: sb.append(r.getMessage());
872: sep = "\n";
873: }
874: }
875: return sb.toString();
876: }
877:
878: static AnnException findOrCreate(Throwable t, boolean create) {
879: if (t instanceof AnnException) {
880: return (AnnException) t;
881: }
882: if (t.getCause() == null) {
883: if (create) {
884: try {
885: t.initCause(new AnnException());
886: } catch (IllegalStateException x) {
887: AnnException ann = extras.get(t);
888: if (ann == null) {
889: ann = new AnnException();
890: ann.initCause(t);
891: Logger.getLogger(
892: ErrorManager.class.getName()).log(
893: Level.FINE,
894: "getCause was null yet initCause failed for "
895: + t, x);
896: extras.put(t, ann);
897: }
898: return ann;
899: }
900: }
901: return (AnnException) t.getCause();
902: }
903: return findOrCreate(t.getCause(), create);
904: }
905:
906: private AnnException() {
907: }
908:
909: public synchronized void addRecord(LogRecord rec) {
910: if (records == null) {
911: records = new ArrayList<LogRecord>();
912: }
913: records.add(rec);
914: }
915:
916: public LogRecord[] call() {
917: List<LogRecord> r = records;
918: LogRecord[] empty = new LogRecord[0];
919: return r == null ? empty : r.toArray(empty);
920: }
921:
922: public void printStackTrace(PrintStream s) {
923: super .printStackTrace(s);
924: logRecords(s);
925: }
926:
927: public void printStackTrace(PrintWriter s) {
928: super .printStackTrace(s);
929: logRecords(s);
930: }
931:
932: public void printStackTrace() {
933: printStackTrace(System.err);
934: }
935:
936: private void logRecords(Appendable a) {
937: List<LogRecord> r = records;
938: if (r == null) {
939: return;
940: }
941: try {
942:
943: for (LogRecord log : r) {
944: if (log.getMessage() != null) {
945: a.append(log.getMessage()).append("\n");
946: ;
947: }
948: if (log.getThrown() != null) {
949: StringWriter w = new StringWriter();
950: log.getThrown().printStackTrace(
951: new PrintWriter(w));
952: a.append(w.toString()).append("\n");
953: }
954: }
955: } catch (IOException ex) {
956: ex.printStackTrace();
957: }
958: }
959: } // end AnnException
960:
961: private static final class OwnLevel extends Level {
962: public static final Level USER = new OwnLevel("USER", 1973); // NOI18N
963: public static final Level UNKNOWN = new OwnLevel("SEVERE",
964: Level.SEVERE.intValue() + 1); // NOI18N
965:
966: private OwnLevel(String s, int i) {
967: super (s, i);
968: }
969: } // end of UserLevel
970: }
|