GUI.java
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.20.70.10.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.00.80.40.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(600300));
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.addMouseListenernew 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(1000new 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 = (longMath.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 0return match.getLabel();
646             case 2return Integer.toString(match.getLineCount());
647             case 1return match.getMarkCount() ? Integer.toString(match.getMarkCount()) "";
648             case 99return match;
649             defaultreturn "";
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 }