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