/******************************************************************************* * GenPlay, Einstein Genome Analyzer * Copyright (C) 2009, 2014 Albert Einstein College of Medicine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * Authors: Julien Lajugie <julien.lajugie@einstein.yu.edu> * Nicolas Fourel <nicolas.fourel@einstein.yu.edu> * Eric Bouhassira <eric.bouhassira@einstein.yu.edu> * * Website: <http://genplay.einstein.yu.edu> ******************************************************************************/ package edu.yu.einstein.genplay.exception; import java.awt.Component; import java.lang.Thread.UncaughtExceptionHandler; import java.util.concurrent.CancellationException; import javax.swing.JComponent; import javax.swing.JOptionPane; import edu.yu.einstein.genplay.exception.exceptions.BinListDifferentWindowSizeException; import edu.yu.einstein.genplay.exception.exceptions.InvalidFileTypeException; import edu.yu.einstein.genplay.exception.report.ReportBuilder; import edu.yu.einstein.genplay.gui.dialog.exceptionDialog.ExceptionReportDialog; import edu.yu.einstein.genplay.gui.mainFrame.MainFrame; /** * Provides a common strategy to handle the exceptions for all GenPlay. * It handles the report creation and can send it within an email. * * It can also be used to notify the user of a simple information that doesn't require the regular handling. * * @author Nicolas Fourel */ public final class ExceptionManager implements UncaughtExceptionHandler { /** Yes option: enable feature */ public static final int NO = 0; /** No option: disable feature */ public static final int YES = 1; /** * @return an instance of a {@link ExceptionManager}. * Makes sure that there is only one unique instance as specified in the singleton pattern */ public static ExceptionManager getInstance() { if (instance == null) { synchronized(ExceptionManager.class) { if (instance == null) { instance = new ExceptionManager(); } } } return instance; } /** Print the stack trace into the console */ private int printStackTrace = YES; /** Print the report into the console */ private int printReport = YES; /** Show the report dialog */ private int showReport = YES; private static ExceptionManager instance = null; // unique instance of the singleton private final ReportBuilder report; // object that creates exception reports /** * Constructor of {@link ExceptionManager} */ private ExceptionManager () { report = new ReportBuilder(); } /** * @param b a boolean * @return YES if b is true, NO otherwise */ private int boolToIn (boolean b) { if (b) { return YES; } return NO; } /** * @param thread a thread * @param throwable an exception */ public void caughtException (Thread thread, Throwable throwable) { caughtException(thread, throwable, null); } /** * @param thread a thread * @param throwable an exception * @param message error message */ public void caughtException (Thread thread, Throwable throwable, String message) { handleThrowable(thread, throwable, message); } /** * Handles the exception * @param throwable an exception */ public void caughtException(Throwable throwable) { caughtException(null, throwable); } /** * @param printReport the printReport to set */ public void enablePrintReport(boolean printReport) { this.printReport = boolToIn(printReport); } /** * @param printStackTrace the printStackTrace to set */ public void enablePrintStackTrace(boolean printStackTrace) { this.printStackTrace = boolToIn(printStackTrace); } /** * @param showReport the printStackTrace to set */ public void enableShowReport(boolean showReport) { this.showReport = boolToIn(showReport); } /** * Handle the {@link Throwable} object * @param thread a thread * @param component a component * @param throwable an exception * @param message error message to display */ private void handleThrowable (Thread thread, Throwable throwable, String message) { if (isProgressBarException(throwable) || isNimbusLAFException(throwable)) { // exception in the look and feel are likely due to swing components // not started in the EDT. We don't want to notify the user for that throwable.printStackTrace(); } else { report.initializeReport(thread, throwable, message); processError(throwable); } } /** * @param i an int * @return true if i meets the YES option, false otherwise */ private boolean intToBool (int i) { if (i == YES) { return true; } return false; } /** * @param throwable * @return true if the exception is due to a nimbus look and feel exception */ private boolean isNimbusLAFException(Throwable throwable) { StackTraceElement[] stackTraceElements = throwable.getStackTrace(); if ((stackTraceElements != null) && (stackTraceElements.length > 0)) { String firstStackTraceClass = stackTraceElements[0].getClassName(); if (firstStackTraceClass != null) { return firstStackTraceClass.contains("nimbus"); } } return false; } /** * @param throwable * @return true if the exception is due to an error while creating the progress bar UI */ private boolean isProgressBarException(Throwable throwable) { StackTraceElement[] stackTraceElements = throwable.getStackTrace(); if ((stackTraceElements != null) && (stackTraceElements.length > 0)) { String firstStackTraceClass = stackTraceElements[0].getClassName(); if (firstStackTraceClass != null) { return firstStackTraceClass.contains("BasicProgressBarUI"); } } return false; } /** * Shows a message associated to the specified exception * @param component a component * @param e exception * @param message default message to print */ public void notifyUser(Component component, Throwable e, String message) { boolean exceptionHandled = false; boolean hasCause = true; Throwable exception = e; while (!exceptionHandled && hasCause) { // no message for the interrupted or cancel exceptions if ((exception instanceof InterruptedException) || (exception instanceof CancellationException)) { exceptionHandled = true; } else if (exception instanceof InvalidFileTypeException) { // case where the user tries to load an invalid file type JOptionPane.showMessageDialog(component, exception.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); exceptionHandled = true; } else if (exception instanceof BinListDifferentWindowSizeException) { // case when the user tries to do an operation on 2 binlists with different binsize JOptionPane.showMessageDialog(component, exception.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); exceptionHandled = true; } if (exception.getCause() != null) { exception = exception.getCause(); } else { hasCause = false; } } if (!exceptionHandled) { e.printStackTrace(); JOptionPane.showMessageDialog(component, message, "Error", JOptionPane.ERROR_MESSAGE); } } /** * @return the printReport */ public boolean printReport() { return intToBool(printReport); } /** * @return the printStackTrace */ public boolean printStackTrace() { return intToBool(printStackTrace); } /** * Process the error workflow */ private void processError (Throwable throwable) { if (printReport()) { System.out.println(report.getReport()); } if (printStackTrace()) { throwable.printStackTrace(); } if (showReport()) { String currentReport = report.getReport() + "\n\n\n"; currentReport += ExceptionReportDialog.getInstance().getReport(); ExceptionReportDialog.getInstance().setReport(currentReport); try { ExceptionReportDialog.getInstance().showDialog(MainFrame.getInstance().getRootPane()); } catch (Exception e) { ExceptionReportDialog.getInstance().showDialog(null); } } } /** * @return the printReport */ public boolean showReport() { return intToBool(showReport); } /** * Shows the error stack track in a dialog box * @param jc a component * @param e an exception */ public void showStack(JComponent jc, Exception e) { String errorMessage = "<html><pre>"; if (e.getCause() != null) { errorMessage += "Caused by: " + e.getCause().toString() + "<br/>"; } for (StackTraceElement currentTrace : e.getStackTrace()) { errorMessage += "\tat " + currentTrace.toString() + "<br/>"; } errorMessage += "</pre></html>"; JOptionPane.showMessageDialog(jc.getRootPane(), errorMessage, "Error Info", JOptionPane.ERROR_MESSAGE); } @Override public void uncaughtException(Thread thread, Throwable throwable) { handleThrowable(thread, throwable, null); } }