View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.cpd;
5   
6   import javax.swing.*;
7   import javax.swing.event.ListSelectionEvent;
8   import javax.swing.event.ListSelectionListener;
9   import javax.swing.event.TableModelListener;
10  import javax.swing.table.DefaultTableCellRenderer;
11  import javax.swing.table.JTableHeader;
12  import javax.swing.table.TableColumn;
13  import javax.swing.table.TableColumnModel;
14  import javax.swing.table.TableModel;
15  import java.awt.BorderLayout;
16  import java.awt.Component;
17  import java.awt.Dimension;
18  import java.awt.Point;
19  import java.awt.Toolkit;
20  import java.awt.datatransfer.StringSelection;
21  import java.awt.event.ActionEvent;
22  import java.awt.event.ActionListener;
23  import java.awt.event.ItemEvent;
24  import java.awt.event.ItemListener;
25  import java.awt.event.KeyEvent;
26  import java.awt.event.MouseAdapter;
27  import java.awt.event.MouseEvent;
28  import java.io.File;
29  import java.io.FileOutputStream;
30  import java.io.IOException;
31  import java.io.PrintWriter;
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.Comparator;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Properties;
41  import java.util.Set;
42  import net.sourceforge.pmd.PMD;
43  
44  public class GUI implements CPDListener {
45  
46  	private interface Renderer {
47  		String render(Iterator<Match> items);
48  	}
49  	
50  	private static final Object[][] rendererSets = new Object[][] {
51  		{ "Text", 		new Renderer() { public String render(Iterator<Match> items) { return new SimpleRenderer().render(items); } } },
52  		{ "XML", 		new Renderer() { public String render(Iterator<Match> items) { return new XMLRenderer().render(items); } } },
53  		{ "CSV (comma)",new Renderer() { public String render(Iterator<Match> items) { return new CSVRenderer(',').render(items); } } },
54  		{ "CSV (tab)",	new Renderer() { public String render(Iterator<Match> items) { return new CSVRenderer('\t').render(items); } } }
55  		};
56  	
57  	private interface LanguageConfig {
58  		Language languageFor(LanguageFactory lf, Properties p);
59  		boolean ignoreLiteralsByDefault();
60  		String[] extensions();
61  	};
62  	
63  	private static final Object[][] languageSets = new Object[][] {
64  		{"Java", 			new LanguageConfig() { 
65  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("java"); }
66  									public boolean ignoreLiteralsByDefault() { return true; }
67  									public String[] extensions() { return new String[] {".java", ".class" }; }; } },
68  		{"JSP", 			new LanguageConfig() { 
69  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("jsp"); }
70  									public boolean ignoreLiteralsByDefault() { return false; }
71  									public String[] extensions() { return new String[] {".jsp" }; }; } },
72  		{"C++", 			new LanguageConfig() { 
73  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("cpp"); }
74  									public boolean ignoreLiteralsByDefault() { return false; }
75  									public String[] extensions() { return new String[] {".cpp", ".c" }; }; } },
76  		{"Ruby",			new LanguageConfig() { 
77  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("ruby"); }
78  									public boolean ignoreLiteralsByDefault() { return false; }
79  									public String[] extensions() { return new String[] {".rb" }; }; } },
80  		{"Fortran",			new LanguageConfig() {
81  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("fortran"); }
82  									public boolean ignoreLiteralsByDefault() { return false; }
83  									public String[] extensions() { return new String[] {".rb" }; }; } },
84  		{"by extension...", new LanguageConfig() {
85  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.BY_EXTENSION, p); }
86  									public boolean ignoreLiteralsByDefault() { return false; }
87  									public String[] extensions() { return new String[] {"" }; }; } },
88  		{"PHP", 			new LanguageConfig() { 
89  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("php"); }
90  									public boolean ignoreLiteralsByDefault() { return false; }
91  									public String[] extensions() { return new String[] {".php" }; };	} },
92  		{"C#", 			    new LanguageConfig() {
93  										public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("cs"); }
94  										public boolean ignoreLiteralsByDefault() { return false; }
95  										public String[] extensions() { return new String[] {".cs" }; };	} },
96  		};
97  	
98  	private static final int		defaultCPDMinimumLength = 75;
99  	private static final Map		langConfigsByLabel = new HashMap(languageSets.length);
100 	private static final KeyStroke	copy = KeyStroke.getKeyStroke(KeyEvent.VK_C,ActionEvent.CTRL_MASK,false);
101 	private static final KeyStroke	delete = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
102 	
103 	private class ColumnSpec {
104 		private String label;
105 		private int alignment;
106 		private int width;
107 		private Comparator<Match> sorter;
108 		
109 		public ColumnSpec(String aLabel, int anAlignment, int aWidth, Comparator<Match> aSorter) {
110 			label = aLabel;
111 			alignment = anAlignment;
112 			width = aWidth;
113 			sorter = aSorter;
114 		}
115 		public String label() { return label; };
116 		public int alignment() { return alignment; };
117 		public int width() { return width; };
118 		public Comparator<Match> sorter() { return sorter; };
119 	}
120 
121 	private final ColumnSpec[] matchColumns = new ColumnSpec[] {
122 		new ColumnSpec("Source", 	SwingConstants.LEFT, -1, Match.LabelComparator),
123 		new ColumnSpec("Matches", 	SwingConstants.RIGHT, 60, Match.MatchesComparator),
124 		new ColumnSpec("Lines", 	SwingConstants.RIGHT, 45, Match.LinesComparator),
125 		};
126     
127 	static {		
128 		for (int i=0; i<languageSets.length; i++) {
129 			langConfigsByLabel.put(languageSets[i][0], languageSets[i][1]);
130 		}
131 	}
132 	
133 	private static LanguageConfig languageConfigFor(String label) {
134 		return (LanguageConfig)langConfigsByLabel.get(label);
135 	}
136 	
137     private static class CancelListener implements ActionListener {
138         public void actionPerformed(ActionEvent e) {
139             System.exit(0);
140         }
141     }
142 
143     private class GoListener implements ActionListener {
144         public void actionPerformed(ActionEvent e) {
145             new Thread(new Runnable() {
146                 public void run() {
147                     tokenizingFilesBar.setValue(0);
148                     tokenizingFilesBar.setString("");
149                     resultsTextArea.setText("");
150                     phaseLabel.setText("");
151                     timeField.setText("");
152                     go();
153                 }
154             }).start();
155         }
156     }
157     
158     private class SaveListener implements ActionListener {
159     	
160     	final Renderer renderer;
161     	
162     	public SaveListener(Renderer theRenderer) {
163     		renderer = theRenderer;
164     	}
165     	
166         public void actionPerformed(ActionEvent evt) {
167             JFileChooser fcSave	= new JFileChooser();
168             int ret = fcSave.showSaveDialog(GUI.this.frame);
169             File f = fcSave.getSelectedFile();
170             if (f == null || ret != JFileChooser.APPROVE_OPTION) return;
171                         
172             if (!f.canWrite()) {
173                 PrintWriter pw = null;
174                 try {
175                     pw = new PrintWriter(new FileOutputStream(f));
176                     pw.write(renderer.render(matches.iterator()));
177                     pw.flush();
178                     JOptionPane.showMessageDialog(frame, "Saved " + matches.size() + " matches");
179                 } catch (IOException e) {
180                     error("Couldn't save file" + f.getAbsolutePath(), e);
181                 } finally {
182                     if (pw != null) pw.close();
183                 }
184             } else {
185                 error("Could not write to file " + f.getAbsolutePath(), null);
186             }
187         }
188 
189         private void error(String message, Exception e) {
190             if (e != null) {
191                 e.printStackTrace();
192             }
193             JOptionPane.showMessageDialog(GUI.this.frame, message);
194         }
195 
196     }
197 
198     private class BrowseListener implements ActionListener {
199         public void actionPerformed(ActionEvent e) {
200             JFileChooser fc = new JFileChooser(rootDirectoryField.getText());
201             fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
202             fc.showDialog(frame, "Select");
203             if (fc.getSelectedFile() != null) {
204                 rootDirectoryField.setText(fc.getSelectedFile().getAbsolutePath());
205             }
206         }
207     }
208     
209 	private class AlignmentRenderer extends DefaultTableCellRenderer {
210 		
211 		private int[] alignments;
212 		
213 		public AlignmentRenderer(int[] theAlignments) {
214 			alignments = theAlignments;
215 		};
216 		
217 		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
218 			super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
219  
220 			setHorizontalAlignment(alignments[column]);
221  
222 			return this;
223 		}
224 	}
225     
226     private JTextField rootDirectoryField	= new JTextField(System.getProperty("user.home"));
227     private JTextField minimumLengthField	= new JTextField(Integer.toString(defaultCPDMinimumLength));
228     private JTextField encodingField		= new JTextField(System.getProperty("file.encoding"));
229     private JTextField timeField			= new JTextField(6);
230     private JLabel phaseLabel				= new JLabel();
231     private JProgressBar tokenizingFilesBar = new JProgressBar();
232     private JTextArea resultsTextArea		= new JTextArea();
233     private JCheckBox recurseCheckbox		= new JCheckBox("", true);
234     private JCheckBox ignoreLiteralsCheckbox = new JCheckBox("", false);
235     private JComboBox languageBox			= new JComboBox();
236     private JTextField extensionField		= new JTextField();
237     private JLabel extensionLabel			= new JLabel("Extension:", SwingConstants.RIGHT);
238     private JTable resultsTable				= new JTable();    
239     private JButton goButton;
240     private JButton cancelButton;
241     private JPanel progressPanel;
242     private JFrame frame;
243     private boolean trimLeadingWhitespace;
244 
245     private List<Match> matches = new ArrayList<Match>();
246 
247     private void addSaveOptionsTo(JMenu menu) {
248     	
249         JMenuItem saveItem;
250         
251         for (int i=0; i<rendererSets.length; i++) {
252         	saveItem = new JMenuItem("Save as " + rendererSets[i][0]);
253         	saveItem.addActionListener(new SaveListener((Renderer)rendererSets[i][1]));
254         	menu.add(saveItem);
255         }
256     }
257     
258     public GUI() {
259         frame = new JFrame("PMD Duplicate Code Detector (v " + PMD.VERSION + ')');
260 
261         timeField.setEditable(false);
262 
263         JMenu fileMenu = new JMenu("File");
264         fileMenu.setMnemonic('f');
265         
266         addSaveOptionsTo(fileMenu);
267              
268         JMenuItem exitItem = new JMenuItem("Exit");
269         exitItem.setMnemonic('x');
270         exitItem.addActionListener(new CancelListener());
271         fileMenu.add(exitItem);
272         JMenu viewMenu = new JMenu("View");
273         fileMenu.setMnemonic('v');
274         JMenuItem trimItem = new JCheckBoxMenuItem("Trim leading whitespace");
275         trimItem.addItemListener(new ItemListener() {
276             public void itemStateChanged(ItemEvent e) {
277                 AbstractButton button = (AbstractButton)e.getItem();
278                 GUI.this.trimLeadingWhitespace = button.isSelected();
279             }
280         });
281         viewMenu.add(trimItem);
282         JMenuBar menuBar = new JMenuBar();
283         menuBar.add(fileMenu);
284         menuBar.add(viewMenu);
285         frame.setJMenuBar(menuBar);
286 
287         // first make all the buttons
288         JButton browseButton = new JButton("Browse");
289         browseButton.setMnemonic('b');
290         browseButton.addActionListener(new BrowseListener());
291         goButton = new JButton("Go");
292         goButton.setMnemonic('g');
293         goButton.addActionListener(new GoListener());
294         cancelButton = new JButton("Cancel");
295         cancelButton.addActionListener(new CancelListener());
296 
297         JPanel settingsPanel = makeSettingsPanel(browseButton, goButton, cancelButton);
298         progressPanel = makeProgressPanel();
299         JPanel resultsPanel = makeResultsPanel();
300 
301         adjustLanguageControlsFor((LanguageConfig)languageSets[0][1]);
302         
303         frame.getContentPane().setLayout(new BorderLayout());
304         JPanel topPanel = new JPanel();
305         topPanel.setLayout(new BorderLayout());
306         topPanel.add(settingsPanel, BorderLayout.NORTH);
307         topPanel.add(progressPanel, BorderLayout.CENTER);
308         setProgressControls(false);	// not running now        
309         frame.getContentPane().add(topPanel, BorderLayout.NORTH);
310         frame.getContentPane().add(resultsPanel, BorderLayout.CENTER);
311         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
312         frame.pack();
313         frame.setVisible(true);
314     }
315 
316     private void adjustLanguageControlsFor(LanguageConfig current) {
317     	 ignoreLiteralsCheckbox.setEnabled(current.ignoreLiteralsByDefault());
318          extensionField.setText(current.extensions()[0]);
319          boolean enableExtension = current.extensions()[0].length() == 0;
320          extensionField.setEnabled(enableExtension);
321          extensionLabel.setEnabled(enableExtension);
322     }
323     
324     private JPanel makeSettingsPanel(JButton browseButton, JButton goButton, JButton cxButton) {
325         JPanel settingsPanel = new JPanel();
326         GridBagHelper helper = new GridBagHelper(settingsPanel, new double[]{0.2, 0.7, 0.1, 0.1});
327         helper.addLabel("Root source directory:");
328         helper.add(rootDirectoryField);
329         helper.add(browseButton, 2);
330         helper.nextRow();
331         helper.addLabel("Report duplicate chunks larger than:");
332         minimumLengthField.setColumns(4);
333         helper.add(minimumLengthField);
334         helper.addLabel("Language:");
335         for (int i=0; i<languageSets.length; i++) {
336         	languageBox.addItem(languageSets[i][0]);
337         }
338         languageBox.addActionListener(new ActionListener() {
339             public void actionPerformed(ActionEvent e) {
340             	adjustLanguageControlsFor(
341             			languageConfigFor((String)languageBox.getSelectedItem())
342             			);
343             }
344         });
345         helper.add(languageBox);
346         helper.nextRow();
347         helper.addLabel("Also scan subdirectories?");
348         helper.add(recurseCheckbox);
349 
350         helper.add(extensionLabel);
351         helper.add(extensionField);
352 
353         helper.nextRow();
354         helper.addLabel("Ignore literals and identifiers?");
355         helper.add(ignoreLiteralsCheckbox);
356         helper.add(goButton);
357         helper.add(cxButton);
358         helper.nextRow();
359 
360         helper.addLabel("File encoding (defaults based upon locale):");
361         encodingField.setColumns(1);
362         helper.add(encodingField);
363         helper.addLabel("");
364         helper.addLabel("");
365         helper.nextRow();
366 //        settingsPanel.setBorder(BorderFactory.createTitledBorder("Settings"));
367         return settingsPanel;
368     }
369 
370     private JPanel makeProgressPanel() {
371         JPanel progressPanel = new JPanel();
372         final double[] weights = {0.0, 0.8, 0.4, 0.2};
373         GridBagHelper helper = new GridBagHelper(progressPanel, weights);
374         helper.addLabel("Tokenizing files:");
375         helper.add(tokenizingFilesBar, 3);
376         helper.nextRow();
377         helper.addLabel("Phase:");
378         helper.add(phaseLabel);
379         helper.addLabel("Time elapsed:");
380         helper.add(timeField);
381         helper.nextRow();
382         progressPanel.setBorder(BorderFactory.createTitledBorder("Progress"));
383         return progressPanel;
384     }
385     
386     private JPanel makeResultsPanel() {
387         JPanel resultsPanel = new JPanel();
388         resultsPanel.setLayout(new BorderLayout());
389         JScrollPane areaScrollPane = new JScrollPane(resultsTextArea);
390         resultsTextArea.setEditable(false);
391         areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
392         areaScrollPane.setPreferredSize(new Dimension(600, 300));
393         
394         resultsPanel.add(makeMatchList(), BorderLayout.WEST);
395         resultsPanel.add(areaScrollPane, BorderLayout.CENTER);
396         return resultsPanel;
397     }
398 
399     private void populateResultArea() {
400     	int[] selectionIndices = resultsTable.getSelectedRows();
401     	TableModel model = resultsTable.getModel();
402     	List<Match> selections = new ArrayList<Match>(selectionIndices.length);
403     	for (int i=0; i<selectionIndices.length; i++) {
404     		selections.add((Match)model.getValueAt(selectionIndices[i], 99));
405     	}
406     	String report = new SimpleRenderer(trimLeadingWhitespace).render(selections.iterator());
407     	resultsTextArea.setText(report);
408     	resultsTextArea.setCaretPosition(0);	// move to the top
409     }
410         
411     private void copyMatchListSelectionsToClipboard() {
412     	
413     	int[] selectionIndices = resultsTable.getSelectedRows();
414     	int colCount = resultsTable.getColumnCount();
415     	
416     	StringBuffer sb = new StringBuffer();
417     	    	
418     	for (int r=0; r<selectionIndices.length; r++) {
419 			if (r > 0) sb.append('\n');
420 			sb.append(resultsTable.getValueAt(selectionIndices[r], 0));
421     		for (int c=1; c<colCount; c++) {
422     			sb.append('\t');
423     			sb.append(resultsTable.getValueAt(selectionIndices[r], c));
424     		}
425     	}
426     	
427     	StringSelection ss = new StringSelection(sb.toString());
428         Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
429     }
430     
431     private void deleteMatchlistSelections() {
432     	
433     	int[] selectionIndices = resultsTable.getSelectedRows();
434     	
435     	for (int i=selectionIndices.length-1; i >=0; i--) {
436     		matches.remove(selectionIndices[i]);
437     	}
438     	
439     	resultsTable.getSelectionModel().clearSelection();
440     	resultsTable.addNotify();
441     }
442     
443     private JComponent makeMatchList() {
444     	
445     	resultsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
446 			public void valueChanged(ListSelectionEvent e) {
447 				populateResultArea();				
448 			}});
449     	
450     	resultsTable.registerKeyboardAction(new ActionListener() {
451 			public void actionPerformed(ActionEvent e) { copyMatchListSelectionsToClipboard(); } 
452     		},"Copy", copy, JComponent.WHEN_FOCUSED);
453     	
454     	resultsTable.registerKeyboardAction(new ActionListener() {
455 			public void actionPerformed(ActionEvent e) { deleteMatchlistSelections(); } 
456     		},"Del", delete, JComponent.WHEN_FOCUSED);
457     	
458     	int[] alignments = new int[matchColumns.length];
459     	for (int i=0; i<alignments.length; i++) alignments[i] = matchColumns[i].alignment();
460 
461     	resultsTable.setDefaultRenderer(Object.class, new AlignmentRenderer(alignments));
462     	
463     	final JTableHeader header = resultsTable.getTableHeader();
464     	header.addMouseListener( new MouseAdapter() {
465 			public void mouseClicked(MouseEvent e) {
466 				sortOnColumn(header.columnAtPoint(new Point(e.getX(), e.getY())));
467 				}
468 			});
469     	
470         return new JScrollPane(resultsTable);
471     }
472     
473     private boolean isLegalPath(String path, LanguageConfig config) {
474     	String[] extensions = config.extensions();
475     	for (int i=0; i<extensions.length; i++) {
476     		if (path.endsWith(extensions[i]) && extensions[i].length() > 0) return true;
477     	}
478     	return false;
479     }
480     
481     private String setLabelFor(Match match) {
482     	
483     	Set<String> sourceIDs = new HashSet<String>(match.getMarkCount());
484     	for (Iterator<TokenEntry> occurrences = match.iterator(); occurrences.hasNext();) {
485              sourceIDs.add(occurrences.next().getTokenSrcID());
486           }
487     	String label;
488     	
489     	if (sourceIDs.size() == 1) {
490     		String sourceId = sourceIDs.iterator().next();
491     		int separatorPos = sourceId.lastIndexOf(File.separatorChar);
492     		label = "..." + sourceId.substring(separatorPos);
493     		} else {
494     	    	label = "(" + sourceIDs.size() + " separate files)";
495     		}
496     		
497     	match.setLabel(label);
498     	return label;
499     }
500     
501     private void setProgressControls(boolean isRunning) {
502         progressPanel.setVisible(isRunning);
503         goButton.setEnabled(!isRunning);
504         cancelButton.setEnabled(isRunning);
505     }
506     
507     private void go() {
508     	String dirPath = rootDirectoryField.getText();
509         try {
510             if (!(new File(dirPath)).exists()) {
511                 JOptionPane.showMessageDialog(frame,
512                         "Can't read from that root source directory",
513                         "Error", JOptionPane.ERROR_MESSAGE);
514                 return;
515             }
516       
517             setProgressControls(true);
518 
519             Properties p = new Properties();
520             p.setProperty(JavaTokenizer.IGNORE_LITERALS, String.valueOf(ignoreLiteralsCheckbox.isSelected()));
521             p.setProperty(LanguageFactory.EXTENSION, extensionField.getText());
522             LanguageConfig conf = languageConfigFor((String)languageBox.getSelectedItem());
523             Language language = conf.languageFor(new LanguageFactory(), p);
524             CPD cpd = new CPD(Integer.parseInt(minimumLengthField.getText()), language);
525             cpd.setEncoding(encodingField.getText());
526             cpd.setCpdListener(this);
527             tokenizingFilesBar.setMinimum(0);
528             phaseLabel.setText("");
529             if (isLegalPath(dirPath, conf)) {	// should use the language file filter instead?
530             	cpd.add(new File(dirPath));
531             } else {
532                 if (recurseCheckbox.isSelected()) {
533                     cpd.addRecursively(dirPath);
534                 } else {
535                     cpd.addAllInDirectory(dirPath);
536                 }
537             }
538             final long start = System.currentTimeMillis();
539             Timer t = new Timer(1000, new ActionListener() {
540                 public void actionPerformed(ActionEvent e) {
541                     long now = System.currentTimeMillis();
542                     long elapsedMillis = now - start;
543                     long elapsedSeconds = elapsedMillis / 1000;
544                     long minutes = (long) Math.floor(elapsedSeconds / 60);
545                     long seconds = elapsedSeconds - (minutes * 60);
546                     timeField.setText(munge(String.valueOf(minutes)) + ':' + munge(String.valueOf(seconds)));
547                 }
548 
549                 private String munge(String in) {
550                     if (in.length() < 2) {
551                         in = "0" + in;
552                     }
553                     return in;
554                 }
555             });
556             t.start();
557             cpd.go();
558             t.stop();
559             
560         	matches = new ArrayList<Match>();
561         	Match match;
562         	for (Iterator<Match> i = cpd.getMatches(); i.hasNext();) {
563         		match = i.next();
564         		setLabelFor(match);
565         		matches.add(match);
566         	}
567 
568             String report = new SimpleRenderer().render(cpd.getMatches());
569             if (report.length() == 0) {
570                 JOptionPane.showMessageDialog(frame,
571                         "Done; couldn't find any duplicates longer than " + minimumLengthField.getText() + " tokens");
572             } else {
573                 resultsTextArea.setText(report);
574                 setListDataFrom(cpd.getMatches());
575                 
576             }
577         } catch (IOException t) {
578             t.printStackTrace();
579             JOptionPane.showMessageDialog(frame, "Halted due to " + t.getClass().getName() + "; " + t.getMessage());
580         } catch (RuntimeException t) {
581             t.printStackTrace();
582             JOptionPane.showMessageDialog(frame, "Halted due to " + t.getClass().getName() + "; " + t.getMessage());
583         }
584         setProgressControls(false);
585     }
586 	
587     private interface SortingTableModel<E> extends TableModel {
588     	public int sortColumn();
589     	public void sortColumn(int column);
590     	public boolean sortDescending();
591     	public void sortDescending(boolean flag);
592     	public void sort(Comparator<E> comparator);
593     }
594     
595     private TableModel tableModelFrom(final List<Match> items) {
596     	
597     	TableModel model = new SortingTableModel<Match>() {
598     		
599     		private int sortColumn;
600     		private boolean sortDescending;
601     		
602     		 public Object getValueAt(int rowIndex, int columnIndex) {
603     			Match match = items.get(rowIndex);
604     			switch (columnIndex) {
605     				case 0: return match.getLabel();
606     				case 2: return Integer.toString(match.getLineCount());
607     				case 1: return match.getMarkCount() > 2 ? Integer.toString(match.getMarkCount()) : "";
608     				case 99: return match;
609     				}
610     			return "";
611     		 	}
612 			public int getColumnCount() { return matchColumns.length;	}
613 			public int getRowCount() {	return items.size(); }
614 			public boolean isCellEditable(int rowIndex, int columnIndex) {	return false;	}
615 			public Class<?> getColumnClass(int columnIndex) { return Object.class;	}
616 			public void setValueAt(Object aValue, int rowIndex, int columnIndex) {	}
617 			public String getColumnName(int i) {	return matchColumns[i].label();	}
618 			public void addTableModelListener(TableModelListener l) { }
619 			public void removeTableModelListener(TableModelListener l) { }
620 			public int sortColumn() { return sortColumn; };
621 			public void sortColumn(int column) { sortColumn = column; };
622 			public boolean sortDescending() { return sortDescending; };
623 			public void sortDescending(boolean flag) { sortDescending = flag; };
624 			public void sort(Comparator<Match> comparator) { 
625 				Collections.sort(items, comparator);
626 				if (sortDescending) Collections.reverse(items);
627 				}
628     		};
629     	
630     	return model;
631     }    
632         
633     private void sortOnColumn(int columnIndex) {
634     	Comparator<Match> comparator = matchColumns[columnIndex].sorter();
635     	SortingTableModel<Match> model = (SortingTableModel<Match>)resultsTable.getModel();
636     	if (model.sortColumn() == columnIndex) {
637     		model.sortDescending(!model.sortDescending());
638     	}
639     	model.sortColumn(columnIndex);
640     	model.sort(comparator);
641     	
642     	resultsTable.getSelectionModel().clearSelection();    	
643     	resultsTable.repaint();
644     }
645     
646     private void setListDataFrom(Iterator iter) {
647 
648     	resultsTable.setModel(tableModelFrom(matches));
649     	
650     	TableColumnModel colModel = resultsTable.getColumnModel();
651     	TableColumn column;
652     	int width;
653     	
654     	for (int i=0; i<matchColumns.length; i++) {
655     		if (matchColumns[i].width() > 0) {
656     			column = colModel.getColumn(i);
657     			width = matchColumns[i].width();
658     			column.setPreferredWidth(width);
659     			column.setMinWidth(width);
660     			column.setMaxWidth(width);
661     		}
662     	}    	
663     }
664     
665     // CPDListener
666     public void phaseUpdate(int phase) {
667         phaseLabel.setText(getPhaseText(phase));
668     }
669 
670     public String getPhaseText(int phase) {
671         switch (phase) {
672             case CPDListener.INIT:
673                 return "Initializing";
674             case CPDListener.HASH:
675                 return "Hashing";
676             case CPDListener.MATCH:
677                 return "Matching";
678             case CPDListener.GROUPING:
679                 return "Grouping";
680             case CPDListener.DONE:
681                 return "Done";
682             default :
683                 return "Unknown";
684         }
685     }
686 
687     public void addedFile(int fileCount, File file) {
688         tokenizingFilesBar.setMaximum(fileCount);
689         tokenizingFilesBar.setValue(tokenizingFilesBar.getValue() + 1);
690     }
691     // CPDListener
692 
693     
694     public static void main(String[] args) {
695     	//this should prevent the disk not found popup
696         // System.setSecurityManager(null);
697         new GUI();
698     }
699 
700 }