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