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