Log in Help
Print
Homegatesrcgateutil 〉 AnnotationDiffer.java
 
/*
 *  Copyright (c) 1995-2011, The University of Sheffield. See the file
 *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
 *
 *  This file is part of GATE (see http://gate.ac.uk/), and is free
 *  software, licenced under the GNU Library General Public License,
 *  Version 2, June 1991 (in the distribution as file licence.html,
 *  and also available at http://gate.ac.uk/gate/licence.html).
 *
 *  Valentin Tablan 28/01/2003
 *
 *  $Id: AnnotationDiffer.java 14675 2011-12-05 19:02:25Z thomas_heitz $
 *
 */
package gate.util;

import java.text.NumberFormat;
import java.util.*;

import gate.Annotation;

/**
 * This class provides the logic used by the Annotation Diff tool. It starts 
 * with two collections of annotation objects, one of key annotations
 * (representing the gold standard) and one of response annotations 
 * (representing the system's responses). It will then pair the keys and 
 * responses in a way that maximises the score. Each key - response pair gets a 
 * score of {@link #CORRECT_VALUE} (2), {@link #PARTIALLY_CORRECT_VALUE} (1) or 
 * {@link #WRONG_VALUE} (0)depending on whether the two annotations match are 
 * overlapping or completely unmatched. Each pairing also has a type of 
 * {@link #CORRECT_TYPE}, {@link #PARTIALLY_CORRECT_TYPE}, 
 * {@link #SPURIOUS_TYPE} or {@link #MISSING_TYPE} further detailing the type of
 * error for the wrong matches (<i>missing</i> being the keys that weren't 
 * matched to a response while  <i>spurious</i> are the responses that were 
 * over-generated and are not matching any key.
 *   
 * Precision, recall and f-measure are also calculated.
 */
public class AnnotationDiffer {

  /**
   * Constructor to be used when you have a collection of AnnotationDiffer
   * and want to consider it as only one AnnotationDiffer.
   * Then you can only use the methods getPrecision/Recall/FMeasure...().
   * @param differs collection to be regrouped in one AnnotationDiffer
   */
  public AnnotationDiffer(Collection<AnnotationDiffer> differs) {
    correctMatches = 0;
    partiallyCorrectMatches = 0;
    missing = 0;
    spurious = 0;
    int keyCount = 0;
    int responseCount = 0;
    for (AnnotationDiffer differ : differs) {
      // set the correct, partial, spurious and missing values to be
      // the sum of those in the collection
      correctMatches += differ.getCorrectMatches();
      partiallyCorrectMatches += differ.getPartiallyCorrectMatches();
      missing += differ.getMissing();
      spurious += differ.getSpurious();
      keyCount += differ.getKeysCount();
      responseCount += differ.getResponsesCount();
    }
    keyList = new ArrayList<Annotation>(Collections.nCopies(keyCount, (Annotation) null));
    responseList = new ArrayList<Annotation>(Collections.nCopies(responseCount, (Annotation) null));
  }

  public AnnotationDiffer() {
    // empty constructor
  }

  /**
   * Interface representing a pairing between a key annotation and a response 
   * one.
   */
  public static interface Pairing{
    /**
     * Gets the key annotation of the pairing. Can be <tt>null</tt> (for 
     * spurious matches).
     * @return an {@link Annotation} object.
     */
    public Annotation getKey();

    public int getResponseIndex();
    public int getValue();
    public int getKeyIndex();
    public int getScore();
    public void setType(int i);
    public void remove();
    
    /**
     * Gets the response annotation of the pairing. Can be <tt>null</tt> (for 
     * missing matches).
     * @return an {@link Annotation} object.
     */
    public Annotation getResponse();
    
    /**
     * Gets the type of the pairing, one of {@link #CORRECT_TYPE},
     * {@link #PARTIALLY_CORRECT_TYPE}, {@link #SPURIOUS_TYPE} or
     * {@link #MISSING_TYPE},
     * @return an <tt>int</tt> value.
     */
    public int getType();
  }

