| /******************************************************************************* |
| * Copyright (c) 2004, 2011 Tasktop Technologies and others. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * https://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Tasktop Technologies - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.bugzilla.ui.tasklist; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Class to hold all of the information about a stack trace |
| * |
| * @author Shawn Minto |
| */ |
| public class StackTrace { |
| |
| /** The length of the stack trace in the original string */ |
| private final int length; |
| |
| /** The offset of the stack trace in the orignal string */ |
| private final int offset; |
| |
| /** The string of the stack trace */ |
| private final String stackTrace; |
| |
| /** |
| * This is the comment that the stack trace appeared in. String if desciption else Comment |
| */ |
| private final Object comment; |
| |
| /** |
| * Constructor |
| * |
| * @param stackTrace |
| * The stack trace string |
| * @param offset |
| * The offset of the stack trace in the original string |
| * @param length |
| * The length of the stack trace in the original string |
| * @param comment |
| * The comment that the stack trace came from |
| */ |
| public StackTrace(String stackTrace, int offset, int length, Object comment) { |
| this.stackTrace = stackTrace; |
| this.offset = offset; |
| this.length = length; |
| this.comment = comment; |
| } |
| |
| /** |
| * Get the offset for the stack trace |
| * |
| * @return Returns the offset. |
| */ |
| public int getOffset() { |
| return offset; |
| } |
| |
| /** |
| * Get the stack trace for the bug |
| * |
| * @return Returns the stackTrace. |
| */ |
| public String getStackTrace() { |
| return stackTrace; |
| } |
| |
| /** |
| * Get the length of the bug |
| * |
| * @return Returns the length. |
| */ |
| public int getLength() { |
| return length; |
| } |
| |
| /** |
| * Get the Comment that this stack trace came from |
| * |
| * @return Returns the Comment if it was a comment else a String if it was the summary |
| */ |
| public Object getComment() { |
| return comment; |
| } |
| |
| /** |
| * Find a standard java stack trace in the given string |
| * |
| * @param s |
| * The string to search for stack traces |
| * @param comment |
| * The comment that the text came from.<br> |
| * Comment if a comment else a String |
| * @return String[] of stack traces - each element is 1 trace |
| */ |
| public static StackTrace[] getStackTrace(String s, Object comment) { |
| |
| // setup the regex used to determine if it looks like we are at a |
| // stack trace and whether it is something that should be skipped |
| String regexExceptionType = "^(.*\\.)+.+(Exception|Error|Throwable).*"; //$NON-NLS-1$ |
| String regexSkip = ".*\\.\\..*"; //$NON-NLS-1$ |
| |
| // get all of the individual lines for the string |
| String[] lines = s.split("\r\n|\n"); //$NON-NLS-1$ |
| |
| // the character start of the current stack trace |
| int charStackStart = 0; |
| |
| // the current character in the string - used for the start and the |
| // offset |
| int[] charPos = { 0 }; // array so pass by reference |
| |
| boolean inStackTrace = false; |
| List<String> stackTrace = null; |
| List<StackTrace> stackTraces = new ArrayList<StackTrace>(); |
| |
| // go through each of the lines of the string |
| for (int i = 0; i < lines.length; i++) { |
| |
| if (lines[i].matches(regexSkip)) { |
| |
| // update the current character position |
| charPos[0] += lines[i].length() + 2; |
| |
| } else if (lines[i].trim().matches(regexExceptionType) && !inStackTrace) { |
| |
| // we have matched the stack trace and we are not already in one |
| |
| // add the old stack trace to the list of stack traces |
| if (stackTrace != null && stackTrace.size() > 1) { |
| stackTraces.add(getStackTrace(stackTrace, charStackStart, charPos[0] - charStackStart, comment)); |
| } |
| |
| // prepare for a new stack trace |
| stackTrace = new ArrayList<String>(); |
| inStackTrace = true; |
| |
| // the current line is the start of our stack trace |
| stackTrace.add(lines[i]); |
| charStackStart = charPos[0]; |
| charPos[0] += lines[i].length() + 2; |
| } else if (inStackTrace) { |
| // we are in a stack trace |
| |
| int[] pos = { i }; // array so pass by reference |
| |
| // get the next at clause of the stack trace |
| String stack = getNextAt(lines, pos, charPos); |
| |
| // check if there was an at |
| if (stack == null) { |
| |
| // there wasn't so we are done this stack trace |
| inStackTrace = false; |
| if (stackTrace != null && stackTrace.size() > 1) { |
| stackTraces.add(getStackTrace(stackTrace, charStackStart, charPos[0] - charStackStart, comment)); |
| } |
| stackTrace = null; |
| } else if (stackTrace != null) { |
| |
| // we had one, so add it to this stack trace |
| stackTrace.add(stack); |
| } |
| |
| // update the position |
| i = pos[0]; |
| } else { |
| // update the current character position |
| charPos[0] += lines[i].length() + 2; |
| } |
| } |
| |
| // make sure to add the stack trace if it was the last in the string |
| if (stackTrace != null && stackTrace.size() > 1) { |
| stackTraces.add(getStackTrace(stackTrace, charStackStart, charPos[0] - charStackStart, comment)); |
| } |
| |
| if (stackTraces.size() == 0) { |
| return null; |
| } |
| |
| // get the string values of the stack traces and return it |
| return getTracesFromList(stackTraces); |
| } |
| |
| /** |
| * Get the next at clause from a potential stack trace -- looks ahead 4 lines |
| * |
| * @param lines |
| * The array of all of the lines in the bug |
| * @param i |
| * The current position to start at |
| * @param charPos |
| * The current character position in the original string |
| * @return The next at clause, or <code>null</code><br> |
| * If an at line is matched, but the end isn't within the 4 lines, only the first line is returned. Also, |
| * charPos is updated as well as i |
| */ |
| private static String getNextAt(String[] lines, int[] i, int[] charPos) { |
| String regexAtString = "^at.*"; //$NON-NLS-1$ |
| String regexEndString = ".*:\\d+\\)$"; //$NON-NLS-1$ |
| int index = i[0]; |
| String l1, l2, l3, l4; |
| l1 = l2 = l3 = l4 = null; |
| String res = null; |
| |
| // get the first line to look at |
| if (lines.length > index) { |
| l1 = lines[index]; |
| } else { |
| // if the first line doesn't exist, we are done and should |
| // return |
| return null; |
| } |
| |
| // get the next 3 lines |
| if (lines.length > index + 1) { |
| l2 = lines[index + 1]; |
| } |
| if (lines.length > index + 2) { |
| l3 = lines[index + 2]; |
| } |
| if (lines.length > index + 3) { |
| l4 = lines[index + 3]; |
| } |
| |
| // make sure that the first line is the start of an at |
| // if not, return null |
| if (l1.trim().matches(regexAtString)) { |
| charPos[0] += l1.length() + 2; |
| res = l1; |
| } else { |
| return null; |
| } |
| |
| // now determine where the end is if it wasn't on 1 line |
| if (!res.trim().matches(regexEndString)) { |
| |
| if (l2 != null && l2.trim().matches(regexEndString)) { |
| |
| // it was on the second line |
| // update the current position and the result string |
| i[0] = index + 1; |
| charPos[0] += l2.length() + 2; |
| res += l2.trim(); |
| } else if (l2 != null && l3 != null && l3.trim().matches(regexEndString)) { |
| |
| // it was on the third line |
| // update the current position and the result string |
| i[0] = index + 2; |
| charPos[0] += l2.length() + l3.length() + 4; |
| res += l2.trim(); |
| res += l3.trim(); |
| } else if (l2 != null && l3 != null && l4 != null && l4.trim().matches(regexEndString)) { |
| |
| // it was on the fourth line |
| // update the current position and the result string |
| i[0] = index + 3; |
| charPos[0] += l2.length() + l3.length() + l4.length() + 6; |
| res += l2.trim(); |
| res += l3.trim(); |
| res += l4.trim(); |
| } |
| } |
| |
| // return the result |
| return res; |
| } |
| |
| /** |
| * Get the StackTrace |
| * |
| * @param l |
| * the list of lines that contain the trace |
| * @param start |
| * the start of the stack trace |
| * @param offset |
| * the offset of the stack trace |
| * @param comment |
| * The comment that the stack trace came from |
| * @return The StackTrace for the given data |
| */ |
| private static StackTrace getStackTrace(List<String> l, int offset, int length, Object comment) { |
| String s = ""; //$NON-NLS-1$ |
| for (String s2 : l) { |
| s += s2 + "\r\n"; //$NON-NLS-1$ |
| } |
| |
| return new StackTrace(s, offset, length, comment); |
| } |
| |
| /** |
| * Convert a List StackTraces to a StackTrace[] <br> |
| * |
| * @param l |
| * The List of StackTraces |
| * @return StackTrace[] of the List |
| */ |
| private static StackTrace[] getTracesFromList(List<StackTrace> l) { |
| |
| // make sure that there is something to convert, else return null |
| if (l == null || l.size() == 0) { |
| return null; |
| } |
| |
| // convert the list of strings to an array of strings |
| int i = 0; |
| StackTrace[] s = new StackTrace[l.size()]; |
| |
| for (StackTrace st : l) { |
| s[i] = st; |
| i++; |
| } |
| |
| // return the string array |
| return s; |
| } |
| |
| /** |
| * Escape all of the special regex characters from the string |
| * |
| * @param s |
| * The string to escape the characters for |
| * @return A string with all of the special characters escaped <br> |
| * <code> |
| * . => \.<br> |
| * $ => \$<br> |
| * ? => \?<br> |
| * { => \{<br> |
| * } => \}<br> |
| * ( => \(<br> |
| * ) => \)<br> |
| * [ => \[<br> |
| * ] => \]<br> |
| * + => \+<br> |
| * * => \*<br> |
| * | => \|<br> |
| * ^ => \^<br> |
| * \ => \\<br> |
| * / => \/<br> |
| * </code> |
| */ |
| public static String escapeForRegex(String s) { |
| String sFixed = s; |
| |
| // replace all special regex characters |
| sFixed = sFixed.replaceAll("\\\\", "\\\\\\\\"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\$", "\\\\\\$"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\.", "\\\\."); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\?", "\\\\?"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\{", "\\\\{"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\}", "\\\\}"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\(", "\\\\("); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\)", "\\\\)"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\[", "\\\\["); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\]", "\\\\]"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\+", "\\\\+"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\*", "\\\\*"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\|", "\\\\|"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\^", "\\\\^"); //$NON-NLS-1$ //$NON-NLS-2$ |
| sFixed = sFixed.replaceAll("\\/", "\\\\/"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| return sFixed; |
| } |
| } |