001package jmri.util.swing;
002
003import javax.annotation.Nonnull;
004
005import jmri.jmrit.mailreport.ReportContext;
006import jmri.util.StringUtil;
007
008/**
009 * Wraps an Exception and allows extra contextual information to be added, such
010 * as what was happening at the time of the Exception, and a hint as to what the
011 * user might do to correct the problem. Also implements a number of methods to
012 * format the data of an Exception.
013 *
014 * @author Gregory Madsen Copyright (C) 2012
015 *
016 */
017public class ExceptionContext {
018
019    protected final Throwable exception;
020    protected String prefaceString = "An error occurred during the following operation."; // NOI18N
021    protected String operation;
022    protected String hint = "";
023
024    /**
025     * Create a new Exception Context.
026     * @param ex the Throwable Exception which has occurred.
027     * @param operation An Operation which was taking place at the time of the
028     *                  Exception.  Use empty String if unknown.
029     * @param hint      A hint as to what might have caused the Exception.
030     *                  Use empty String if unknown.
031     */
032    public ExceptionContext(@Nonnull Throwable ex, @Nonnull String operation, @Nonnull String hint) {
033        this.exception = ex;
034        this.operation = operation;
035        this.hint = hint;
036    }
037
038    /**
039     * Get the Exception being wrapped.
040     * @return the Exception.
041     */
042    public Throwable getException() {
043        return exception;
044    }
045
046    /**
047     * Used to give a more friendly message.
048     * @return the Preface String.
049     */
050    public String getPreface() {
051        return prefaceString;
052    }
053
054    /**
055     * Get what was happening when the exception occurred.
056     * @return empty String if unknown.
057     */
058    public String getOperation() {
059        return operation;
060    }
061
062    /**
063     * Get suggestion to the user to correct the problem.
064     * @return empty string if no hint, else the hint text.
065     */
066    @Nonnull
067    public String getHint() {
068        return hint;
069    }
070
071    /**
072     * Get a String to use as the Title for this Context.
073     * @return Localised Exception message, truncated to 80 chars.
074     */
075    public String getTitle() {
076        String msg = exception.getLocalizedMessage();
077        return msg.substring(0, Math.min(msg.length(), 80));
078    }
079
080    /**
081     * Returns a user friendly summary of the Exception.
082     * Empty data is excluded.
083     * @return A string summary.
084     */
085    public String getSummary() {
086        StringBuilder sb = new StringBuilder();
087        if (!getPreface().isBlank()) {
088            sb.append(getPreface())
089                .append(System.lineSeparator()).append(System.lineSeparator());
090        }
091
092        if (!getOperation().isBlank()) {
093            sb.append(getOperation()).append(System.lineSeparator());
094        }
095
096        if (!getHint().isBlank()) {
097            sb.append(getHint()).append(System.lineSeparator());
098        }
099
100        String msg = getException().getMessage();
101        if ( msg != null && !msg.equals(getException().getLocalizedMessage())) {
102            sb.append(getException().getLocalizedMessage()).append(System.lineSeparator());
103        }
104
105        sb.append(getException().getMessage()).append(System.lineSeparator());
106        sb.append(getException().getClass().getName()).append(System.lineSeparator());
107
108        Throwable cause = getException().getCause();
109        if (cause != null) {
110            sb.append(cause.toString()).append(System.lineSeparator());
111        }
112        sb.append(getException().toString());
113        return StringUtil.stripHtmlTags(sb.toString());
114    }
115
116    /**
117     * Returns up to the given number of stack trace elements concatenated into
118     * one string.
119     * @param maxLevels The number of stack trace elements to return.
120     * @return A string stack trace.
121     *
122     */
123    public String getStackTraceAsString(int maxLevels) {
124        StringBuilder sb = new StringBuilder();
125
126        StackTraceElement[] stElements = exception.getStackTrace();
127
128        int limit = Math.min(maxLevels, stElements.length);
129        for (int i = 0; i < limit; i++) {
130            sb.append(" at "); // NOI18N
131            sb.append(stElements[i].toString());
132            sb.append(System.lineSeparator());
133        }
134
135        // If there are more levels than included, add a note to the end
136        if (stElements.length > limit) {
137            sb.append(" plus "); // NOI18N
138            sb.append(stElements.length - limit);
139            sb.append(" more."); // NOI18N
140        }
141        return sb.toString();
142    }
143
144    /**
145     * Get the Full Stack Trace String.
146     * @return unabridged Stack Trace String.
147     */
148    public String getStackTraceString() {
149        return getStackTraceAsString(Integer.MAX_VALUE);
150    }
151
152    /**
153     * Get a String form of this Context for use in pasting to Clipboard.
154     * @param includeSysInfo true to include System Information,
155     *                       false for just the Exception details.
156     * @return String for use in Clipboard text.
157     */
158    public String getClipboardString(boolean includeSysInfo){
159        StringBuilder sb = new StringBuilder();
160        sb.append(getSummary());
161        sb.append(System.lineSeparator());
162        sb.append(getStackTraceString());
163        if ( includeSysInfo ) {
164            sb.append(System.lineSeparator());
165            sb.append(new ReportContext().getReport(true));
166        }
167        return sb.toString();
168    }
169
170}