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