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