1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: Reporter.java 2099 2011-03-07 17:13:00Z dmsmith $
21   */
22  package org.crosswire.common.util;
23  
24  import java.util.Properties;
25  
26  import org.crosswire.jsword.JSMsg;
27  
28  /**
29   * This package looks after Exceptions and messages as they happen. It would be
30   * nice not to need this class - the principle being that any library that
31   * encounters an error can throw an exception to indicate that there is a
32   * problem. However this is not always the case. For example:
33   * <ul>
34   * <li>static class constructors should not throw, unless the class really is of
35   * no use given the error, and yet we may want to tell the user that there was a
36   * (non-critical) error.</li>
37   * <li>Any library routine that works in a loop, applying some (potentially
38   * failing) functionality, may want to continue the work without throwing in
39   * response to a single error.</li>
40   * <li>The class being implemented may implement an interface that disallows
41   * nested exceptions and yet does not want to loose the root cause error
42   * information. (This is the weakest of the above arguments, but probably still
43   * valid.)</li> However in many of the times this class is used, this is the
44   * reason:
45   * <li>Within UI specific code - to throw up a dialog box (or whatever). Now
46   * this use is currently tolerated, however it is probably a poor idea to use
47   * GUI agnostic messaging in a GUI specific context. But I'm not bothered enough
48   * to change it now. Specifically this use is deprecated because it makes the
49   * app more susceptible to the configuration of the things that listen to
50   * reports.</li>
51   * </ul>
52   * 
53   * @see gnu.lgpl.License for license details.<br>
54   *      The copyright to this program is held by it's authors.
55   * @author Joe Walker [joe at eireneh dot com]
56   */
57  public final class Reporter {
58      /**
59       * Enforce Singleton
60       */
61      private Reporter() {
62      }
63  
64      /**
65       * Something has gone wrong. We need to tell the user or someone, but we can
66       * carry on. In general having caught an exception and passed it to
67       * Reporter.informUser(), you should not throw another Exception. Called to
68       * fire a commandEntered event to all the Listeners
69       * 
70       * @param source
71       *            The cause of the problem, a Component if possible.
72       * @param prob
73       */
74      public static void informUser(Object source, Throwable prob) {
75          Class<?> cat = (source != null) ? source.getClass() : Reporter.class;
76          Logger templog = Logger.getLogger(cat);
77  
78          templog.warn(prob.getMessage(), prob);
79  
80          fireCapture(new ReporterEvent(source, prob));
81      }
82  
83      /**
84       * Something has gone wrong. We need to tell the user or someone, but we can
85       * carry on. In general having caught an exception and passed it to
86       * Reporter.informUser(), you should not throw another Exception. Called to
87       * fire a commandEntered event to all the Listeners
88       * 
89       * @param source
90       *            The cause of the problem, a Component if possible.
91       * @param prob
92       *            The Exception that was thrown
93       */
94      public static void informUser(Object source, LucidException prob) {
95          Class<?> cat = (source != null) ? source.getClass() : Reporter.class;
96          Logger templog = Logger.getLogger(cat);
97  
98          templog.warn(prob.getMessage(), prob);
99  
100         fireCapture(new ReporterEvent(source, prob));
101     }
102 
103     /**
104      * Something has gone wrong. We need to tell the user or someone, but we can
105      * carry on. In general having caught an exception and passed it to
106      * Reporter.informUser(), you should not throw another Exception. Called to
107      * fire a commandEntered event to all the Listeners
108      * 
109      * @param source
110      *            The cause of the problem, a Component if possible.
111      * @param prob
112      *            The Exception that was thrown
113      */
114     public static void informUser(Object source, LucidRuntimeException prob) {
115         Class<?> cat = (source != null) ? source.getClass() : Reporter.class;
116         Logger templog = Logger.getLogger(cat);
117 
118         templog.warn(prob.getMessage(), prob);
119 
120         fireCapture(new ReporterEvent(source, prob));
121     }
122 
123     /**
124      * Something has happened. We need to tell the user or someone.
125      * 
126      * @param source
127      *            The cause of the message, a Component if possible.
128      * @param message
129      *            The message to pass to the user
130      */
131     public static void informUser(Object source, String message) {
132         log.debug(message);
133 
134         fireCapture(new ReporterEvent(source, message));
135     }
136 
137     /**
138      * Add an Exception listener to the list of things wanting to know whenever
139      * we capture an Exception
140      */
141     public static void addReporterListener(ReporterListener li) {
142         LISTENERS.add(ReporterListener.class, li);
143     }
144 
145     /**
146      * Remove an Exception listener from the list of things wanting to know
147      * whenever we capture an Exception
148      */
149     public static void removeReporterListener(ReporterListener li) {
150         LISTENERS.remove(ReporterListener.class, li);
151     }
152 
153     /**
154      * Log a message.
155      */
156     protected static void fireCapture(ReporterEvent ev) {
157         // Guaranteed to return a non-null array
158         Object[] liArr = LISTENERS.getListenerList();
159 
160         if (liArr.length == 0) {
161             log.warn("Nothing to listen to report: message=" + ev.getMessage(), ev.getException());
162         }
163 
164         // Process the listeners last to first, notifying
165         // those that are interested in this event
166         for (int i = liArr.length - 2; i >= 0; i -= 2) {
167             if (liArr[i] == ReporterListener.class) {
168                 ReporterListener li = (ReporterListener) liArr[i + 1];
169                 if (ev.getException() != null) {
170                     li.reportException(ev);
171                 } else {
172                     li.reportMessage(ev);
173                 }
174             }
175         }
176     }
177 
178     /**
179      * Sets the parent of any exception windows.
180      */
181     public static void grabAWTExecptions(boolean grab) {
182         if (grab) {
183             // register ourselves
184             System.setProperty(AWT_HANDLER_PROPERTY, OUR_NAME);
185         } else {
186             // deregister ourselves
187             String current = System.getProperty(AWT_HANDLER_PROPERTY);
188             if (current != null && current.equals(OUR_NAME)) {
189                 Properties prop = System.getProperties();
190                 prop.remove(AWT_HANDLER_PROPERTY);
191             }
192         }
193     }
194 
195     /**
196      * The system property name for registering AWT exceptions
197      */
198     private static final String AWT_HANDLER_PROPERTY = "sun.awt.exception.handler";
199 
200     /**
201      * The name of the class to register for AWT exceptions
202      */
203     private static final String OUR_NAME = CustomAWTExceptionHandler.class.getName();
204 
205     /**
206      * The log stream
207      */
208     private static final Logger log = Logger.getLogger(Reporter.class);
209 
210     /**
211      * The list of listeners
212      */
213     private static final EventListenerList LISTENERS = new EventListenerList();
214 
215     /**
216      * A class to handle AWT caught Exceptions
217      */
218     public static final class CustomAWTExceptionHandler {
219         /**
220          * Its important that we have a no-arg ctor to make this work. So if we
221          * ever create an arged ctor then we need to add: public
222          * CustomAWTExceptionHandler() { }
223          */
224 
225         /**
226          * Handle AWT exceptions
227          */
228         public void handle(Throwable ex) {
229             // Only allow one to be reported every so often.
230             // This interval control was needed because AWT exceptions
231             // were causing recursive AWT exceptions
232             // and way too many dialogs were being thrown up on the screen.
233             if (gate.open()) {
234                 // TRANSLATOR: Very frequent error condition: The program has encountered a severe problem and it is likely that the program is unusable.
235                 Reporter.informUser(this, new LucidException(JSMsg.gettext("Unexpected internal problem. You may need to restart."), ex));
236             }
237         }
238 
239         private static TimeGate gate = new TimeGate(2000);
240     }
241 }
242