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