1   /*
2    *  Copyright (c) 1998-2005, The University of Sheffield.
3    *
4    *  This file is part of GATE (see http://gate.ac.uk/), and is free
5    *  software, licenced under the GNU Library General Public License,
6    *  Version 2, June 1991 (in the distribution as file licence.html,
7    *  and also available at http://gate.ac.uk/gate/licence.html).
8    *
9    *  AnnotationDiffGUI.java
10   *
11   *  Valentin Tablan, 24-Jun-2004
12   *
13   *  $Id: AnnotationDiffGUI.java,v 1.16 2005/04/12 21:57:17 valyt Exp $
14   */
15  
16  package gate.gui;
17  
18  import java.awt.*;
19  import java.awt.event.*;
20  import java.io.*;
21  import java.io.File;
22  import java.io.FileWriter;
23  import java.text.NumberFormat;
24  import java.util.*;
25  import java.util.List;
26  import javax.swing.*;
27  import javax.swing.JTable;
28  import javax.swing.table.AbstractTableModel;
29  import javax.swing.table.DefaultTableCellRenderer;
30  import gate.*;
31  import gate.Annotation;
32  import gate.Document;
33  import gate.swing.XJTable;
34  import gate.util.*;
35  
36  /**
37   */
38  public class AnnotationDiffGUI extends JFrame{
39  
40    public AnnotationDiffGUI(String title){
41      super(title);
42      initLocalData();
43      initGUI();
44      initListeners();
45      populateGUI();
46    }
47  
48    protected void initLocalData(){
49      differ = new AnnotationDiffer();
50      pairings = new ArrayList();
51      significantFeatures = new HashSet();
52      keyDoc = null;
53      resDoc = null;
54    }
55  
56  
57    protected void initGUI(){
58      getContentPane().setLayout(new GridBagLayout());
59      GridBagConstraints constraints = new GridBagConstraints();
60      //defaults
61      constraints.gridy = 0;
62      constraints.gridx = GridBagConstraints.RELATIVE;
63      constraints.weightx = 0;
64      constraints.weighty = 0;
65      constraints.anchor = GridBagConstraints.WEST;
66      constraints.fill = GridBagConstraints.HORIZONTAL;
67      constraints.insets = new Insets(2,4,2,4);
68      //ROW 0
69      constraints.gridx = 1;
70      getContentPane().add(new JLabel("Document"), constraints);
71      constraints.gridx = GridBagConstraints.RELATIVE;
72      getContentPane().add(new JLabel("Annotation Set"), constraints);
73      //ROW 1
74      constraints.gridy = 1;
75      constraints.gridx = GridBagConstraints.RELATIVE;
76      constraints.gridwidth = 1;
77      getContentPane().add(new JLabel("Key:"), constraints);
78      keyDocCombo = new JComboBox();
79      getContentPane().add(keyDocCombo, constraints);
80      keySetCombo = new JComboBox();
81      getContentPane().add(keySetCombo, constraints);
82      getContentPane().add(new JLabel("Annotation Type:"), constraints);
83      annTypeCombo = new JComboBox();
84      constraints.gridwidth = 3;
85      getContentPane().add(annTypeCombo, constraints);
86      constraints.gridwidth = 1;
87      getContentPane().add(new JLabel("F-Measure Weight"), constraints);
88      constraints.gridheight = 2;
89      doDiffBtn = new JButton(new DiffAction());
90      getContentPane().add(doDiffBtn, constraints);
91      constraints.weightx = 1;
92      getContentPane().add(Box.createHorizontalGlue(), constraints);
93      //ROW 2
94      constraints.gridy = 2;
95      constraints.gridx = 0;
96      constraints.gridheight = 1;
97      constraints.weightx = 0;
98      getContentPane().add(new JLabel("Response:"), constraints);
99      constraints.gridx = GridBagConstraints.RELATIVE;
100     resDocCombo = new JComboBox();
101     getContentPane().add(resDocCombo, constraints);
102     resSetCombo = new JComboBox();
103     getContentPane().add(resSetCombo, constraints);
104     getContentPane().add(new JLabel("Features:"), constraints);
105     ButtonGroup btnGrp = new ButtonGroup();
106     allFeaturesBtn = new JRadioButton("All");
107     allFeaturesBtn.setOpaque(false);
108     btnGrp.add(allFeaturesBtn);
109     getContentPane().add(allFeaturesBtn, constraints);
110     someFeaturesBtn = new JRadioButton("Some");
111     someFeaturesBtn.setOpaque(false);
112     btnGrp.add(someFeaturesBtn);
113     getContentPane().add(someFeaturesBtn, constraints);
114     noFeaturesBtn = new JRadioButton("None");
115     noFeaturesBtn.setOpaque(false);
116     btnGrp.add(noFeaturesBtn);
117     getContentPane().add(noFeaturesBtn, constraints);
118     noFeaturesBtn.setSelected(true);
119     weightTxt = new JTextField("1.00");
120     getContentPane().add(weightTxt, constraints);
121     //ROW 3 -> the table
122     constraints.gridy = 3;
123     constraints.gridx = 0;
124     constraints.gridwidth = 10;
125     constraints.weightx = 1;
126     constraints.weighty = 1;
127     constraints.fill = GridBagConstraints.BOTH;
128     diffTableModel = new DiffTableModel();
129     diffTable = new XJTable(diffTableModel);
130     diffTable.setDefaultRenderer(String.class, new DiffTableCellRenderer());
131     diffTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
132     diffTable.setComparator(DiffTableModel.COL_MATCH, new Comparator(){
133       public int compare(Object o1, Object o2){
134         String label1 = (String)o1;
135         String label2 = (String)o2;
136         int match1 = 0;
137         while(!label1.equals(matchLabel[match1])) match1++;
138         int match2 = 0;
139         while(!label2.equals(matchLabel[match2])) match2++;
140 
141         return match1 - match2;
142       }
143     });
144 
145     /* Niraj */
146     Comparator startEndComparator = new Comparator() {
147       public int compare(Object o1, Object o2) {
148         String no1 = (String) o1;
149         String no2 = (String) o2;
150         if (no1.trim().equals("") && no2.trim().equals("")) {
151           return 0;
152         }
153         else if (no1.trim().equals("")) {
154           return -1;
155         }
156         else if (no2.trim().equals("")) {
157           return 1;
158         }
159         int n1 = Integer.parseInt(no1);
160         int n2 = Integer.parseInt(no2);
161         if(n1 == n2)
162           return 0;
163         else if(n1 > n2)
164           return 1;
165         else
166           return -1;
167       }
168     };
169 
170     diffTable.setComparator(DiffTableModel.COL_KEY_START, startEndComparator);
171     diffTable.setComparator(DiffTableModel.COL_KEY_END, startEndComparator);
172     diffTable.setComparator(DiffTableModel.COL_RES_START, startEndComparator);
173     diffTable.setComparator(DiffTableModel.COL_RES_END, startEndComparator);
174     /* End */
175 
176     diffTable.setSortable(true);
177     diffTable.setSortedColumn(DiffTableModel.COL_MATCH);
178     diffTable.setAscending(false);
179     scroller = new JScrollPane(diffTable);
180     getContentPane().add(scroller, constraints);
181 
182     //build the results pane
183     resultsPane = new JPanel();
184     resultsPane.setLayout(new GridBagLayout());
185     //COLUMN 0
186     constraints.gridy = GridBagConstraints.RELATIVE;
187     constraints.gridx = 0;
188     constraints.weightx = 0;
189     constraints.weighty = 0;
190     constraints.gridwidth = 1;
191     constraints.gridheight = 1;
192     constraints.anchor = GridBagConstraints.WEST;
193     constraints.fill = GridBagConstraints.NONE;
194     JLabel lbl = new JLabel("Correct:");
195     lbl.setBackground(diffTable.getBackground());
196     resultsPane.add(lbl, constraints);
197     lbl = new JLabel("Partially Correct:");
198     lbl.setBackground(PARTIALLY_CORRECT_BG);
199     lbl.setOpaque(true);
200     resultsPane.add(lbl, constraints);
201     lbl = new JLabel("Missing:");
202     lbl.setBackground(MISSING_BG);
203     lbl.setOpaque(true);
204     resultsPane.add(lbl, constraints);
205     lbl = new JLabel("False Positives:");
206     lbl.setBackground(FALSE_POZITIVE_BG);
207     lbl.setOpaque(true);
208     resultsPane.add(lbl, constraints);
209 
210     //COLUMN 1
211     constraints.gridx = 1;
212     correctLbl = new JLabel("0");
213     resultsPane.add(correctLbl, constraints);
214     partiallyCorrectLbl = new JLabel("0");
215     resultsPane.add(partiallyCorrectLbl, constraints);
216     missingLbl = new JLabel("0");
217     resultsPane.add(missingLbl, constraints);
218     falsePozLbl = new JLabel("0");
219     resultsPane.add(falsePozLbl, constraints);
220 
221     //COLMUN 2
222     constraints.gridx = 2;
223     constraints.insets = new Insets(4, 30, 4, 4);
224     resultsPane.add(Box.createGlue());
225     lbl = new JLabel("Strict:");
226     resultsPane.add(lbl, constraints);
227     lbl = new JLabel("Lenient:");
228     resultsPane.add(lbl, constraints);
229     lbl = new JLabel("Average:");
230     resultsPane.add(lbl, constraints);
231 
232     //COLMUN 3
233     constraints.gridx = 3;
234     constraints.insets = new Insets(4, 4, 4, 4);
235     lbl = new JLabel("Recall");
236     resultsPane.add(lbl, constraints);
237     recallStrictLbl = new JLabel("0.0000");
238     resultsPane.add(recallStrictLbl, constraints);
239     recallLenientLbl = new JLabel("0.0000");
240     resultsPane.add(recallLenientLbl, constraints);
241     recallAveLbl = new JLabel("0.0000");
242     resultsPane.add(recallAveLbl, constraints);
243 
244     //COLMUN 4
245     constraints.gridx = 4;
246     lbl = new JLabel("Precision");
247     resultsPane.add(lbl, constraints);
248     precisionStrictLbl = new JLabel("0.0000");
249     resultsPane.add(precisionStrictLbl, constraints);
250     precisionLenientLbl = new JLabel("0.0000");
251     resultsPane.add(precisionLenientLbl, constraints);
252     precisionAveLbl = new JLabel("0.0000");
253     resultsPane.add(precisionAveLbl, constraints);
254 
255     //COLMUN 5
256     constraints.gridx = 5;
257     lbl = new JLabel("F-Measure");
258     resultsPane.add(lbl, constraints);
259     fmeasureStrictLbl = new JLabel("0.0000");
260     resultsPane.add(fmeasureStrictLbl, constraints);
261     fmeasureLenientLbl = new JLabel("0.0000");
262     resultsPane.add(fmeasureLenientLbl, constraints);
263     fmeasureAveLbl = new JLabel("0.0000");
264     resultsPane.add(fmeasureAveLbl, constraints);
265 
266     //COLMUN 6
267     constraints.gridx = 6;
268     resultsPane.add(new JButton(new HTMLExportAction()), constraints);
269 
270     //Finished building the results pane
271     //Add it to the dialog
272 
273     resultsPane.setBackground(Color.red);
274 
275     //ROW 4 - the results
276     constraints.gridy = 4;
277     constraints.gridx = 0;
278     constraints.weightx = 0;
279     constraints.weighty = 0;
280     constraints.gridwidth = 8;
281     constraints.gridheight = 1;
282     constraints.anchor = GridBagConstraints.WEST;
283     constraints.fill = GridBagConstraints.NONE;
284     getContentPane().add(resultsPane, constraints);
285 
286     // the progress bar
287     
288     progressBar = new JProgressBar();
289     constraints.gridx = 8;
290     constraints.weightx = 1;
291     constraints.anchor = GridBagConstraints.SOUTHEAST;
292     constraints.fill = GridBagConstraints.HORIZONTAL;
293     getContentPane().add(progressBar, constraints);
294     
295 
296     
297     //set the colours
298     Color background = diffTable.getBackground();
299     getContentPane().setBackground(background);
300     scroller.setBackground(background);
301     scroller.getViewport().setBackground(background);
302     resultsPane.setBackground(background);
303 
304     featureslistModel = new DefaultListModel();
305     featuresList = new JList(featureslistModel);
306     featuresList.
307         setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
308   }
309 
310   protected void initListeners(){
311     keyDocCombo.addActionListener(new ActionListener(){
312       public void actionPerformed(ActionEvent evt){
313         Document newDoc = (Document)documents.get(keyDocCombo.getSelectedIndex());
314         if(keyDoc != newDoc){
315           pairings.clear();
316           diffTableModel.fireTableDataChanged();
317           keyDoc = newDoc;
318           keySets = new ArrayList();
319           List keySetNames = new ArrayList();
320           keySets.add(keyDoc.getAnnotations());
321           keySetNames.add("[Default set]");
322 
323           if(keyDoc.getNamedAnnotationSets() != null) {
324             Iterator setIter = keyDoc.getNamedAnnotationSets().keySet().
325                                iterator();
326             while (setIter.hasNext()) {
327               String name = (String) setIter.next();
328               keySetNames.add(name);
329               keySets.add(keyDoc.getAnnotations(name));
330             }
331           }
332           keySetCombo.setModel(new DefaultComboBoxModel(keySetNames.toArray()));
333           if(!keySetNames.isEmpty())keySetCombo.setSelectedIndex(0);
334 
335         }
336       }
337     });
338 
339     resDocCombo.addActionListener(new ActionListener(){
340       public void actionPerformed(ActionEvent evt){
341         Document newDoc = (Document)documents.get(resDocCombo.getSelectedIndex());
342         if(resDoc != newDoc){
343           resDoc = newDoc;
344           pairings.clear();
345           diffTableModel.fireTableDataChanged();
346           resSets = new ArrayList();
347           List resSetNames = new ArrayList();
348           resSets.add(resDoc.getAnnotations());
349           resSetNames.add("[Default set]");
350           if(resDoc.getNamedAnnotationSets() != null) {
351             Iterator setIter = resDoc.getNamedAnnotationSets().keySet().
352                                iterator();
353             while (setIter.hasNext()) {
354               String name = (String) setIter.next();
355               resSetNames.add(name);
356               resSets.add(resDoc.getAnnotations(name));
357             }
358           }
359           resSetCombo.setModel(new DefaultComboBoxModel(resSetNames.toArray()));
360           if(!resSetNames.isEmpty())resSetCombo.setSelectedIndex(0);
361 
362         }
363       }
364     });
365 
366     /**
367      * This populates the types combo when set selection changes
368      */
369     ActionListener setComboActionListener = new ActionListener(){
370       public void actionPerformed(ActionEvent evt){
371         keySet = keySets == null || keySets.isEmpty()?
372                  null :
373                 (AnnotationSet)keySets.get(keySetCombo.getSelectedIndex());
374         resSet = resSets == null || resSets.isEmpty()?
375                 null :
376                (AnnotationSet)resSets.get(resSetCombo.getSelectedIndex());
377         Set keyTypes = (keySet == null || keySet.isEmpty()) ?
378                 new HashSet() : keySet.getAllTypes();
379         Set resTypes = (resSet == null || resSet.isEmpty()) ?
380                 new HashSet() : resSet.getAllTypes();
381         Set types = new HashSet(keyTypes);
382         types.retainAll(resTypes);
383         List typesList = new ArrayList(types);
384         Collections.sort(typesList);
385         annTypeCombo.setModel(new DefaultComboBoxModel(typesList.toArray()));
386         if(typesList.size() > 0) annTypeCombo.setSelectedIndex(0);
387       }
388     };
389     keySetCombo.addActionListener(setComboActionListener);
390 
391     resSetCombo.addActionListener(setComboActionListener);
392 
393     someFeaturesBtn.addActionListener(new ActionListener(){
394       public void actionPerformed(ActionEvent evt){
395         if(someFeaturesBtn.isSelected()){
396           if(keySet == null || keySet.isEmpty() ||
397                   annTypeCombo.getSelectedItem() == null) return;
398           Iterator annIter = keySet.
399               get((String)annTypeCombo.getSelectedItem()).iterator();
400           Set featureSet = new HashSet();
401           while(annIter.hasNext()){
402             Annotation ann = (Annotation)annIter.next();
403             Map someFeatures = ann.getFeatures();
404             if(someFeatures != null) featureSet.addAll(someFeatures.keySet());
405           }
406           List featureLst = new ArrayList(featureSet);
407           Collections.sort(featureLst);
408           featureslistModel.clear();
409           Iterator featIter = featureLst.iterator();
410           int index = 0;
411           while(featIter.hasNext()){
412             String aFeature = (String)featIter.next();
413             featureslistModel.addElement(aFeature);
414             if(significantFeatures.contains(aFeature))
415               featuresList.addSelectionInterval(index, index);
416             index ++;
417           }
418            int ret = JOptionPane.showConfirmDialog(AnnotationDiffGUI.this,
419                   new JScrollPane(featuresList),
420                   "Select features",
421                   JOptionPane.OK_CANCEL_OPTION,
422                   JOptionPane.QUESTION_MESSAGE);
423            if(ret == JOptionPane.OK_OPTION){
424              significantFeatures.clear();
425              int[] selIdxs = featuresList.getSelectedIndices();
426              for(int i = 0; i < selIdxs.length; i++){
427                significantFeatures.add(featureslistModel.get(selIdxs[i]));
428              }
429            }
430         }
431       }
432     });
433   }
434 
435 
436   public void pack(){
437     super.pack();
438 
439     setSize(getWidth(), getHeight() + 100);
440   }
441   protected void populateGUI(){
442     try{
443       documents = Gate.getCreoleRegister().getAllInstances("gate.Document");
444     }catch(GateException ge){
445       throw new GateRuntimeException(ge);
446     }
447     List documentNames = new ArrayList(documents.size());
448     for(int i =0; i < documents.size(); i++){
449       Document doc = (Document)documents.get(i);
450       documentNames.add(doc.getName());
451     }
452     keyDocCombo.setModel(new DefaultComboBoxModel(documentNames.toArray()));
453     resDocCombo.setModel(new DefaultComboBoxModel(documentNames.toArray()));
454     if(!documents.isEmpty()){
455       keyDocCombo.setSelectedIndex(0);
456       resDocCombo.setSelectedIndex(0);
457     }
458   }
459 
460 
461   protected class DiffAction extends AbstractAction{
462     public DiffAction(){
463       super("Do Diff");
464       putValue(SHORT_DESCRIPTION, "Performs the diff");
465     }
466 
467     public void actionPerformed(ActionEvent evt){
468       //start the progress bar
469       progressBar.setIndeterminate(true);
470       
471       //start a new thread for the processing
472       Runnable runnable = new Runnable(){
473         public void run(){
474           Set keys = keySet.get((String)annTypeCombo.getSelectedItem());
475           Set responses = resSet.get((String)annTypeCombo.getSelectedItem());
476           if(keys == null) keys = new HashSet();
477           if(responses == null) responses = new HashSet();
478           if(someFeaturesBtn.isSelected())
479             differ.setSignificantFeaturesSet(significantFeatures);
480           else if(allFeaturesBtn.isSelected())
481             differ.setSignificantFeaturesSet(null);
482           else differ.setSignificantFeaturesSet(new HashSet());
483           pairings.clear();
484           pairings.addAll(differ.calculateDiff(keys, responses));
485           //return to the Swing thread to update the GUI
486           SwingUtilities.invokeLater(new Runnable(){
487             public void run(){
488               diffTableModel.fireTableDataChanged();
489               correctLbl.setText(Integer.toString(differ.getCorrectMatches()));
490               partiallyCorrectLbl.setText(
491                       Integer.toString(differ.getPartiallyCorrectMatches()));
492               missingLbl.setText(Integer.toString(differ.getMissing()));
493               falsePozLbl.setText(Integer.toString(differ.getSpurious()));
494 
495               NumberFormat formatter = NumberFormat.getInstance();
496               formatter.setMaximumFractionDigits(4);
497               formatter.setMinimumFractionDigits(2);
498               recallStrictLbl.setText(formatter.format(differ.getRecallStrict()));
499               recallLenientLbl.setText(formatter.format(differ.getRecallLenient()));
500               recallAveLbl.setText(formatter.format(differ.getRecallAverage()));
501               precisionStrictLbl.setText(formatter.format(differ.getPrecisionStrict()));
502               precisionLenientLbl.setText(formatter.format(differ.getPrecisionLenient()));
503               precisionAveLbl.setText(formatter.format(differ.getPrecisionAverage()));
504 
505               double weight = Double.parseDouble(weightTxt.getText());
506               fmeasureStrictLbl.setText(formatter.format(differ.getFMeasureStrict(weight)));
507               fmeasureLenientLbl.setText(formatter.format(differ.getFMeasureLenient(weight)));
508               fmeasureAveLbl.setText(formatter.format(differ.getFMeasureAverage(weight)));
509               //stop the progress bar
510               progressBar.setIndeterminate(false);
511             }
512           });  
513         }
514       };
515       Thread thread = new Thread(runnable);
516       thread.setPriority(Thread.MIN_PRIORITY);
517       thread.start();
518       
519       
520     }
521   }
522 
523   protected class HTMLExportAction extends AbstractAction{
524     public HTMLExportAction(){
525       super("Export to HTML");
526     }
527     public void actionPerformed(ActionEvent evt){
528       JFileChooser fileChooser = MainFrame.getFileChooser();
529       File currentFile = fileChooser.getSelectedFile();
530       String nl = Strings.getNl();
531       String parent = (currentFile != null) ? currentFile.getParent() :
532         System.getProperty("user.home");
533       String fileName = (resDoc.getSourceUrl() != null) ?
534               resDoc.getSourceUrl().getFile() :
535               resDoc.getName();
536       fileName += "_" + annTypeCombo.getSelectedItem().toString();
537       fileName += ".html";
538       fileChooser.setSelectedFile(new File(parent, fileName));
539       ExtensionFileFilter fileFilter = new ExtensionFileFilter();
540       fileFilter.addExtension(".html");
541       fileChooser.setFileFilter(fileFilter);
542       fileChooser.setAcceptAllFileFilterUsed(true);
543       fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
544       int res = fileChooser.showSaveDialog(AnnotationDiffGUI.this);
545       if(res == JFileChooser.APPROVE_OPTION){
546         File saveFile = fileChooser.getSelectedFile();
547         try{
548           Writer fw = new BufferedWriter(new FileWriter(saveFile));
549           //write the header
550           fw.write(HEADER_1);
551           fw.write(resDoc.getName() + " " +
552                   annTypeCombo.getSelectedItem().toString() +
553                   " annotations");
554           fw.write(HEADER_2 + nl);
555           fw.write("<H2>Annotation Diff - comparing " +
556                   annTypeCombo.getSelectedItem().toString() +
557                   " annotations" + "</H2>");
558           fw.write("<TABLE cellpadding=\"5\" border=\"0\"");
559           fw.write(nl);
560           fw.write("<TR>" + nl);
561           fw.write("\t<TH align=\"left\">&nbsp;</TH>" + nl);
562           fw.write("\t<TH align=\"left\">Document</TH>" + nl);
563           fw.write("\t<TH align=\"left\">Annotation Set</TH>" + nl);
564           fw.write("</TR>" + nl);
565 
566           fw.write("<TR>" + nl);
567           fw.write("\t<TH align=\"left\">Key</TH>" + nl);
568           fw.write("\t<TD align=\"left\">" + keyDoc.getName() + "</TD>" + nl);
569           fw.write("\t<TD align=\"left\">" + keySet.getName() + "</TD>" + nl);
570           fw.write("</TR>" + nl);
571           fw.write("<TR>" + nl);
572           fw.write("\t<TH align=\"left\">Response</TH>" + nl);
573           fw.write("\t<TD align=\"left\">" + resDoc.getName() + "</TD>" + nl);
574           fw.write("\t<TD align=\"left\">" + resSet.getName() + "</TD>" + nl);
575           fw.write("</TR>" + nl);
576           fw.write("</TABLE>" + nl);
577           fw.write("<BR><BR><BR>" + nl);
578           //write the results
579           java.text.NumberFormat format = java.text.NumberFormat.getInstance();
580           format.setMaximumFractionDigits(4);
581           fw.write("Recall: " + format.format(differ.getRecallStrict()) + "<br>" + nl);
582           fw.write("Precision: " + format.format(differ.getPrecisionStrict()) + "<br>" + nl);
583           fw.write("F-measure: " + format.format(differ.getFMeasureStrict(1)) + "<br>" + nl);
584           fw.write("<br>");
585           fw.write("Correct matches: " + differ.getCorrectMatches() + "<br>" + nl);
586           fw.write("Partially Correct matches: " +
587               differ.getPartiallyCorrectMatches() + "<br>" + nl);
588           fw.write("Missing: " + differ.getMissing() + "<br>" + nl);
589           fw.write("False positives: " + differ.getSpurious() + "<br>" + nl);
590 //          fw.write("<hr>" + nl);
591           //get a list of columns that need to be displayed
592           int[] cols = new int[diffTableModel.getColumnCount()];
593           int maxColIdx = -1;
594           for(int i = 0; i < cols.length; i++){
595             if(!diffTable.isColumnHidden(i)){
596               maxColIdx ++;
597               cols[maxColIdx] = i;
598             }
599           }
600           fw.write(HEADER_3 + nl + "<TR>" + nl);
601           for(int col = 0; col <= maxColIdx; col++){
602             fw.write("\t<TH align=\"left\">" + diffTable.getColumnName(cols[col]) +
603                     "</TH>" + nl);
604           }
605           fw.write("</TR>");
606           int rowCnt = diffTableModel.getRowCount();
607           for(int row = 0; row < rowCnt; row ++){
608             fw.write("<TR>");
609             for(int col = 0; col <= maxColIdx; col++){
610               Color bgCol = diffTableModel.getBackgroundAt(
611                       diffTable.rowViewToModel(row),
612                       diffTable.convertColumnIndexToModel(cols[col]));
613               fw.write("\t<TD bgcolor=\"#" +
614                       Integer.toHexString(bgCol.getRGB()).substring(2) +
615                       "\">" +
616                       diffTable.getValueAt(row, cols[col]) +
617                       "</TD>" + nl);
618             }
619             fw.write("</TR>");
620           }
621           fw.write(FOOTER);
622           fw.flush();
623           fw.close();
624 
625         }catch(IOException ioe){
626           JOptionPane.showMessageDialog(AnnotationDiffGUI.this, ioe.toString(),
627                   "GATE", JOptionPane.ERROR_MESSAGE);
628           ioe.printStackTrace();
629         }
630       }
631     }
632 
633     static final String HEADER_1 = "<html><head><title>";
634     static final String HEADER_2 = "</title></head><body>";
635     static final String HEADER_3 = "<table cellpadding=\"0\" border=\"1\">";
636     static final String FOOTER = "</table></body></html>";
637   }
638 
639   protected class DiffTableCellRenderer extends DefaultTableCellRenderer{
640     public Component getTableCellRendererComponent(JTable table,
641             Object value,
642             boolean isSelected,
643             boolean hasFocus,
644             int row,
645             int column){
646       Component res = super.getTableCellRendererComponent(table,
647               value, false, hasFocus, row, column);
648       res.setBackground(isSelected ? table.getSelectionBackground() :
649               diffTableModel.getBackgroundAt(diffTable.rowViewToModel(row),
650                       column));
651       res.setForeground(isSelected ? table.getSelectionForeground() :
652         table.getForeground());
653       return res;
654     }
655   }
656 
657   protected class DiffTableModel extends AbstractTableModel{
658     public int getRowCount(){
659       return pairings.size();
660     }
661 
662     public Class getColumnClass(int columnIndex){
663       return String.class;
664     }
665 
666     public int getColumnCount(){
667       return COL_CNT;
668     }
669 
670     public String getColumnName(int column){
671       switch(column){
672         case COL_KEY_START: return "Start";
673         case COL_KEY_END: return "End";
674         case COL_KEY_STRING: return "Key";
675         case COL_KEY_FEATURES: return "Features";
676         case COL_MATCH: return "";
677         case COL_RES_START: return "Start";
678         case COL_RES_END: return "End";
679         case COL_RES_STRING: return "Response";
680         case COL_RES_FEATURES: return "Features";
681         default: return "?";
682       }
683     }
684 
685     public Color getBackgroundAt(int row, int column){
686       AnnotationDiffer.Pairing pairing =
687         (AnnotationDiffer.Pairing)pairings.get(row);
688       Color colKey = pairing.getType() == AnnotationDiffer.CORRECT ?
689                      diffTable.getBackground() :
690                        (pairing.getType() == AnnotationDiffer.PARTIALLY_CORRECT ?
691                        PARTIALLY_CORRECT_BG :
692                          MISSING_BG);
693       if(pairing.getKey() == null) colKey = diffTable.getBackground();
694       Color colRes = pairing.getType() == AnnotationDiffer.CORRECT ?
695                      diffTable.getBackground() :
696                        (pairing.getType() == AnnotationDiffer.PARTIALLY_CORRECT ?
697                        PARTIALLY_CORRECT_BG :
698                          FALSE_POZITIVE_BG);
699       if(pairing.getResponse() == null) colRes = diffTable.getBackground();
700       switch(column){
701         case COL_KEY_START: return colKey;
702         case COL_KEY_END: return colKey;
703         case COL_KEY_STRING: return colKey;
704         case COL_KEY_FEATURES: return colKey;
705         case COL_MATCH: return diffTable.getBackground();
706         case COL_RES_START: return colRes;
707         case COL_RES_END: return colRes;
708         case COL_RES_STRING: return colRes;
709         case COL_RES_FEATURES: return colRes;
710         default: return diffTable.getBackground();
711       }
712     }
713 
714     public Object getValueAt(int row, int column){
715       AnnotationDiffer.Pairing pairing =
716         (AnnotationDiffer.Pairing)pairings.get(row);
717       Annotation key = pairing.getKey();
718       String keyStr = "";
719       try{
720         if(key != null && keyDoc != null){
721           keyStr = keyDoc.getContent().getContent(key.getStartNode().getOffset(),
722                   key.getEndNode().getOffset()).toString();
723         }
724       }catch(InvalidOffsetException ioe){
725         //this should never happen
726         throw new GateRuntimeException(ioe);
727       }
728       Annotation res = pairing.getResponse();
729       String resStr = "";
730       try{
731         if(res != null && resDoc != null){
732           resStr = resDoc.getContent().getContent(res.getStartNode().getOffset(),
733                   res.getEndNode().getOffset()).toString();
734         }
735       }catch(InvalidOffsetException ioe){
736         //this should never happen
737         throw new GateRuntimeException(ioe);
738       }
739 
740       switch(column){
741         case COL_KEY_START: return key == null ? "" :
742           key.getStartNode().getOffset().toString();
743         case COL_KEY_END: return key == null ? "" :
744           key.getEndNode().getOffset().toString();
745         case COL_KEY_STRING: return keyStr;
746         case COL_KEY_FEATURES: return key == null ? "" :
747           key.getFeatures().toString();
748         case COL_MATCH: return matchLabel[pairing.getType()];
749         case COL_RES_START: return res == null ? "" :
750           res.getStartNode().getOffset().toString();
751         case COL_RES_END: return res == null ? "" :
752           res.getEndNode().getOffset().toString();
753         case COL_RES_STRING: return resStr;
754         case COL_RES_FEATURES: return res == null ? "" :
755           res.getFeatures().toString();
756         default: return "?";
757       }
758     }
759 
760     protected static final int COL_CNT = 9;
761     protected static final int COL_KEY_START = 0;
762     protected static final int COL_KEY_END = 1;
763     protected static final int COL_KEY_STRING = 2;
764     protected static final int COL_KEY_FEATURES = 3;
765     protected static final int COL_MATCH = 4;
766     protected static final int COL_RES_START = 5;
767     protected static final int COL_RES_END = 6;
768     protected static final int COL_RES_STRING = 7;
769     protected static final int COL_RES_FEATURES = 8;
770   }
771 
772   protected AnnotationDiffer differ;
773   protected List pairings;
774   protected Document keyDoc;
775   protected Document resDoc;
776   protected Set significantFeatures;
777   protected List documents;
778   protected List keySets;
779   protected List resSets;
780   protected AnnotationSet keySet;
781   protected AnnotationSet resSet;
782 
783   protected JList featuresList;
784   protected DefaultListModel featureslistModel;
785   protected DiffTableModel diffTableModel;
786   protected XJTable diffTable;
787   protected JScrollPane scroller;
788   protected JComboBox keyDocCombo;
789   protected JComboBox keySetCombo;
790   protected JComboBox annTypeCombo;
791   protected JComboBox resDocCombo;
792   protected JComboBox resSetCombo;
793   protected JProgressBar progressBar;
794 
795   protected JRadioButton allFeaturesBtn;
796   protected JRadioButton someFeaturesBtn;
797   protected JRadioButton noFeaturesBtn;
798   protected JTextField weightTxt;
799   protected JButton doDiffBtn;
800 
801   protected JPanel resultsPane;
802   protected JLabel correctLbl;
803   protected JLabel partiallyCorrectLbl;
804   protected JLabel missingLbl;
805   protected JLabel falsePozLbl;
806   protected JLabel recallStrictLbl;
807   protected JLabel precisionStrictLbl;
808   protected JLabel fmeasureStrictLbl;
809   protected JLabel recallLenientLbl;
810   protected JLabel precisionLenientLbl;
811   protected JLabel fmeasureLenientLbl;
812   protected JLabel recallAveLbl;
813   protected JLabel precisionAveLbl;
814   protected JLabel fmeasureAveLbl;
815 
816   protected static final Color PARTIALLY_CORRECT_BG = new Color(173,215,255);
817   protected static final Color MISSING_BG = new Color(255,173,181);;
818   protected static final Color FALSE_POZITIVE_BG = new Color(255,231,173);
819   protected static final String[] matchLabel;
820   static{
821     matchLabel = new String[3];
822     matchLabel[AnnotationDiffer.CORRECT] = "=";
823     matchLabel[AnnotationDiffer.PARTIALLY_CORRECT] = "~";
824     matchLabel[AnnotationDiffer.WRONG] = "!=";
825   }
826 }
827