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