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(400, 380);
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(400, 380);
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 }
|