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