001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.util.location;
018:
019: import java.lang.reflect.Method;
020: import java.util.ArrayList;
021: import java.util.Collections;
022: import java.util.List;
023:
024: import org.apache.commons.lang.exception.ExceptionUtils;
025: import org.apache.commons.lang.exception.NestableException;
026:
027: /**
028: * A cascading and located <code>Exception</code>. It is also {@link MultiLocatable} to easily build
029: * stack traces.
030: *
031: * @since 2.1.8
032: * @version $Id: LocatedException.java 446917 2006-09-16 19:10:40Z vgritsenko $
033: */
034: public class LocatedException extends NestableException implements
035: LocatableException, MultiLocatable {
036:
037: private List locations;
038:
039: public LocatedException(String message) {
040: this (message, null, null);
041: }
042:
043: public LocatedException(String message, Throwable cause) {
044: this (message, cause, null);
045: }
046:
047: public LocatedException(String message, Location location) {
048: this (message, null, location);
049: }
050:
051: public LocatedException(String message, Throwable cause,
052: Location location) {
053: super (message, cause);
054: ensureCauseChainIsSet(cause);
055: addCauseLocations(this , cause);
056: addLocation(location);
057: }
058:
059: private static Method INIT_CAUSE_METHOD = null;
060: static {
061: try {
062: INIT_CAUSE_METHOD = Throwable.class.getMethod("initCause",
063: new Class[] { Throwable.class });
064: } catch (Exception e) {
065: // JDK < 1.4: ignore
066: }
067: }
068:
069: /**
070: * Crawl the cause chain and ensure causes are properly set using "initCause" on JDK >= 1.4.
071: * This is needed because some exceptions (e.g. SAXException) don't have a getCause() that is
072: * used to print stacktraces.
073: */
074: public static void ensureCauseChainIsSet(Throwable thr) {
075: if (INIT_CAUSE_METHOD == null) {
076: return;
077: }
078:
079: // Loop either until null or encountering exceptions that use this method.
080: while (thr != null && !(thr instanceof LocatedRuntimeException)
081: && !(thr instanceof LocatedException)) {
082: Throwable parent = ExceptionUtils.getCause(thr);
083: if (parent != null) {
084: try {
085: INIT_CAUSE_METHOD.invoke(thr,
086: new Object[] { parent });
087: } catch (Exception e) {
088: // can happen if parent already set on exception
089: }
090: }
091: thr = parent;
092: }
093: }
094:
095: /**
096: * Add to the location stack all locations of an exception chain. This allows to have all possible
097: * location information in the stacktrace, as some exceptions like SAXParseException don't output
098: * their location in printStackTrace().
099: * <p>
100: * Traversal of the call chain stops at the first <code>Locatable</code> exception which is supposed
101: * to handle the loction of its causes by itself.
102: * <p>
103: * This method is static as a convenience for {@link LocatedRuntimeException other implementations}
104: * of locatable exceptions.
105: *
106: * @param self the current locatable exception
107: * @param cause a cause of <code>self</code>
108: */
109: public static void addCauseLocations(MultiLocatable self,
110: Throwable cause) {
111: if (cause == null || cause instanceof Locatable) {
112: // Locatable handles its location itself
113: return;
114: }
115:
116: // Add parent location first
117: addCauseLocations(self, ExceptionUtils.getCause(cause));
118: // then ourselve's
119: Location loc = LocationUtils.getLocation(cause);
120: if (LocationUtils.isKnown(loc)) {
121: // Get the exception's short name
122: String name = cause.getClass().getName();
123: int pos = name.lastIndexOf('.');
124: if (pos != -1) {
125: name = name.substring(pos + 1);
126: }
127: loc = new LocationImpl("[" + name + "]", loc.getURI(), loc
128: .getLineNumber(), loc.getColumnNumber());
129: self.addLocation(loc);
130: }
131: }
132:
133: public Location getLocation() {
134: return locations == null ? null : (Location) locations.get(0);
135: }
136:
137: public List getLocations() {
138: return locations == null ? Collections.EMPTY_LIST : locations;
139: }
140:
141: public String getRawMessage() {
142: return super .getMessage();
143: }
144:
145: /**
146: * Standard way of building the message of a {@link LocatableException}, as a Java-like
147: * stack trace of locations.
148: *
149: * @param message the exception's message, given by <code>super.getMessage()</code> (can be null)
150: * @param locations the location list (can be null)
151: *
152: * @return the message, or <code>null</code> no message and locations were given.
153: */
154: public static String getMessage(String message, List locations) {
155: if (locations == null || locations.isEmpty()) {
156: return message;
157: }
158:
159: // Produce a Java-like stacktrace with locations
160: StringBuffer buf = message == null ? new StringBuffer()
161: : new StringBuffer(message);
162: for (int i = 0; i < locations.size(); i++) {
163: buf.append("\n\tat ")
164: .append(
165: LocationUtils.toString((Location) locations
166: .get(i)));
167: }
168: return buf.toString();
169: }
170:
171: public String getMessage() {
172: return getMessage(super .getMessage(), locations);
173: }
174:
175: public void addLocation(Location loc) {
176: if (LocationUtils.isUnknown(loc)) {
177: return;
178: }
179:
180: if (locations == null) {
181: this .locations = new ArrayList(1); // Start small
182: }
183: locations.add(LocationImpl.get(loc));
184: }
185: }
|