  /**
   * Computes a diff between two collections of annotations.
   * @param key the collection of key annotations.
   * @param response the collection of response annotations.
   * @return a list of {@link Pairing} objects representing the pairing set
   * that results in the best score.
   */
  public List<Pairing> calculateDiff(Collection<Annotation> key, Collection<Annotation> response){
    
    //initialise data structures
    if(key == null || key.size() == 0)
      keyList = new ArrayList<Annotation>();
    else
      keyList = new ArrayList<Annotation>(key);

    if(response == null || response.size() == 0)
      responseList = new ArrayList<Annotation>();
    else
      responseList = new ArrayList<Annotation>(response);

    if(correctAnnotations != null) {
      correctAnnotations.clear();
    }
    else {
      correctAnnotations = new HashSet<Annotation>();
    }
    if(partiallyCorrectAnnotations != null) {
      partiallyCorrectAnnotations.clear();
    }
    else {
      partiallyCorrectAnnotations = new HashSet<Annotation>();
    }
    if(missingAnnotations != null) {
      missingAnnotations.clear();
    }
    else {
      missingAnnotations = new HashSet<Annotation>();
    }
    if(spuriousAnnotations != null) {
      spuriousAnnotations.clear();
    }
    else {
      spuriousAnnotations = new HashSet<Annotation>();
    }
    
    keyChoices = new ArrayList<List<Pairing>>(keyList.size());
    keyChoices.addAll(Collections.nCopies(keyList.size(), (List<Pairing>) null));
    responseChoices = new ArrayList<List<Pairing>>(responseList.size());
    responseChoices.addAll(Collections.nCopies(responseList.size(), (List<Pairing>) null));

    possibleChoices = new ArrayList<Pairing>();

    //1) try all possible pairings
    for(int i = 0; i < keyList.size(); i++){
      for(int j =0; j < responseList.size(); j++){
        Annotation keyAnn = (Annotation)keyList.get(i);
        Annotation resAnn = (Annotation)responseList.get(j);
        PairingImpl choice = null;
        if(keyAnn.coextensive(resAnn)){
          //we have full overlap -> CORRECT or WRONG
          if(keyAnn.isCompatible(resAnn, significantFeaturesSet)){
            //we have a full match
            choice = new PairingImpl(i, j, CORRECT_VALUE);
          }else{
            //the two annotations are coextensive but don't match
            //we have a missmatch
            choice = new PairingImpl(i, j, MISMATCH_VALUE);
          }
        }else if(keyAnn.overlaps(resAnn)){
          //we have partial overlap -> PARTIALLY_CORRECT or WRONG
          if(keyAnn.isPartiallyCompatible(resAnn, significantFeaturesSet)){
            choice = new PairingImpl(i, j, PARTIALLY_CORRECT_VALUE);
          }else{
            choice = new PairingImpl(i, j, WRONG_VALUE);
          }
        }

        //add the new choice if any
        if (choice != null) {
          addPairing(choice, i, keyChoices);
          addPairing(choice, j, responseChoices);
          possibleChoices.add(choice);
        }
      }//for j
    }//for i

    //2) from all possible pairings, find the maximal set that also
    //maximises the total score
    Collections.sort(possibleChoices, new PairingScoreComparator());
    Collections.reverse(possibleChoices);
    finalChoices = new ArrayList<Pairing>();
    correctMatches = 0;
    partiallyCorrectMatches = 0;
    missing = 0;
    spurious = 0;

    while(!possibleChoices.isEmpty()){
      PairingImpl bestChoice = (PairingImpl)possibleChoices.remove(0);
      bestChoice.consume();
      finalChoices.add(bestChoice);
      switch(bestChoice.value){
        case CORRECT_VALUE:{
          correctAnnotations.add(bestChoice.getResponse());
          correctMatches++;
          bestChoice.setType(CORRECT_TYPE);
          break;
        }
        case PARTIALLY_CORRECT_VALUE:{
          partiallyCorrectAnnotations.add(bestChoice.getResponse());
          partiallyCorrectMatches++;
          bestChoice.setType(PARTIALLY_CORRECT_TYPE);
          break;
        }
        case MISMATCH_VALUE:{
          //this is a missing and a spurious annotations together
          missingAnnotations.add(bestChoice.getKey());
          missing ++;
          spuriousAnnotations.add(bestChoice.getResponse());
          spurious ++;
          bestChoice.setType(MISMATCH_TYPE);
          break;
        }
        case WRONG_VALUE:{
          if(bestChoice.getKey() != null){
            //we have a missed key
            if(missingAnnotations == null) missingAnnotations = new HashSet<Annotation>();
            missingAnnotations.add(bestChoice.getKey());
            missing ++;
            bestChoice.setType(MISSING_TYPE);
          }
          if(bestChoice.getResponse() != null){
            //we have a spurious response
            if(spuriousAnnotations == null) spuriousAnnotations = new HashSet<Annotation>();
            spuriousAnnotations.add(bestChoice.getResponse());
            spurious ++;
            bestChoice.setType(SPURIOUS_TYPE);
          }
          break;
        }
        default:{
          throw new GateRuntimeException("Invalid pairing type: " +
                  bestChoice.value);
        }
      }
    }
    //add choices for the incorrect matches (MISSED, SPURIOUS)
    //get the unmatched keys
    for(int i = 0; i < keyChoices.size(); i++){
      List<Pairing> aList = keyChoices.get(i);
      if(aList == null || aList.isEmpty()){
        if(missingAnnotations == null) missingAnnotations = new HashSet<Annotation>();
        missingAnnotations.add((Annotation)(keyList.get(i)));
        Pairing choice = new PairingImpl(i, -1, WRONG_VALUE);
        choice.setType(MISSING_TYPE);
        finalChoices.add(choice);
        missing ++;
      }
    }

    //get the unmatched responses
    for(int i = 0; i < responseChoices.size(); i++){
      List<Pairing> aList = responseChoices.get(i);
      if(aList == null || aList.isEmpty()){
        if(spuriousAnnotations == null) spuriousAnnotations = new HashSet<Annotation>();
        spuriousAnnotations.add((Annotation)(responseList.get(i)));
        PairingImpl choice = new PairingImpl(-1, i, WRONG_VALUE);
        choice.setType(SPURIOUS_TYPE);
        finalChoices.add(choice);
        spurious ++;
      }
    }

    return finalChoices;
  }

