DocWhatsUp.java
001 /*
002  * These classes are part of DocWhatsUp, a bug profiling and -reporting lib.
003  
004  * Copyright (C)
005  
006  * This library is free software; you can redistribute it and/or modify it
007  * under the terms of the GNU General Public License as published by the
008  * Free Software Foundation; either version 2 of the License, or (at your
009  * option) any later version.
010  
011  * This program is distributed in the hope that it will be useful, but
012  * WITHOUT ANY WARRANTY; without even the implied warranty of
013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
014  * Public License for more details.
015  *  
016  * You should have received a copy of the GNU General Public License along
017  * with this program; if not, write to the Free Software Foundation, Inc.,
018  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019  
020  * EXTENSION:
021  * Linking DocWhatsUp statically or dynamically with other modules is making a
022  * combined work based on DocWhatsUp.  Thus, the terms and conditions of the 
023  * GNU General Public License cover the whole combination.
024  *
025  * As a special exception, the copyright holder of DocWhatsUp give you
026  * permission to link DocWhatsUp with independent modules that communicate with
027  * DocWhatsUp solely through the DocWhatsUp.java interface, regardless of the 
028  * license terms of these independent modules, and to copy and distribute the
029  * resulting combined work under terms of your choice, provided that
030  * every copy of the combined work is accompanied by a complete copy of
031  * the source code of DocWhatsUp (the version of DocWhatsUp used to produce the
032  * combined work), being distributed under the terms of the GNU General
033  * Public License plus this exception.  An independent module is a module
034  * which is not derived from or based on DocWhatsUp.
035 
036  * Note that people who make modified versions of DocWhatsUp are not obligated
037  * to grant this special exception for their modified versions; it is
038  * their choice whether to do so.  The GNU General Public License gives
039  * permission to release a modified version without this exception; this
040  * exception also makes it possible to release a modified version which
041  * carries forward this exception.
042  
043  * Author: Philipp Bartsch; codeshaker@gmx.net
044  */
045 
046 package org.shaker.dwu;
047 
048 import java.awt.Dialog;
049 import java.awt.Frame;
050 import java.awt.Window;
051 import java.io.CharArrayWriter;
052 import java.io.File;
053 import java.io.IOException;
054 import java.io.PrintWriter;
055 import java.util.ArrayList;
056 import java.util.Locale;
057 
058 import javax.swing.ImageIcon;
059 import javax.swing.JDialog;
060 import javax.swing.JFrame;
061 
062 /**
063  * This static factory provides:
064  <UL>
065  <LI>Message dialogs</LI>
066  <LI>Black Board: an embeddable panel that summarizes all functions</LI>
067  <LI>BugProfiles</LI>
068  <LI>Immediate submissions without GUI prompting</LI>
069  </UL>
070  @see #main(String[]) an implementation example... 
071  @author <a href="mailto:codeshaker@gmx.net">
072  *            Philipp Bartsch (codeshaker@gmx.net)</a>
073  *            <A HREF="../../../../gpl.txt">GPL License</A>
074  */
075 public final class DocWhatsUp {
076         
077     /** stacktrace storage provider **/
078     static final CharArrayWriter STACKWR     = new CharArrayWriter();
079     /** stacktrace storage provider **/
080     static final PrintWriter     PRINTWRITER = new PrintWriter(STACKWR,
081                                                                true);
082     /**A reference to the settings*/
083     static final Settings          SETTINGS    = new Settings();
084     
085     /**
086      * Invisible constructor. This class is a factory, so it`s not meant to be
087      * instantiated.
088      */
089     private DocWhatsUp () {}
090         
091     //-------------------------------------------------------------------------
092     // Dialogs
093     
094     /**
095      * Returns a message dialog.
096      
097      @param parent  the parental Frame
098      @param problem problem description; shouldn`t be empty
099      @param hint    optional error hint/solution (can be empty)
100      */
101     public static final void showMsgDialog(final Frame  parent,
102                                            final String problem,
103                                            final String hint) {
104         if (SETTINGS.isDialogEnabled()) {
105             try {           
106                 ToolBox.checkUp();
107                 new BugDialog(parent,
108                               problem,
109                               hint);
110             catch (Exception e) {
111                 ToolBox.logException(e);
112             }
113         }
114     }
115 
116     /**
117      * Returns a BugProfile driven error dialog. 
118      
119      @param profile the BugProfile
120      @param parent  the parental Frame
121      @param problem problem description
122      @param hint    optional error solution (can be empty)
123      */
124     public static final void showErrDialog(final BugProfile  profile,
125                                            final Frame       parent,
126                                            final String      problem,
127                                            final String      hint) {
128         if (SETTINGS.isDialogEnabled()) {
129             try {           
130                 ToolBox.checkUp();
131                 if (profile == null)
132                     new BugDialog(parent,
133                                   problem,
134                                   hint);
135                 else
136                     new BugDialog(profile,
137                                   parent,
138                                   problem,
139                                   hint);
140             catch (Exception e) {
141                 ToolBox.logException(e);
142             }
143         }
144     }
145     
146     //-------------------------------------------------------------------------
147     // BlackBoards
148     
149     /**
150      * Returns new BlackBoard panel.
151      *
152      @param  parent the parental window object (a dialog, frame ...)
153      @return        the BlackBoard panel
154      */
155     public static final BlackBoard getBlackBoard (final Window parent) {
156         try {           
157             ToolBox.checkUp();
158             return new BlackBoard(parent);
159         catch (Exception e) {
160             ToolBox.logException(e);
161             return null;
162         }
163     }
164         
165     /**
166      * Returns a standalone, modal and invisible dialog that contains a
167      * BlackBoard.
168      
169      @param  parent   parental frame
170      @return          the BlackBoard dialog
171      */
172     public static final JDialog getBBDialog (final Frame parent) {
173         try {
174             final JDialog dialog = new JDialog (parent,
175                                                 true)
176             dialog.setTitle(ToolBox.localize("blackboard_dialog"));
177             dialog.setContentPane(new BlackBoard(dialog));
178             dialog.setSize(400380);
179             
180             return dialog;
181         catch (Exception e) {
182             ToolBox.logException(e);
183             return null;
184         }       
185     }
186         
187     /**
188      * Returns a standalone, modal and invisible dialog that contains a
189      * BlackBoard.
190      
191      @param  parent   parental dialog
192      @return          the BlackBoard dialog
193      */
194     public static final JDialog getBBDialog (final Dialog parent) {
195         try {
196             final JDialog dialog = new JDialog (parent,
197                                                 true)
198             dialog.setTitle(ToolBox.localize("blackboard_dialog"));
199             dialog.setContentPane(new BlackBoard(dialog));
200             dialog.setSize(400380);
201             
202             return dialog;
203         catch (Exception e) {
204                 ToolBox.logException(e);
205                 return null;
206         }
207     }
208     
209     
210     //-------------------------------------------------------------------------
211     // Just Send
212         
213     /**
214      * Submits the whole BugProfile queue immediately without promting.<br>
215      * This method needs a working mail configuration (please use a custom 
216      * configuration, not the delivered one!)
217      *
218      @return empty string or an error description
219      */
220     public static final String submitBugQueue () {
221        if (!SETTINGS.isMailingEnabled())
222            return "";
223            
224        final String state = MailEngine.submit(null,
225                                               DocWhatsUp.getQueuedBugs());
226         if (state.length() 0
227             return "Submission Error: " + state;
228         else
229             return "";                            
230     }
231 
232 
233     // ------------------------------------------------------------------------
234     // BugProfile
235 
236     /**
237      * Returns, based on the given message, a BugProfile or
238      * null, if such a Profile has already been submitted.
239      *
240      @param message       a thrown exception or an object, that returns a 
241      *                         description/message on toString()
242      @param eClass        error class string (your error code)
243      @param mailTo        the address where this profile has to be send to
244      @return                 the bugprofile or null, if it has already been
245      *                      submitted
246      */
247     public static final BugProfile createBugProfile (final Object message,
248                                                      final String eClass,
249                                                      final String mailTo) {                                                            
250         try {
251             if (message instanceof Exception) {            
252                 STACKWR.reset();
253                 ((Exception)message).printStackTrace(PRINTWRITER);
254 
255                 return new BugProfile(((Exception)message).getMessage() "",
256                                       STACKWR.toString(),
257                                       eClass,
258                                       mailTo);
259             else
260                 return new BugProfile (message.toString(),
261                                        "no trace",
262                                        eClass,
263                                        mailTo);
264         catch (Exception e) {
265            return null;
266         }                                                                                               
267     }
268 
269     /**
270      * Returns, based on a given message, a BugProfile that  will be send to
271      * the "alt" address, if not specified otherwise.<BR>
272      * Returns null, if such a Profile has already been submitted.
273      *
274      @param message       a thrown exception or an object, that returns a 
275      *                         description/message by toString()
276      @param eClass        error class string (your error code)
277      @return              the BugProfile or null, if it has already been
278      *                      submitted
279      */
280     public static final BugProfile createBugProfile (final Object message,
281                                                      final String eClass) {
282         try {
283             if (message instanceof Exception) {            
284                 STACKWR.reset();
285                 ((Exception)message).printStackTrace(PRINTWRITER);
286             
287                 return new BugProfile(((Exception)message).getMessage(),
288                                       STACKWR.toString(),
289                                       eClass,
290                                       SETTINGS.getAlternativeRecipient());
291             else {            
292                 STACKWR.reset();
293                 // create a pseudo exception for a working profile sig
294                 return new BugProfile (message.toString(),
295                                        new Exception(message.toString())
296                                            .hashCode(),
297                                        eClass,
298                                        SETTINGS.getAlternativeRecipient());
299             }
300        catch (Exception e) {
301            return null;
302        }                                                                                   
303     }
304     
305     /**
306      * Returns all queued BugProfiles as an array.
307      *
308      @return the BugProfile array 
309      */
310     static BugProfile[] getQueuedBugs () {                
311         try {           
312             final String[] names = ToolBox.DWU_DIR.list(new BugFilter());
313             final ArrayList list = new ArrayList();
314             for (int i = names.length - 1; i >= 0; i--) {
315                 try {
316                     list.add(new BugProfile(names[i]));
317                 catch (IOException ioe) {
318                     // the file is not a BugProfile file
319                 }
320             }                       
321             return (BugProfile[]) list.toArray(new BugProfile[0]);                   
322         catch (Exception e) {
323             ToolBox.logException(e);
324             return null;
325         }
326     }
327     
328     /**
329      * Returns the number of currently stored Profiles in the queue.
330      *
331      @return the number of currently stored BugProfiles
332      */     
333     public static int getQueueCount () {
334         return ToolBox.DWU_DIR.list(new BugFilter()).length;
335     }
336     
337     // ------------------------------------------------------------------------
338     // misc. 
339     
340     /**
341      * Returns the icon of DWU.
342      
343      @return the dwu icon
344      */
345     public static ImageIcon getDWUIcon () {
346         return GUIFactory.getIcon("docwhatsup.png");
347     }
348     
349     /**
350      * Sets a new locale if you don`t want DWU to use the system locale.<BR> 
351      * Please provide the appropriate language ressource file
352      * dwu.jar!/data/i10n_LOCALENAME.properties or DWU falls back
353      * to english.
354      
355      @param locale the corresponding locale
356      */
357     public static void setLocale (final Locale locale) {
358         ToolBox.setLocale(locale);        
359     }
360 
361     // ------------------------------------------------------------------------
362     // MAIN section
363 
364     /**
365      * THIS MAIN METHOD IS FOR TESTING PURPOSES ONLY!<br>
366      *
367      * Example:
368      <pre>
369      * try {
370      *    foo (); //malicious method, that throws an exception
371      * } catch (Exception e) {
372      * // Create a BugProfile based on the exception object and a error 
373      * // category
374      * BugProfile bugProfile = DocWhatsUp.createBugProfile(e,
375      *                                                     "foo-error");
376      *
377      * // The BugProfile gets visualised by the ErrorDoc
378      * // (Alternativly you can send it (and all queued Profiles too) 
379      * // immediately and quietly with submitBugQueue ();!)  
380      * DocWhatsUp.getErrDialog(bugProfile,
381      *                         parentalFrame,
382      *                         errorMessageForTheUser,
383      *                         hintMessageForTheUser);
384      *
385      </pre>
386      @param args the argument array
387      */
388     public static void main (final String[] args) {
389         //
390         if (args.length == 0) {
391             printErrorMessageAndQuit();
392             System.exit(0);                 
393         }
394         String mailAddress = null;
395         String sendFormat  = "text";
396         boolean immediateSubmission = false;        
397         int i = 0;
398         while (i < args.length) {               
399             if ((args[i].equals("--to")
400                  || args[i].equals("-t")) 
401                 && args[i+1].indexOf("@"!= -1) {
402                 mailAddress = args[i+1];
403                 i+=2;
404             else if (args[i].equals("--nogui")
405                        || args[i].equals("-n")) {
406                 immediateSubmission = true;
407                 i++;
408             else if (args[i].equals("--format")
409                        || args[i].equals("-f")) {
410                 sendFormat = args[i+1];
411                 i+=2;
412             else if (args[i].equals("--lang")
413                        || args[i].equals("-l")) {
414                 setLocale(new Locale(args[i+1]));
415                 i+=2;                 
416             else {
417                 printErrorMessageAndQuit();
418             }
419         }
420         if (mailAddress == null) {
421             printErrorMessageAndQuit();
422         }
423         
424         /*
425          * SET EXPECTED PROPERTIES! These informations will be submitted within
426          * the bug report mails. I think it`s helpful to know which project has 
427          * the submitted flaw and formats like Bugzilla even need it to work 
428          * correctly.
429          */
430         System.setProperty("product.name"
431                            "a damaged test product");
432         System.setProperty("product.version"
433                            "0.1 pre-alpha");
434         /*
435          * Ok, we need some components:
436          * - an error discription for the user (not null)
437          * - a hint or solution (can be null or empty)
438          * - an exception is always nice but not necessary (string otherwise)
439          * - a parental frame for the appearing modal dialog
440          */
441         Exception e = new Exception("Dummy Exception. Timestamp: "
442                                     + System.currentTimeMillis());
443         String errorMessageForTheUser = "Dear user,this is just a test error"
444                                         " message!" + BugMail.NEW_LINE
445                                         "Here you can display your error description." 
446                                           + BugMail.NEW_LINE;
447         String hintMessageForTheUser = "This lower pane contains hints"
448                                         " like workarounds or other stuff " 
449                                         "or simply nothing!";
450                                         
451         /*
452          * For a "speaking" subject line (of the report mail) we need a 
453          * error-code/error-description/topic.
454          * Example: Your application works with the important ini file 
455          * foo.xml and everything that can fail in that context (malicious 
456          * content, not readable, whatever), is labeled as "Errorcode 3" or 
457          * "foo.xml crashes". Referring to this, you will later receive a mail 
458          * with the following subject line:<br>
459          * <b>"DWU: Errorcode 3 ExceptionMessage"</b>!
460          
461          * It`s much easier to (automatically) filter 1 billion incoming mails 
462          * by their errorcode, maybe into a specific "errorcode 3" subfolder! 
463          */
464         String errorCode = "Testdrive errors";
465                 
466         /*
467          * A BugProfile has to be created. It collects basic informations about 
468          * the occured error. To get an idea how to control, what kind of 
469          * details the profile collects by itself, have a look at the dwu 
470          * documentation.
471          */
472         BugProfile bugProfile = DocWhatsUp.createBugProfile(e,
473                                                             errorCode,
474                                                             mailAddress);
475         
476         /*
477          * bugProfile is null, if you already submitted it!
478          */
479         if (bugProfile != null) {               
480             /*
481              * If there are further things you want to keep in mind (and to be
482              * submitted), you can simply set them as properties, because 
483              * BugProfile is a Properties object.
484              */
485             bugProfile.setProperty("Developers Comment",
486                                    "This seems to be a test error!");
487             /*
488              * Sets the specified mail format. If nothing is specified, it uses 
489              * plain text, which is clean and small.
490              */
491             bugProfile.setProperty(BugProfile.SEND_FORMAT,
492                                    sendFormat);            
493         }       
494         
495         /*
496          * Check, if immediate reporting is requested
497          */
498         if (immediateSubmission) {
499             String message;
500             if ((message = submitBugQueue()).length() 0) {
501                 System.out.println(message);
502                 System.exit(1);
503             else
504                 System.exit(0);
505         }
506                         
507         /*
508          * We are ready to pop up the error message.
509          * Alternative: call DWUFactory.createMessageDoc for a simple
510          * message dialog without bug reporting functionality.
511          */              
512         JFrame parentalFrame = new JFrame("");
513         parentalFrame.setIconImage(GUIFactory.getIcon("docwhatsup.png")
514                                              .getImage());
515         DocWhatsUp.showErrDialog(bugProfile,
516                                  parentalFrame,
517                                  errorMessageForTheUser,
518                                  hintMessageForTheUser);
519                                                                                           
520         /* <montypython>
521          * And now for something completely different:
522          * </montypython>
523          *  
524          * Finally we pop up DWU`s BlackBoard. It summarizes the configuration
525          * and enables the user to send queued BugProfiles.
526          * You can create it as an embeddable ready-to-use panel which can be
527          * integrated whereever you like (as a part of the about-dialog of your
528          * project?) or as a dialog.
529          
530          * The following example uses a dialog.
531          */
532         JDialog bbDialog = DocWhatsUp.getBBDialog(parentalFrame);
533         bbDialog.show();
534         
535         System.exit(0);         
536     }       
537     
538     /**
539      * Prints a console info message and exits with code 1.
540      */
541     private static final void printErrorMessageAndQuit () {
542         System.out.println();
543         System.out.println("Usage....: java -jar dwu.jar -t recipient@host.net "
544                            "[-f format] [-l code] [-n]");        
545         System.out.println("Example..: java -jar dwu.jar -t bugs@host.com -f "
546                            "html -l de");
547         System.out.println("");
548         System.out.println("    --nogui/-n   ... tries to report immediately " +
549                            "without prompting.");
550         System.out.println("                     uses configuration in file " +
551                            "~/docwhatsup/.custom.settings, ");
552         System.out.println("                     or, if not present, " +
553                            "~/dwu.jar!/.default.settings!");
554         System.out.println("    --to/-t      ... determines the recipient.");
555         System.out.println("    --format/-f  ... determines the send format" 
556                          " (either text, html or bugzilla).");
557         System.out.println("    --lang/-l    ... sets a locale based on the " +
558                            "specified county code.");
559         System.out.println("                     Please provide an appropriate ");
560         System.out.println("                     dwu.jar!/data/l10n_CODE.properties " +
561                            "ressource file.");
562         System.out.println("    --help       ... prints this message.");
563         System.out.println("");
564         System.out.println("Report bugs to <codeshaker@gmx.net>");        
565         System.exit(1)
566     }    
567 }
568 
569 /**
570  * This inner class provides a simple FilenameFilter. Its a positive 
571  * filter, that returns every filename that ends with ".bug" (BugProfile log 
572  * file).
573  
574  @author <A HREF="mailto:codeshaker@gmx.net">
575  *            Philipp Bartsch (codeshaker@gmx.net)</A>
576  *            <A HREF="../../../../gpl.txt">GPL License</A>
577  */
578 final class BugFilter implements java.io.FilenameFilter {
579                         
580     /** Filter constructor*/
581     BugFilter () {}
582     
583     /**
584      * Returns true, if the filename ends with <PRE>".bug"</PRE>.
585      *
586      @param  dir  the directory of the file (ignored)
587      @param  name the file name (has to end with .bug)
588      @return      matching flag
589      */ 
590     public boolean accept (final File   dir,
591                            final String name) {
592         return name.endsWith(".bug");           
593     }       
594 }