| Reporter.java |
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