  /**
   * Gets the strict precision (the ratio of correct responses out of all the 
   * provided responses).
   * @return a <tt>double</tt> value.
   */
  public double getPrecisionStrict(){
    if(responseList.size() == 0) {
      return 1.0;
    }
    return correctMatches/(double)responseList.size();
  }

  /**
   * Gets the strict recall (the ratio of key matched to a response out of all 
   * the keys).
   * @return a <tt>double</tt> value.
   */  
  public double getRecallStrict(){
    if(keyList.size() == 0) {
      return 1.0;
    }
    return correctMatches/(double)keyList.size();
  }

  /**
   * Gets the lenient precision (where the partial matches are considered as
   * correct).
   * @return a <tt>double</tt> value.
   */
  public double getPrecisionLenient(){
    if(responseList.size() == 0) {
      return 1.0;
    }
    return ((double)correctMatches + partiallyCorrectMatches) / (double)responseList.size();
  }

  /**
   * Gets the average of the strict and lenient precision values.
   * @return a <tt>double</tt> value.
   */
  public double getPrecisionAverage() {
    return ((double)getPrecisionLenient() + getPrecisionStrict()) / (double)(2.0);
  }

  /**
   * Gets the lenient recall (where the partial matches are considered as
   * correct).
   * @return a <tt>double</tt> value.
   */
  public double getRecallLenient(){
    if(keyList.size() == 0) {
      return 1.0;
    }
    return ((double)correctMatches + partiallyCorrectMatches) / (double)keyList.size();
  }

