001/* Base class for displaying exceptions, warnings, and messages.
002
003 Copyright (c) 2003-2016 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 */
027package ptolemy.util;
028
029import java.io.BufferedReader;
030import java.io.IOException;
031import java.io.InputStreamReader;
032import java.lang.ref.WeakReference;
033import java.util.Locale;
034
035///////////////////////////////////////////////////////////////////
036//// MessageHandler
037
038/**
039 This is a class that is used to report errors.  It provides a
040 set of static methods that are called to report errors.  However, the
041 actual reporting of the errors is deferred to an instance of this class
042 that is set using the setMessageHandler() method.  Normally there
043 is only one instance, set up by the application, so the class is
044 a singleton.  But this is not enforced.
045 <p>
046 This base class simply writes the errors to System.err.
047 When an applet or application starts up, it may wish to set a subclass
048 of this class as the message handler, to allow a nicer way of
049 reporting errors.  For example, a Swing application will probably
050 want to report errors in a dialog box, using for example
051 the derived class GraphicalMessageHandler.
052
053 <p>See ptolemy.gui.GraphicalMessageHandler</p>
054
055 @author  Edward A. Lee, Steve Neuendorffer, Elaine Cheong
056 @version $Id$
057 @since Ptolemy II 4.0
058 @Pt.ProposedRating Green (cxh)
059 @Pt.AcceptedRating Green (cxh)
060 */
061public class MessageHandler implements Thread.UncaughtExceptionHandler {
062
063    /** Create a MessageHandler.
064     */
065    public MessageHandler() {
066        // Note that kepler/loader/src/org/kepler/ExecutionEngine.java
067        // invokes new MessageHandler().
068        Thread.setDefaultUncaughtExceptionHandler(this);
069    }
070
071    ///////////////////////////////////////////////////////////////////
072    ////                         public methods                    ////
073
074    /** Defer to the set message handler to show the specified
075     *  error message.
076     *
077     *  <p>Note that within Ptolemy, most user code should not call
078     *  this method directly.  Instead, throw an exception, which will
079     *  be caught by the system elsewhere and include information
080     *  about what object caused the error.
081     *  @param info The message.
082     */
083    public static void error(String info) {
084        _handler._error(info);
085    }
086
087    /** Defer to the set message handler to
088     *  show the specified message and throwable information.
089     *  If the throwable is an instance of CancelException, then it
090     *  is not shown.  By default, only the message of the throwable
091     *  is thrown.  The stack trace information is only shown if the
092     *  user clicks on the "Display Stack Trace" button.
093     *
094     *  <p>Note that within Ptolemy, most user code should not call
095     *  this method directly.  Instead, throw an exception, which will
096     *  be caught by the system elsewhere and include information
097     *  about what object caused the error.
098     *  @param info The message.
099     *  @param throwable The throwable.
100     *  @see CancelException
101     */
102    public static void error(String info, Throwable throwable) {
103        // Sometimes you find that errors are reported multiple times.
104        // To find out who is calling this method, uncomment the following.
105        // System.out.println("------ reporting error:" + throwable);
106        // throwable.printStackTrace();
107        // System.out.println("------ called from:");
108        // (new Exception()).printStackTrace();
109        try {
110            _handler._error(info, throwable);
111        } catch (Throwable throwable2) {
112            // An applet was throwing an exception while handling
113            // the error - so we print the original message if _error() fails.
114            if (_handler instanceof SimpleMessageHandler) {
115                throw new RuntimeException(throwable);
116            } else {
117                System.err.println("Internal Error, exception thrown while "
118                        + "handling error: \"" + info + "\"\n");
119                throwable.printStackTrace();
120                System.err.println("Internal Error:\n");
121                throwable2.printStackTrace();
122            }
123        }
124    }
125
126    /** Return the message handler instance that is used by the static
127     *  methods in this class.
128     *  @return The message handler.
129     *  @see #setMessageHandler(MessageHandler)
130     */
131    public static MessageHandler getMessageHandler() {
132        return _handler;
133    }
134
135    /** Return true if the current process is a non-interactive session.
136     *  If the nightly build is running, then return true.
137     *
138     *  <p>This method merely checks to see if the
139     *  "ptolemy.ptII.isRunningNightlyBuild" property exists and is not empty
140     *  or if the "ptolemy.ptII.batchMode" property exists and is not empty
141     *  and the property "ptolemyII.ptII.testingMessageHandler" is not set.</p>
142     *
143     *  <p>To run the test suite in the Nightly Build mode, use</p>
144     *  <pre>
145     *  make nightly
146     *  </pre>
147     *  @return True if the nightly build is running.
148     */
149    public static boolean isNonInteractive() {
150        // This method is necessary because Ptolemy can download models
151        // and files from websites. It is a best practice to prompt the user
152        // and ask them if they actually want to download the resource.
153        // However, code like the nightly build and the actor indexing code
154        // should run without user interaction, so we set a property
155        // when running non-interactive, batch mode code.
156        if ((StringUtilities.getProperty("ptolemy.ptII.isRunningNightlyBuild")
157                .length() > 0
158                || StringUtilities.getProperty("ptolemy.ptII.batchMode")
159                        .length() > 0)
160                && StringUtilities
161                        .getProperty("ptolemy.ptII.testingMessageHandler")
162                        .length() == 0) {
163            return true;
164        }
165
166        return false;
167    }
168
169    /** Defer to the set message handler to show the specified
170     *  message.  An implementation may block, for example with a modal dialog.
171     *  @param info The message.
172     *  @see #status(String)
173     */
174    public static void message(String info) {
175        _handler._message(info);
176    }
177
178    /** Set the message handler instance that is used by the static
179     *  methods in this class.  If the given handler is null, then
180     *  do nothing.
181     *  @param handler The message handler.
182     *  @see #getMessageHandler()
183     */
184    public static void setMessageHandler(MessageHandler handler) {
185        if (handler != null) {
186            _handler = handler;
187        }
188    }
189
190    /** Set the specified status handler, replacing any previously
191     *  set handler.
192     *  @param handler The handler, or null to set no handler.
193     *  @see #status(String)
194     */
195    public static void setStatusHandler(StatusHandler handler) {
196        _statusHandler = new WeakReference<StatusHandler>(handler);
197    }
198
199    /** Return a short description of the throwable.
200     *  @param throwable The throwable
201     *  @return If the throwable is an Exception, return "Exception",
202     *  if it is an Error, return "Error", if it is a Throwable, return
203     *  "Throwable".
204     */
205    public static String shortDescription(Throwable throwable) {
206        String throwableType = null;
207
208        if (throwable instanceof Exception) {
209            throwableType = "Exception";
210        } else if (throwable instanceof Error) {
211            throwableType = "Error";
212        } else {
213            throwableType = "Throwable";
214        }
215
216        return throwableType;
217    }
218
219    /** Display a status message to the user.
220     *  This method is intended for keeping users informed of what is being done.
221     *  The message may be displayed for a very short time and may be cleared after some time.
222     *  This method is not intended for logging or for persistent messages, nor for messages
223     *  that require some acknowledgement from the user.
224     *  If a StatusHandler has been registered using #addStatusHandler(StatusHandler),
225     *  then delegate displaying the message to that status handler.
226     *  Otherwise, display on standard out.
227     *  @param message The message to display.
228     *  @see #message(String)
229     */
230    public static void status(String message) {
231        if (_statusHandler != null && _statusHandler.get() != null) {
232            _statusHandler.get().status(message);
233        } else {
234            System.out.println(message);
235        }
236    }
237
238    /** Handle uncaught exceptions in a standard way.
239     *  @param thread The thread throwing the exception.
240     *  @param exception The exception.
241     */
242    @Override
243    public void uncaughtException(Thread thread, Throwable exception) {
244        _error("UNCAUGHT EXCEPTION: " + exception.getMessage(), exception);
245    }
246
247    /** Defer to the set message handler to
248     *  show the specified message in a modal dialog.  If the user
249     *  clicks on the "Cancel" button, then throw an exception.
250     *  This gives the user the option of not continuing the
251     *  execution, something that is particularly useful if continuing
252     *  execution will result in repeated warnings.
253     *
254     *  <p>Note that within Ptolemy, most user code should not call
255     *  this method directly.  Instead, throw an exception, which will
256     *  be caught by the system elsewhere and include information
257     *  about what object caused the warning.
258     *
259     *  @param info The message.
260     *  @exception CancelException If the user clicks on the "Cancel" button.
261     */
262    public static void warning(String info) throws CancelException {
263        _handler._warning(info);
264    }
265
266    /** Show the specified message and throwable information
267     *  in a modal dialog.  If the user
268     *  clicks on the "Cancel" button, then throw an exception.
269     *  This gives the user the option of not continuing the
270     *  execution, something that is particularly useful if continuing
271     *  execution will result in repeated warnings.
272     *  By default, only the message of the throwable
273     *  is thrown.  The stack trace information is only shown if the
274     *  user clicks on the "Display Stack Trace" button.
275     *
276     *  <p>Note that within Ptolemy, most user code should not call
277     *  this method directly.  Instead, throw an exception, which will
278     *  be caught by the system elsewhere and include information
279     *  about what object caused the warning.
280     *
281     *  @param info The message.
282     *  @param throwable The throwable associated with this warning.
283     *  @exception CancelException If the user clicks on the "Cancel" button.
284     */
285    public static void warning(String info, Throwable throwable)
286            throws CancelException {
287        _handler._warning(info + ": " + throwable.getMessage(), throwable);
288    }
289
290    /** Ask the user a yes/no question, and return true if the answer
291     *  is yes. This method returns true without asking the user if
292     *  the property "ptolemy.ptII.isRunningNightlyBuild" is set.
293     *  In the regression tests, there is no user to answer the question.
294     *  @param question The yes/no question.
295     *  @return True if the answer is yes.
296     */
297    public static boolean yesNoQuestion(String question) {
298        if (!isNonInteractive()) {
299            return _handler._yesNoQuestion(question);
300        } else {
301            return true;
302        }
303    }
304
305    /** Ask the user a yes/no/cancel question, and return true if the
306     *  answer is yes.  If the user clicks on the "Cancel" button,
307     *  then throw an exception.
308     *
309     *  @param question The yes/no/cancel question.
310     *  @return True if the answer is yes.
311     *  @exception ptolemy.util.CancelException If the user clicks on
312     *  the "Cancel" button.
313     */
314    public static boolean yesNoCancelQuestion(String question)
315            throws ptolemy.util.CancelException {
316        return yesNoCancelQuestion(question, "Yes", "No", "Cancel");
317    }
318
319    /** Ask the user a question with three possible answers;
320     *  return true if the answer is the first one and false if
321     *  the answer is the second one; throw an exception if the
322     *  user selects the third one.
323     *
324     *  @param question The question.
325     *  @param trueOption The option for which to return true.
326     *  @param falseOption The option for which to return false.
327     *  @param exceptionOption The option for which to throw an exception.
328     *  @return True if the answer is the first option, false if it is the second.
329     *  @exception ptolemy.util.CancelException If the user selects the third option.
330     */
331    public static boolean yesNoCancelQuestion(String question,
332            String trueOption, String falseOption, String exceptionOption)
333            throws ptolemy.util.CancelException {
334        if (!isNonInteractive()) {
335            return _handler._yesNoCancelQuestion(question, trueOption,
336                    falseOption, exceptionOption);
337        } else {
338            return true;
339        }
340    }
341
342    ///////////////////////////////////////////////////////////////////
343    ////                         protected methods                 ////
344
345    /** Show the specified error message.
346     *  @param info The message.
347     */
348    protected void _error(String info) {
349        System.err.println(info);
350    }
351
352    /** Show the specified message and throwable information.
353     *  If the throwable is an instance of CancelException, then nothing
354     *  is not shown.  By default, only the message of the exception
355     *  is thrown.  The stack trace information is only shown if the
356     *  user clicks on the "Display Stack Trace" button.
357     *
358     *  @param info The message.
359     *  @param throwable The throwable.
360     *  @see CancelException
361     */
362    protected void _error(String info, Throwable throwable) {
363        if (throwable instanceof CancelException) {
364            return;
365        }
366
367        System.err.println(info);
368        throwable.printStackTrace();
369    }
370
371    /** Display the warning message.  In this base class, the
372     *  the default handler merely prints the warning to stderr.
373     *  @param info The message.
374     */
375    protected void _message(String info) {
376        System.err.println(info);
377    }
378
379    /** Show the specified message.  In this base class, the message
380     *  is printed to standard error.
381     *  <p>Derived classes might show the specified message in a modal
382     *  dialog.  If the user clicks on the "Cancel" button, then throw
383     *  an exception.  This gives the user the option of not
384     *  continuing the execution, something that is particularly
385     *  useful if continuing execution will result in repeated
386     *  warnings.
387     *  @param info The message.
388     *  @exception CancelException If the user clicks on the "Cancel" button.
389     */
390    protected void _warning(String info) throws CancelException {
391        _error(info);
392    }
393
394    /** Display the warning message and throwable information.  In
395     *  this base class, the the default handler merely prints the
396     *  warning to stderr.  If the user clicks on the "Cancel" button,
397     *  then throw an exception.  This gives the user the option of
398     *  not continuing the execution, something that is particularly
399     *  useful if continuing execution will result in repeated
400     *  warnings.  By default, only the message of the throwable is
401     *  thrown.  The stack trace information is only shown if the user
402     *  clicks on the "Display Stack Trace" button.
403     *  @param info The message.
404     *  @param throwable The Throwable.
405     *  @exception CancelException If the user clicks on the "Cancel" button.
406     */
407    protected void _warning(String info, Throwable throwable)
408            throws CancelException {
409        _error(info, throwable);
410    }
411
412    /** Ask the user a yes/no question, and return true if the answer
413     *  is yes.  In this base class, this prints the question on standard
414     *  output and looks for the reply on standard input.
415     *  @param question The yes/no question to be asked.
416     *  @return True if the answer is yes.
417     */
418    protected boolean _yesNoQuestion(String question) {
419        System.out.print(question);
420        System.out.print(" (yes or no) ");
421
422        BufferedReader stdIn = new BufferedReader(
423                new InputStreamReader(System.in));
424
425        try {
426            String reply = stdIn.readLine();
427
428            if (reply == null) {
429                return false;
430            } else if (reply.trim().toLowerCase(Locale.getDefault())
431                    .equals("yes")) {
432                return true;
433            }
434        } catch (IOException ex) {
435        }
436
437        return false;
438    }
439
440    /** Ask the user a question with three possible answers;
441     *  return true if the answer is the first one and false if
442     *  the answer is the second one; throw an exception if the
443     *  user selects the third one.
444     *  @param question The question.
445     *  @param trueOption The option for which to return true.
446     *  @param falseOption The option for which to return false.
447     *  @param exceptionOption The option for which to throw an exception.
448     *  @return True if the answer is the first option, false if it is the second.
449     *  @exception ptolemy.util.CancelException If the user selects the third option.
450     */
451    protected boolean _yesNoCancelQuestion(String question, String trueOption,
452            String falseOption, String exceptionOption)
453            throws ptolemy.util.CancelException {
454        System.out.print(question + " (" + trueOption + " or " + falseOption
455                + " or " + exceptionOption + ") ");
456
457        BufferedReader stdIn = new BufferedReader(
458                new InputStreamReader(System.in));
459
460        try {
461            String reply = stdIn.readLine();
462
463            if (reply == null) {
464                return false;
465            } else {
466                if (reply.trim().toLowerCase(Locale.getDefault())
467                        .equals(trueOption.toLowerCase(Locale.getDefault()))) {
468                    return true;
469                } else if (reply.trim().toLowerCase(Locale.getDefault()).equals(
470                        exceptionOption.toLowerCase(Locale.getDefault()))) {
471                    throw new ptolemy.util.CancelException(
472                            "Cancelled: " + question);
473                }
474            }
475        } catch (IOException ex) {
476        }
477
478        return false;
479    }
480
481    ///////////////////////////////////////////////////////////////////
482    ////                         private variables                 ////
483
484    /** The message handler. */
485    private static MessageHandler _handler = new MessageHandler();
486
487    /** The status handlers, if any. */
488    private static transient WeakReference<StatusHandler> _statusHandler;
489}