1
2
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
73
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
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);
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
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);
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)) {
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
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
774
775
776 public static void main(String[] args) {
777
778
779 new GUI();
780 }
781
782 }