| /* |
| * 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()]); |
| } |
| } |