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