View Javadoc
1   package eu.simuline.testhelpers;
2   
3   import eu.simuline.util.images.GifResource;
4   
5   import eu.simuline.junit.Logo;
6   import eu.simuline.junit.Hierarchy;
7   import eu.simuline.junit.SmallLogo;
8   
9   import eu.simuline.util.JavaPath;
10  
11  import org.junit.runner.Description;
12  
13  import java.awt.Container;
14  import java.awt.Component;
15  import java.awt.Color;
16  import java.awt.Rectangle;
17  
18  import java.util.List;
19  
20  import java.io.IOException;
21  import java.io.File;
22  
23  import javax.swing.JFrame;
24  import javax.swing.JLabel;
25  import javax.swing.JMenuBar;
26  import javax.swing.JFileChooser;
27  import javax.swing.JMenuItem;
28  import javax.swing.DefaultBoundedRangeModel;
29  import javax.swing.Icon;
30  import javax.swing.JProgressBar;
31  import javax.swing.JSplitPane;
32  import javax.swing.JComponent;
33  import javax.swing.JTabbedPane;
34  import javax.swing.JTree;
35  import javax.swing.JSeparator;
36  import javax.swing.JScrollPane;
37  import javax.swing.JButton;
38  import javax.swing.BoxLayout;
39  import javax.swing.Box;
40  import javax.swing.ImageIcon;
41  import javax.swing.DefaultListModel;
42  import javax.swing.JList;
43  import javax.swing.ListSelectionModel;
44  import javax.swing.DefaultListSelectionModel;
45  import javax.swing.ListCellRenderer;
46  import javax.swing.SwingConstants;
47  import javax.swing.SwingUtilities;
48  import javax.swing.UIManager;
49  
50  import javax.swing.filechooser.FileFilter;
51  
52  import javax.swing.tree.TreeModel;
53  import javax.swing.tree.DefaultTreeModel;
54  import javax.swing.tree.TreeNode;
55  import javax.swing.tree.MutableTreeNode;
56  import javax.swing.tree.DefaultMutableTreeNode;
57  import javax.swing.tree.TreePath;
58  import javax.swing.tree.TreeSelectionModel;
59  import javax.swing.tree.DefaultTreeSelectionModel;
60  import javax.swing.tree.DefaultTreeCellRenderer;
61  
62  import javax.swing.event.TreeSelectionEvent;
63  import javax.swing.event.TreeSelectionListener;
64  import javax.swing.event.ListSelectionEvent;
65  import javax.swing.event.ListSelectionListener;
66  import javax.swing.event.ChangeListener;
67  import javax.swing.event.ChangeEvent;
68  
69  import javax.swing.border.Border;
70  import javax.swing.border.EmptyBorder;
71  
72  
73  import java.io.Serializable;
74  
75  import java.util.Map;
76  import java.util.EnumMap;
77  import java.util.Arrays;
78  import java.util.Collection;
79  
80  /**
81   * The GUI of a JUnit test-runner. 
82   * Comprises 
83   * <ul>
84   * <li>
85   * a menubar to select running, stopping or breaking a testcase 
86   * or even exiting the application 
87   * <li>
88   * A label for the name of the testclass. 
89   * <li>
90   * A progress bar represented by the class {@link GUIRunner.TestProgressBar}. 
91   * <li>
92   * A component displaying the number of tests to run, 
93   * already runned, ignored and failed. 
94   * This is an instance of the class {@link GUIRunner.StatisticsTestState}. 
95   * <li>
96   * a treeview on the testsuite 
97   * represented by the class {@link GUIRunner.HierarchyWrapper}. 
98   * This needs support from the classes and 
99   * {@link GUIRunner.TreePathIterator}, 
100  * {@link GUIRunner.TestTreeCellRenderer}. 
101  * <li>
102  * a list of the testcases that failed in a sense 
103  * given as a {@link GUIRunner.TestCaseLister}. 
104  * <li>
105  * Closely tied to the list of testcases failed so far, 
106  * is the list-view on the stacktraces 
107  * given by a {@link GUIRunner.StackTraceLister}. 
108  * <li>
109  * a statusbar. 
110  * <li>
111  * Note that the selection on the treeview of all testcases 
112  * and on the listview of the testcases already failed 
113  * must be synchronized. 
114  * This is performed by the class {@link GUIRunner.TabChangeListener} 
115  * and the interface {@link GUIRunner.Selector}. 
116  * </ul>
117  *
118  * Partially the methods serve to make up the gui; 
119  * the others are invoked by the {@link GUIRunListener}s 
120  * to report the current state of the testsuitey. 
121  *
122  * Created: Sat Jun  3 18:29:52 2006
123  *
124  * @author <a href="mailto:ernst.reissner@simuline.eu">Ernst Reissner</a>
125  * @version 1.0
126  */
127 @SuppressWarnings("PMD.ExcessiveClassLength")
128 class GUIRunner {
129 
130     /* -------------------------------------------------------------------- *
131      * constants.                                                           *
132      * -------------------------------------------------------------------- */
133 
134 
135     /**
136      * The (big) JUnit-logo. 
137      */
138     private static final ImageIcon  LOGO_ICON = 
139 	GifResource.getIcon(Logo.class);
140 
141     /**
142      * The icon representing the hierarchy of tests: 
143      * used for the tabbed pane. 
144      */
145     private static final ImageIcon HIERARCHY_ICON = 
146 	GifResource.getIcon(Hierarchy.class);
147 
148     /**
149      * The small JUnit-logo on top left of this frame. 
150      * **** still this is not displayed properly ****. 
151      */
152     private static final ImageIcon SMALL_LOGO_ICON = 
153 	GifResource.getIcon(SmallLogo.class);
154 
155     /**
156      * Represents the horizontal space used for the boundary. 
157      */
158     private static final Component HORIZ_BOUNDARY = 
159 	Box.createHorizontalStrut(10);
160 
161     /**
162      * Represents double of the horizontal space used for the boundary. 
163      */
164     private static final Component HORIZ_BOUNDARY2 = 
165 	Box.createHorizontalStrut(20);
166 
167     /**
168      * Represents the vertical space used for the boundary. 
169      */
170     private static final Component VERTI_BOUNDARY = 
171 	Box.createVerticalStrut(10);
172 
173     /**
174      * The horizontal size of the frame. 
175      */
176     private static final int HORIZ_FRAME = 800;
177 
178     /**
179      * The vertical size of the frame. 
180      */
181     private static final int VERTI_FRAME = 800;
182 
183     /**
184      * The name of the system property 
185      * the value of which points to the directory 
186      * which is opened by the file chooser. 
187      * This is the directory containing the test classes. 
188      */
189     private static final String CHOOSE_CLASSPATH = "chooseClasspath";
190 
191     /**
192      * The name of the system property 
193      * the value of which is the source path. 
194      * This comprises the product sources (in maven called main sources) 
195      * and the test sources. 
196      * This is used to edit sources at places throwing an exception. 
197      */
198     private static final String SOURCEPATH = "sourcepath";
199 
200 
201 
202     /* -------------------------------------------------------------------- *
203      * inner classes.                                                       *
204      * -------------------------------------------------------------------- */
205 
206     /**
207      * The progress bar indicating how much of the testcases already passed. 
208      * After the first testcase ending irregular, 
209      * the bar takes {@link Quality#COLOR_FAIL}. 
210      * Else after the first ignored testscase, 
211      * the bar takes {@link Quality#COLOR_IGNORED}. 
212      * Else, the bar takes {@link Quality#COLOR_OK}. 
213      */
214     static class TestProgressBar extends JProgressBar {
215 
216 	/* ---------------------------------------------------------------- *
217 	 * constants.                                                       *
218 	 * ---------------------------------------------------------------- */
219 
220 	private static final long serialVersionUID = -2479143000061671589L;
221 
222 	/* ---------------------------------------------------------------- *
223 	 * attributes.                                                      *
224 	 * ---------------------------------------------------------------- */
225 
226 	/**
227 	 * The maximal quality found in testcases so far. 
228 	 * This determines the foreground color of this progress bar. 
229 	 * This is <code>null</code> initially 
230 	 * and initialized in {@link #startTestRun(TestCase)}. 
231 	 */
232 	private Quality qual;
233 
234 
235 	/* ---------------------------------------------------------------- *
236 	 * constructors.                                                    *
237 	 * ---------------------------------------------------------------- */
238 
239 	/**
240 	 * Creates a new <code>TestProgressBar</code> instance. 
241 	 */
242 	TestProgressBar() {
243 	    super(new DefaultBoundedRangeModel());
244 	    this.model.setValueIsAdjusting(true);
245 	    this.qual = null;
246 	    //.setString("progress");//*** even better; fail or ok
247 	    //.setStringPainted(true);
248 	}
249 
250 	/* ---------------------------------------------------------------- *
251 	 * methods.                                                         *
252 	 * ---------------------------------------------------------------- */
253 
254 	/**
255 	 * Notifies that the structure of the test class may have been updated. 
256 	 * <p>
257 	 * Sets maximum, minimum an current value of the progress bar. 
258 	 * Sets {@link #qual} to {@link Quality#Scheduled} 
259 	 * indicating that no failure occurred yet 
260 	 * and the color is the ok-color. 
261 	 *
262 	 * @param desc
263 	 *    a description of the test structure defined in the test class 
264 	 *    which is a hierarchy of suites and singular tests. 
265 	 */
266 	void initClassStructure(Description desc) {
267 	    setMinimum(0);
268 	    setMaximum(desc.testCount());
269 	    setValue(0);
270 	    this.qual = Quality.Scheduled; // color OK 
271 	    //this.model.setExtent(0);/// **** how much is visible: 
272 	    //   minimum <= value <= value+extent <= maximum 
273 	}
274 
275 	// for TestProgressBar 
276 	/**
277 	 * Notifies that a test run given by <code>testCase</code> 
278 	 * is going to be run next. 
279 	 * <p>
280 	 * Only the tests which are {@link Quality#Scheduled} are really run. 
281 	 * The others just go into the length and color of the progress bar 
282 	 * when starting the run: 
283 	 * The initial length depends on the number of non-scheduled testcases 
284 	 * and the color is the worst-case color of all these testcases. 
285 	 *
286 	 * @param testCase
287 	 *    the hierarchy of all tests defined by the test class 
288 	 *    and in particular of those to be run: 
289 	 *    those which are {@link Quality#Scheduled}. 
290 	 */
291 	void startTestRun(TestCase testCase) {
292 	    this.qual = Quality.Scheduled; // color OK 
293 	    setValue(0);
294 	    detValQualRec(testCase);
295 	    setForeground(this.qual.getColor());
296 	}
297 
298 	/**
299 	 * Determines recursively the length of the progress bar 
300 	 * and {@link #qual} based on the non-scheduled test cases. 
301 	 *
302 	 * @param testCase
303 	 *    the hierarchy of all tests defined by the test class 
304 	 *    and in particular of those to be run: 
305 	 *    those which are {@link Quality#Scheduled}. 
306 	 */
307 	private void detValQualRec(TestCase testCase) {
308 	    if (testCase.isTest()) {
309 		Quality qual = testCase.getQuality();
310 		switch (qual) {
311 		case Started:
312 		    assert false;
313 		    break;
314 		case Scheduled:
315 		    break;
316 		default:
317 		    // all but Scheduled and Started: 
318 		    setValue(getValue() + 1);
319 		    this.qual = this.qual.max(qual);
320 		    break;
321 		}
322 
323 		return;
324 	    }
325 	    for (TestCase child : testCase.getChildren()) {
326 		detValQualRec(child);
327 	    }
328 	}
329 
330 	/**
331 	 * Notifies that the singular test <code>testCase</code> is finished. 
332 	 * <p>
333 	 * Pushes the progress bar one further 
334 	 * and upates the color of the progress bar 
335 	 * as described in {@link GUIRunner.TestProgressBar}. 
336 	 *
337 	 * @param testCase
338 	 *    The testcase comprising the result of the singular test finished. 
339 	 */
340 	void noteReportResult(TestCase testCase) {
341 	    setValue(getValue() + 1);
342 	    this.qual = this.qual.max(testCase.getQuality());
343 	    setForeground(this.qual.getColor());
344 	}
345 
346     } // class TestProgressBar 
347 
348 
349     /**
350      * To render a cell of the hierarchy tree. 
351      * The icon represents the state of the {@link TestCase}. 
352      */
353     static class TestTreeCellRenderer extends DefaultTreeCellRenderer {
354 
355 	private static final long serialVersionUID = -2479143000061671589L;
356 
357 	/* ---------------------------------------------------------------- *
358 	 * constructors.                                                    *
359 	 * ---------------------------------------------------------------- */
360 
361 	/**
362 	 * Creates a new <code>TestTreeCellRenderer</code> instance.
363 	 * The user objects of the tree nodes are always testcases. 
364 	 * We display essentially the icon 
365 	 * attached to {@link TestCase#getQuality}. 
366 	 */
367 	TestTreeCellRenderer() {
368 	    // is empty. 
369 	}
370 
371 	/* ---------------------------------------------------------------- *
372 	 * methods.                                                         *
373 	 * ---------------------------------------------------------------- */
374 
375 	/**
376 	 * Renders the <code>value</code> interpreting it as Node 
377 	 * the user object of which is a {@link TestCase}. 
378 	 * Rendering is by setting the icon 
379 	 * associated with the state of the @link TestCase}. 
380 	 */
381 	public Component getTreeCellRendererComponent(JTree tree, 
382 						      Object value,
383 						      boolean sel, 
384 						      boolean expanded, 
385 						      boolean leaf, 
386 						      int row, 
387 						      boolean hasFocus) {
388 
389 	    DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
390 	    TestCase userObj = (TestCase) node.getUserObject();
391 	    assert userObj != null;
392 	    if (userObj.getQuality() != null) {
393 		// node is a leaf 
394 		setLeafIcon(userObj.getQuality().getIcon());
395 	    }
396 
397 	    return super.getTreeCellRendererComponent(tree, 
398 						      value, 
399 						      sel, 
400 						      expanded, 
401 						      leaf, 
402 						      row, 
403 						      hasFocus);
404 	}
405     } // class TestTreeCellRenderer 
406 
407     /**
408      * Minimal interface for notifying about singular selection events. 
409      * Implemented by {@link GUIRunner.HierarchyWrapper}, 
410      * by {@link GUIRunner.TestCaseLister} and 
411      * by {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}. 
412      *
413      * @see GUIRunner.TabChangeListener
414      */
415     interface Selector {
416 
417 	/**
418 	 * Sets selection of <code>index</code>th item 
419 	 * and clears other selections. 
420 	 *
421 	 * @param index 
422 	 *    a non-negative <code>int</code> value 
423 	 *    representing the index of a testcase. 
424 	 * **** CAUTION: This presupposes that in a tree 
425 	 *    only the leaves can be selected. 
426 	 */
427 	void setSelection(int index);
428 
429 	/**
430 	 * Clears the selection. 
431 	 */
432 	void clearSelection();
433 
434 	/**
435 	 * Acquaints this selector with another one 
436 	 * which is notified of the selection events 
437 	 * of this <code>Selector</code>. 
438 	 * The one in the foreground is notified directly by a mouse event, 
439 	 * whereas the one in the background is selected via registration. 
440 	 * The one in the background in turn 
441 	 * notifies the empty selector 
442 	 * {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR} 
443 	 * which takes no actions. 
444 	 *
445 	 * @param sel 
446 	 *    another <code>Selector</code>. 
447 	 * @throws IllegalStateException
448 	 *    only for {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}. 
449 	 * @see GUIRunner.TabChangeListener#setSelUnSel(int)
450 	 */
451 	void registerSelector(Selector sel);
452 
453     } // interface Selector 
454 
455     /**
456      * Represents a path {@link #currPath} 
457      * in the tree of testsuites represented by {@link #treeModel}. 
458      * This is initialized by {@link #setFirstPath()} 
459      * to the uppermost complete path, 
460      * can be incremented via {@link #incPath()} 
461      * and be returned by {@link #getPath()}. 
462      */
463     static class TreePathIterator {
464 
465 
466 	/* ---------------------------------------------------------------- *
467 	 * inner class.                                                     *
468 	 * ---------------------------------------------------------------- */
469 
470 	/**
471 	 * Expands the tree along the current path, 
472 	 * for {@link #Generic} after incrementing the current path. 
473 	 */
474 	enum TreePathUpdater {
475 
476 	    First() {
477 		void updatePath(TreePathIterator treePathIter) {
478 		    treePathIter.treePathUpdater = Generic;
479 		    treePathIter.setFirstPath();
480 		}
481 	    },
482 	    Generic() {
483 		void updatePath(TreePathIterator treePathIter) {
484 		    treePathIter.incPath();
485 		}
486 	    };
487 	    abstract void updatePath(TreePathIterator treePathIter);
488 	} // enum TreePathUpdater 
489 
490 	/* ---------------------------------------------------------------- *
491 	 * attributes.                                                      *
492 	 * ---------------------------------------------------------------- */
493 
494 	/**
495 	 * A model of the tree of testsuites and tests. 
496 	 */
497 	private final TreeModel treeModel;
498 
499 	// is null after this is created. 
500 	private       TreePath currPath;
501 
502 	/**
503 	 * Decides how to update the current path: 
504 	 * For the first path, just inkoke ***** . 
505 	 */
506 	private TreePathUpdater treePathUpdater;
507 
508 
509 
510 	/* ---------------------------------------------------------------- *
511 	 * constructors.                                                    *
512 	 * ---------------------------------------------------------------- */
513 
514 	// for index==0, treePathUpdater is First, 
515 	// setFirstPath not invoked and so like null-pointer 
516 	// and so after next update iterator points to 0th entry 
517 	// for index>0, treePathUpdater is Generic, 
518 	// iterator points to one before correct position 
519 	// and so aftter next update iterator points to index-th entry 
520 	TreePathIterator(JTree tree, int index) {
521 	    this.treeModel = tree.getModel();
522 	    this.currPath = null; // formally only 
523 	    this.treePathUpdater = TreePathUpdater.First;
524 	    for (int i = 0; i < index; i++) {
525 		updatePathI();
526 	    }
527 	}
528 
529 
530 	/* ---------------------------------------------------------------- *
531 	 * methods.                                                         *
532 	 * ---------------------------------------------------------------- */
533 
534 	void updatePathI() {
535 	    this.treePathUpdater.updatePath(this);
536 	}
537 
538 	/**
539 	 * Initializes {@link #currPath} 
540 	 * with the first path in {@link #treeModel}. 
541 	 * **** Shall be invoked by TreePathUpdater only **** 
542 	 */
543 	void setFirstPath() {
544 	    TreeNode lastNode = (TreeNode) this.treeModel.getRoot();
545 	    this.currPath = prolonguePath(new TreePath(lastNode));
546 	}
547 
548 	/**
549 	 * Prolongues path as long as possible in each step with 0th child. 
550 	 */
551 	static TreePath prolonguePath(TreePath path) {
552 	    TreeNode lastNode = (TreeNode) path.getLastPathComponent();
553 	    while (!lastNode.isLeaf()) {
554 		// one has to add the 0th child of lastNode. 
555 		lastNode = lastNode.getChildAt(0);
556 		path = path.pathByAddingChild(lastNode);
557 	    }
558 	    assert lastNode.isLeaf();
559 	    return path;
560 	}
561 
562 	/**
563 	 * Replaces {@link #currPath} removing the last node 
564 	 * as long as the last node in the path 
565 	 * is the last child of the last but one node 
566 	 * and after having done this 
567 	 * returns the index of the last node 
568 	 * as a child of the last but one node. 
569 	 * <p>
570 	 * CAUTION: This method shall be invoked only 
571 	 * if {@link #currPath} can be incremented. 
572 	 *
573 	 * @throws NullPointerException
574 	 *    if {@link #currPath} cannot be incremented. 
575 	 */
576 	private int shortenPath() {
577 	    TreeNode lastNode = (TreeNode)
578 		this.currPath.getLastPathComponent();
579 	    TreePath prefix = this.currPath.getParentPath();
580 	    TreeNode lastButOneNode = (TreeNode) prefix.getLastPathComponent();
581 	    int index = lastButOneNode.getIndex(lastNode);
582 
583 	    while (index == lastButOneNode.getChildCount() - 1) {
584 		this.currPath = prefix;
585 		lastNode = lastButOneNode;
586 		// if currPath is the root, prefix is null 
587 		prefix = this.currPath.getParentPath();
588 		assert prefix != null : "tried to ";
589 		lastButOneNode = (TreeNode) prefix.getLastPathComponent();
590 		index = lastButOneNode.getIndex(lastNode);
591 	    }
592 	    return index;
593 	}
594 
595 	// **** Shall be invoked by TreePathUpdater only **** 
596 	/**
597 	 * Increments {@link #currPath} and returns the result. 
598 	 */
599 	TreePath incPath() {
600 	    int index = shortenPath();
601 	    TreePath prefix = this.currPath.getParentPath();
602 	    TreeNode lastButOneNode = (TreeNode) prefix.getLastPathComponent();
603 	    TreeNode lastNode = lastButOneNode.getChildAt(index + 1);
604 	    this.currPath = prefix.pathByAddingChild(lastNode);
605 	    this.currPath = prolonguePath(this.currPath);
606 	    return this.currPath;
607 	}
608 
609 	TreePath getPath() {
610 	    return this.currPath;
611 	}
612     } // class TreePathIterator 
613 
614     /**
615      * Represents the hierarchy of testsuites and testcases 
616      * as a tree {@link #hierarchyTree} possibly with a single selected node 
617      * given by {@link #singleSelectedNode}. 
618      */
619     static class HierarchyWrapper 
620 	implements Selector, TreeSelectionListener {
621 
622 
623 	/* ---------------------------------------------------------------- *
624 	 * fields.                                                          *
625 	 * ---------------------------------------------------------------- */
626 
627 
628 	/**
629 	 * The hierarchy of testsuites and testcases as a tree. 
630 	 * After creation this is a default tree, 
631 	 * after invocation of {@link #initClassStructure(TestCase)} 
632 	 * the hierarchy reflects the given testcase. 
633 	 * The selection model is given by {@link #treeSelection} 
634 	 * and this is the {@link TreeSelectionListener}. 
635 	 */
636 	private final JTree hierarchyTree;
637 
638 	/**
639 	 * The selection model for {@link #hierarchyTree}. 
640 	 * At most one entry is selected. 
641 	 * If the entry represents a failure, the according element in 
642 	 * {@link GUIRunner.TestCaseLister#failureSelection} is selected also. 
643 	 *
644 	 * @see #valueChanged(TreeSelectionEvent)
645 	 */
646 	private final TreeSelectionModel treeSelection;
647 
648 	/**
649 	 * This is used only by {@link #valueChanged(TreeSelectionEvent)} 
650 	 * to set the filter via 
651 	 * {@link Actions#setFilter(Description)}. 
652 	 */
653 	private final Actions actions;
654 
655 	/**
656 	 * The the {@link TestCaseLister} 
657 	 * listing the failed test cases. 
658 	 * This is used in {@link #noteReportResult(TestCase)} only 
659 	 * and is to notify that a certain tescase failed 
660 	 * and is selected. 
661 	 */
662 	private final TestCaseLister testCaseLister;
663 
664 	/**
665 	 * Represents the selected node in {@link #hierarchyTree}. 
666 	 * This is <code>null</code> if nothing selected 
667 	 * which is also the initial value. 
668 	 * This is set by {@link #valueChanged(TreeSelectionEvent)} 
669 	 * and updated also by {@link #initClassStructure(TestCase)}. 
670 	 */
671 	private DefaultMutableTreeNode singleSelectedNode;
672 
673 	/**
674 	 * Represents the path to the testcase currently run. 
675 	 * This is <code>null</code> after this is created 
676 	 * and is initialized by {@link #startTestRun(Description)}. 
677 	 */
678 	private TreePathIterator currPathIter;
679 
680 	/**
681 	 * Selector to be influenced: 
682 	 * If this is in the selected tab, {@link #selector} 
683 	 * is the tab with the {@link GUIRunner.TestCaseLister TestCaseLister}; 
684 	 * otherwise it is {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}. 
685 	 * Set by {@link #registerSelector(GUIRunner.Selector)}. 
686 	 */
687 	private Selector selector;
688 
689 	/* ---------------------------------------------------------------- *
690 	 * constructors.                                                    *
691 	 * ---------------------------------------------------------------- */
692 
693 	/**
694 	 * Creates a new HierarchyWrapper 
695 	 * with the given <code>actions</code> and <code>testCaseLister</code>
696 	 * which are used 
697 	 * to initialize {@link #actions} and {@link #testCaseLister}. 
698 	 *
699 	 * @param actions
700 	 *    the Actions to be written into {@link #actions}. 
701 	 * @param testCaseLister
702 	 *    TestCaseLister to be written into {@link #testCaseLister}. 
703 	 */
704 	HierarchyWrapper(Actions actions,
705 			 TestCaseLister testCaseLister) {
706 	    assert SwingUtilities.isEventDispatchThread();
707 
708 	    this.hierarchyTree = new JTree();
709 	    // generate selection model 
710 	    this.treeSelection = new DefaultTreeSelectionModel();
711 	    this.treeSelection.setSelectionMode
712 		(TreeSelectionModel.SINGLE_TREE_SELECTION);
713 	    this.hierarchyTree.setSelectionModel(this.treeSelection);
714 	    this.hierarchyTree.addTreeSelectionListener(this);
715 	    this.hierarchyTree.setRootVisible(true);
716 
717 	    TreeNode root = (TreeNode) this.hierarchyTree.getModel().getRoot();
718 	    this.hierarchyTree.setModel(new DefaultTreeModel(root));
719 
720 	    assert actions != null;
721 	    this.actions = actions;
722 	    assert testCaseLister != null;
723 	    this.testCaseLister = testCaseLister;
724 
725 	    // purely formally 
726 	    this.singleSelectedNode = null;
727 	    this.currPathIter       = null;
728 	    this.selector           = null;
729 	}
730 
731 	/* ---------------------------------------------------------------- *
732 	 * methods.                                                         *
733 	 * ---------------------------------------------------------------- */
734 
735 	/**
736 	 * Converts the (tree of) testcases given by <code>testCase</code>. 
737 	 * **** 
738 	 */
739 	private static 
740 	    DefaultMutableTreeNode testCase2treeNode(TestCase testCase) {
741 	    if (testCase.isTest()) {
742 		return new DefaultMutableTreeNode(testCase);
743 	    }
744 	    DefaultMutableTreeNode ret = new DefaultMutableTreeNode(testCase);
745 	    List<TestCase> childrenIn = testCase.getChildren();
746 	    DefaultMutableTreeNode childOut;
747 	    for (TestCase childIn : childrenIn) {
748 		childOut = testCase2treeNode(childIn);
749 		ret.add(childOut);
750 	    }
751 	    return ret;
752 	}
753 
754 	TestCase getRoot() {
755 	    DefaultMutableTreeNode root = (DefaultMutableTreeNode)
756 		this.hierarchyTree.getModel().getRoot();
757 	    return (TestCase) root.getUserObject();
758 	}
759 
760 	/**
761 	 * Returns the action. Invoked by the enclosing GUIRunner. 
762 	 *
763 	 * @see GUIRunner#testRunStarted(Description)
764 	 * @see GUIRunner#testRunFinished(long)
765 	 * @see GUIRunner#testRunAborted()
766 	 */
767 	Actions getActions() {
768 	    return this.actions;
769 	}
770 
771 	/* ---------------------------------------------------------------- *
772 	 * further methods.                                                 *
773 	 * ---------------------------------------------------------------- */
774 
775 
776 	/**
777 	 * Notifies that the structure of the test class may have been updated. 
778 	 * <p>
779 	 * Converts the overall test case hierarchy <code>testCase</code> 
780 	 * into a a tree node hierarchy 
781 	 * invoking {@link #testCase2treeNode(TestCase)}. 
782 	 * This defines the tree model of the hierarchy {@link #hierarchyTree}. 
783 	 * The selected node {@link #singleSelectedNode} is set to the root. 
784 	 * <p>
785 	 * Since {@link #currPathIter} points to the first testcase run, 
786 	 * it is set to <code>null</code>. 
787 	 * Finally, {@link #actions} is notified about the test to be run next 
788 	 * which is specified by the testcase given by the selected node. 
789 	 * by invoking {@link #setFilter()}. 
790 	 *
791 	 * @param testCase
792 	 *    a description of the test structure defined in the test class 
793 	 *    which is a hierarchy of suites and singular tests. 
794 	 */
795 	void initClassStructure(TestCase testCase) {
796 	    this.singleSelectedNode = testCase2treeNode(testCase);
797 	    TreeModel newModel = new DefaultTreeModel(this.singleSelectedNode);
798 	    this.hierarchyTree.setModel(newModel);
799 	    // cell renderer after model. 
800 	    this.hierarchyTree.setCellRenderer(new TestTreeCellRenderer());
801 
802 	    this.currPathIter = null;
803 	    setFilter();
804 	}
805 
806 	// invoked by {@link #initClassStructure(TestCase)} 
807 	// and by {@link #valueChanged(TreeSelectionEvent)} 
808 	/**
809 	 * Sets the filter (given by a description) 
810 	 * invoking {@link Actions#setFilter(Description)}: 
811 	 * The description is taken from {@link #singleSelectedNode}. 
812 	 */
813 	private void setFilter() {
814 	    TestCase testCase = (TestCase) this.singleSelectedNode
815 		.getUserObject();
816 	    this.actions.setFilter(testCase.getDesc());
817 	}
818 
819 	// for HierarchyWrapper 
820 	/**
821 	 * Notifies that a test run with structure given by <code>desc</code> 
822 	 * is going to be run next. 
823 	 * <p>
824 	 * Sets the quality of the testcases 
825 	 * given by {@link #singleSelectedNode} 
826 	 * recursively to {@link Quality#Scheduled} 
827 	 * and sets {@link #currPathIter}. 
828 	 *
829 	 * @param desc 
830 	 *    describes the (hierarchy of) tests to be run. 
831 	 *    This is a sub-hierarchy of the one given by the test class. 
832 	 *    This parameter is purely formally 
833 	 *    because it is given by {@link #singleSelectedNode}. 
834 	 */
835 	void startTestRun(Description desc) {
836 	    TestCase testCase = (TestCase)
837 		this.singleSelectedNode.getUserObject();
838 	    // no longer true: **** 
839 //assert testCase.getDesc().equals(desc);
840 //assert desc == testCase.getDesc();
841 	    testCase.setScheduledRec();
842 
843 	    testCase = (TestCase) this.singleSelectedNode.getFirstLeaf()
844 		.getUserObject();
845 	    this.currPathIter = new TreePathIterator
846 		(this.hierarchyTree, testCase.getIdx());
847 	}
848 
849 	/**
850 	 * Expands the path to the leaf {@link #currPathIter} points to. 
851 	 * This is invoked by {@link #noteTestStartedI(Quality)} 
852 	 * when a testcase is started or ignored. 
853 	 *
854 	 * @see #collapseAlongPath()
855 	 */
856 	void expandAlongPath() {
857 	    this.hierarchyTree.expandPath(this.currPathIter.getPath()
858 					  // that the last node is no leaf 
859 					  .getParentPath());
860 	}
861 
862 	/**
863 	 * Collapses the path to the leaf {@link #currPathIter} points to 
864 	 * as much as possible in order not to hide {@link #singleSelectedNode} 
865 	 * and the leafs corresponding with singular tests 
866 	 * which failed already (assumption failure, failure and error). 
867 	 * This is invoked by {@link #noteReportResult(TestCase)} 
868 	 * when a testcase is finished or after being ignored. 
869 	 *
870 	 * @see #expandAlongPath()
871 	 */
872 	void collapseAlongPath() {
873 	    Collection<?> selPath = 
874 		Arrays.asList(this.singleSelectedNode.getPath());
875 
876 	    TreePath treePath = this.currPathIter.getPath().getParentPath();
877 	    Object[] pathArr = treePath.getPath();
878 	    TestCase testCase;
879 	    DefaultMutableTreeNode node;
880 	    for (int idx = pathArr.length - 1; idx >= 0; idx--) {
881 		node = (DefaultMutableTreeNode) pathArr[idx];
882 		if (selPath.contains(node)) {
883 		    break;
884 		}
885 
886 		testCase = (TestCase) node.getUserObject();
887 
888 		if (testCase.fullSuccess()) {
889 		    Object[] pathArrNew = new Object[idx + 1]; // 0,...,idx 
890 		    System.arraycopy(pathArr, 0,
891 				     pathArrNew, 0,
892 				     pathArrNew.length);
893 		    treePath = new TreePath(pathArrNew);
894 		    assert treePath.getLastPathComponent() == pathArr[idx];
895 		    this.hierarchyTree.collapsePath(treePath);
896 		}
897 	    } // for 
898 	}
899 
900 	JTree getTree() {
901 	    return this.hierarchyTree;
902 	}
903 
904 	// **** for tests only 
905 	TreePath getPath() {
906 	    return this.currPathIter.getPath();
907 	}
908 
909 	// invoked by noteTestStartedI(Quality)
910 	/**
911 	 * Notifies that an atomic test is started or ignored. 
912 	 * <p>
913 	 * To Updates {@link #currPathIter} pointing to the current testcase 
914 	 * and expands along the path given by the current testcase. 
915 	 * Then sets the quality of the according testcase 
916 	 * to <code>qual</code> and updates the according tree node. 
917 	 *
918 	 * @param qual
919 	 *    the quality of the testcase: 
920 	 *    This is {@link Quality#Started} or {@link Quality#Ignored}. 
921 	 * @return
922 	 *    the current testcase. 
923 	 */
924 	TestCase noteTestStartedI(Quality qual) {
925 	    this.currPathIter.updatePathI();
926 	    expandAlongPath();
927 
928 	    DefaultMutableTreeNode lastNode = (DefaultMutableTreeNode)
929 		this.currPathIter.getPath().getLastPathComponent();
930 	    TestCase result = (TestCase) lastNode.getUserObject();
931 	    result.setQualStartedIgnored(qual);
932 	    ((DefaultTreeModel) this.hierarchyTree.getModel())
933 	    	.nodeChanged(lastNode);
934 	    return result;
935 	}
936 
937 	// invoked by noteReportResult(TestCase) 
938 	/**
939 	 * Notifies that the singular test <code>testCase</code> is finished. 
940 	 * <p>
941 	 * Collapses the current path invoking {@link #collapseAlongPath()}, 
942 	 * updates the tree node {@link #currPathIter} points to 
943 	 * and notifies {@link #testCaseLister} 
944 	 * if <code>testCase</code> failed and is selected invoking 
945 	 * {@link TestCaseLister#addSelectedTestCaseByNeed(TestCase)}. 
946 	 *
947 	 * @param testCase
948 	 *    The testcase comprising the result of the singular test finished. 
949 	 */
950 	void noteReportResult(TestCase testCase) {
951 	    collapseAlongPath();
952 
953 	    MutableTreeNode lastNode = (MutableTreeNode)
954 		this.currPathIter.getPath().getLastPathComponent();
955 	    ((DefaultTreeModel) this.hierarchyTree.getModel())
956 	    	.nodeChanged(lastNode);
957 
958 	    // If testCase is selected and testCase.hasFailed(), 
959 	    // it shall be added to testCaseLister if not yet listed. 
960 	    if (testCase.hasFailed() && isSelected(testCase)) {
961 		this.testCaseLister.addSelectedTestCaseByNeed(testCase);
962 	    }
963 	}
964 
965 	/**
966 	 * Returns whether the given testcase <code>testCase</code> 
967 	 * is selected in this HierarchyTree. 
968 	 *
969 	 * @return
970 	 *    whether the given testcase <code>testCase</code> 
971 	 *    is selected in this HierarchyTree. 
972 	 */
973 	private boolean isSelected(TestCase testCase) {
974 	    int numSel = this.treeSelection.getSelectionCount();
975 	    if (numSel == 0) {
976 		return false;
977 	    }
978 	    assert numSel == 1;
979 	    DefaultMutableTreeNode selNode = (DefaultMutableTreeNode)
980 		this.treeSelection.getSelectionPath().getLastPathComponent();
981 	    TestCase selTestCase = (TestCase) selNode.getUserObject();
982 
983 	    return selTestCase == testCase; // NOPMD
984 	}
985 
986 	/* ---------------------------------------------------------------- *
987 	 * methods implementing Selector.                                   *
988 	 * ---------------------------------------------------------------- */
989 
990 	// api-docs inherited from Selector 
991 	public void setSelection(int index) {
992 	    // **** note: for index==0 one update is required and so index+1
993 	    // abuse of TreePathIterator to obtain the right selection path 
994 	    TreePathIterator currPathIter = 
995 		new TreePathIterator(this.hierarchyTree, index + 1);
996 	    this.treeSelection.addSelectionPath(currPathIter.getPath());
997 	    //**** still a problem with update 
998 	    // what follows does not solve the problem. 
999 	    //this.frame.update(this.frame.getGraphics());
1000 	}
1001 
1002 	// api-docs inherited from Selector 
1003 	public void clearSelection() {
1004 	    this.treeSelection.clearSelection();
1005 	}
1006 
1007 	// api-docs inherited from Selector 
1008 	public void registerSelector(Selector selector) {
1009 	    assert selector instanceof TestCaseLister
1010 		|| selector == TabChangeListener.EMPTY_SELECTOR;
1011 	    this.selector = selector;
1012 	}
1013 
1014 	/* ---------------------------------------------------------------- *
1015 	 * methods implementing TreeSelectionListener.                      *
1016 	 * ---------------------------------------------------------------- */
1017 
1018 	/**
1019 	 * Called whenever the value of the selection changes. 
1020 	 * Note that deselection is treated as selection of the root, 
1021 	 * so w.l.o.g, we have to consider selections only. 
1022 	 * <p>
1023 	 * If an entry is selected, 
1024 	 * {@link #singleSelectedNode} holds the selected node.
1025 	 * Next {@link #selector} is notified of the selection: 
1026 	 * <ul>
1027 	 * <li>
1028 	 * if a failure is selected (which is a leaf) 
1029 	 * triggers according selection in the failure list, 
1030 	 * otherwise triggers a deselection. 
1031 	 * </ul>
1032 	 * Both have further effects invoking 
1033 	 * {@link GUIRunner.TestCaseLister#valueChanged(ListSelectionEvent)}.
1034 	 * <p>
1035 	 * Finally, invokes {@link #setFilter()} 
1036 	 * setting the filter for the next test run. 
1037 	 */
1038 	public void valueChanged(TreeSelectionEvent selEvent) {
1039 	    // paths is a list of paths with changed selection 
1040 	    TreePath[] paths = selEvent.getPaths();
1041 	    // two paths if one selected and the other deselected. 
1042 	    // one path: either selected or deselected 
1043 	    assert paths.length == 1 || paths.length == 2;
1044 
1045 	    for (int i = 0; i < paths.length; i++) {
1046 
1047 		// set singleSelectedNode 
1048 		if (selEvent.isAddedPath(paths[i])) {
1049 		    // Here, paths[i] has been selected 
1050 		    this.singleSelectedNode = (DefaultMutableTreeNode)
1051 			paths[i].getLastPathComponent();
1052 		} else {
1053 		    // Here, paths[i] has been deselected 
1054 		    if (paths.length == 2) {
1055 			// Here, the other path is selected 
1056 			// which handles all we need 
1057 			continue;
1058 		    }
1059 
1060 		    // Here, the deselection is the only change 
1061 		    assert paths.length == 1;
1062 		    // treat as if root was selected 
1063 		    // **** this is merely a copy! 
1064 		    this.singleSelectedNode = (DefaultMutableTreeNode)
1065 			paths[i].getPathComponent(0);
1066 		}
1067 
1068 		setFilter();
1069 
1070 		if (this.singleSelectedNode.isLeaf()) {
1071 		    TestCase testCase = (TestCase)
1072 			this.singleSelectedNode.getUserObject();
1073 		    this.selector.setSelection(testCase.getIdx());
1074 		} else {
1075 		    this.selector.clearSelection();
1076 		}
1077 	    } // for
1078 	}
1079 
1080     } // class HierarchyWrapper 
1081 
1082     /**
1083      * Represents the table displaying the number of runs, 
1084      * both, passed and to be performed altogether, 
1085      * the tests already ignored and those a failure or an error was found. 
1086      * <p>
1087      * The numbers {@link #numRunsDone}, {@link #numRuns} and {@link #qual2num} 
1088      * represent the number of runs done, 
1089      * the overall number of runs, done or not, and 
1090      * the number of finished tests of the according quality. 
1091      */
1092     static class StatisticsTestState extends JComponent {
1093 
1094 	private static final long serialVersionUID = -2479143000061671589L;
1095 
1096 	/* ---------------------------------------------------------------- *
1097 	 * attributes.                                                      *
1098 	 * ---------------------------------------------------------------- */
1099 
1100 	/**
1101 	 * Maps the quality with level <code>&gt; 0</code> 
1102 	 * to the according labels. 
1103 	 */
1104 	private final Map<Quality, JLabel>  qual2label;
1105 
1106 	/**
1107 	 * Maps the quality with any level
1108 	 * to the number of testcases already finished or ignored 
1109 	 * with according state. 
1110 	 */
1111 	private final Map<Quality, Integer> qual2num;
1112 
1113 	/**
1114 	 * Label for the number of runs done and to be executed. 
1115 	 *
1116 	 * @see #numRunsDone
1117 	 * @see #numRuns
1118 	 */
1119 	private final JLabel runs;
1120 
1121 	/**
1122 	 * The number of runs already finished. 
1123 	 */
1124 	private int numRunsDone;
1125 
1126 	/**
1127 	 * The overall number of runs, to be done, 
1128 	 * in execution or not yet started. 
1129 	 * The constructor initializes this with <code>0</code>. 
1130 	 */
1131 	private int numRuns;
1132 
1133 
1134 	/* ---------------------------------------------------------------- *
1135 	 * constructors.                                                    *
1136 	 * ---------------------------------------------------------------- */
1137 
1138 	StatisticsTestState() {
1139 	    super();
1140 
1141 	    this.runs    = new JLabel();
1142 	    //this.numRuns = 0;// formally. This is set by #start()
1143 
1144 	    this.qual2label = new EnumMap<Quality, JLabel>(Quality.class);
1145 	    for (Quality qual : Quality.values()) {
1146 		if (qual.isNeutral()) {
1147 		    continue;
1148 		}
1149 		this.qual2label.put(qual, new JLabel());
1150 	    }
1151 	    this.qual2num = new EnumMap<Quality, Integer>(Quality.class);
1152 	}
1153 
1154 	/* ---------------------------------------------------------------- *
1155 	 * methods.                                                         *
1156 	 * ---------------------------------------------------------------- */
1157 
1158 	/**
1159 	 * Returns a horizontal box with the labels in {@link #qual2label}
1160 	 * intermangled with according icons. 
1161 	 */
1162 	Box getBox() {
1163 	    Box res = Box.createHorizontalBox();
1164 	    res.add(this.runs);
1165 	    for (Quality qual : Quality.values()) {
1166 		if (qual.isNeutral()) {
1167 		    continue;
1168 		}
1169 		res.add(new JLabel(qual.getIcon()));
1170 		res.add(this.qual2label.get(qual));
1171 	    }
1172 
1173 	    res.add(Box.createGlue());
1174 	    return res;
1175 	}
1176 
1177 	/**
1178 	 * Notifies that the structure of the test class may have been updated. 
1179 	 * <p>
1180 	 * Initiates 
1181 	 * <ul>
1182 	 * <li>
1183 	 * {@link #numRuns} with the testcount given by <code>desc</code>, 
1184 	 * <li>
1185 	 * {@link #numRunsDone} and all numbers in {@link #qual2num} 
1186 	 * with <code>0</code>, 
1187 	 * </ul>
1188 	 * and updates the labels {@link #runs} 
1189 	 * and those in {@link #qual2label}. 
1190 	 *
1191 	 * @param desc
1192 	 *    a description of the test structure defined in the test class 
1193 	 *    which is a hierarchy of suites and singular tests. 
1194 	 */
1195 	void initClassStructure(Description desc) {
1196 	    this.numRuns = desc.testCount();
1197 	    this.numRunsDone = 0;
1198 
1199 	    for (Quality qual : Quality.values()) {
1200 		this.qual2num.put(qual, 0);
1201 	    }
1202 
1203 	    updateLabels();
1204 	}
1205 
1206 	// for StatisticsTestState 
1207 	/**
1208 	 * Notifies that a test with structure given by <code>desc</code> 
1209 	 * is going to be run next. 
1210 	 * <p>
1211 	 * 
1212 	 * Initiates 
1213 	 * <ul>
1214 	 * <li>
1215 	 * {@link #numRunsDone} to <code>0</code>, 
1216 	 * <li>
1217 	 * {@link #qual2num} with the testcount given by <code>testCase</code> 
1218 	 * invoking {@link #detQual2NumRec(TestCase)}, 
1219 	 * </ul>
1220 	 * and updates the labels {@link #runs} 
1221 	 * and those in {@link #qual2label}. 
1222 	 *
1223 	 * @param testCase 
1224 	 *    describes the (hierarchy of) tests defined by the test class. 
1225 	 *    Those which are in phase {@link Quality#Scheduled} 
1226 	 *    are to be run. 
1227 	 */
1228 	void startTestRun(TestCase testCase) {
1229 	    this.numRunsDone = 0;
1230 	    detQual2NumRec(testCase);
1231 	    updateLabels();
1232 	}
1233 
1234 	/**
1235 	 * Initializes teh statistics in {@link #qual2num} 
1236 	 * according to <code>testCase</code>. 
1237 	 *
1238 	 * @param testCase 
1239 	 *    describes the (hierarchy of) tests defined by the test class. 
1240 	 *    Those which are in phase {@link Quality#Scheduled} 
1241 	 *    are to be run. 
1242 	 */
1243 	private void detQual2NumRec(TestCase testCase) {
1244 	    if (testCase.isTest()) {
1245 		Quality qual = testCase.getQuality();
1246 		switch (qual) {
1247 		case Started:
1248 		    assert false;
1249 		    break;
1250 		case Scheduled:
1251 		    break;
1252 		default:
1253 		    this.numRunsDone++;
1254 		    break;
1255 		}
1256 
1257 		int num = this.qual2num.get(qual);
1258 		this.qual2num.put(qual, ++num);
1259 		return;
1260 	    }
1261 	    assert !testCase.isTest();
1262 	    for (TestCase child : testCase.getChildren()) {
1263 		detQual2NumRec(child);
1264 	    }
1265 	}
1266 
1267 	/**
1268 	 * Notifies that the singular test <code>testCase</code> is finished. 
1269 	 * <p>
1270 	 * Updates all counters and labels 
1271 	 * according to <code>testCase</code>'s quality: 
1272 	 * It may succeed, be ignored, had a failure or 
1273 	 * could not be executed due to an exception or an error. 
1274 	 *
1275 	 * @param testCase
1276 	 *    The testcase comprising the result of the singular test finished. 
1277 	 * @see TestCase#getQuality()
1278 	 */
1279 	void noteReportResult(TestCase testCase) {
1280 	    this.numRunsDone++;
1281 
1282 	    int num = this.qual2num.get(testCase.getQuality());
1283 	    /*      */this.qual2num.put(testCase.getQuality(), ++num);
1284 
1285 	    updateLabels();
1286 	}
1287 
1288 	/**
1289 	 * Updates all labels if a counter has changed. 
1290 	 * This is invoked by {@link #initClassStructure(Description)} 
1291 	 * and by {@link #startTestRun(TestCase)}. 
1292 	 */
1293 	private void updateLabels() {
1294 	    this.runs.setText("Runs: " + this.numRunsDone + 
1295 			      "/"      + this.numRuns     + "    ");
1296 
1297 	    for (Quality qual : Quality.values()) {
1298 		if (qual.isNeutral()) {
1299 		    continue;
1300 		}
1301 		/*         */this.qual2label.get(qual)
1302 		    .setText(qual.toString()           + "s: " + 
1303 			     this.qual2num  .get(qual) + "    ");
1304 	    }
1305 	}
1306     } // class StatisticsTestState 
1307 
1308 
1309     /**
1310      * A special renderer object 
1311      * consisting of a label and a location within java code 
1312      * for an item in a failure list. 
1313      */
1314     static class TestListCellRenderer 
1315 	implements ListCellRenderer<TestCase>, Serializable {
1316 
1317 	/**
1318 	 * A special kind of label which fires only change in the text 
1319 	 * and which allows to set details: selection and focus. 
1320 	 */
1321 	static class XLabel extends JLabel {
1322 
1323 	    private static final long serialVersionUID = -2479143000061671589L;
1324 
1325 	    XLabel(Icon icon) {
1326 		super(icon);
1327 	    }
1328 
1329 	    XLabel(String text) {
1330 		super(text);
1331 	    }
1332 
1333 	    void setDetails(JList<?> list,
1334 			    boolean isSelected,
1335 			    boolean cellHasFocus) {
1336 		if (isSelected) {
1337 		    setBackground(list.getSelectionBackground());
1338 		    setForeground(list.getSelectionForeground());
1339 		} else {
1340 		    setBackground(list.getBackground());
1341 		    setForeground(list.getForeground());
1342 		}
1343 		setEnabled(list.isEnabled());
1344 		setFont(list.getFont());
1345 		setBorder(cellHasFocus
1346 			  ? UIManager.getBorder(BORDER_DESC) 
1347 			  : NO_FOCUS_BORDER);
1348 		setOpaque(true);
1349 	    }
1350 
1351 	    /**
1352 	     * Overridden for performance reasons.
1353 	     * See the <a href="#override">Implementation Note</a> 
1354 	     * for more information.
1355 	     *
1356 	     * @since 1.5
1357 	     * @return <code>true</code> if the background is completely opaque
1358 	     *         and differs from the JList's background;
1359 	     *         <code>false</code> otherwise
1360 	     */
1361 	    public boolean isOpaque() { 
1362 		Color back = getBackground();
1363 		Component comp = getParent(); 
1364 		if (comp != null) { 
1365 		    comp = comp.getParent(); 
1366 		}
1367 		// comp should now be the JList. 
1368 		boolean colorMatch = 
1369 		    back != null && 
1370 		    comp != null && 
1371 		    back.equals(comp.getBackground()) && 
1372 		    comp.isOpaque();
1373 		return !colorMatch && super.isOpaque(); 
1374 	    }
1375 
1376 	    /**
1377 	     * Overridden for performance reasons.
1378 	     * See the <a href="#override">Implementation Note</a>
1379 	     * for more information.
1380 	     */
1381 	    public void validate() {
1382 		// is empty. 
1383 	    }
1384 
1385 	    /**
1386 	     * Overridden for performance reasons.
1387 	     * See the <a href="#override">Implementation Note</a>
1388 	     * for more information.
1389 	     *
1390 	     * @since 1.5
1391 	     */
1392 	    public void invalidate() {
1393 		// is empty. 
1394 	    }
1395 
1396 	    /**
1397 	     * Overridden for performance reasons.
1398 	     * See the <a href="#override">Implementation Note</a>
1399 	     * for more information.
1400 	     *
1401 	     * @since 1.5
1402 	     */
1403 	    public void repaint() {
1404 		// is empty. 
1405 	    }
1406 
1407 	    /**
1408 	     * Overridden for performance reasons.
1409 	     * See the <a href="#override">Implementation Note</a>
1410 	     * for more information.
1411 	     */
1412 	    public void revalidate() {
1413 		// is empty. 
1414 	    }
1415 
1416 	    /**
1417 	     * Overridden for performance reasons.
1418 	     * See the <a href="#override">Implementation Note</a>
1419 	     * for more information.
1420 	     */
1421 	    public void repaint(long timm, 
1422 				int xCoord, int yCoord, 
1423 				int width, int height) {
1424 		// is empty. 
1425 	    }
1426 
1427 	    /**
1428 	     * Overridden for performance reasons.
1429 	     * See the <a href="#override">Implementation Note</a>
1430 	     * for more information.
1431 	     */
1432 	    public void repaint(Rectangle rect) {
1433 		// is empty. 
1434 	    }
1435 
1436 	    /**
1437 	     * Overridden for performance reasons.
1438 	     * See the <a href="#override">Implementation Note</a>
1439 	     * for more information.
1440 	     */
1441 	    protected void firePropertyChange(String propertyName, 
1442 					      Object oldValue, 
1443 					      Object newValue) {
1444 		// Strings get interned...
1445 		// if (propertyName == "text") { // NOPMD
1446 		//   super.firePropertyChange(propertyName, oldValue, newValue);
1447 		// }
1448 	    }
1449 
1450 	    /**
1451 	     * Overridden for performance reasons.
1452 	     * See the <a href="#override">Implementation Note</a>
1453 	     * for more information.
1454 	     */
1455 	    public void firePropertyChange(String propertyName, 
1456 					   byte oldValue, 
1457 					   byte newValue) {
1458 		// is empty. 
1459 	    }
1460 
1461 	    /**
1462 	     * Overridden for performance reasons.
1463 	     * See the <a href="#override">Implementation Note</a>
1464 	     * for more information.
1465 	     */
1466 	    public void firePropertyChange(String propertyName, 
1467 					   char oldValue, 
1468 					   char newValue) {
1469 		// is empty. 
1470 	    }
1471 
1472 	    /**
1473 	     * Overridden for performance reasons.
1474 	     * See the <a href="#override">Implementation Note</a>
1475 	     * for more information.
1476 	     */
1477 	    public void firePropertyChange(String propertyName, 
1478 					   short oldValue, 
1479 					   short newValue) {
1480 		// is empty. 
1481 	    }
1482 
1483 	    /**
1484 	     * Overridden for performance reasons.
1485 	     * See the <a href="#override">Implementation Note</a>
1486 	     * for more information.
1487 	     */
1488 	    public void firePropertyChange(String propertyName, 
1489 					   int oldValue, 
1490 					   int newValue) {
1491 		// is empty. 
1492 	    }
1493 
1494 	    /**
1495 	     * Overridden for performance reasons.
1496 	     * See the <a href="#override">Implementation Note</a>
1497 	     * for more information.
1498 	     */
1499 	    public void firePropertyChange(String propertyName, 
1500 					   long oldValue, 
1501 					   long newValue) {
1502 		// is empty. 
1503 	    }
1504 
1505 	    /**
1506 	     * Overridden for performance reasons.
1507 	     * See the <a href="#override">Implementation Note</a>
1508 	     * for more information.
1509 	     */
1510 	    public void firePropertyChange(String propertyName, 
1511 					   float oldValue, 
1512 					   float newValue) {
1513 		// is empty. 
1514 	    }
1515 
1516 	    /**
1517 	     * Overridden for performance reasons.
1518 	     * See the <a href="#override">Implementation Note</a>
1519 	     * for more information.
1520 	     */
1521 	    public void firePropertyChange(String propertyName, 
1522 					   double oldValue, 
1523 					   double newValue) {
1524 		// is empty. 
1525 	    }
1526 
1527 	    /**
1528 	     * Overridden for performance reasons.
1529 	     * See the <a href="#override">Implementation Note</a>
1530 	     * for more information.
1531 	     */
1532 	    public void firePropertyChange(String propertyName,
1533 					   boolean oldValue, 
1534 					   boolean newValue) {
1535 		// is empty. 
1536 	    }
1537 	} // class XLabel 
1538 
1539 	private static final long serialVersionUID = -2479143000061671589L;
1540 
1541 	private static final String BORDER_DESC = 
1542 	    "List.focusCellHighlightBorder";
1543 
1544 	protected static final Border NO_FOCUS_BORDER = 
1545 	    new EmptyBorder(1, 1, 1, 1);
1546 
1547 	/* ------------------------------------------------------------------ *
1548 	 * constructor.                                                       *
1549 	 * ------------------------------------------------------------------ */
1550 
1551 	/**
1552 	 * Constructs a special renderer object 
1553 	 * consisting of a label and a location within java code 
1554 	 * for an item in a failure list. 
1555 	 */
1556 	TestListCellRenderer() {
1557 	    super();
1558 	}
1559 
1560 	/* ------------------------------------------------------------------ *
1561 	 * methods.                                                           *
1562 	 * ------------------------------------------------------------------ */
1563 
1564 	// **** hack ****
1565 	private String thrwToString(Throwable thrw) {
1566 	    // if ("null".equals(thrw.getMessage())) {
1567 	    //     return thrw.getClass().toString();
1568 	    // } else {
1569 	    //     return thrw           .toString();
1570 	    // }
1571 
1572 
1573 	    return "null".equals(thrw.getMessage())	    
1574 		? thrw.getClass().toString()
1575 		: thrw           .toString();
1576 	}
1577 
1578 	public Component getListCellRendererComponent
1579 	    (JList<? extends TestCase> list,
1580 	     TestCase testCase,
1581 	     int index,
1582 	     boolean isSelected,
1583 	     boolean cellHasFocus) {
1584 
1585 	    Box failureEntry = Box.createHorizontalBox();
1586 
1587 	    XLabel iconLabel = new XLabel(testCase.getQuality().getIcon());
1588 	
1589 	    // for rerun testcases which eventually do no longer fail. 
1590 	    XLabel textLabel = new XLabel(testCase.hasFailed() 
1591 					? thrwToString(testCase.getThrown()) 
1592 					: testCase.getQuality().getMessage());
1593 	    iconLabel.setDetails(list, isSelected, cellHasFocus);
1594 	    iconLabel.setDetails(list, isSelected, cellHasFocus);
1595 	    failureEntry.add(iconLabel);
1596 	    failureEntry.add(textLabel);
1597 
1598 
1599 	    return failureEntry;
1600 	}
1601 
1602 	/**
1603 	 * A subclass of DefaultListCellRenderer that implements UIResource.
1604 	 * DefaultListCellRenderer doesn't implement UIResource
1605 	 * directly so that applications can safely override the
1606 	 * cellRenderer property with DefaultListCellRenderer subclasses.
1607 	 * <p>
1608 	 * <strong>Warning:</strong>
1609 	 * Serialized objects of this class will not be compatible with
1610 	 * future Swing releases. The current serialization support is
1611 	 * appropriate for short term storage or RMI 
1612 	 * between applications running the same version of Swing.  
1613 	 * As of 1.4, support for long term storage
1614 	 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1615 	 * has been added to the <code>java.beans</code> package.
1616 	 * Please see <code>java.beans.XMLEncoder</code>.
1617 	 */
1618 	/*
1619 	  public static class UIResource extends MyListCellRenderer
1620 	  implements javax.swing.plaf.UIResource {
1621 	  private static final long serialVersionUID = -2479143000061671589L;
1622 	  }
1623 	*/
1624 
1625     } // class TestListCellRenderer 
1626 
1627 
1628 
1629     /**
1630      * Represents the list of testcases already failed 
1631      * shown in one of the tabs 
1632      * and a {@link #stackTraceLister} 
1633      * which represents the stack trace box below the tabbed pane. 
1634      */
1635     static class TestCaseLister implements ListSelectionListener, Selector {
1636 
1637 	/* ---------------------------------------------------------------- *
1638 	 * fields.                                                          *
1639 	 * ---------------------------------------------------------------- */
1640 
1641 	/**
1642 	 * The current selection of {@link #failureListMod}. 
1643 	 * At most one entry is selected. 
1644 	 * If so, the according element in 
1645 	 * {@link GUIRunner.HierarchyWrapper#treeSelection} is selected also. 
1646 	 *
1647 	 * @see #valueChanged(ListSelectionEvent)
1648 	 */
1649 	private final ListSelectionModel failureSelection;
1650 
1651 	/**
1652 	 * The list of testcases which are either ignored, 
1653 	 * failed in some sense or with hurt assumption. 
1654 	 */
1655 	private final DefaultListModel<TestCase> failureListMod;
1656 
1657 	/**
1658 	 * Contains the stack trace 
1659 	 * if a failure in {@link #failureListMod} is selected 
1660 	 * which caused an exception. 
1661 	 * This is true if an assertion was wrong, an assumption was wron 
1662 	 * or if the execution of a testcase could not be completed 
1663 	 * due to an exception. 
1664 	 */
1665 	private final StackTraceLister stackTraceLister;
1666 
1667 	// selector to be influenced. 
1668 	/**
1669 	 * Selector to be influenced: 
1670 	 * If this is in the selected tab, {@link #selector} 
1671 	 * is the tab with the {@link GUIRunner.HierarchyWrapper}; 
1672 	 * otherwise it is {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}. 
1673 	 * Set by {@link #registerSelector(GUIRunner.Selector)}. 
1674 	 */
1675 	private Selector selector;
1676 
1677 	/* ---------------------------------------------------------------- *
1678 	 * constructors.                                                    *
1679 	 * ---------------------------------------------------------------- */
1680 
1681 	TestCaseLister() {
1682 	    // init failureSelection 
1683 	    this.failureSelection = new DefaultListSelectionModel();
1684 	    this.failureSelection
1685 		.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1686 	    this.failureSelection.setValueIsAdjusting(true);
1687 	    this.failureSelection.addListSelectionListener(this);
1688 	    // init failureListMod 
1689 	    this.failureListMod = new DefaultListModel<TestCase>();
1690 	    // init stacktrace 
1691 	    this.stackTraceLister = new StackTraceLister();
1692 	}
1693 
1694 
1695 	/* ---------------------------------------------------------------- *
1696 	 * methods.                                                         *
1697 	 * ---------------------------------------------------------------- */
1698 
1699 	// invoked by {@link GUIRunner#setCenter(Actions)} 
1700 	JList<TestCase> getFailList() {
1701 	    JList<TestCase> jFailureList = 
1702 		new JList<TestCase>(this.failureListMod);
1703 	    jFailureList.setCellRenderer(new TestListCellRenderer());
1704 	    jFailureList.setSelectionModel(this.failureSelection);
1705 	    return jFailureList;
1706 	}
1707 
1708 	// invoked by {@link GUIRunner#setCenter(Actions)} 
1709 	Component getStackTraceBox() {
1710 	    return this.stackTraceLister.getStackTraceBox();
1711 	}
1712 
1713 	/* ---------------------------------------------------------------- *
1714 	 * further methods.                                                 *
1715 	 * ---------------------------------------------------------------- */
1716 
1717 	/**
1718 	 * Clears the failure list, its selection and the failure stack. 
1719 	 */
1720 	void initClassStructure() {
1721 	    this.failureSelection.clearSelection();
1722 	    this.failureListMod  .clear();
1723 	    this.stackTraceLister.clearStack();
1724 	}
1725 
1726 	// for TestCaseLister
1727 
1728 	// maybe remove from failureListMod only 
1729 	// what is in desc 
1730 	void startTestRun() {
1731 	}
1732 
1733 	/**
1734 	 * Notifies that the singular test <code>testCase</code> is finished. 
1735 	 * <p>
1736 	 * If the test failed, 
1737 	 * it should be in the failure list {@link #failureListMod}. 
1738 	 * If so the element is added if not yet present. 
1739 	 * <p>
1740 	 * If <code>testCase</code> is selected 
1741 	 * then if <code>testCase</code> failed, 
1742 	 * the stack trace {@link #stackTraceLister} 
1743 	 * is set to the stacktrace of the failure; 
1744 	 * otherwise the stacktrace is cleared. 
1745 	 *
1746 	 * @param testCase
1747 	 *    The testcase comprising the result of the singular test finished. 
1748 	 */
1749 	void noteReportResult(TestCase testCase) {
1750 	    // add testCase to failureListMod by need 
1751 	    if (testCase.hasFailed()
1752 		&& !this.failureListMod.contains  (testCase)) {
1753 		/**/this.failureListMod.addElement(testCase);
1754 		// Here, the new element is not selected 
1755 		// and so stackTraceLister needs no update. 
1756 		return;
1757 	    }
1758 	    assert !testCase.hasFailed() 
1759 		|| this.failureListMod.contains(testCase);
1760 
1761 	    // update stackTraceLister by need 
1762 	    int selIndex = this.failureSelection.getMinSelectionIndex();
1763 	    if (selIndex == -1 
1764 		|| testCase != this.failureListMod.getElementAt(selIndex)) {
1765 		// Here, no testcase is selected 
1766 		// or some testcase is selected but not testCase. 
1767 		// In any case, stackTraceLister is empty and needs no update. 
1768 		return;
1769 	    }
1770 
1771 	    // Here, testCase is selected 
1772 	    if (testCase.hasFailed()) {
1773 		this.stackTraceLister.setStack(testCase.getThrown());
1774 	    } else {
1775 		this.stackTraceLister.clearStack();
1776 	    }
1777 	}
1778 
1779 	/**
1780 	 * Adds <code>testCase</code> to the failure list if not yet listed. 
1781 	 * It is assumed that <code>testCase</code> failed 
1782 	 * and that it is selected in the {@link HierarchyWrapper}. 
1783 	 *
1784 	 * @param testCase
1785 	 *    a testcase which failed. 
1786 	 */
1787 	void addSelectedTestCaseByNeed(TestCase testCase) {
1788 	    assert testCase.hasFailed();
1789 	    int selIndex;
1790 	    if (this.failureListMod.contains(testCase)) {
1791 		// nothing to be added; check only 
1792 		selIndex = this.failureSelection.getMinSelectionIndex();
1793 		assert this.failureListMod.get(selIndex) == testCase;
1794 	    } else {
1795 		// add testCase and select it 
1796 		this.failureListMod.addElement(testCase);
1797 
1798 		selIndex = this.failureSelection.getMinSelectionIndex();
1799 		// because testCase is selected in the HierarchyTree, 
1800 		// and selections are synchronized, 
1801 		// nothing is selected in the list
1802 		assert selIndex == -1;
1803 
1804 		selIndex = this.failureListMod.size() - 1;
1805 		assert this.failureListMod.get(selIndex) == testCase;
1806 		this.failureSelection.setSelectionInterval(selIndex, selIndex);
1807 		// Here, testCase is in the list and is selected. 
1808 	    }
1809 	}
1810 
1811 	/* ---------------------------------------------------------------- *
1812 	 * methods implementing Selector.                                   *
1813 	 * ---------------------------------------------------------------- */
1814 
1815 	// api-docs inherited from Selector 
1816 	// index starts with 0 
1817 	public void setSelection(int index) {
1818 	    for (int idx = 0; idx < this.failureListMod.getSize(); idx++) {
1819 		// **** this.failureListMod.get(idx) == testCase should do 
1820 		if (this.failureListMod.get(idx).getIdx() == index) {
1821 		    this.failureSelection.setSelectionInterval(idx, idx);
1822 		    return;
1823 		}
1824 	    }
1825 	    clearSelection();
1826 	}
1827 
1828 	// api-docs inherited from Selector 
1829 	public void clearSelection() {
1830 	    this.failureSelection.clearSelection();
1831 	    this.stackTraceLister.clearStack();
1832 	}
1833 
1834 	// api-docs inherited from Selector 
1835 	public void registerSelector(Selector selector) {
1836 	    assert selector instanceof HierarchyWrapper
1837 		|| selector == TabChangeListener.EMPTY_SELECTOR;
1838 	    this.selector = selector;
1839 	}
1840 
1841 	/* ---------------------------------------------------------------- *
1842 	 * methods implementing ListSelectionListener.                      *
1843 	 * ---------------------------------------------------------------- */
1844 
1845 	/**
1846 	 * Called whenever the value of the selection changes. 
1847 	 * <p>
1848 	 * CAUTION: From the documentation of {@link ListSelectionEvent}: 
1849 	 * queries from {@link ListSelectionModel}, 
1850 	 * rather from {@link ListSelectionEvent}. 
1851 	 * <p>
1852 	 * If an entry is selected, 
1853 	 * {@link #stackTraceLister} is notified of the stacktrace 
1854 	 * of the throwable of the according testcase 
1855 	 * and {@link #selector} is notified of the selection 
1856 	 * to trigger the according selection 
1857 	 * which in turn has further effects invoking 
1858 	 * {@link GUIRunner.HierarchyWrapper#valueChanged(TreeSelectionEvent)}.
1859 	 * Deselection causes {@link #stackTraceLister} to clear the stacktrace 
1860 	 * and also affects {@link #selector} like selection of the root. 
1861 	 */
1862 	public void valueChanged(ListSelectionEvent lse) {
1863 	    // the index of the (single) selected entry or -1 if none selected 
1864 	    int selIndex = this.failureSelection.getMinSelectionIndex();
1865 	    if (selIndex == -1) {
1866 		// Here, the selection is empty. 
1867 		this.stackTraceLister.clearStack();
1868 		this.selector.clearSelection();
1869 		return;
1870 	    }
1871 	    // Here, the selection consists of a single entry. 
1872 	    TestCase testCase = this.failureListMod.getElementAt(selIndex);
1873 	    this.stackTraceLister.setStack(testCase.getThrown());
1874 	    this.selector.setSelection(testCase.getIdx());
1875 
1876 	    //GUIRunner.this.splitPane.resetToPreferredSizes();
1877 	}
1878 
1879     } // class TestCaseLister 
1880 
1881     /**
1882      * Represents the stack trace of the throwable, {@link #thrw} 
1883      * currently selected in the error list. 
1884      * The representation consists in 
1885      * the string representation {@link #thrwMessager} 
1886      * and in the stack trace given by {@link #stacktrace}. 
1887      * <p>
1888      * This class is also a {@link ListSelectionListener} 
1889      * which opens <code>emacsclient</code> 
1890      * with the source file and the line number 
1891      * given by the selected stack trace element. 
1892      */
1893     static class StackTraceLister implements ListSelectionListener {
1894 
1895 	/* ---------------------------------------------------------------- *
1896 	 * attributes.                                                      *
1897 	 * ---------------------------------------------------------------- */
1898 
1899 	/**
1900 	 * Represents a throwable or is <code>null</code> 
1901 	 * if no throwable is represented. 
1902 	 * This is also the initial value. 
1903 	 */
1904 	private Throwable thrw;
1905 
1906 	/**
1907 	 * Is empty iff {@link #thrw} is <code>null</code> 
1908 	 * and contains the string representation 
1909 	 * of the represented throwable {@link #thrw}. 
1910 	 */
1911 	private final JLabel thrwMessager;
1912 
1913 	/**
1914 	 * Is either empty or contains the stacktrace
1915 	 * of the represented throwable {@link #thrw}. 
1916 	 */
1917 	private final DefaultListModel<String> stacktrace;
1918 
1919 	/**
1920 	 * The selection of this stack trace: 
1921 	 * This is either empty 
1922 	 * (which is mandatory for empty {@link #thrwMessager}) 
1923 	 * or selects a single stack element. 
1924 	 * If so, by selection <code>emacsclient</code> is started 
1925 	 * at the place the stack element points to. 
1926 	 */
1927 	private final ListSelectionModel stackElemSelection;
1928 
1929 
1930 	/* ---------------------------------------------------------------- *
1931 	 * constructors.                                                    *
1932 	 * ---------------------------------------------------------------- */
1933 
1934 	/**
1935 	 * Creates a new StackTraceLister with empty throwable. 
1936 	 */
1937 	StackTraceLister() {
1938 	    // init thrw and its string representation. 
1939 	    this.thrw = null;
1940 	    this.thrwMessager = new JLabel("", SwingConstants.LEADING);
1941 
1942 	    // init stacktrace 
1943 	    this.stacktrace = new DefaultListModel<String>();
1944 	    // init stackElemSelection 
1945 	    this.stackElemSelection = new DefaultListSelectionModel();
1946 	    this.stackElemSelection
1947 		.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1948 	    this.stackElemSelection.setValueIsAdjusting(true);
1949 	    this.stackElemSelection
1950 		.addListSelectionListener(this);
1951 	}
1952 
1953 	/* ---------------------------------------------------------------- *
1954 	 * methods.                                                         *
1955 	 * ---------------------------------------------------------------- */
1956 
1957 	/**
1958 	 * Returns a graphical representation of this StackTraceLister. 
1959 	 */
1960 	Component getStackTraceBox() {
1961 	    JList<String> stacktraceList = new JList<String>(this.stacktrace);
1962 	    stacktraceList.setSelectionModel(this.stackElemSelection);
1963 
1964 	    Box stackTraceBox = Box.createVerticalBox();
1965 	    stackTraceBox.add(this.thrwMessager);
1966 	    stackTraceBox.add(new JScrollPane(stacktraceList));
1967 	    return stackTraceBox;
1968 	}
1969 
1970 	/**
1971 	 * Clears the represented stack including {@link #thrw} and text. 
1972 	 */
1973 	void clearStack() {
1974 	    this.thrw = null;
1975 	    this.thrwMessager.setText("");
1976 	    //this.thrwMessager.setHorizontalAlignment(SwingConstants.LEADING);
1977 	    this.stacktrace.clear();
1978 	    this.stackElemSelection.clearSelection();
1979 	}
1980 
1981 	/**
1982 	 * Represents the throwable <code>thrw</code> 
1983 	 * if this is not <code>null</code>; 
1984 	 * otherwise just clears this stack trace lister 
1985 	 * as is done by {@link #clearStack()}. 
1986 	 * The selection is cleared anyway. 
1987 	 */
1988 	void setStack(Throwable thrw) {
1989 	    clearStack();
1990 	    if (thrw == null) {
1991 		// **** can only occur for rerun testcases 
1992 		// which eventually succeed. 
1993 		// and from testcase selected without exception, 
1994 		// as ignored testcases. 
1995 		// This is like clearing the stack trace 
1996 		return;
1997 	    }
1998 
1999 	    this.thrw = thrw;
2000 	    this.thrwMessager.setText(this.thrw.toString());
2001 	    StackTraceElement[] stack = this.thrw.getStackTrace();
2002 	    for (int i = 0; i < stack.length; i++) {
2003 		this.stacktrace.addElement(stack[i].toString());
2004 	    }
2005 	}
2006 
2007 	/**
2008 	 * If an entry is selected, move with emacs to the according place. 
2009 	 */
2010 	// api-docs inherited from ListSelectionListener 
2011 	public void valueChanged(ListSelectionEvent lse) {
2012 	    // ***Here, the stacktrace cannot be empty. *** not right. 
2013 	    int selIndex = this.stackElemSelection.getMinSelectionIndex();
2014 	    if (selIndex == -1) {
2015 		// this can come only from invoking clear. 
2016 		// *** not ok because event is fired although nothing happened 
2017 		//assert this.stacktrace.getSize() == 0;
2018 		return;
2019 	    }
2020 
2021 	    StackTraceElement location = this.thrw.getStackTrace()[selIndex];
2022 	    System.out.println("location: " + location);
2023 	    JavaPath jPath = new JavaPath(System.getProperty(SOURCEPATH));
2024 	    File toBeLoaded = jPath.getFile(location.getClassName(),
2025 					    JavaPath.ClsSrc.Source);
2026 	    if (toBeLoaded == null) {
2027 		return;
2028 	    }
2029 
2030 	    // move with emacs to the selected position 
2031 	    try {
2032 		Runtime.getRuntime().exec(new String[] {
2033 			"emacsclient", 
2034 			"--no-wait", 
2035 			"+" + location.getLineNumber(), 
2036 			toBeLoaded.getPath()
2037 		    },
2038 		    // environment variables and working directory 
2039 		    // inherited from the current process 
2040 		    null, null);
2041 	    } catch (IOException ioe) {
2042 		System.err.println("Failed to invoke emacs. ");
2043 		ioe.printStackTrace();
2044 	    }
2045 	}
2046     } // class StackTraceLister 
2047 
2048     /**
2049      * A listener to the switching of a tab in the foreground; 
2050      * the other in the background. 
2051      */
2052     static class TabChangeListener implements ChangeListener {
2053 
2054 	/* ---------------------------------------------------------------- *
2055 	 * constants.                                                       *
2056 	 * ---------------------------------------------------------------- */
2057 
2058 	/**
2059 	 * The index of the tab selected initially. 
2060 	 * This may be 0 or 1. 
2061 	 * Note that the failure list is the 0th tab for some reason 
2062 	 * as seen from {@link GUIRunner#setCenter(Actions)}. 
2063 	 * It must be the one initially in the foreground. 
2064 	 */
2065 	private static final int SEL_IND1 = 0;
2066 
2067 	/**
2068 	 * Registered with the unselected Selector. 
2069 	 */
2070 	private static final Selector EMPTY_SELECTOR = 
2071 	    new Selector() {
2072 		public void setSelection(int index) {
2073 		    // is empty. 
2074 		}
2075 		public void clearSelection() {
2076 		    // is empty. 
2077 		}
2078 		public void registerSelector(Selector sel) { 
2079 		    throw new IllegalStateException();
2080 		}
2081 	    }; // EMPTY_SELECTOR
2082 
2083 	/* ---------------------------------------------------------------- *
2084 	 * attributes.                                                      *
2085 	 * ---------------------------------------------------------------- */
2086 
2087 	private final JTabbedPane tabbedPane;
2088 	private final Selector[] selectors;
2089 
2090 	/* ---------------------------------------------------------------- *
2091 	 * constructors.                                                    *
2092 	 * ---------------------------------------------------------------- */
2093 
2094 	TabChangeListener(JTabbedPane tabbedPane,
2095 			  TestCaseLister testCaseLister,
2096 			  HierarchyWrapper testHierarchy) {
2097 	    this.tabbedPane = tabbedPane;
2098 	    this.selectors = new Selector[] {
2099 		testHierarchy,
2100 		testCaseLister,
2101 	    };
2102 	    setSelUnSel(SEL_IND1);
2103 	}
2104 
2105 	/**
2106 	 * Makes the tab with the given index in the foreground 
2107 	 * and the other one in the background. 
2108 	 * This must be invoked 
2109 	 * with the index of the tab initially in the foreground 
2110 	 * and if the tab is changed, this methd must be invoked accordingly. 
2111 	 * <p>
2112 	 * As described 
2113 	 * for {@link GUIRunner.Selector#registerSelector(GUIRunner.Selector)}, 
2114 	 * the tab in the foreground receives its selection events directly, 
2115 	 * whereas the one in the background 
2116 	 * must be registered at the one in the foreground 
2117 	 * to receive the according selections. 
2118 	 * For sake of unification, the one in the background also sends 
2119 	 * selection events to the one which is registered, 
2120 	 * but this is just {@link #EMPTY_SELECTOR}. 
2121 	 *
2122 	 * @param index
2123 	 *   the index of the tab/Selector in the foreground. 
2124 	 *   This may be either 0 or 1. 
2125 	 */
2126 	private void setSelUnSel(int index) {
2127 	    assert index == 0 || index == 1;
2128 	    Selector    sel = this.selectors[    index];
2129 	    Selector notSel = this.selectors[1 - index];
2130 	    sel   .registerSelector(notSel);
2131 	    notSel.registerSelector(EMPTY_SELECTOR);
2132 	}
2133 
2134 	/* ---------------------------------------------------------------- *
2135 	 * methods implementing ChangeListener.                             *
2136 	 * ---------------------------------------------------------------- */
2137 
2138 	// Invoked when the target of the listener has changed its state.
2139 	public void stateChanged(ChangeEvent che) {
2140 	    assert this.tabbedPane == che.getSource();
2141 	    setSelUnSel(this.tabbedPane.getSelectedIndex());
2142 	}
2143     } // class TabChangeListener 
2144 
2145     /**
2146      * Provides a method to choose a test class. 
2147      * This is triggered by clicking on the 'open' icon 
2148      * in the enclosing GUIRunner. 
2149      * Class ClassChooser is based 
2150      * on the {@link JFileChooser} {@link #clsFileChooser}
2151      * It is not static because of its access to {@link GUIRunner#frame}. 
2152      */
2153     class ClassChooser {
2154 
2155 	/* ---------------------------------------------------------------- *
2156 	 * attributes.                                                      *
2157 	 * ---------------------------------------------------------------- */
2158 
2159 	/**
2160 	 * File chooser for class files representing test classes. 
2161 	 */
2162 	private final JFileChooser clsFileChooser;
2163 
2164 	/**
2165 	 * The class path for test classes. 
2166 	 */
2167 	private final JavaPath clsPath;
2168 
2169 	/* ---------------------------------------------------------------- *
2170 	 * constructors.                                                    *
2171 	 * ---------------------------------------------------------------- */
2172 
2173 	ClassChooser() {
2174 	    String classpath = System.getProperty(CHOOSE_CLASSPATH);
2175 	    this.clsPath = new JavaPath(classpath);
2176 	    this.clsFileChooser = new JFileChooser(classpath);
2177 	    this.clsFileChooser.setMultiSelectionEnabled(false);
2178 	    this.clsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
2179 	    this.clsFileChooser.setFileHidingEnabled(true);
2180 	    // File filter accepting directories 
2181 	    // and files ending with class but without inner classes 
2182 	    this.clsFileChooser.setFileFilter(new FileFilter() {
2183 		    public boolean accept(File file) {
2184 			if (!file.isFile()) {
2185 			    // directories accepted 
2186 			    return true;
2187 			}
2188 			String name = file.getName();
2189 			// file accepted if class file but no inner class 
2190 			return name.endsWith(".class") && !name.contains("$");
2191 		    }
2192 		    public String getDescription() {
2193 			return "Java class files ending with .class ";
2194 		    }
2195 		});
2196 	    this.clsFileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
2197 	    this.clsFileChooser.setDialogTitle("Testclasses");
2198 	}
2199 
2200 	/* ---------------------------------------------------------------- *
2201 	 * methods.                                                         *
2202 	 * ---------------------------------------------------------------- */
2203 
2204 
2205 	/**
2206 	 * Opens the class chooser dialog 
2207 	 * and returns the choosen class or <code>null</code> 
2208 	 * if either no file was selected, 
2209 	 * a file is selected which does not exist 
2210 	 * or does not represent a java class file. 
2211 	 */
2212 	String getChosenClass() {
2213 	    if (this.clsFileChooser.showOpenDialog(GUIRunner.this.frame) != 
2214 		JFileChooser.APPROVE_OPTION) {
2215 		//
2216 		System.out.println("selection not approved.");
2217 		return null;
2218 	    }
2219 	    File clsFile = this.clsFileChooser.getSelectedFile();
2220 	    if (!clsFile.exists()) {
2221 		System.out.println("Approved file '" + clsFile
2222 				   + "' does not exist.");
2223 		return null;
2224 	    }
2225 	    return this.clsPath.absFile2cls(clsFile,
2226 					    JavaPath.ClsSrc.Class);
2227 	}
2228     } // class ClassChooser 
2229 
2230 
2231 
2232     /* -------------------------------------------------------------------- *
2233      * fields.                                                              *
2234      * -------------------------------------------------------------------- */
2235 
2236     /**
2237      * The frame in which the Testrunne GUI is displayed. 
2238      * Since this is the outermost component, 
2239      * this is used in {@link GUIRunner.ClassChooser} 
2240      * and in {@link #updateG()}. 
2241      */
2242     private final JFrame frame;
2243 
2244     /**
2245      * A chooser for testclasses. 
2246      */
2247     private final ClassChooser classChooser;
2248 
2249     /**
2250      * Contains the fully qualified name of the testclasse 
2251      * currently under consideration. 
2252      */
2253     private final JLabel className;
2254 
2255     /**
2256      * The progress bar indicating how much of the testcases already passed. 
2257      */
2258     private final TestProgressBar progress;
2259 
2260     /**
2261      * Represents the table displaying the number of runs, 
2262      * both passed and to be performed altogether, 
2263      * the tests already ignored and those in which an error was found. 
2264      */
2265     private final StatisticsTestState statisticsTestState;
2266 
2267     /**
2268      * Represents the list of testcases already failed. 
2269      * This is shown in one of the tabs. 
2270      */
2271     private TestCaseLister testCaseLister;
2272 
2273     /**
2274      * Represents the hierarchy of testcases. 
2275      * This is shown in one of the tabs. 
2276      */
2277     private HierarchyWrapper testHierarchy;
2278 
2279     /**
2280      * Represents the split pane with a tabbed pane as top component 
2281      * and the stack trace box as bottom component. 
2282      * The tabbed pane contains a tab for the {@link #testCaseLister} 
2283      * and another one for the {@link #testHierarchy}. 
2284      * The stack trace box is given by 
2285      * {@link GUIRunner.TestCaseLister#getStackTraceBox()}. 
2286      * <p>
2287      * This field is used to reset to preferred size. 
2288      */
2289     private JSplitPane splitPane;
2290 
2291     /**
2292      * Contains a status message. 
2293      */
2294     private final JLabel statusBar;
2295 
2296 
2297     /* -------------------------------------------------------------------- *
2298      * constructor with its methods.                                        *
2299      * -------------------------------------------------------------------- */
2300 
2301     // TBC: not used. 
2302     /**
2303      * Opens the class chooser dialog 
2304      * and returns the choosen class or <code>null</code> 
2305      * if either no file was selected, 
2306      * a file is selected which does not exist 
2307      * or does not represent a java class file. 
2308      * <p>
2309      * Essentially delegates 
2310      * to {@link GUIRunner.ClassChooser#getChosenClass()}. 
2311      */
2312     String openClassChooser() {
2313 	return this.classChooser.getChosenClass();
2314     }
2315 
2316     /**
2317      * Creates a new <code>GUIRunner</code> instance 
2318      * which defines components with unloaded test class. 
2319      * Loading is done 
2320      * by invoking {@link #testClassStructureLoaded(Description)}. 
2321      */
2322     GUIRunner(Actions actions) {
2323 	assert SwingUtilities.isEventDispatchThread();
2324 	this.frame = new JFrame("JUnit GUI");
2325 	this.frame.setIconImage(SMALL_LOGO_ICON.getImage());
2326 	this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
2327 
2328 	this.classChooser = new ClassChooser();
2329 
2330 	setMenuBar(actions);
2331 
2332 	this.className = new JLabel("class name");
2333 	this.statusBar = new JLabel("status bar");
2334 
2335 	Container content = this.frame.getContentPane();
2336 	content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
2337 	//content.setLayout(new BorderLayout());
2338 
2339 
2340 	this.statisticsTestState = new StatisticsTestState();
2341 
2342 	setCenter(actions);
2343 
2344 	Box testClassName = Box.createHorizontalBox();
2345 	// add classname 
2346 	testClassName.add(new JLabel("Test class name:    "));
2347 	testClassName.add(this.className);
2348 	testClassName.add(Box.createHorizontalGlue());
2349  	this.frame.add(testClassName);
2350  	//this.frame.add(new JLabel("Test class name:    "));
2351  	//this.frame.add(this.className);
2352 
2353 	// add progress bar 
2354 	Box progLogo = Box.createHorizontalBox();
2355 	this.progress = new TestProgressBar();
2356 	progLogo.add(this.progress);
2357 	progLogo.add(HORIZ_BOUNDARY2);
2358 	progLogo.add(new JLabel(LOGO_ICON));
2359 	progLogo.add(HORIZ_BOUNDARY);
2360 	this.frame.add(progLogo);
2361 
2362 	// add separator and line with runs statistics 
2363  	this.frame.add(this.statisticsTestState.getBox());
2364  	this.frame.add(new JSeparator());
2365 	this.frame.add(VERTI_BOUNDARY);
2366 
2367 	this.frame.add(Box.createVerticalGlue());
2368 
2369 	// add results-line 
2370 	Box results = Box.createHorizontalBox();
2371 	results.add(HORIZ_BOUNDARY);
2372 	results.add(new JLabel("Results:"));
2373 	results.add(Box.createGlue());
2374 	this.frame.add(results);
2375  	//this.frame.add(new JLabel("Results:"));
2376 
2377 	//this.frame.add(Box.createGlue());
2378 
2379 	// add split pane and Run2-button 
2380 	Box resRun2 = Box.createHorizontalBox();
2381 	resRun2.add(HORIZ_BOUNDARY);
2382 	resRun2.add(this.splitPane);
2383 	resRun2.add(Box.createGlue());
2384 	resRun2.add(new JButton("Run2"));
2385 	resRun2.add(HORIZ_BOUNDARY);
2386 	this.frame.add(resRun2);
2387 //this.frame.add(this.splitPane);
2388 
2389 	// add status bar and Exit-button 
2390 	Box statExit = Box.createHorizontalBox();
2391 	statExit.add(HORIZ_BOUNDARY);
2392 	statExit.add(this.statusBar);
2393 	statExit.add(Box.createGlue());
2394 	statExit.add(new JButton("Exit"));
2395 	statExit.add(HORIZ_BOUNDARY);
2396 
2397  	this.frame.add(statExit);
2398 
2399 	this.frame.setSize(HORIZ_FRAME, VERTI_FRAME);
2400 	//this.frame.pack();
2401 	this.frame.setVisible(true);
2402     }
2403 
2404     final void setMenuBar(Actions actions) {
2405 	JMenuBar menubar = new JMenuBar();
2406 	//JMenu file = new JMenu("File");
2407 	//menubar.add(file);
2408 
2409 	menubar.add(new JMenuItem(actions.getOpenAction()));
2410 	menubar.add(new JMenuItem(actions.getStartAction()));
2411 	menubar.add(new JMenuItem(actions.getStopAction()));
2412 	menubar.add(new JMenuItem(actions.getBreakAction()));
2413 	menubar.add(new JMenuItem(actions.getExitAction()));
2414 	// **** hack to have buttons aligned left 
2415 	menubar.add(new JLabel("                                           "));
2416 
2417 // 	menubar.add(new JMenuItem(exit));
2418 // 	menubar.add(new JMenu("help"));
2419 
2420 
2421 	//JMenu help = new JMenu("Help");
2422 	//menubar.setHelpMenu(help);
2423 
2424 	this.frame.setJMenuBar(menubar);
2425     }
2426 
2427     final void setCenter(Actions actions) {
2428 	final JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
2429 	// add FailureList 
2430 	this.testCaseLister = new TestCaseLister();
2431 	// add Tree as second because otherwise a problem becomes visible 
2432 	this.testHierarchy = new HierarchyWrapper(actions,
2433 						  this.testCaseLister);
2434 
2435 
2436 	tabbedPane.addTab("Test Hierarchy",
2437 			  HIERARCHY_ICON,
2438 			  new JScrollPane(this.testHierarchy.getTree()));
2439 	tabbedPane.addTab("Failures",
2440 			  Quality.Error.getIcon(),
2441 			  new JScrollPane(this.testCaseLister.getFailList()));
2442 	tabbedPane
2443 	    .addChangeListener(new TabChangeListener(tabbedPane,
2444 						     this.testCaseLister,
2445 						     this.testHierarchy));
2446 
2447 	// make up SplitPane 
2448 	this.splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
2449 	this.splitPane.setTopComponent(tabbedPane);
2450 	this.splitPane.setBottomComponent
2451 	    (this.testCaseLister.getStackTraceBox());
2452     }
2453 
2454     /* -------------------------------------------------------------------- *
2455      * methods not tied to constructor.                                     *
2456      * -------------------------------------------------------------------- */
2457 
2458     private void updateG() {
2459 	this.frame.update(this.frame.getGraphics());
2460     }
2461 
2462     /**
2463      * Sets the message <code>msg</code> to the status bar. 
2464      */
2465     void setStatus(String msg) {
2466 //	assert SwingUtilities.isEventDispatchThread();
2467 	this.statusBar.setText(msg);
2468 	updateG();
2469     }
2470 
2471     /**
2472      * Sets a status message describing <code>testCase</code> 
2473      * to the status bar using {@link #setStatus(String)}. 
2474      */
2475     private void setStatus(TestCase testCase) {
2476 	setStatus("Test " + 
2477 		  testCase.getQuality().getMessage() + ": " + 
2478 		  testCase.getDesc());
2479     }
2480 
2481     /**
2482      * Notifies that an atomic test is started or ignored. 
2483      *
2484      * @param qual
2485      *   the quality of the testcase: 
2486      *   This is either {@link Quality#Started} or {@link Quality#Ignored}. 
2487      */
2488     TestCase noteTestStartedI(Quality qual) {
2489 	
2490 //	assert SwingUtilities.isEventDispatchThread();
2491 	assert qual == Quality.Started
2492 	    || qual == Quality.Ignored;
2493 	TestCase testCase = this.testHierarchy.noteTestStartedI(qual); 
2494 	setStatus(testCase);
2495 	return testCase;
2496     }
2497 
2498     /**
2499      * Notifies that the singular test <code>testCase</code> is finished, 
2500      * whether successful or not or that a test is ignored 
2501      * after invoking {@link #noteTestStartedI(Quality)} 
2502      * with parameter {@link Quality#Ignored}. 
2503      *
2504      * @param testCase
2505      *    The testcase comprising the result of the singular test finished. 
2506      */
2507     void noteReportResult(TestCase testCase) {
2508 	//	assert SwingUtilities.isEventDispatchThread();
2509 	
2510 	assert testCase.getQuality() == Quality.Ignored
2511 	    || testCase.getQuality() == Quality.Invalidated
2512 	    || testCase.getQuality() == Quality.Success
2513 	    || testCase.getQuality() == Quality.Failure
2514 	    || testCase.getQuality() == Quality.Error;
2515 
2516 	this.testHierarchy      .noteReportResult(testCase);
2517 	this.progress           .noteReportResult(testCase);
2518 	this.statisticsTestState.noteReportResult(testCase);
2519 	this.testCaseLister     .noteReportResult(testCase);
2520 	setStatus                                (testCase);
2521 
2522 	//this.splitPane          .resetToPreferredSizes();
2523     }
2524 
2525     /**
2526      * Notifies that the structure of the test class may have been updated. 
2527      *
2528      * @param desc
2529      *    a description of the test structure defined in the test class 
2530      *    which is a hierarchy of suites and singular tests. 
2531      * @see #testRunStarted(Description)
2532      */
2533     void testClassStructureLoaded(Description desc) {
2534 //	assert SwingUtilities.isEventDispatchThread();
2535 	setStatus("testClassLoaded(");
2536 
2537 	// **** strange way to obtain the classname ***** 
2538 	this.className.setText(desc.getChildren().get(0).toString());
2539 	TestCase testCaseAll = new TestCase(desc);
2540 
2541 	this.testHierarchy      .initClassStructure(testCaseAll);
2542 	this.testCaseLister     .initClassStructure();
2543 	this.progress           .initClassStructure(desc);
2544 	this.statisticsTestState.initClassStructure(desc);
2545     }
2546 
2547     /**
2548      * Notifies that a test with structure given by <code>desc</code> 
2549      * is going to be run next. 
2550      * Called before any tests have been run. 
2551      * At least once before this one, 
2552      * {@link #testClassStructureLoaded(Description)} has been invoked. 
2553      *
2554      * @param desc 
2555      *    describes the (hierarchy of) tests to be run. 
2556      *    This is a sub-hierarchy of the one given by the test class. 
2557      */
2558     void testRunStarted(Description desc) {
2559  //	assert SwingUtilities.isEventDispatchThread();
2560 	// **** strange way to obtain the classname ***** 
2561 	setStatus("testRunStarted(");
2562 	this.testHierarchy.getActions().setEnableForRun(true); //running 
2563 
2564 	TestCase root = this.testHierarchy.getRoot();
2565 
2566 	this.testHierarchy      .startTestRun(desc);
2567 	this.testCaseLister     .startTestRun(); // purely formally 
2568 	this.progress           .startTestRun(root);
2569 	this.statisticsTestState.startTestRun(root);
2570     }
2571 
2572     /**
2573      * Notifies that a test has been finished sufficiently or not. 
2574      *
2575      * @param runTime
2576      *    the time execution of the test took in milliseconds. 
2577      */
2578     void testRunFinished(long runTime) {
2579 	setStatus("testRunFinished(required: " + 
2580 		 runTime + "ms. ");
2581 	this.testHierarchy.getActions().setEnableForRun(false); //!running
2582     }
2583 
2584     /**
2585      * Notifies that a test has been aborted by the user. 
2586      */
2587     void testRunAborted() {
2588 	setStatus("testRunAborted(");
2589 	this.testHierarchy.getActions().setEnableForRun(false); //!running
2590     }
2591 
2592 } // NOPMD coupling is not that high!
2593