001/* Base class for exceptions that report the names of Nameable objects.
002
003 Copyright (c) 1997-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.kernel.util;
029
030import java.io.PrintStream;
031import java.io.PrintWriter;
032import java.io.StringWriter;
033import java.util.Collection;
034import java.util.Iterator;
035
036///////////////////////////////////////////////////////////////////
037//// KernelException
038
039/**
040 Base class for Ptolemy exceptions.  This class extends the basic
041 JavaException with a constructor that can take a Nameable as
042 an argument.
043
044 (Note however, that it is better to use a class derived from
045 KernelException than it is to throw a KernelException directly.)
046
047 <p>JDK1.4 and later support exception chaining.  We are implementing a
048 version of exception chaining here ourselves so that we can use JVMs
049 earlier than JDK1.4.
050
051 <p>In this implementation, we have the following differences from
052 the JDK1.4 exception chaining implementation:
053 <menu>
054 <li>In this implementation, the detail message includes the detail
055 message from the cause argument.
056 <li>In this implementation, we implement a protected _setCause()
057 method, but not the public initCause() method that JDK1.4 has
058 </menu>
059
060
061 @see KernelRuntimeException
062 @author John S. Davis, II, Edward A. Lee, Christopher Hylands
063 @version $Id$
064 @since Ptolemy II 0.2
065 @Pt.ProposedRating Green (cxh)
066 @Pt.AcceptedRating Green (cxh)
067 */
068@SuppressWarnings("serial")
069public class KernelException extends Exception {
070    /** Construct an exception with a no specific detail message. */
071    public KernelException() {
072        this(null, null, null, null);
073    }
074
075    /** Construct an exception with a detail message that includes the
076     *  names of the first two arguments plus the third argument
077     *  string.  If one or more of the parameters are null, then the
078     *  message of the exception is adjusted accordingly.
079     *
080     *  @param object1 The first object.
081     *  @param object2 The second object.
082     *  @param detail The message.
083     */
084    public KernelException(Nameable object1, Nameable object2, String detail) {
085        this(object1, object2, null, detail);
086    }
087
088    /** Construct an exception with a detail message that includes the
089     *  names of the first two arguments plus the third argument
090     *  string.  If the cause argument is non-null, then the message
091     *  of this exception will include the message of the cause
092     *  argument.  The stack trace of the cause argument is used when
093     *  we print the stack trace of this exception.  If one or more of
094     *  the parameters are null, then the message of the exception is
095     *  adjusted accordingly.
096     *
097     *  @param object1 The first object.
098     *  @param object2 The second object.
099     *  @param cause The cause of this exception.
100     *  @param detail The message.
101     */
102    public KernelException(Nameable object1, Nameable object2, Throwable cause,
103            String detail) {
104        _object1 = object1;
105        _object2 = object2;
106        _setMessage(generateMessage(object1, object2, cause, detail));
107        _setCause(cause);
108    }
109
110    ///////////////////////////////////////////////////////////////////
111    ////                         public methods                    ////
112
113    /** Generate a properly formatted exception message.
114     *  If one or more of the parameters are null, then the message
115     *  of the exception is adjusted accordingly.  In particular, if
116     *  the first two arguments are non-null, then the exception
117     *  message may include the full names of the first two arguments.
118     *
119     *  <p>This method is public static so that both
120     *  KernelException and KernelRuntimeException and any classes
121     *  derived from those classes can use it.
122     *  KernelRuntimeException must extend RuntimeException so that
123     *  the java compiler will allow methods that throw
124     *  KernelRuntimeException to not declare that they throw it, and
125     *  KernelException cannot extend RuntimeException for the same
126     *  reason.
127     *
128     *  @param object1 The first object.
129     *  @param object2 The second object.
130     *  @param cause The cause of this exception.
131     *  @param detail The detail message.
132     *  @return A properly formatted message
133     */
134    public static String generateMessage(Nameable object1, Nameable object2,
135            Throwable cause, String detail) {
136        String object1String = getFullName(object1);
137        String object2String = getFullName(object2);
138        String whereString = null;
139
140        // KernelException.getFullName() returns the empty string if
141        // argument was null, and it returns "<Unnamed Object>" if the
142        // argument was the empty string, so if the return value of
143        // getFullName() is the empty string, then the argument was
144        // null, so we adjust accordingly.
145        if (!object1String.equals("")) {
146            if (!object2String.equals("")) {
147                whereString = "  in " + object1String + " and " + object2String;
148            } else {
149                whereString = "  in " + object1String;
150            }
151        } else {
152            if (!object2String.equals("")) {
153                whereString = "  in " + object2String;
154            }
155        }
156
157        return generateMessage(whereString, cause, detail);
158    }
159
160    /** Generate a properly formatted exception message where the
161     *  origin of the error is a collection.
162     *  <p>This method is public static so that both
163     *  KernelException and KernelRuntimeException and any classes
164     *  derived from those classes can use it.
165     *  KernelRuntimeException must extend RuntimeException so that
166     *  the java compiler will allow methods that throw
167     *  KernelRuntimeException to not declare that they throw it, and
168     *  KernelException cannot extend RuntimeException for the same
169     *  reason.
170     *  @param objects The where objects.
171     *  @param cause The cause of this exception.
172     *  @param detail The detail message.
173     *  @return A properly formatted message
174     */
175    public static String generateMessage(Collection objects, Throwable cause,
176            String detail) {
177        StringBuffer prefixBuffer = new StringBuffer("  in ");
178        Iterator objectIterator = objects.iterator();
179
180        while (objectIterator.hasNext()) {
181            Object object = objectIterator.next();
182
183            if (object instanceof Nameable) {
184                prefixBuffer
185                        .append(KernelException.getFullName((Nameable) object));
186            } else {
187                prefixBuffer.append("<Object of class "
188                        + object.getClass().getName() + ">");
189            }
190
191            if (objectIterator.hasNext()) {
192                prefixBuffer.append(", ");
193            }
194        }
195
196        return generateMessage(prefixBuffer.toString(), cause, detail);
197    }
198
199    /** Generate a properly formatted detail message.
200     *  If one or more of the parameters are null, then the message
201     *  of this exception is adjusted accordingly.
202     *
203     *  <p>This method is public static so that both
204     *  KernelException and KernelRuntimeException and any classes
205     *  derived from those classes can use it.
206     *  KernelRuntimeException must extend RuntimeException so that
207     *  the java compiler will allow methods that throw
208     *  KernelRuntimeException to not declare that they throw it, and
209     *  KernelException cannot extend RuntimeException for the same
210     *  reason.
211     *
212     *  @param whereString The string that identifies where the error occurred,
213     *   as in for example "in object: foo".
214     *  @param cause The cause of this exception.
215     *  @param detail The message.
216     *  @return A properly formatted message
217     */
218    public static String generateMessage(String whereString, Throwable cause,
219            String detail) {
220        // We need this method to support the constructors
221        // in InvalidStateException that take Enumerations and Lists.
222        // Using 'boolean ? if true : if false' is usually frowned
223        // upon, but in this case, the alternatives are a very large
224        // and complex if/else tree or else the creation of a bunch
225        // of temporary strings with a smaller if/else tree.
226        boolean whereNullOrEmpty = whereString == null
227                || whereString.equals("");
228        boolean detailNullOrEmpty = detail == null || detail.equals("");
229        return
230        // Do we print the detail?
231        (detailNullOrEmpty ? "" : detail)
232                // Do we add a \n?
233                + (!whereNullOrEmpty && !detailNullOrEmpty ? "\n" : "")
234                // Do we print the whereString?
235                + (whereNullOrEmpty ? "" : whereString)
236                // Do we add a \n?
237                + ((!whereNullOrEmpty || !detailNullOrEmpty) && cause != null
238                        ? "\n"
239                        : "")
240                // Do we print the cause?
241                + (cause == null ? ""
242                        : "Because:\n" + (cause.getMessage() != null
243                                ? cause.getMessage()
244                                : cause.toString()));
245    }
246
247    /** Get the cause of this exception.
248     *  @return The cause that was passed in as an argument to the
249     *  constructor, or null if no cause was specified.
250     */
251    @Override
252    public Throwable getCause() {
253        return _cause;
254    }
255
256    /** Get the name of a Nameable object.  This method uses
257     *  getName(), concatenating what it returns for each object in the
258     *  hierarchy, separated by periods.
259     *  If the argument is a null reference, return an empty string.
260     *  If the name of the argument or any of its containers is the
261     *  empty string, then that name is replaced with "&lt;Unnamed Object&gt;".
262     *  <p>
263     *  This method is public static so that both
264     *  KernelException and KernelRuntimeException and any classes
265     *  derived from those classes can use it.
266     *  KernelRuntimeException must extend RuntimeException so that
267     *  the java compiler will allow methods that throw
268     *  KernelRuntimeException to not declare that they throw it, and
269     *  KernelException cannot extend RuntimeException for the same
270     *  reason.
271     *
272     *  @param object An object with a full name.
273     *  @return The full name of the argument.
274     */
275    public static String getFullName(Nameable object) {
276        if (object == null) {
277            return "";
278        } else {
279            String name = getName(object);
280
281            // First, check for recursive containment by calling getFullName().
282            try {
283                object.getFullName();
284            } catch (InvalidStateException ex) {
285                // If we have recursive containment, just return the
286                // name of the immediate object.
287                return name;
288            }
289
290            // Without recursive containment, we can elaborate the name.
291            Nameable container = object.getContainer();
292
293            if (container == null) {
294                return "." + name;
295            }
296
297            while (container != null) {
298                name = getName(container) + "." + name;
299                container = container.getContainer();
300            }
301
302            name = "." + name;
303            return name;
304        }
305    }
306
307    /** Get the message of this exception.  The message may have been
308     *  adjusted if one of the constructor arguments was null, so the
309     *  value returned by this method may not necessarily equal the
310     *  value of the detail argument of of the constructor.
311     *
312     *  @return The error message.
313     */
314    @Override
315    public String getMessage() {
316        return _message;
317    }
318
319    /** Get the name of a Nameable object.
320     *  If the argument is a null reference, return an empty string.
321     *  If the name is the empty string, then we return
322     *  "&lt;Unnamed Object&gt;".
323     *
324     *  @param object An object with a name.
325     *  @return The name of the argument.
326     */
327    public static String getName(Nameable object) {
328        if (object == null) {
329            return "";
330        } else {
331            String name;
332            name = object.getName();
333
334            if (name == null || name.equals("")) {
335                // If NamedObj.setName() throws an exception, then the
336                // nameable object might be non-null, but the name might
337                // not yet have been set and getName() might return null.
338                // The tcl command:
339                // java::new ptolemy.kernel.util.NamedObj "This.name.has.dots"
340                // will trigger this sort of error.
341                return "<Unnamed Object>";
342            }
343
344            return name;
345        }
346    }
347
348    /** Get the first Nameable, if any, that was passed as an argument.
349     *  @return The first Nameable that was passed in.  If no Nameable
350     *  was passed in, then return null.
351     */
352    public Nameable getNameable1() {
353        return _object1;
354    }
355
356    /** Get the second Nameable, if any, that was passed as an argument.
357     *  @return The second Nameable that was passed in.  If no Nameable
358     *  was passed in, then return null.
359     */
360    public Nameable getNameable2() {
361        return _object2;
362    }
363
364    /** Print a stack trace message to stderr including
365     *  this exception, its stack trace and if the cause
366     *  exception is known, print the cause exception and the
367     *  cause stacktrace.
368     */
369    @Override
370    public void printStackTrace() {
371        // Note that chained exceptions are new JDK1.4.
372        // We are implement them ourselves here so that we can
373        // use JVMs earlier than JDK1.4.  The JDK1.4 Throwable.getCause()
374        // documentation states that it is not necessary to overwrite
375        // printStackTrace, but this is only the case when we have a JDK1.4
376        // JVM.
377        printStackTrace(new PrintWriter(System.err));
378    }
379
380    /** Print a stack trace message to printStream including this
381     *  exception, its stack trace and if the cause exception is
382     *  known, print the cause exception and the cause stacktrace.
383     *
384     *  @param printStream The PrintStream to write to.
385     */
386    @Override
387    public void printStackTrace(PrintStream printStream) {
388        printStackTrace(new PrintWriter(printStream));
389    }
390
391    /** Print a stack trace message to printWriter including this
392     *  exception, its stack trace and if the cause exception is
393     *  known, print the cause exception and the cause stacktrace.
394     *
395     *  @param printWriter The PrintWriter to write to.
396     */
397    @Override
398    public void printStackTrace(PrintWriter printWriter) {
399        super.printStackTrace(printWriter);
400
401        if (_cause != null) {
402            printWriter.print("Caused by: ");
403            _cause.printStackTrace(printWriter);
404        }
405
406        printWriter.flush();
407    }
408
409    /** Return the stack trace of the given argument as a String.
410     *  This method is useful if we are catching and rethrowing
411     *  a throwable that does not take a throwable cause argument.
412     *  For example, the XML parser exception does not take
413     *  a cause argument, so we call this method instead.
414     *  This method should be used instead of
415     *  Throwable.printStackTrace(), which prints the stack trace
416     *  to stderr, which is likely to be hidden if we are running
417     *  a Ptolemy application from anything but a shell console.
418     *  @param throwable A throwable.
419     *  @return The stack trace of the throwable.
420     */
421    public static String stackTraceToString(Throwable throwable) {
422        StringWriter stringWriter = new StringWriter();
423        PrintWriter printWriter = new PrintWriter(stringWriter);
424        if (throwable != null) {
425            throwable.printStackTrace(printWriter);
426        }
427        return stringWriter.toString();
428    }
429
430    ///////////////////////////////////////////////////////////////////
431    ////                         protected methods                 ////
432
433    /** Set the cause to the specified throwable.
434     *  @param cause The cause of this exception
435     */
436    protected void _setCause(Throwable cause) {
437        _cause = cause;
438    }
439
440    /** Sets the error message to the specified string.
441     *  If the message argument is null, then the error
442     *  message is set to the empty string.
443     *  @param message The message.
444     */
445    protected void _setMessage(String message) {
446        // We could try to remove _setMessage() and have all of the
447        // constructors call super(), which would eventually set
448        // the message in Exception, where Exception.getMessage()
449        // returns the right thing, but this results in having to
450        // create private static methods that properly formats the
451        // message for us in some of the derived classes like
452        // NameDuplicationException.  The resulting code is difficult
453        // to read, and forces developers to write classes in a stilted
454        // fashion, so we stick with _setMessage().
455        if (message == null) {
456            _message = "";
457        } else {
458            _message = message;
459        }
460    }
461
462    ///////////////////////////////////////////////////////////////////
463    ////                         private variables                 ////
464
465    // The detail message
466    private String _message;
467
468    // Jianwu Wang writes:
469    // "When implementing remote execution interfaces, such as
470    // java.rmi.Remote, IllegalActionException and KernelException can
471    // not be thrown since the _object1 and _object2 variables in
472    // ptolemy.kernel.util.KernelException may not be realizable"
473    // The fix is to make them transient.
474
475    // The first nameable, if any, that was passed in.
476    private transient Nameable _object1;
477
478    // The second nameable, if any, that was passed in.
479    private transient Nameable _object2;
480
481    // The cause of this exception.
482    private Throwable _cause;
483}