  /**
   * Gets the average of the strict and lenient recall values.
   * @return a <tt>double</tt> value.
   */
  public double getRecallAverage() {
    return ((double) getRecallLenient() + getRecallStrict()) / (double)(2.0);
  }

  /**
   * Gets the strict F-Measure (the harmonic weighted mean of the strict
   * precision and the strict recall) using the provided parameter as relative 
   * weight. 
   * @param beta The relative weight of precision and recall. A value of 1 
   * gives equal weights to precision and recall. A value of 0 takes the recall 
   * value completely out of the equation.
   * @return a <tt>double</tt>value.
   */
  public double getFMeasureStrict(double beta){
    double precision = getPrecisionStrict();
    double recall = getRecallStrict();
    double betaSq = beta * beta;
    double answer = (double)(((double)(betaSq + 1) * precision * recall ) / (double)(betaSq * precision + recall));
    if(Double.isNaN(answer)) answer = 0.0;
    return answer;
  }

  /**
   * Gets the lenient F-Measure (F-Measure where the lenient precision and 
   * recall values are used) using the provided parameter as relative weight. 
   * @param beta The relative weight of precision and recall. A value of 1 
   * gives equal weights to precision and recall. A value of 0 takes the recall 
   * value completely out of the equation.
   * @return a <tt>double</tt>value.
   */
  public double getFMeasureLenient(double beta){
    double precision = getPrecisionLenient();
    double recall = getRecallLenient();
    double betaSq = beta * beta;
    double answer = (double)(((double)(betaSq + 1) * precision * recall) / ((double)betaSq * precision + recall));
    if(Double.isNaN(answer)) answer = 0.0;
    return answer;
  }

  /**
   * Gets the average of strict and lenient F-Measure values.
   * @param beta The relative weight of precision and recall. A value of 1 
   * gives equal weights to precision and recall. A value of 0 takes the recall 
   * value completely out of the equation.
   * @return a <tt>double</tt>value.
   */  
  public double getFMeasureAverage(double beta) {
    double answer = ((double)getFMeasureLenient(beta) + (double)getFMeasureStrict(beta)) / (double)(2.0);
    return answer;
  }

  /**
   * Gets the number of correct matches.
   * @return an <tt>int<tt> value.
   */
  public int getCorrectMatches(){
    return correctMatches;
  }

  /**
   * Gets the number of partially correct matches.
   * @return an <tt>int<tt> value.
   */
  public int getPartiallyCorrectMatches(){
    return partiallyCorrectMatches;
  }

  /**
   * Gets the number of pairings of type {@link #MISSING_TYPE}.
   * @return an <tt>int<tt> value.
   */
  public int getMissing(){
    return missing;
  }

  /**
   * Gets the number of pairings of type {@link #SPURIOUS_TYPE}.
   * @return an <tt>int<tt> value.
   */
  public int getSpurious(){
    return spurious;
  }

  /**
   * Gets the number of pairings of type {@link #SPURIOUS_TYPE}.
   * @return an <tt>int<tt> value.
   */
  public int getFalsePositivesStrict(){
    return responseList.size() - correctMatches;
  }

  /**
   * Gets the number of responses that aren't either correct or partially 
   * correct.
   * @return an <tt>int<tt> value.
   */
  public int getFalsePositivesLenient(){
    return responseList.size() - correctMatches - partiallyCorrectMatches;
  }

  /**
   * Gets the number of keys provided.
   * @return an <tt>int<tt> value.
   */
  public int getKeysCount() {
    return keyList.size();
  }

  /**
   * Gets the number of responses provided.
   * @return an <tt>int<tt> value.
   */
  public int getResponsesCount() {
    return responseList.size();
  }

