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        if (!getException().getMessage().equals(getException().getLocalizedMessage())) {
101            sb.append(getException().getLocalizedMessage()).append(System.lineSeparator());
102        }
103
104        sb.append(getException().getMessage()).append(System.lineSeparator());
105        sb.append(getException().getClass().getName()).append(System.lineSeparator());
106
107        Throwable cause = getException().getCause();
108        if (cause != null) {
109            sb.append(cause.toString()).append(System.lineSeparator());
110        }
111        sb.append(getException().toString());
112        return StringUtil.stripHtmlTags(sb.toString());
113    }
114
115    /**
116     * Returns up to the given number of stack trace elements concatenated into
117     * one string.
118     * @param maxLevels The number of stack trace elements to return.
119     * @return A string stack trace.
120     *
121     */
122    public String getStackTraceAsString(int maxLevels) {
123        StringBuilder sb = new StringBuilder();
124
125        StackTraceElement[] stElements = exception.getStackTrace();
126
127        int limit = Math.min(maxLevels, stElements.length);
128        for (int i = 0; i < limit; i++) {
129            sb.append(" at "); // NOI18N
130            sb.append(stElements[i].toString());
131            sb.append(System.lineSeparator());
132        }
133
134        // If there are more levels than included, add a note to the end
135        if (stElements.length > limit) {
136            sb.append(" plus "); // NOI18N
137            sb.append(stElements.length - limit);
138            sb.append(" more."); // NOI18N
139        }
140        return sb.toString();
141    }
142
143    /**
144     * Get the Full Stack Trace String.
145     * @return unabridged Stack Trace String.
146     */
147    public String getStackTraceString() {
148        return getStackTraceAsString(Integer.MAX_VALUE);
149    }
150
151    /**
152     * Get a String form of this Context for use in pasting to Clipboard.
153     * @param includeSysInfo true to include System Information,
154     *                       false for just the Exception details.
155     * @return String for use in Clipboard text.
156     */
157    public String getClipboardString(boolean includeSysInfo){
158        StringBuilder sb = new StringBuilder();
159        sb.append(getSummary());
160        sb.append(System.lineSeparator());
161        sb.append(getStackTraceString());
162        if ( includeSysInfo ) {
163            sb.append(System.lineSeparator());
164            sb.append(new ReportContext().getReport(true));
165        }
166        return sb.toString();
167    }
168
169}