blob: 5c60e9f9c66c9f7fb9031a28a21f57c7c8a2d945 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.junit.remote.testrunner;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RemoteExecutionException extends RuntimeException {
private final String trace;
private final StackTraceElement[] stackTrace;
/**
* Matches on lines in the format {@code at package.class.method(source.java:123)} with 4 (1,2,3,4) or
* {@code at package.class.method(Native method)} with 3 different groups (1,2,5)
*/
private static final Pattern TRACE_PATTERN = Pattern
.compile("\\s*at\\s+([\\w\\.$_]+)\\.([\\w$_]+)(?:\\((.*\\.java):(\\d+)\\)|(\\(.*\\)))");
/**
* Matches on lines in the format {@code Caused by: java.io.IOException: Some message} with 1 group containing the
* part after the first colon
*/
private static final Pattern CAUSED_BY_PATTERN = Pattern.compile("\\s*Caused by:\\s+(.*)");
private static final int NATIVE_METHOD_LINE_NUMBER = -2;
public static RemoteExecutionException getExceptionFromTrace(String trace) throws IOException {
// first line of trace is something like "java.lang.RuntimeException: Wrapper exception"
BufferedReader reader = new BufferedReader(new StringReader(trace));
final String firstLine;
try {
firstLine = reader.readLine();
} finally {
reader.close();
}
return new RemoteExecutionException(firstLine, trace);
}
public RemoteExecutionException(String failure, String trace) throws NumberFormatException, IOException {
super(failure);
this.trace = trace;
this.stackTrace = getStackTraceFromString(trace);
}
@Override
public void printStackTrace(PrintStream s) {
if (trace != null) {
s.print(trace);
}
}
@Override
public void printStackTrace(PrintWriter s) {
if (trace != null) {
s.print(trace);
}
}
@Override
public StackTraceElement[] getStackTrace() {
return stackTrace;
}
/**
* Returns all StackTraceElement created from the given String. Also evaluates the cause of an exception by setting {@link #initCause(Throwable)}.
* Example format for given trace:
*
* <pre>
* {@code
* java.lang.RuntimeException: Wrapper exception
* at org.apache.sling.junit.remote.testrunner.RemoteExecutionExceptionTest.testGetNestedStackTraceFromString(RemoteExecutionExceptionTest.java:55)
* at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
* at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
* at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
* at java.lang.reflect.Method.invoke(Method.java:606)
* ...
* Caused by: java.lang.IllegalStateException: Some message
* at org.apache.sling.junit.remote.testrunner.RemoteExecutionExceptionTest.testGetNestedStackTraceFromString(RemoteExecutionExceptionTest.java:53)
* ... 23 more
* }
* </pre>
*
* @param trace a serialized stack trace.
* @return an array of {@link StackTraceElement}s.
* @throws IOException
* @throws NumberFormatException
*/
private final StackTraceElement[] getStackTraceFromString(String trace) throws NumberFormatException, IOException {
if (trace == null) {
return new StackTraceElement[0];
}
List<StackTraceElement> stackTraceElements = new ArrayList<StackTraceElement>();
BufferedReader reader = new BufferedReader(new StringReader(trace));
try {
String line = null;
while ((line = reader.readLine()) != null) {
Matcher traceMatcher = TRACE_PATTERN.matcher(line);
if (traceMatcher.find()) {
String className = traceMatcher.group(1);
String methodName = traceMatcher.group(2);
String sourceFile;
int lineNumber;
if (traceMatcher.group(3) != null) {
// java file with line number
sourceFile = traceMatcher.group(3);
if (traceMatcher.group(4) != null) {
lineNumber = Integer.parseInt(traceMatcher.group(4));
} else {
lineNumber = -1;
}
} else {
// probably a native method
sourceFile = traceMatcher.group(5);
lineNumber = NATIVE_METHOD_LINE_NUMBER;
}
// null checks
stackTraceElements.add(new StackTraceElement(className, methodName, sourceFile, lineNumber));
}
// is this a caused by
Matcher causedByMatcher = CAUSED_BY_PATTERN.matcher(line);
if (causedByMatcher.find()) {
// all remaining lines of the trace should be given to the wrapped exception
char[] cbuf = new char[trace.length()];
if (reader.read(cbuf) > 0) {
this.initCause(new RemoteExecutionException(causedByMatcher.group(1), new String(cbuf)));
}
}
}
} finally {
reader.close();
}
return stackTraceElements.toArray(new StackTraceElement[stackTraceElements.size()]);
}
}