  /**
   * Prints to System.out the pairings that are not correct.
   */
  public void printMissmatches(){
    //get the partial correct matches
    for (Pairing aChoice : finalChoices) {
      switch(aChoice.getValue()){
        case PARTIALLY_CORRECT_VALUE:{
          System.out.println("Missmatch (partially correct):");
          System.out.println("Key: " + keyList.get(aChoice.getKeyIndex()).toString());
          System.out.println("Response: " + responseList.get(aChoice.getResponseIndex()).toString());
          break;
        }
      }
    }

    //get the unmatched keys
    for(int i = 0; i < keyChoices.size(); i++){
      List<Pairing> aList = keyChoices.get(i);
      if(aList == null || aList.isEmpty()){
        System.out.println("Missed Key: " + keyList.get(i).toString());
      }
    }

    //get the unmatched responses
    for(int i = 0; i < responseChoices.size(); i++){
      List<Pairing> aList = responseChoices.get(i);
      if(aList == null || aList.isEmpty()){
        System.out.println("Spurious Response: " + responseList.get(i).toString());
      }
    }
  }



  /**
   * Performs some basic checks over the internal data structures from the last
   * run.
   * @throws Exception
   */
  void sanityCheck()throws Exception{
    //all keys and responses should have at most one choice left
    for (List<Pairing> choices : keyChoices)  {
      if(choices != null){
        if(choices.size() > 1){
          throw new Exception("Multiple choices found!");
        }
        else if(!choices.isEmpty()){
          //size must be 1
          Pairing aChoice = choices.get(0);
          //the SAME choice should be found for the associated response
          List<?> otherChoices = (List<?>) responseChoices.get(aChoice.getResponseIndex());
          if(otherChoices == null ||
             otherChoices.size() != 1 ||
             otherChoices.get(0) != aChoice){
            throw new Exception("Reciprocity error!");
          }
        }
      }
    }

    for (List<Pairing> choices : responseChoices) {
      if(choices != null){
        if(choices.size() > 1){
          throw new Exception("Multiple choices found!");
        }
        else if(!choices.isEmpty()){
          //size must be 1
          Pairing aChoice = choices.get(0);
          //the SAME choice should be found for the associated response
          List<?> otherChoices = (List<?>) keyChoices.get(aChoice.getKeyIndex());
          if(otherChoices == null){
            throw new Exception("Reciprocity error : null!");
          }else if(otherChoices.size() != 1){
            throw new Exception("Reciprocity error: not 1!");
          }else if(otherChoices.get(0) != aChoice){
            throw new Exception("Reciprocity error: different!");
          }
        }
      }
    }
  }
  
  /**
   * Adds a new pairing to the internal data structures.
   * @param pairing the pairing to be added
   * @param index the index in the list of pairings
   * @param listOfPairings the list of {@link Pairing}s where the
   * pairing should be added
   */
  protected void addPairing(Pairing pairing, int index, List<List<Pairing>> listOfPairings){
    List<Pairing> existingChoices = listOfPairings.get(index);
    if(existingChoices == null){
      existingChoices = new ArrayList<Pairing>();
      listOfPairings.set(index, existingChoices);
    }
    existingChoices.add(pairing);
  }

  /**
   * Gets the set of features considered significant for the matching algorithm.
   * @return a Set.
   */
  public java.util.Set<?> getSignificantFeaturesSet() {
    return significantFeaturesSet;
  }

  /**
   * Set the set of features considered significant for the matching algorithm.
   * A <tt>null</tt> value means that all features are significant, an empty 
   * set value means that no features are significant while a set of String 
   * values specifies that only features with names included in the set are 
   * significant. 
   * @param significantFeaturesSet a Set of String values or <tt>null<tt>.
   */
  public void setSignificantFeaturesSet(java.util.Set<?> significantFeaturesSet) {
    this.significantFeaturesSet = significantFeaturesSet;
  }

