001: /*
002: * Created on 22-Aug-2003
003: */
004: package uk.org.ponder.util;
005:
006: import java.io.PrintStream;
007: import java.io.PrintWriter;
008: import java.lang.reflect.InvocationTargetException;
009: import java.util.ArrayList;
010:
011: import org.xml.sax.SAXException;
012:
013: import uk.org.ponder.stringutil.CharWrap;
014:
015: /**
016: * The root of unchecked runtime exceptions thrown by the libraries. There is a
017: * general movement to make most exceptions runtime exceptions,
018: * since exception specifications often add verbosity without facility.
019: * <p>
020: * Checked exceptions are most appropriate for signalling problems between
021: * libraries with a wide degree of separation. Within a single body of code,
022: * unchecked exceptions should be used to propagate error conditions to the next
023: * boundary.
024: *
025: * <a href="http://www.mindview.net/Etc/Discussions/CheckedExceptions/">Does Java need Checked Exceptions?</a>
026: * <p>
027: * This class has a useful (and growing) body of schemes for absorbing the
028: * target exceptions from other types of wrapping exceptions and rewrapping
029: * them.
030: * <p>
031: * What we wish to preserve is a) the ultimate stack trace from the cause of the
032: * problem and b) a set of increasingly detailed messages that can be accreted
033: * onto the exception as it winds up the stack.
034: * <p>The typical usage in client code looks as follows:
035: * <xmp>
036: * try {
037: * .... block generated checked or unchecked exceptions
038: * }
039: * catch (Exception e) {
040: * throw UniversalRuntimeException.accumulate(e, "What I learned in this catch block" );
041: * }
042: * </xmp>
043: * A UniversalRuntimeException also contains a Class representing its
044: * "category", a point in the inheritance hierarchy that may be used to classify
045: * the nature of exceptions, as being distinct from the wrapped target exception
046: * intended to record its cause. An object of the category need not ever be
047: * created, the inheritance hierachy may be queried via
048: * Class.isAssignableFrom().
049: * <p>
050: * The exception category defaults to the concrete class of the exception being
051: * wrapped.
052: *
053: * @author Bosmon
054: */
055: public class UniversalRuntimeException extends RuntimeException
056: implements WrappingException {
057: private static String[] unwrapclasses = new String[] { "uk.org.ponder.servletutil.ServletExceptionUnwrapper" };
058: private Throwable targetexception;
059: private String message;
060: private Class category;
061: private static ArrayList unwrappers = new ArrayList();
062: static {
063: for (int i = 0; i < unwrapclasses.length; ++i) {
064: try {
065: Class unwrapclass = Thread.currentThread()
066: .getContextClassLoader().loadClass(
067: unwrapclasses[i]);
068: ExceptionUnwrapper unwrapper = (ExceptionUnwrapper) unwrapclass
069: .newInstance();
070: if (unwrapper.isValid()) {
071: addUnwrapper(unwrapper);
072: }
073: } catch (Throwable t) {
074: Logger.log.debug("Couldn't load unwrapper "
075: + unwrapclasses[i]);
076: }
077: }
078: }
079:
080: public static synchronized void addUnwrapper(
081: ExceptionUnwrapper muncher) {
082: unwrappers.add(muncher);
083: }
084:
085: public UniversalRuntimeException(String s) {
086: message = s;
087: }
088:
089: public String getMessage() {
090: // fix for failure in log4j to tolerate null message.
091: return message == null ? "" : message;
092: }
093:
094: public Class getCategory() {
095: return category;
096: }
097:
098: public void setCategory(Class category) {
099: this .category = category;
100: }
101:
102: public UniversalRuntimeException(Throwable t) {
103: message = t.getMessage();
104: targetexception = t;
105: }
106:
107: public Throwable getTargetException() {
108: return targetexception;
109: }
110:
111: private static String computeMessage(String extradetail, String orig) {
112: CharWrap accumulate = new CharWrap();
113: accumulate.append(extradetail);
114:
115: if (orig != null && orig.length() > 0) {
116: accumulate.append("\n--> ").append(orig);
117: }
118: return accumulate.toString();
119: }
120:
121: /**
122: * Accumulates the message supplied message onto the beginning of any existing
123: * exception message, and wraps supplied exception as the target exception of
124: * the returned UniversalRuntimeException.
125: * <p>
126: * If the supplied exception is already a UniversalRuntimeException, the same
127: * object is returned.
128: * <p>
129: * If the supplied exception is a wrapping exception of one of the recognised
130: * kinds (InvocationTargetException, or if registered, ServletException &c),
131: * it is unwrapped and its target exception becomes the wrapped exception.
132: *
133: * @param t
134: * An encountered exception, to be wrapped.
135: * @param fullmsg
136: * The message to be added to the exceptions information.
137: * @return
138: */
139: public static UniversalRuntimeException accumulate(Throwable t,
140: String extradetail) {
141: String message = computeMessage(extradetail, t.getMessage());
142: UniversalRuntimeException togo = accumulateMsg(t, t.getClass(),
143: message);
144: return togo;
145: }
146:
147: /** Used to "pass-through" an exception leaving its message unchanged */
148: public static UniversalRuntimeException accumulate(Throwable t) {
149: UniversalRuntimeException togo = accumulateMsg(t, t.getClass(),
150: t.getMessage());
151: return togo;
152: }
153:
154: public static UniversalRuntimeException accumulate(Throwable t,
155: Class category, String extradetail) {
156: String message = computeMessage(extradetail, t.getMessage());
157: UniversalRuntimeException togo = accumulateMsg(t, category,
158: message);
159: return togo;
160: }
161:
162: private static Throwable unwrapExceptionInternal(Throwable tounwrap) {
163: if (tounwrap instanceof InvocationTargetException) {
164: return ((InvocationTargetException) tounwrap)
165: .getTargetException();
166: }
167: if (tounwrap instanceof SAXException) {
168: return ((SAXException) tounwrap).getException();
169: }
170: for (int i = 0; i < unwrappers.size(); ++i) {
171: Throwable unwrapped = ((ExceptionUnwrapper) unwrappers
172: .get(i)).unwrapException(tounwrap);
173: if (unwrapped != null) {
174: return unwrapped;
175: }
176: }
177: return null;
178: }
179:
180: public static Throwable unwrapException(Throwable tounwrap) {
181: if (tounwrap instanceof UniversalRuntimeException) {
182: return ((UniversalRuntimeException) tounwrap)
183: .getTargetException();
184: } else {
185: Throwable unwrapped = unwrapExceptionInternal(tounwrap);
186: return unwrapped == null ? tounwrap : unwrapped;
187: }
188: }
189:
190: // Problem: URE(ITE(URE)) will lose message.
191: // SAXException eg RETAINS inner message, ITE wrapping layer
192: // discards it. This suggests that unwrapException needs to be somehow
193: // stateful in that it signals when it unwraps, whether a *NEW* message
194: // might have been uncovered. The hack below is no good.
195:
196: public static UniversalRuntimeException accumulateMsg(Throwable t,
197: Class category, String fullmsg) {
198: Throwable tounwrap;
199: do {
200: tounwrap = t;
201: t = unwrapExceptionInternal(tounwrap);
202: } while (t != null);
203: t = tounwrap;
204:
205: UniversalRuntimeException togo = null;
206: if (t instanceof UniversalRuntimeException) {
207: togo = (UniversalRuntimeException) t;
208: } else {
209: togo = new UniversalRuntimeException(t);
210: togo.category = category;
211: }
212: // InvokationTargetException wrapping randomly trashes the message. Guard
213: // against this with this generally well-motivated hack:
214: if (fullmsg != null && !fullmsg.equals("")) {
215: togo.message = fullmsg;
216: }
217: return togo;
218: }
219:
220: public String getStackHead() {
221: CharWrap togo = new CharWrap();
222: if (targetexception != null) {
223: togo.append("Target exception of "
224: + targetexception.getClass());
225: } else if (category != null) {
226: togo.append("Exception category " + category);
227:
228: }
229: togo
230: .append(
231: "\nSuccessive lines until stack trace show causes progressing to exception site:\n")
232: .append(getMessage());
233: return togo.toString();
234: }
235:
236: // QQQQQ move these three methods to static utility for all WrappingExceptions
237: public void printStackTrace() {
238: if (targetexception != null) {
239: System.err.println(getStackHead());
240: targetexception.printStackTrace();
241: } else
242: super .printStackTrace();
243: }
244:
245: public void printStackTrace(PrintWriter pw) {
246: if (targetexception != null) {
247: pw.println(getStackHead());
248: targetexception.printStackTrace(pw);
249: } else
250: super .printStackTrace(pw);
251: }
252:
253: public void printStackTrace(PrintStream ps) {
254: if (targetexception != null) {
255: ps.println(getStackHead());
256: targetexception.printStackTrace(ps);
257: } else
258: super .printStackTrace(ps);
259: }
260:
261: public StackTraceElement[] getStackTrace() {
262: return targetexception != null ? targetexception
263: .getStackTrace() : super.getStackTrace();
264: }
265: }
|