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