  /**
   * Represents a pairing of a key annotation with a response annotation and
   * the associated score for that pairing.
   */
   public class PairingImpl implements Pairing{
    PairingImpl(int keyIndex, int responseIndex, int value) {
      this.keyIndex = keyIndex;
      this.responseIndex = responseIndex;
      this.value = value;
      scoreCalculated = false;
	    }

    public int getScore(){
      if(scoreCalculated) return score;
      else{
        calculateScore();
        return score;
      }
    }

    public int getKeyIndex() {
      return this.keyIndex;
    }
    
    public int getResponseIndex() {
      return this.responseIndex;
    }
    
    public int getValue() {
      return this.value;
    }
    
    public Annotation getKey(){
      return keyIndex == -1 ? null : (Annotation)keyList.get(keyIndex);
    }

    public Annotation getResponse(){
      return responseIndex == -1 ? null :
        (Annotation)responseList.get(responseIndex);
    }

    public int getType(){
      return type;
    }
    

    public void setType(int type) {
      this.type = type;
    }

    /**
     * Removes all mutually exclusive OTHER choices possible from
     * the data structures.
     * <tt>this</tt> gets removed from {@link #possibleChoices} as well.
     */
    public void consume(){
      possibleChoices.remove(this);
      List<Pairing> sameKeyChoices = keyChoices.get(keyIndex);
      sameKeyChoices.remove(this);
      possibleChoices.removeAll(sameKeyChoices);

      List<Pairing> sameResponseChoices = responseChoices.get(responseIndex);
      sameResponseChoices.remove(this);
      possibleChoices.removeAll(sameResponseChoices);

      for (Pairing item : new ArrayList<Pairing>(sameKeyChoices)) {
        item.remove();
      }
      for (Pairing item : new ArrayList<Pairing>(sameResponseChoices)) {
        item.remove();
      }
      sameKeyChoices.add(this);
      sameResponseChoices.add(this);
    }

    /**
     * Removes this choice from the two lists it belongs to
     */
    public void remove(){
      List<Pairing> fromKey = keyChoices.get(keyIndex);
      fromKey.remove(this);
      List<Pairing> fromResponse = responseChoices.get(responseIndex);
      fromResponse.remove(this);
    }

    /**
     * Calculates the score for this choice as:
     * type - sum of all the types of all OTHER mutually exclusive choices
     */
    void calculateScore(){
      //this needs to be a set so we don't count conflicts twice
      Set<Pairing> conflictSet = new HashSet<Pairing>();
      //add all the choices from the same response annotation
      conflictSet.addAll(responseChoices.get(responseIndex));
      //add all the choices from the same key annotation
      conflictSet.addAll(keyChoices.get(keyIndex));
      //remove this choice from the conflict set
      conflictSet.remove(this);
      score = value;
      for (Pairing item : conflictSet) {
        score -= item.getValue();
      }
      scoreCalculated = true;
    }

    /**
     * The index in the key collection of the key annotation for this pairing
     */
    int keyIndex;
    /**
     * The index in the response collection of the response annotation for this
     * pairing
     */
    int responseIndex;
    
    /**
     * The type of this pairing.
     */
    int type;
    
    /**
     * The value for this pairing. This value depends only on this pairing, not
     * on the conflict set.
     */
    int value;
    
    /**
     * The score of this pairing (calculated based on value and conflict set).
     */
    int score;
    boolean scoreCalculated;
  }

   /**
    * Compares two pairings:
    * the better score is preferred;
    * for the same score the better type is preferred (exact matches are
    * preffered to partial ones).
    */   
	protected static class PairingScoreComparator implements Comparator<Pairing> {
    /**
     * Compares two choices:
     * the better score is preferred;
     * for the same score the better type is preferred (exact matches are
     * preffered to partial ones).
     * @return a positive value if the first pairing is better than the second,
     * zero if they score the same or negative otherwise.
     */

