1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: DTMException.java 468653 2006-10-28 07:07:05Z minchau $
20 */
21package org.apache.xml.dtm;
22
23
24import java.lang.reflect.InvocationTargetException;
25import java.lang.reflect.Method;
26
27import javax.xml.transform.SourceLocator;
28
29import org.apache.xml.res.XMLErrorResources;
30import org.apache.xml.res.XMLMessages;
31
32
33/**
34 * This class specifies an exceptional condition that occured
35 * in the DTM module.
36 */
37public class DTMException extends RuntimeException {
38    static final long serialVersionUID = -775576419181334734L;
39
40    /** Field locator specifies where the error occured.
41     *  @serial */
42    SourceLocator locator;
43
44    /**
45     * Method getLocator retrieves an instance of a SourceLocator
46     * object that specifies where an error occured.
47     *
48     * @return A SourceLocator object, or null if none was specified.
49     */
50    public SourceLocator getLocator() {
51        return locator;
52    }
53
54    /**
55     * Method setLocator sets an instance of a SourceLocator
56     * object that specifies where an error occured.
57     *
58     * @param location A SourceLocator object, or null to clear the location.
59     */
60    public void setLocator(SourceLocator location) {
61        locator = location;
62    }
63
64    /** Field containedException specifies a wrapped exception.  May be null.
65     *  @serial */
66    Throwable containedException;
67
68    /**
69     * This method retrieves an exception that this exception wraps.
70     *
71     * @return An Throwable object, or null.
72     * @see #getCause
73     */
74    public Throwable getException() {
75        return containedException;
76    }
77
78    /**
79     * Returns the cause of this throwable or <code>null</code> if the
80     * cause is nonexistent or unknown.  (The cause is the throwable that
81     * caused this throwable to get thrown.)
82     */
83    public Throwable getCause() {
84
85        return ((containedException == this)
86                ? null
87                : containedException);
88    }
89
90    /**
91     * Initializes the <i>cause</i> of this throwable to the specified value.
92     * (The cause is the throwable that caused this throwable to get thrown.)
93     *
94     * <p>This method can be called at most once.  It is generally called from
95     * within the constructor, or immediately after creating the
96     * throwable.  If this throwable was created
97     * with {@link #DTMException(Throwable)} or
98     * {@link #DTMException(String,Throwable)}, this method cannot be called
99     * even once.
100     *
101     * @param  cause the cause (which is saved for later retrieval by the
102     *         {@link #getCause()} method).  (A <tt>null</tt> value is
103     *         permitted, and indicates that the cause is nonexistent or
104     *         unknown.)
105     * @return  a reference to this <code>Throwable</code> instance.
106     * @throws IllegalArgumentException if <code>cause</code> is this
107     *         throwable.  (A throwable cannot
108     *         be its own cause.)
109     * @throws IllegalStateException if this throwable was
110     *         created with {@link #DTMException(Throwable)} or
111     *         {@link #DTMException(String,Throwable)}, or this method has already
112     *         been called on this throwable.
113     */
114    public synchronized Throwable initCause(Throwable cause) {
115
116        if ((this.containedException == null) && (cause != null)) {
117            throw new IllegalStateException(XMLMessages.createXMLMessage(XMLErrorResources.ER_CANNOT_OVERWRITE_CAUSE, null)); //"Can't overwrite cause");
118        }
119
120        if (cause == this) {
121            throw new IllegalArgumentException(
122                XMLMessages.createXMLMessage(XMLErrorResources.ER_SELF_CAUSATION_NOT_PERMITTED, null)); //"Self-causation not permitted");
123        }
124
125        this.containedException = cause;
126
127        return this;
128    }
129
130    /**
131     * Create a new DTMException.
132     *
133     * @param message The error or warning message.
134     */
135    public DTMException(String message) {
136
137        super(message);
138
139        this.containedException = null;
140        this.locator            = null;
141    }
142
143    /**
144     * Create a new DTMException wrapping an existing exception.
145     *
146     * @param e The exception to be wrapped.
147     */
148    public DTMException(Throwable e) {
149
150        super(e.getMessage());
151
152        this.containedException = e;
153        this.locator            = null;
154    }
155
156    /**
157     * Wrap an existing exception in a DTMException.
158     *
159     * <p>This is used for throwing processor exceptions before
160     * the processing has started.</p>
161     *
162     * @param message The error or warning message, or null to
163     *                use the message from the embedded exception.
164     * @param e Any exception
165     */
166    public DTMException(String message, Throwable e) {
167
168        super(((message == null) || (message.length() == 0))
169              ? e.getMessage()
170              : message);
171
172        this.containedException = e;
173        this.locator            = null;
174    }
175
176    /**
177     * Create a new DTMException from a message and a Locator.
178     *
179     * <p>This constructor is especially useful when an application is
180     * creating its own exception from within a DocumentHandler
181     * callback.</p>
182     *
183     * @param message The error or warning message.
184     * @param locator The locator object for the error or warning.
185     */
186    public DTMException(String message, SourceLocator locator) {
187
188        super(message);
189
190        this.containedException = null;
191        this.locator            = locator;
192    }
193
194    /**
195     * Wrap an existing exception in a DTMException.
196     *
197     * @param message The error or warning message, or null to
198     *                use the message from the embedded exception.
199     * @param locator The locator object for the error or warning.
200     * @param e Any exception
201     */
202    public DTMException(String message, SourceLocator locator,
203                                Throwable e) {
204
205        super(message);
206
207        this.containedException = e;
208        this.locator            = locator;
209    }
210
211    /**
212     * Get the error message with location information
213     * appended.
214     */
215    public String getMessageAndLocation() {
216
217        StringBuffer sbuffer = new StringBuffer();
218        String       message = super.getMessage();
219
220        if (null != message) {
221            sbuffer.append(message);
222        }
223
224        if (null != locator) {
225            String systemID = locator.getSystemId();
226            int    line     = locator.getLineNumber();
227            int    column   = locator.getColumnNumber();
228
229            if (null != systemID) {
230                sbuffer.append("; SystemID: ");
231                sbuffer.append(systemID);
232            }
233
234            if (0 != line) {
235                sbuffer.append("; Line#: ");
236                sbuffer.append(line);
237            }
238
239            if (0 != column) {
240                sbuffer.append("; Column#: ");
241                sbuffer.append(column);
242            }
243        }
244
245        return sbuffer.toString();
246    }
247
248    /**
249     * Get the location information as a string.
250     *
251     * @return A string with location info, or null
252     * if there is no location information.
253     */
254    public String getLocationAsString() {
255
256        if (null != locator) {
257            StringBuffer sbuffer  = new StringBuffer();
258            String       systemID = locator.getSystemId();
259            int          line     = locator.getLineNumber();
260            int          column   = locator.getColumnNumber();
261
262            if (null != systemID) {
263                sbuffer.append("; SystemID: ");
264                sbuffer.append(systemID);
265            }
266
267            if (0 != line) {
268                sbuffer.append("; Line#: ");
269                sbuffer.append(line);
270            }
271
272            if (0 != column) {
273                sbuffer.append("; Column#: ");
274                sbuffer.append(column);
275            }
276
277            return sbuffer.toString();
278        } else {
279            return null;
280        }
281    }
282
283    /**
284     * Print the the trace of methods from where the error
285     * originated.  This will trace all nested exception
286     * objects, as well as this object.
287     */
288    public void printStackTrace() {
289        printStackTrace(new java.io.PrintWriter(System.err, true));
290    }
291
292    /**
293     * Print the the trace of methods from where the error
294     * originated.  This will trace all nested exception
295     * objects, as well as this object.
296     * @param s The stream where the dump will be sent to.
297     */
298    public void printStackTrace(java.io.PrintStream s) {
299        printStackTrace(new java.io.PrintWriter(s));
300    }
301
302    /**
303     * Print the the trace of methods from where the error
304     * originated.  This will trace all nested exception
305     * objects, as well as this object.
306     * @param s The writer where the dump will be sent to.
307     */
308    public void printStackTrace(java.io.PrintWriter s) {
309
310        if (s == null) {
311            s = new java.io.PrintWriter(System.err, true);
312        }
313
314        try {
315            String locInfo = getLocationAsString();
316
317            if (null != locInfo) {
318                s.println(locInfo);
319            }
320
321            super.printStackTrace(s);
322        } catch (Throwable e) {}
323
324        boolean isJdk14OrHigher = false;
325        try {
326            Throwable.class.getMethod("getCause", (Class<?>) null);
327            isJdk14OrHigher = true;
328        } catch (NoSuchMethodException nsme) {
329            // do nothing
330        }
331
332        // The printStackTrace method of the Throwable class in jdk 1.4
333        // and higher will include the cause when printing the backtrace.
334        // The following code is only required when using jdk 1.3 or lower
335        if (!isJdk14OrHigher) {
336            Throwable exception = getException();
337
338            for (int i = 0; (i < 10) && (null != exception); i++) {
339                s.println("---------");
340
341                try {
342                    if (exception instanceof DTMException) {
343                        String locInfo =
344                            ((DTMException) exception)
345                                .getLocationAsString();
346
347                        if (null != locInfo) {
348                            s.println(locInfo);
349                        }
350                    }
351
352                    exception.printStackTrace(s);
353                } catch (Throwable e) {
354                    s.println("Could not print stack trace...");
355                }
356
357                try {
358                    Method meth =
359                        ((Object) exception).getClass().getMethod("getException",
360                            (Class<?>) null);
361
362                    if (null != meth) {
363                        Throwable prev = exception;
364
365                        exception = (Throwable) meth.invoke(exception, (Class<?>) null);
366
367                        if (prev == exception) {
368                            break;
369                        }
370                    } else {
371                        exception = null;
372                    }
373                } catch (InvocationTargetException ite) {
374                    exception = null;
375                } catch (IllegalAccessException iae) {
376                    exception = null;
377                } catch (NoSuchMethodException nsme) {
378                    exception = null;
379                }
380            }
381        }
382    }
383}
384