View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.awt.Frame;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.EnumSet;
7   import java.util.List;
8   import java.util.Set;
9   
10  import javax.swing.JOptionPane;
11  
12  import org.slf4j.LoggerFactory;
13  
14  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
15  
16  import ch.qos.logback.classic.Logger;
17  import ij.IJ;
18  
19  /**
20   * Template of exceptions thrown by QuimP API.
21   * 
22   * <p>Allow for handling them and present to user depending on {@link MessageSinkTypes}.
23   * 
24   * @author p.baniukiewicz
25   *
26   */
27  public class QuimpException extends Exception {
28  
29    /**
30     * If true sink type will not be changed in {@link #setMessageSinkType(MessageSinkTypes...)} and
31     * {@link #setMessageSinkType(Set)}.
32     */
33    protected boolean persistent = false;
34    /**
35     * The LOGGER.
36     */
37    public Logger logger = (Logger) LoggerFactory.getLogger(this.getClass());
38  
39    /**
40     * Define where the message should be displayed. Anu combination of values is supported by
41     * {@link QuimpException#handleException(Frame, String)}.
42     * <ol>
43     * <li>CONSOLE - default, message goes to console.
44     * <li>GUI - message should be shown in GUI
45     * <li>IJERROR - use IJ error handling
46     * <li>MESSAGE - print message in console using INFO level
47     * <li>NONE - {@link QuimpException#handleException(Frame, String)} will return just formatted
48     * string without any action.
49     * </ol>
50     * 
51     * @author p.baniukiewicz
52     *
53     */
54    public enum MessageSinkTypes {
55  
56      /**
57       * Console sink (stderr).
58       */
59      CONSOLE,
60      /**
61       * Display window with message.
62       */
63      GUI,
64      /**
65       * Use IJ.error for log.
66       */
67      IJERROR,
68      /**
69       * Print message in console.
70       * 
71       * <p>Similar to {@link #CONSOLE} but message has INFO level.
72       */
73      MESSAGE,
74      /**
75       * None of above, just return formatted exception string.
76       */
77      NONE;
78    }
79  
80    /**
81     * Message sinks - where they will appear.
82     * 
83     * @see MessageSinkTypes
84     */
85    protected EnumSet<MessageSinkTypes> messageSinkType;
86  
87    /**
88     * Set message sink. It can be combination of {@link MessageSinkTypes} values.
89     * 
90     * @param messageSinkType the messageSinkType to set.
91     */
92    public void setMessageSinkType(MessageSinkTypes... messageSinkType) {
93      if (persistent) {
94        return;
95      }
96      this.messageSinkType = EnumSet.copyOf(Arrays.asList(messageSinkType));
97    }
98  
99    /**
100    * Set message sink. It can be combination of {@link MessageSinkTypes} values.
101    * 
102    * @param messageSinkType the messageSinkType to set.
103    */
104   public void setMessageSinkType(Set<MessageSinkTypes> messageSinkType) {
105     if (persistent) {
106       return;
107     }
108     this.messageSinkType = EnumSet.copyOf(messageSinkType);
109   }
110 
111   /**
112    * Get message sink.
113    * 
114    * @return the type
115    */
116   public Set<MessageSinkTypes> getMessageSinkType() {
117     return messageSinkType;
118   }
119 
120   /**
121    * serialVersionUID.
122    */
123   private static final long serialVersionUID = -7943488580659917234L;
124 
125   /**
126    * Default constructor, set message sink to console.
127    */
128   public QuimpException() {
129     this.messageSinkType = EnumSet.of(MessageSinkTypes.CONSOLE);
130   }
131 
132   /**
133    * Allow to set type of message, where it should be displayed.
134    * 
135    * @param types of message (list)
136    * @see MessageSinkTypes
137    */
138   public QuimpException(MessageSinkTypes... types) {
139     setMessageSinkType(types);
140   }
141 
142   /**
143    * Allow to set type of message, where it should be displayed.
144    * 
145    * @param type of message (one)
146    * @see MessageSinkTypes
147    */
148   public QuimpException(MessageSinkTypes type) {
149     this.messageSinkType = EnumSet.of(type);
150   }
151 
152   /**
153    * Default exception with message. Will be shown in console.
154    * 
155    * @param message message
156    */
157   public QuimpException(String message) {
158     super(message);
159     this.messageSinkType = EnumSet.of(MessageSinkTypes.CONSOLE);
160   }
161 
162   /**
163    * Exception with message.
164    * 
165    * @param message message
166    * @param type where to show message
167    */
168   public QuimpException(String message, MessageSinkTypes type) {
169     super(message);
170     this.messageSinkType = EnumSet.of(type);
171   }
172 
173   /**
174    * Exception with message.
175    * 
176    * @param message message
177    * @param type where to show message
178    * @param persistent if true the sink will not be overwritten
179    */
180   public QuimpException(String message, MessageSinkTypes type, boolean persistent) {
181     super(message);
182     this.messageSinkType = EnumSet.of(type);
183     this.persistent = persistent;
184   }
185 
186   /**
187    * Exception with message.
188    * 
189    * @param message message
190    * @param types where to show message
191    */
192   public QuimpException(String message, MessageSinkTypes... types) {
193     super(message);
194     setMessageSinkType(types);
195   }
196 
197   /**
198    * QuimpException.
199    * 
200    * @param cause cause
201    */
202   public QuimpException(Throwable cause) {
203     super(cause);
204     this.messageSinkType = EnumSet.of(MessageSinkTypes.CONSOLE);
205   }
206 
207   /**
208    * QuimpException.
209    * 
210    * @param cause cause
211    * @param type type (one)
212    */
213   public QuimpException(Throwable cause, MessageSinkTypes type) {
214     super(cause);
215     this.messageSinkType = EnumSet.of(type);
216   }
217 
218   /**
219    * QuimpException.
220    * 
221    * @param cause cause
222    * @param types type (list)
223    */
224   public QuimpException(Throwable cause, MessageSinkTypes... types) {
225     super(cause);
226     setMessageSinkType(types);
227   }
228 
229   /**
230    * QuimpException.
231    * 
232    * @param message message
233    * @param cause cause
234    */
235   public QuimpException(String message, Throwable cause) {
236     super(message, cause);
237     this.messageSinkType = EnumSet.of(MessageSinkTypes.CONSOLE);
238   }
239 
240   /**
241    * QuimpException.
242    * 
243    * @param message message
244    * @param cause cause
245    * @param enableSuppression enableSuppression
246    * @param writableStackTrace writableStackTrace
247    */
248   public QuimpException(String message, Throwable cause, boolean enableSuppression,
249           boolean writableStackTrace) {
250     super(message, cause, enableSuppression, writableStackTrace);
251     this.messageSinkType = EnumSet.of(MessageSinkTypes.CONSOLE);
252   }
253 
254   /**
255    * QuimpException.
256    * 
257    * @param message message
258    * @param cause cause
259    * @param type where to show message (one)
260    */
261   public QuimpException(String message, Throwable cause, MessageSinkTypes type) {
262     super(message, cause);
263     this.messageSinkType = EnumSet.of(type);
264   }
265 
266   /**
267    * QuimpException.
268    * 
269    * @param message message
270    * @param cause cause
271    * @param types where to show message (list)
272    */
273   public QuimpException(String message, Throwable cause, MessageSinkTypes... types) {
274     super(message, cause);
275     setMessageSinkType(types);
276   }
277 
278   /**
279    * Handle this exception displaying it and logging depending on {@link #messageSinkType}.
280    * 
281    * @param frame Swing frame to display message for user, can be null
282    * @param appendMessage Message added to beginning of the exception message, can be ""
283    * @return Exception message
284    */
285   public String handleException(Frame frame, String appendMessage) {
286     logger.debug(getMessage(), this);
287     String message = prepareMessage(this, appendMessage);
288 
289     if (getMessageSinkType().contains(MessageSinkTypes.CONSOLE)) {
290       logger.error(message);
291     }
292     if (getMessageSinkType().contains(MessageSinkTypes.MESSAGE)) {
293       logger.info(message);
294     }
295     if (getMessageSinkType().contains(MessageSinkTypes.GUI)) {
296       showGuiWithMessage(frame, message);
297     }
298     if (getMessageSinkType().contains(MessageSinkTypes.IJERROR)) {
299       List<String> ex = getExceptionMessageChain(this);
300       if (ex.size() > 1) {
301         IJ.log("Error messages stack:");
302         for (int i = 0; i < ex.size(); i++) {
303           if (ex.get(i) != null) {
304             if (i == 0) {
305               IJ.log(" " + ex.get(i));
306             } else {
307               IJ.log("  ->" + ex.get(i));
308             }
309           }
310         }
311       }
312       IJ.handleException(this);
313       IJ.error(QuimpToolsCollection.stringWrap(message, QuimP.LINE_WRAP));
314     }
315     return message;
316   }
317 
318   /**
319    * Show UI with message.
320    * 
321    * @param frame Swing frame to display message for user, can be null
322    * @param message message produced by {@link #prepareMessage(Exception, String)}
323    */
324   public static void showGuiWithMessage(Frame frame, String message) {
325     JOptionPane.showMessageDialog(frame, QuimpToolsCollection.stringWrap(message, QuimP.LINE_WRAP),
326             "Error", JOptionPane.ERROR_MESSAGE);
327   }
328 
329   /**
330    * Prepare formatted message to show.
331    * 
332    * @param ex Exception to dig in.
333    * @param appendMessage Message added to beginning of the exception message, can be ""
334    * @return Exception message
335    */
336   public static String prepareMessage(Exception ex, String appendMessage) {
337     String message = appendMessage;
338     List<String> ch = getExceptionMessageChain(ex);
339     int l = 0;
340     for (String c : ch) {
341       if (c != null) {
342         if (l == 0) {
343           message = message.concat(" (" + c);
344         } else {
345           message = message.concat(" -> " + c);
346         }
347         l++;
348       }
349     }
350     if (l > 0) {
351       message = message.concat(")");
352     }
353     return message;
354   }
355 
356   /**
357    * Get messages from exception stack.
358    * 
359    * <p>Taken from stackoverflow.com/questions/15987258/
360    * 
361    * @param throwable exception
362    * @return list of messages from underlying exceptions
363    */
364   public static List<String> getExceptionMessageChain(Throwable throwable) {
365     List<String> result = new ArrayList<String>();
366     while (throwable != null) {
367       result.add(throwable.getMessage());
368       throwable = throwable.getCause();
369     }
370     return result;
371   }
372 
373 }