	  public int compare(Pairing first, Pairing second){
      //compare by score
      int res = first.getScore() - second.getScore();
      //compare by type
      if(res == 0) res = first.getType() - second.getType();
      //compare by completeness (a wrong match with both key and response
      //is "better" than one with only key or response
      if(res == 0){
        res = (first.getKey() == null ? 0 : 1) +
              (first.getResponse() == null ? 0 : 1) +
              (second.getKey() == null ? 0 : -1) +
              (second.getResponse() == null ? 0 : -1);
      }
      return res;
	  }
	}

    /**
     * Compares two choices based on start offset of key (or response
     * if key not present) and type if offsets are equal.
     */
	public static class PairingOffsetComparator implements Comparator<Pairing> {
    /**
     * Compares two choices based on start offset of key (or response
     * if key not present) and type if offsets are equal.
     */
	  public int compare(Pairing first, Pairing second){
	    Annotation key1 = first.getKey();
	    Annotation key2 = second.getKey();
	    Annotation res1 = first.getResponse();
	    Annotation res2 = second.getResponse();
	    Long start1 = key1 == null ? null : key1.getStartNode().getOffset();
	    if(start1 == null) start1 = res1.getStartNode().getOffset();
	    Long start2 = key2 == null ? null : key2.getStartNode().getOffset();
	    if(start2 == null) start2 = res2.getStartNode().getOffset();
	    int res = start1.compareTo(start2);
	    if(res == 0){
	      //compare by type
	      res = second.getType() - first.getType();
	    }

//
//
//
//	    //choices with keys are smaller than ones without
//	    if(key1 == null && key2 != null) return 1;
//	    if(key1 != null && key2 == null) return -1;
//	    if(key1 == null){
//	      //both keys are null
//	      res = res1.getStartNode().getOffset().
//	      		compareTo(res2.getStartNode().getOffset());
//	      if(res == 0) res = res1.getEndNode().getOffset().
//      				compareTo(res2.getEndNode().getOffset());
//	      if(res == 0) res = second.getType() - first.getType();
//	    }else{
//	      //both keys are present
//	      res = key1.getStartNode().getOffset().compareTo(
//	          key2.getStartNode().getOffset());
//
//	      if(res == 0){
//		      //choices with responses are smaller than ones without
//		      if(res1 == null && res2 != null) return 1;
//		      if(res1 != null && res2 == null) return -1;
//		      if(res1 != null){
//			      res = res1.getStartNode().getOffset().
//    						compareTo(res2.getStartNode().getOffset());
//		      }
//		      if(res == 0)res = key1.getEndNode().getOffset().compareTo(
//		              key2.getEndNode().getOffset());
//		      if(res == 0 && res1 != null){
//				      res = res1.getEndNode().getOffset().
//	    						compareTo(res2.getEndNode().getOffset());
//		      }
//		      if(res == 0) res = second.getType() - first.getType();
//	      }
//	    }
      return res;
	  }

	}

  /**
   * A method that returns specific type of annotations
   * @param type
   * @return a {@link Set} of {@link Annotation}s.
   */
  public Set<Annotation> getAnnotationsOfType(int type) {
    switch(type) {
      case CORRECT_TYPE:
        return (correctAnnotations == null)? new HashSet<Annotation>() : correctAnnotations;
      case PARTIALLY_CORRECT_TYPE:
        return (partiallyCorrectAnnotations == null) ? new HashSet<Annotation>() : partiallyCorrectAnnotations;
      case SPURIOUS_TYPE:
        return (spuriousAnnotations == null) ? new HashSet<Annotation>() : spuriousAnnotations;
      case MISSING_TYPE:
        return (missingAnnotations == null) ? new HashSet<Annotation>() : missingAnnotations;
      default:
        return new HashSet<Annotation>();
    }
  }

  /**
   * @return annotation type for all the annotations
   */
  public String getAnnotationType() {
    if (!keyList.isEmpty()) {
      return ((Annotation) keyList.iterator().next()).getType();
    } else if (!responseList.isEmpty()) {
      return ((Annotation) responseList.iterator().next()).getType();
    } else {
      return "";
    }
  }

