/* * JBoss, Home of Professional Open Source * Copyright 2011, Red Hat, Inc., and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * Licensed 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.jboss.solder.exception.filter; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.List; import org.jboss.solder.exception.control.ExceptionStack; import org.jboss.solder.exception.control.ExceptionStackItem; import org.jboss.solder.exception.filter.StackFrame; import org.jboss.solder.exception.filter.StackFrameFilter; /** * This replaces the typical output of originalException stack traces. The stack is printed inverted of the standard * way, meaning the stack is unwrapped and the root cause is printed first followed by the next exception that wrapped * the root cause. This class is immutable. * <p/> * It may also make use of {@link StackFrameFilter} instances to filter the stack trace output. */ public class ExceptionStackOutput<T extends Throwable> { private final Deque<ExceptionStackItem> exceptionStackItems; private final StackFrameFilter<T> filter; // TODO: Really needs to be a properties file or something public static final String ROOT_CAUSE_TEXT = new StringBuilder("Root exception {0}").append(System.getProperty("line.separator")).toString(); public static final String AT_TEXT = new StringBuilder("\t at {0}").append(System.getProperty("line.separator")).toString(); public static final String WRAPPED_BY_TEXT = new StringBuilder("Wrapped within {0} and re-thrown").append(System.getProperty("line.separator")).toString(); /** * Constructor to be used if not filtering is desired. * * @param exception Exception containing stack to be displayed */ public ExceptionStackOutput(final T exception) { this(exception, null); } /** * Constructor which includes filtering * * @param exception Exception containing stack to be displayed * @param filter a {@link StackFrameFilter} instance used to do the filtering */ public ExceptionStackOutput(final T exception, final StackFrameFilter<T> filter) { this.exceptionStackItems = new ExceptionStack(exception).getOrigExceptionStackItems(); this.filter = filter; } /** * Prints the stack trace for this instance, using any current filters. * * @return stack trace in string representation */ public String printTrace() { final StringBuilder traceBuffer = new StringBuilder(); final int exceptionStackItemsSize = this.exceptionStackItems.size(); for (int i = 0; i < exceptionStackItemsSize; i++) { final ExceptionStackItem item = this.exceptionStackItems.removeFirst(); final ExceptionStackItem nextItem = this.exceptionStackItems.peekFirst(); if (i == 0) { traceBuffer.append(MessageFormat.format(ROOT_CAUSE_TEXT, item.getThrowable())); } else { traceBuffer.append(MessageFormat.format(WRAPPED_BY_TEXT, item.getThrowable())); } Collection<StackFrame> stackFrames; if (nextItem != null) { stackFrames = this.buildCollectionUpToNextWrapper(item.getThrowable(), nextItem.getThrowable()); } else { stackFrames = this.createStackFrameCollectionFrom(item.getThrowable()); } trace_loop: for (StackFrame stackFrame : stackFrames) { if (this.filter != null) { switch (this.filter.process(stackFrame)) { case TERMINATE_AFTER: traceBuffer.append(MessageFormat.format(AT_TEXT, stackFrame.getStackTraceElement())); case TERMINATE: case DROP_REMAINING: break trace_loop; case DROP: continue; default: traceBuffer.append(MessageFormat.format(AT_TEXT, stackFrame.getStackTraceElement())); } } else { traceBuffer.append(MessageFormat.format(AT_TEXT, stackFrame.getStackTraceElement())); } } } return traceBuffer.toString(); } private Collection<StackFrame> createStackFrameCollectionFrom(final Throwable throwable) { final List<StackFrame> frameList = new ArrayList<StackFrame>(throwable.getStackTrace().length); for (int i = 0; i < throwable.getStackTrace().length; i++) { if (i == 0) { frameList.add(new StackFrameImpl(throwable)); } else { frameList.add(new StackFrameImpl((StackFrameImpl) frameList.get(i - 1), throwable.getStackTrace()[i], i)); } } return frameList; } private Collection<StackFrame> buildCollectionUpToNextWrapper(final Throwable t1, final Throwable t2) { final List<StackFrame> returningCollection = new ArrayList<StackFrame>(); final StackTraceElement[] t1StackTraceElements = t1.getStackTrace(); final StackTraceElement[] t2StackTraceElements = t2.getStackTrace(); StackFrame previousFrame; int i; for (i = 0; i < t1StackTraceElements.length; i++) { if (i == 0) { final StackFrameImpl newFrame = new StackFrameImpl(t1); previousFrame = newFrame; if (!returningCollection.contains(newFrame)) { returningCollection.add(newFrame); } } else { final StackFrameImpl newFrame = new StackFrameImpl((StackFrameImpl) returningCollection.get(i - 1), t1StackTraceElements[i + 1], i); previousFrame = newFrame; if (!returningCollection.contains(newFrame)) { returningCollection.add(newFrame); } } for (StackTraceElement t2StackTraceElement : t2StackTraceElements) { if (t1StackTraceElements[i].equals(t2StackTraceElement)) { returningCollection.remove(previousFrame); return returningCollection; } } } return returningCollection; } }