/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide; import java.util.Set; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import org.openide.util.Lookup; import org.openide.util.LookupListener; import org.openide.util.LookupEvent; import org.openide.util.WeakSet; /** A system of managing, annotating, and classifying errors and log messages. * Rather than printing raw exceptions to the console, or popping up dialog boxes * with exceptions, or implementing custom debug or logging facililities, code may * use the error manager to access logging and error-reporting in a higher-level * fashion. Standard error manager implementations can then provide generic ways * of customizing logging levels for different components, and so on. * <p>Especially important is the attaching of annotations such as stack traces to * exceptions, permitting you to throw an exception of a type permitted by your * API signature while safely encapsulating the root cause of the problem (in terms * of other nested exceptions). Code should use {@link #notify(Throwable)} rather * than directly printing caught exceptions, to make sure nested annotations are not lost. * <p>Also localized messages may be annotated to exceptions so that code which can deal * with a caught exception with a user-visible UI can display a polite and helpful message. * Messages with no localized annotation can be handled in a default way while the details * are reserved for the log file. * <p>A simple example of usage to keep nested stacktraces: * <pre> * public void doSomething () throws IOException { * try { * doSomethingElse (); * } catch (IllegalArgumentException iae) { * IOException ioe = new IOException ("did not work"); * ErrorManager.getDefault ().annotate (ioe, iae); * throw ioe; * } * } * // ... * try { * foo.doSomething (); * } catch (IOException ioe) { * ErrorManager.getDefault ().notify (ioe); * } * </pre> * @author Jaroslav Tulach * @see <a href="http://openide.netbeans.org/proposals/ErrorManagerUsage.html">Philosophy and usage scenarios</a> */ public abstract class ErrorManager extends Object { // XXX note that these levels accidentally used hex rather than binary, // so it goes 0, 1, 16, 256, .... // Unfortunately too late to change now: public int constants are part of the // API - documented, inlined into compiled code, etc. /** * Undefined severity. * May be used only in {@link #notify(int, Throwable)}. */ public static final int UNKNOWN = 0x00000000; /** Message that would be useful for tracing events but which need not be a problem. */ public static final int INFORMATIONAL = 0x00000001; /** Something went wrong in the software, but it is continuing and the user need not be bothered. */ public static final int WARNING = 0x00000010; /** Something the user should be aware of. */ public static final int USER = 0x00000100; /** Something went wrong, though it can be recovered. */ public static final int EXCEPTION = 0x00001000; /** Serious problem, application may be crippled. */ public static final int ERROR = 0x00010000; /** We keep a reference to our proxy ErrorManager here. */ private static DelegatingErrorManager current; /** Getter for the default version of error manager. * @return the error manager installed in the system * @since 2.1 */ public static ErrorManager getDefault () { synchronized (ErrorManager.class) { if (current != null) { return current; } } return getDefaultDelegate(); } private static DelegatingErrorManager getDefaultDelegate() { DelegatingErrorManager c = new DelegatingErrorManager(""); // NOI18N try { c.initialize(); synchronized (ErrorManager.class) { if (current == null) { current = c; // r is not null after c.initialize(); current.r.addLookupListener(current); } } } catch (RuntimeException e) { // #20467 e.printStackTrace(); current = c; } catch (LinkageError e) { // #20467 e.printStackTrace(); current = c; } return current; } /** Associates annotations with an exception. * * @param t the exception * @param arr array of annotations (or <code>null</code>) * @return the same exception <code>t</code> (as a convenience) */ public abstract Throwable attachAnnotations (Throwable t, Annotation[] arr); /** Finds annotations associated with a given exception. * @param t the exception * @return array of annotations or <code>null</code> */ public abstract Annotation[] findAnnotations (Throwable t); /** Annotates given exception with given values. All the * previous annotations are kept and this new one is added at * the top of the annotation stack (index 0 of the annotation * array). * * @param t the exception * @param severity integer describing severity, e.g. {@link #EXCEPTION} * @param message message to attach to the exception or <code>null</code> * @param localizedMessage localized message for the user or <code>null</code> * @param stackTrace exception representing the stack trace or <code>null</code> * @param date date or <code>null</code> * @return the same exception <code>t</code> (as a convenience) */ public abstract Throwable annotate ( Throwable t, int severity, String message, String localizedMessage, Throwable stackTrace, java.util.Date date ); /** Prints the exception to the log file and (possibly) notifies the user. * Use of {@link #UNKNOWN} severity means that the error manager should automatically * select an appropriate severity level, for example based on the contents of * annotations in the throwable. * @param severity the severity to be applied to the exception (overrides default), e.g. {@link #EXCEPTION} * @param t the exception to notify */ public abstract void notify (int severity, Throwable t); /** Prints the exception to the log file and (possibly) notifies the user. * Guesses at the severity. * @param t the exception to notify * @see #UNKNOWN * @see #notify(int, Throwable) */ public final void notify (Throwable t) { notify(UNKNOWN, t); } /** Logs the message to a file and (possibly) tells the user. * @param severity the severity to be applied (overrides default) * @param s the log message */ public abstract void log(int severity, String s); /** Logs the message to log file and (possibly) tells the user. * Uses a default severity. * @param s the log message */ public final void log(String s) { // log(INFORMATIONAL, s); } /** Test whether a messages with given severity will be logged in advance. * Can be used to avoid the construction of complicated and expensive * logging messages. * <p>The default implementation just returns true. Subclasses * should override to be more precise - <strong>treat this method as abstract</strong>. * @param severity the severity to check, e.g. {@link #EXCEPTION} * @return <code>false</code> if the next call to {@link #log(int,String)} with this severity will * discard the message */ public boolean isLoggable (int severity) { return true; } /** * Test whether a throwable, if {@link #notify(int, Throwable) notified} at the given * level, will actually be displayed in any way (even to a log file etc.). * If not, there is no point in constructing it. * <p>This method is distinct from {@link #isLoggable} because an error manager * implementation may choose to notify stack traces at a level where it would * not log messages. See issue #24056 for justification. * <p>The default implementation just calls {@link #isLoggable}. Subclasses * should override to be more precise - <strong>treat this method as abstract</strong>. * @param severity a notification severity * @return true if a throwable notified at this severity will be used; false if it will be ignored * @since 3.18 */ public boolean isNotifiable(int severity) { return isLoggable(severity); } /** Returns an instance with given name. * <p>By convention, you can name error managers the same as packages (or classes) * they are designed to report information from. * For example, <code>org.netbeans.modules.mymodule.ComplicatedParser</code>. * <p>The error manager implementation should provide some way of configuring e.g. * the logging level for error managers of different names. For example, in the basic * NetBeans core implementation, you can define a system property with the same name * as the future error manager (or a package prefix of it) whose value is the numeric * logging level (e.g. <samp>-J-Dorg.netbeans.modules.mymodule.ComplicatedParser=0</samp> * to log everything). Other implementations may have quite different ways of configuring * the error managers. * @param name the desired identifying name * @return a new error manager keyed off of that name */ public abstract ErrorManager getInstance(String name); // // Helper methods // /** Annotates given exception with given values. All the * previous annotations are kept and this new is added at * the top of the annotation stack (index 0 of the annotation * array). * * @param t the exception * @param localizedMessage localized message for the user or null * @return the same exception <code>t</code> (as a convenience) */ public final Throwable annotate ( Throwable t, String localizedMessage ) { return annotate (t, UNKNOWN, null, localizedMessage, null, null); } /** Annotates target exception with given exception. All the * previous annotations are kept and this new is added at * the top of the annotation stack (index 0 of the annotation * array). * * @param target the exception to be annotated * @param t the exception that will be added * @return the same exception <code>target</code> (as a convenience) */ public final Throwable annotate (Throwable target, Throwable t) { return annotate (target, UNKNOWN, null, null, t, null); } /** Takes annotations from one exception and associates * them with another one. * * @param t the exception to annotate * @param copyFrom exception to take annotations from * @return the same exception <code>t</code> (as a convenience) * @deprecated Now does the same thing as {@link #annotate(Throwable,Throwable)} * except marks the annotation {@link #UNKNOWN} severity. Otherwise * you used to have inadvertent data loss when <code>copyFrom</code> * had annotations of its own: the subannotations were kept but the * main stack trace in <code>copyFrom</code> was discarded. In practice * you usually want to keep all of <code>copyFrom</code>; if for some * reason you just want to keep annotations, please do so explicitly * using {@link #findAnnotations} and {@link #attachAnnotations}. */ public final Throwable copyAnnotation (Throwable t, Throwable copyFrom) { // Cf. #17874 for the change in behavior. /* Annotation[] arr = findAnnotations (copyFrom); if (arr != null) { return attachAnnotations ( t, arr ); } else { */ return annotate (t, UNKNOWN, null, null, copyFrom, null); /* } */ } /** * Implementation of ErrorManager that delegates to the ones found by * lookup. */ private static class DelegatingErrorManager extends ErrorManager implements LookupListener { private String name = null; /** * The set of instances we delegate to. Elements type is ErrorManager. */ private Set delegates = new HashSet(); /** * A set that has to be updated when the list of delegates * changes. All instances created by getInstance are held here. * It is a set of DelagatingErrorManager. */ private WeakSet createdByMe = new WeakSet(); /** If we are the "central" delagate this is not null and * we listen on the result. On newly created delegates this * is null. */ Lookup.Result r; public DelegatingErrorManager(String name) { this.name = name; } /** If the name is not empty creates new instance of * DelegatingErrorManager. Adds it to createdByMe. */ public ErrorManager getInstance(String name) { if ((name == null) || ("".equals(name))) { // NOI18N return this; } DelegatingErrorManager dem = new DelegatingErrorManager(name); synchronized (this) { attachNewDelegates(dem, name); createdByMe.add(dem); } return dem; } /** Calls all delegates. */ public Throwable attachAnnotations (Throwable t, Annotation[] arr) { for (Iterator i = delegates.iterator(); i.hasNext(); ) { ErrorManager em = (ErrorManager)i.next(); em.attachAnnotations(t, arr); } return t; } /** Calls all delegates. */ public Annotation[] findAnnotations (Throwable t) { for (Iterator i = delegates.iterator(); i.hasNext(); ) { ErrorManager em = (ErrorManager)i.next(); Annotation[] res = em.findAnnotations(t); if ((res != null) && (res.length > 0)) { return res; } } return new Annotation[0]; } /** Calls all delegates. */ public Throwable annotate ( Throwable t, int severity, String message, String localizedMessage, Throwable stackTrace, java.util.Date date) { for (Iterator i = delegates.iterator(); i.hasNext(); ) { ErrorManager em = (ErrorManager)i.next(); em.annotate(t, severity, message, localizedMessage, stackTrace, date); } return t; } /** Calls all delegates. */ public void notify (int severity, Throwable t) { if (delegates.isEmpty()) { t.printStackTrace(); } try { for (Iterator i = delegates.iterator(); i.hasNext(); ) { ErrorManager em = (ErrorManager)i.next(); em.notify(severity, t); } } catch (RuntimeException e) { // #20467 e.printStackTrace(); t.printStackTrace(); } catch (LinkageError e) { // #20467 e.printStackTrace(); t.printStackTrace(); } } /** Calls all delegates. */ public void log(int severity, String s) { if (delegates.isEmpty()) { System.err.println ("Log: " + severity + " msg: " + s); // NOI18N } for (Iterator i = delegates.iterator(); i.hasNext(); ) { ErrorManager em = (ErrorManager)i.next(); em.log(severity, s); } } /** Calls all delegates. */ public boolean isLoggable (int severity) { if (delegates.isEmpty()) { return true; } for (Iterator i = delegates.iterator(); i.hasNext(); ) { ErrorManager em = (ErrorManager)i.next(); if (em.isLoggable(severity)) { return true; } } return false; } /** Calls all delegates. */ public boolean isNotifiable(int severity) { if (delegates.isEmpty()) { return true; } for (Iterator i = delegates.iterator(); i.hasNext(); ) { ErrorManager em = (ErrorManager)i.next(); if (em.isNotifiable(severity)) { return true; } } return false; } /** * Updates the list of delegates. Also updates all instances created * by ourselves. */ public synchronized void setDelegates(Collection newDelegates) { HashSet d = new HashSet(newDelegates); for (Iterator i = createdByMe.iterator(); i.hasNext(); ) { DelegatingErrorManager dem = (DelegatingErrorManager)i.next(); attachNewDelegates(dem, dem.getName()); } delegates = d; } private String getName() { return name; } /** * Takes all our delegates, asks them for an instance identified by * name and adds those results as new delegates for dem. * @param String name * @param DelagatingErrorManager d the instance to which we will attach */ private void attachNewDelegates(DelegatingErrorManager dem, String name) { Set newDelegatesForDem = new HashSet(); for (Iterator j = delegates.iterator(); j.hasNext(); ) { ErrorManager e = (ErrorManager)j.next(); newDelegatesForDem.add(e.getInstance(name)); } dem.setDelegates(newDelegatesForDem); } /** Blocks on lookup and after the lookup returns updates * delegates and adds a listener. */ public void initialize() { r = Lookup.getDefault().lookup( new Lookup.Template(ErrorManager.class)); Collection instances = r.allInstances(); setDelegates(instances); } /** Updates the delegates.*/ public void resultChanged (LookupEvent ev) { if (r != null) { Collection instances = r.allInstances(); setDelegates(instances); } } } /** Annotation that can be attached to an error. */ public static interface Annotation { /** Non-localized message. * @return associated message or <code>null</code> */ public abstract String getMessage (); /** Localized message. * @return message to be presented to the user or <code>null</code> */ public abstract String getLocalizedMessage (); /** Stack trace. The stack trace should locate the method * and position in the method where the error occurred. * * @return exception representing the location of the error or <code>null</code> */ public abstract Throwable getStackTrace (); /** Time at which the exception occurred. * @return the time or <code>null</code> */ public abstract java.util.Date getDate (); /** Severity of the exception. * {@link #UNKNOWN} serves as the default. * @return number representing the severity, e.g. {@link ErrorManager#EXCEPTION} */ public abstract int getSeverity (); } // end of Annotation }