  public List<String> getMeasuresRow(Object[] measures, String title) {
    NumberFormat f = NumberFormat.getInstance(Locale.ENGLISH);
    f.setMaximumFractionDigits(2);
    f.setMinimumFractionDigits(2);
    List<String> row = new ArrayList<String>();
    row.add(title);
    row.add(Integer.toString(getCorrectMatches()));
    row.add(Integer.toString(getMissing()));
    row.add(Integer.toString(getSpurious()));
    row.add(Integer.toString(getPartiallyCorrectMatches()));
    for (Object object : measures) {
      String measure = (String) object;
      double beta = Double.valueOf(
        measure.substring(1,measure.indexOf('-')));
      if (measure.endsWith("strict")) {
        row.add(f.format(getPrecisionStrict()));
        row.add(f.format(getRecallStrict()));
        row.add(f.format(getFMeasureStrict(beta)));
      } else if (measure.endsWith("lenient")) {
        row.add(f.format(getPrecisionLenient()));
        row.add(f.format(getRecallLenient()));
        row.add(f.format(getFMeasureLenient(beta)));
      } else if (measure.endsWith("average")) {
        row.add(f.format(getPrecisionAverage()));
        row.add(f.format(getRecallAverage()));
        row.add(f.format(getFMeasureAverage(beta)));
      }
    }
    return row;
  }

  public Set<Annotation> correctAnnotations, partiallyCorrectAnnotations,
                 missingAnnotations, spuriousAnnotations;


  /** Type for correct pairings (when the key and response match completely)*/
  public static final int CORRECT_TYPE = 0;
  
  /** 
   * Type for partially correct pairings (when the key and response match 
   * in type and significant features but the spans are just overlapping and 
   * not identical.
   */
  public static final int PARTIALLY_CORRECT_TYPE = 1;
  
  /**
   * Type for missing pairings (where the key was not matched to a response).
   */
  public static final int MISSING_TYPE = 2;

  /**
   * Type for spurious pairings (where the response is not matching any key).
   */
  public static final int SPURIOUS_TYPE = 3;
  
  /**
   * Type for mismatched pairings (where the key and response are co-extensive
   * but they don't match).
   */
  public static final int MISMATCH_TYPE = 4;

  /**
   * Score for a correct pairing.
   */
  private static final int CORRECT_VALUE = 3;

  /**
   * Score for a partially correct pairing.
   */
  private static final int PARTIALLY_CORRECT_VALUE = 2;
  
  
  /**
   * Score for a mismatched pairing (higher then for WRONG as at least the 
   * offsets were right).
   */
  private static final int MISMATCH_VALUE = 1;
  
  /**
   * Score for a wrong (missing or spurious) pairing.
   */  
  private static final int WRONG_VALUE = 0;

  /**
   * The set of significant features used for matching.
   */
  private java.util.Set<?> significantFeaturesSet;

  /**
   * The number of correct matches.
   */
  protected int correctMatches;

  /**
   * The number of partially correct matches.
   */
  protected int partiallyCorrectMatches;
  
  /**
   * The number of missing matches.
   */
  protected int missing;
  
  /**
   * The number of spurious matches.
   */  
  protected int spurious;

  /**
   * A list with all the key annotations
   */
  protected List<Annotation> keyList;

  /**
   * A list with all the response annotations
   */
  protected List<Annotation> responseList;

  /**
   * A list of lists representing all possible choices for each key
   */
  protected List<List<Pairing>> keyChoices;

  /**
   * A list of lists representing all possible choices for each response
   */
  protected List<List<Pairing>> responseChoices;

  /**
   * All the posible choices are added to this list for easy iteration.
   */
  protected List<Pairing> possibleChoices;

  /**
   * A list with the choices selected for the best result.
   */
  protected List<Pairing> finalChoices;

}