/** * CertWare Project */ package net.certware.core.ui.dialog; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.Dictionary; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Plugin; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.window.IShellProvider; import org.eclipse.jface.window.SameShellProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; /** * Provides a dialog presenting the option to view exception details. * @author mrb */ public class ExceptionDetailsDialog extends AbstractDetailsDialog { /** the exception details, with Throwable and IStatus special treatment */ private final Object details; /** the hosting plugin, used for additional context information */ private final Plugin plugin; /** * Constructor when given the parent shell. * @param parentShell parent shell * @param title dialog title * @param image dialog image, from display * @param message dialog message body * @param details exception details object for details pane * @param plugin plug-in host for context information */ public ExceptionDetailsDialog(Shell parentShell, String title, Image image, String message, Object details, Plugin plugin) { this(new SameShellProvider(parentShell), title, image, message, details, plugin); } /** * Constructor when given the parent shell provider. * @param parentShell parent shell * @param title dialog title * @param image dialog image, from display * @param message dialog message body * @param details exception details object for details pane * @param plugin plug-in host for context information */ public ExceptionDetailsDialog(IShellProvider parentShell, String title, Image image, String message, Object details, Plugin plugin) { super(parentShell, getTitle(title, details), getImage(image, details), getMessage(message,details)); this.details = details; this.plugin = plugin; } /** * Returns the title for the dialog. If the title is given, return it. * If the details are <code>Throwable</code>, return the class name of the * last invocation target exception. Otherwise, returns "Exception". * @param title dialog title * @param details exception details * @return title, exception class name, or "Exception" */ public static String getTitle(String title, Object details) { if ( title != null ) return title; if ( details instanceof Throwable ) { Throwable e = (Throwable)details; while (e instanceof InvocationTargetException) e = ((InvocationTargetException)e).getTargetException(); String name = e.getClass().getName(); return name.substring(name.lastIndexOf('.')+1); } return "Exception"; } /** * Returns the image for the dialog. * If the image is given, return it. * If the details is an <code>IStatus</code> return the corresponding * image from the display defaults. * Otherwise, return the display default error icon. * @param image dialog icon image * @param details exception details * @return one of the <code>SWT</code> icon images. */ public static Image getImage(Image image, Object details) { if ( image != null ) return image; Display display = Display.getCurrent(); if ( display == null ) return null; if ( details instanceof IStatus ) { switch(((IStatus)details).getSeverity()) { case IStatus.ERROR : return display.getSystemImage(SWT.ICON_ERROR); case IStatus.WARNING : return display.getSystemImage(SWT.ICON_WARNING); case IStatus.INFO : return display.getSystemImage(SWT.ICON_INFORMATION); case IStatus.OK : return null; } } return display.getSystemImage(SWT.ICON_ERROR); } /** * Returns the dialog message. * If the details are <code>Throwable</code> then return the message * associated with the last invocation target exception. * If the details are <code>IStatus</code> then return the associated message. * If the message is given, return it. * Otherwise, return "An exception occurred.". * @param message dialog message or null * @param details exception details object or null * @return exception message, status message, message, or "An exception occurred." */ public static String getMessage(String message, Object details) { if ( details instanceof Throwable ) { Throwable e = (Throwable)details; while ( e instanceof InvocationTargetException ) e = ((InvocationTargetException)e).getTargetException(); if ( message == null ) return e.toString(); return MessageFormat.format(message, new Object[] { e.toString() } ); } // throwable if ( details instanceof IStatus ) { String statusMessage = ((IStatus)details).getMessage(); if ( message == null ) return statusMessage; return MessageFormat.format(message, new Object[] { statusMessage } ); } // status if ( message != null ) return message; return "An exception occurred."; } /** * Appends an exception to the message stack trace. * Handles <code>CoreException</code> and <code>InvocationTargetException</code> * throwables, ignoring others. * @param writer the writer containing the trace * @param ex the dialog exception details */ public static void appendException(PrintWriter writer, Throwable ex) { if ( ex instanceof CoreException ) { appendStatus(writer, ((CoreException)ex).getStatus(), 0); writer.println(); } appendStackTrace(writer, ex); if ( ex instanceof InvocationTargetException ) appendException(writer, ((InvocationTargetException) ex).getTargetException()); } /** * Appends an exception to the message stack trace. * Handles <code>IStatus</code> objects. * @param writer the writer containing the trace * @param status the exception details * @param nesting the nesting level for status messages, each yielding two space indent */ public static void appendStatus(PrintWriter writer, IStatus status, int nesting) { for ( int i = 0; i < nesting; i++ ) writer.print(" "); // $NON-NLS-1$ writer.println(status.getMessage()); IStatus[] children = status.getChildren(); for ( int i = 0; i < children.length; i++ ) appendStatus(writer,children[i],nesting+1); } /** * Prints the exception stack trace to the writer. * @param writer the writer destination for the stack trace * @param ex an exception containing the stack */ public static void appendStackTrace(PrintWriter writer, Throwable ex) { ex.printStackTrace(writer); } /** * Creates the details panel and content for a dialog parent. * Meant to be used in response to a details button request. * @param parent part of the details dialog * @return the new panel */ protected Control createDetailsArea(Composite parent) { // create details panel Composite panel = new Composite(parent,SWT.NONE); panel.setLayoutData(new GridData(GridData.FILL_BOTH)); GridLayout layout = new GridLayout(); layout.marginHeight = 0; layout.marginWidth = 0; panel.setLayout(layout); // create details content createProductInfoArea(panel); createDetailsViewer(panel); return panel; } /** * Creates the product information area of the details area. * Includes information about the plug-in identified in the constructor. * Two-column grid layout. * @param parent details panel * @return the new composite */ protected Composite createProductInfoArea(Composite parent) { // if no plugins specified, nothing to display if ( plugin == null ) return null; Composite composite = new Composite(parent, SWT.NULL); composite.setLayoutData(new GridData()); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); composite.setLayout(layout); Dictionary <String,String>bundleHeaders = plugin.getBundle().getHeaders(); String pluginId = plugin.getBundle().getSymbolicName(); String pluginVendor = bundleHeaders.get("Bundle-Vendor"); String pluginName = bundleHeaders.get("Bundle-Name"); String pluginVersion = bundleHeaders.get("Bundle-Version"); new Label(composite, SWT.NONE).setText("Provider:"); new Label(composite, SWT.NONE).setText(pluginVendor); new Label(composite, SWT.NONE).setText("Plug-in Name:"); new Label(composite, SWT.NONE).setText(pluginName); new Label(composite, SWT.NONE).setText("Plug-in ID:"); new Label(composite, SWT.NONE).setText(pluginId); new Label(composite, SWT.NONE).setText("Version:"); new Label(composite, SWT.NONE).setText(pluginVersion); return composite; } /** * Creates the details trace area of the details panel. * Provided as text area, read-only, scrolling. * @param parent details panel * @return new text widget */ protected Control createDetailsViewer(Composite parent) { if ( details == null ) return null; Text text = new Text(parent, SWT.MULTI | SWT.READ_ONLY | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); text.setLayoutData(new GridData(GridData.FILL_BOTH)); // create the content StringWriter writer = new StringWriter(1000); if ( details instanceof Throwable ) appendException(new PrintWriter(writer), (Throwable)details); else if (details instanceof IStatus ) appendStatus(new PrintWriter(writer), (IStatus)details, 0); text.setText(writer.toString()); return text; } }