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 "<Unnamed Object>". 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 * "<Unnamed Object>". 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}