OrthoMatcher.java
0001 /*
0002  *  OrthoMatcher.java
0003  *
0004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution as file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Kalina Bontcheva, 24/August/2001
0013  *
0014  *  Major update by Andrew Borthwick of Spock Networks, 11/13/2007 - 8/3/2008:
0015  *    1.  matchWithPrevious now searches for matching annotations in order, starting from current and working backwards
0016  *    until it finds a match.  This compares with the previous behavior, which searched randomly among previous annotations
0017  *    for a match (because it used an iterator across an AnnotationSet, whereas now we iterate across an ArrayList<Annotation>)
0018  *    2.  We no longer require that identical strings always refer to the same entity.  We can correctly match
0019  *    the sequence "David Jones ... David ... David Smith ... David" as referring to two people, tying the first
0020  *    David to "David Jones" and the second David to "David Smith".  Ditto with David Jones .. Mr. Jones ..
0021  *    Richard Jones .. Mr. Jones
0022  *    3.  We now allow for nickname matches for Persons (David = Dave) via the "fuzzyMatch" method which is referenced
0023  *    in some of the matching rules.
0024  *    4.  Optional parameter highPrecisionOrgs only allows high precision matches for organizations and
0025  *    turns off the riskier rules.  Under this option, need to match on something like IBM = IBM Corp.
0026  *    5.  Various fixes to a number of rules
0027  *
0028  *  $Id: OrthoMatcher.java 8929 2007-07-12 16:49:55Z ian_roberts $
0029  */
0030 
0031 package gate.creole.orthomatcher;
0032 
0033 import gate.Annotation;
0034 import gate.AnnotationSet;
0035 import gate.Resource;
0036 import gate.creole.AbstractLanguageAnalyser;
0037 import gate.creole.ExecutionException;
0038 import gate.creole.ResourceInstantiationException;
0039 import gate.creole.metadata.CreoleParameter;
0040 import gate.creole.metadata.CreoleResource;
0041 import gate.creole.metadata.Optional;
0042 import gate.creole.metadata.RunTime;
0043 import gate.util.BomStrippingInputStreamReader;
0044 import gate.util.GateRuntimeException;
0045 import gate.util.InvalidOffsetException;
0046 import gate.util.OffsetComparator;
0047 import gate.util.Out;
0048 
0049 import java.io.BufferedReader;
0050 import java.io.IOException;
0051 import java.net.URL;
0052 import java.util.ArrayList;
0053 import java.util.Collections;
0054 import java.util.HashMap;
0055 import java.util.HashSet;
0056 import java.util.Iterator;
0057 import java.util.List;
0058 import java.util.Map;
0059 import java.util.Set;
0060 import java.util.regex.Matcher;
0061 import java.util.regex.Pattern;
0062 
0063 import org.apache.commons.io.IOUtils;
0064 import org.apache.log4j.Logger;
0065 @CreoleResource(name="ANNIE OrthoMatcher", comment="ANNIE orthographical coreference component.", helpURL="http://gate.ac.uk/userguide/sec:annie:orthomatcher", icon="ortho-matcher")
0066 public class OrthoMatcher extends AbstractLanguageAnalyser {
0067 
0068   private static final long serialVersionUID = -6258229350677707465L;
0069 
0070   protected static final Logger log = Logger.getLogger(OrthoMatcher.class);
0071 
0072   public static final boolean DEBUG = false;
0073 
0074   public static final String
0075   OM_DOCUMENT_PARAMETER_NAME = "document";
0076 
0077   public static final String
0078   OM_ANN_SET_PARAMETER_NAME = "annotationSetName";
0079 
0080   public static final String
0081   OM_CASE_SENSITIVE_PARAMETER_NAME = "caseSensitive";
0082 
0083   public static final String
0084   OM_ANN_TYPES_PARAMETER_NAME = "annotationTypes";
0085 
0086   public static final String
0087   OM_ORG_TYPE_PARAMETER_NAME = "organizationType";
0088 
0089   public static final String
0090   OM_PERSON_TYPE_PARAMETER_NAME = "personType";
0091 
0092   public static final String
0093   OM_EXT_LISTS_PARAMETER_NAME = "extLists";
0094 
0095   protected static final String CDGLISTNAME = "cdg";
0096   protected static final String ALIASLISTNAME = "alias";
0097   protected static final String ARTLISTNAME = "def_art";
0098   protected static final String PREPLISTNAME = "prepos";
0099   protected static final String CONNECTORLISTNAME = "connector";
0100   protected static final String SPURLISTNAME = "spur_match";
0101 
0102   protected static final String PUNCTUATION_VALUE = "punctuation";
0103   protected static final String THE_VALUE = "The";
0104 
0105 
0106   /**the name of the annotation set*/
0107   protected String annotationSetName;
0108 
0109   /** the types of the annotation */
0110   protected List<String> annotationTypes = new ArrayList<String>(10);
0111 
0112   /** the organization type*/
0113   protected String organizationType = ORGANIZATION_ANNOTATION_TYPE;
0114 
0115   /** the person type*/
0116   protected String personType = PERSON_ANNOTATION_TYPE;
0117 
0118   protected String unknownType = "Unknown";
0119 
0120   /** internal or external list */
0121   protected boolean extLists = true;
0122 
0123   /** Use only high precision rules for Organizations */
0124   protected Boolean highPrecisionOrgs = false;
0125 
0126   /** matching unknowns or not*/
0127   protected boolean matchingUnknowns = true;
0128 
0129   /** This is an internal variable to indicate whether
0130    *  we matched using a rule that requires that
0131    *  the newly matched annotation matches all the others
0132    *  This is needed, because organizations can share
0133    *  first/last tokens like News and be different
0134    */
0135   protected boolean allMatchingNeeded = false;
0136 
0137   //** Orthomatching is not case-sensitive by default*/
0138   protected boolean caseSensitive = false;
0139 
0140   //protected FeatureMap queryFM = Factory.newFeatureMap();
0141 
0142   // name lookup tables (used for namematch)
0143   //gave them bigger default size, coz rehash is expensive
0144   protected HashMap<String, String> alias = new HashMap<String, String>(100);
0145   protected Set<String> cdg = new HashSet<String>();
0146   protected HashMap<String, String> spur_match = new HashMap<String, String>(100);
0147   protected HashMap<String, String> def_art = new HashMap<String, String>(20);
0148   protected HashMap<String, String> connector = new HashMap<String, String>(20);
0149   protected HashMap<String, String> prepos = new HashMap<String, String>(30);
0150 
0151 
0152   protected AnnotationSet nameAllAnnots = null;
0153 
0154   protected HashMap<Integer, String> processedAnnots = new HashMap<Integer, String>(150);
0155   protected HashMap<Integer, String> annots2Remove = new HashMap<Integer, String>(75);
0156   protected List<List<Integer>> matchesDocFeature = new ArrayList<List<Integer>>();
0157   //maps annotation ids to array lists of tokens
0158   protected HashMap<Integer, List<Annotation>> tokensMap = new HashMap<Integer, List<Annotation>>(150);
0159   public Map<Integer, List<Annotation>> getTokensMap() {
0160     return tokensMap;
0161   }
0162 
0163   protected Map<Integer, List<Annotation>> normalizedTokensMap = new HashMap<Integer, List<Annotation>>(150);
0164 
0165   protected Annotation shortAnnot;
0166   protected Annotation longAnnot;
0167 
0168   protected ArrayList<Annotation> tokensLongAnnot;
0169   protected ArrayList<Annotation> tokensShortAnnot;
0170 
0171   protected ArrayList<Annotation> normalizedTokensLongAnnot, normalizedTokensShortAnnot;
0172 
0173   /**
0174    * URL to the file containing the definition for this orthomatcher
0175    */
0176   private java.net.URL definitionFileURL;
0177 
0178   private Double minimumNicknameLikelihood;
0179 
0180   /** The encoding used for the definition file and associated lists.*/
0181   private String encoding;
0182 
0183   private Map<Integer,OrthoMatcherRule> rules=new HashMap<Integer,OrthoMatcherRule>();
0184 
0185   /** to be initialized in init() */
0186   private AnnotationOrthography orthoAnnotation;
0187 
0188   /** @link dependency */
0189   /*#OrthoMatcher lnkOrthoMatcher;*/
0190 
0191   public OrthoMatcher () {
0192     annotationTypes.add(organizationType);
0193     annotationTypes.add(personType);
0194     annotationTypes.add("Location");
0195     annotationTypes.add("Date");
0196   }
0197 
0198   /** Initialise the rules. The orthomatcher loads its build-in rules. */
0199   private void initRules(){
0200     //this line should be executed after spur_match is loaded
0201     rules.put(0,  new MatchRule0(this));
0202     rules.put(1,  new MatchRule1(this));
0203     rules.put(2,  new MatchRule2(this));
0204     rules.put(3,  new MatchRule3(this));
0205     rules.put(4,  new MatchRule4(this));
0206     rules.put(5,  new MatchRule5(this));
0207     rules.put(6,  new MatchRule6(this));
0208     rules.put(7,  new MatchRule7(this));
0209     rules.put(8,  new MatchRule8(this));
0210     rules.put(9,  new MatchRule9(this));
0211     rules.put(10new MatchRule10(this));
0212     rules.put(11new MatchRule11(this));
0213     rules.put(12new MatchRule12(this));
0214     rules.put(13new MatchRule13(this));
0215     rules.put(14new MatchRule14(this));
0216     rules.put(15new MatchRule15(this));
0217     rules.put(16new MatchRule16(this));
0218     rules.put(17new MatchRule17(this));
0219 
0220   }
0221 
0222   /** Override this method to add, replace, remove rules */
0223   protected void modifyRules(Map<Integer,OrthoMatcherRule> rules) {
0224 
0225   }
0226 
0227   /** Initialise this resource, and return it. */
0228   @SuppressWarnings("resource")
0229   @Override
0230   public Resource init() throws ResourceInstantiationException {
0231     //initialise the list of annotations which we will match
0232     if(definitionFileURL == null){
0233       throw new ResourceInstantiationException(
0234       "No URL provided for the definition file!");
0235     }
0236     String nicknameFile = null;
0237     BufferedReader reader = null;
0238     //at this point we have the definition file
0239     try{
0240       reader = new BomStrippingInputStreamReader(
0241           definitionFileURL.openStream(), encoding);
0242       String lineRead = null;
0243       //boolean foundANickname = false;
0244       while ((lineRead = reader.readLine()) != null){
0245         int index = lineRead.indexOf(":");
0246         if (index != -1){
0247           String nameFile = lineRead.substring(0,index);
0248           String nameList = lineRead.substring(index+1,lineRead.length());
0249           if (nameList.equals("nickname")) {
0250             if (minimumNicknameLikelihood == null) {
0251               throw new ResourceInstantiationException(
0252                   "No value for the required parameter " +
0253                   "minimumNicknameLikelihood!");
0254             }
0255             nicknameFile = nameFile;
0256           else {
0257             createAnnotList(nameFile,nameList);
0258           }
0259         }// if
0260       }//while
0261       reader.close();
0262 
0263       URL nicknameURL = null;
0264       if (nicknameFile != null)
0265         nicknameURL = new URL(definitionFileURL, nicknameFile);
0266       this.orthoAnnotation = new BasicAnnotationOrthography(
0267               personType,extLists,unknownType,nicknameURL,
0268               minimumNicknameLikelihood, encoding);
0269       initRules();
0270       modifyRules(rules);
0271 
0272     }catch(IOException ioe){
0273       throw new ResourceInstantiationException(ioe);
0274     }
0275     finally {
0276       IOUtils.closeQuietly(reader);
0277     }
0278 
0279 
0280     return this;
0281   // init()
0282 
0283 
0284   /**  Run the resource. It doesn't make sense not to override
0285    *  this in subclasses so the default implementation signals an
0286    *  exception.
0287    */
0288   @Override
0289   public void execute() throws ExecutionException{
0290     try{
0291       //check the input
0292       if(document == null) {
0293         throw new ExecutionException(
0294                 "No document for namematch!"
0295         );
0296       }
0297       fireStatusChanged("OrthoMatcher processing: " +  document.getName());
0298 
0299       // get the annotations from document
0300       if ((annotationSetName == null)|| (annotationSetName.equals("")))
0301         nameAllAnnots = document.getAnnotations();
0302       else
0303         nameAllAnnots = document.getAnnotations(annotationSetName);
0304 
0305       //if none found, print warning and exit
0306       if ((nameAllAnnots == null|| nameAllAnnots.isEmpty()) {
0307         Out.prln("OrthoMatcher Warning: No annotations found for processing");
0308         return;
0309       }
0310 
0311       //check if we've been run on this document before
0312       //and clean the doc if needed
0313       docCleanup();
0314       @SuppressWarnings("unchecked")
0315       Map<String, List<List<Integer>>> matchesMap = (Map<String, List<List<Integer>>>)document.getFeatures().
0316       get(DOCUMENT_COREF_FEATURE_NAME);
0317 
0318 
0319       // creates the cdg list from the document
0320       //no need to create otherwise, coz already done in init()
0321       if (!extLists)
0322         cdg=orthoAnnotation.buildTables(nameAllAnnots);
0323 
0324 
0325       //Match all name annotations and unknown annotations
0326       matchNameAnnotations();
0327 
0328       //used to check if the Orthomatcher works properly
0329       //OrthoMatcherHelper.setMatchesPositions(nameAllAnnots);
0330 
0331       // set the matches of the document
0332       //    determineMatchesDocument();
0333       if (! matchesDocFeature.isEmpty()) {
0334         if(matchesMap == null){
0335           matchesMap = new HashMap<String, List<List<Integer>>>();
0336         }
0337         matchesMap.put(nameAllAnnots.getName(), matchesDocFeature);
0338         // System.out.println("matchesMap is: " + matchesMap);
0339         //we need to put it even if it was already present in order to triger
0340         //the update events
0341         document.getFeatures().put(DOCUMENT_COREF_FEATURE_NAME, matchesMap);
0342 
0343         //cannot do clear() as this has already been put on the document
0344         //so I need a new one for the next run of matcher
0345         matchesDocFeature = new ArrayList<List<Integer>>();
0346 
0347 
0348         fireStatusChanged("OrthoMatcher completed");
0349       }
0350     }finally{
0351       //make sure the cleanup happens even if there are errors.
0352       //    Out.prln("Processed strings" + processedAnnots.values());
0353       //clean-up the internal data structures for next run
0354       nameAllAnnots = null;
0355       processedAnnots.clear();
0356       annots2Remove.clear();
0357       tokensMap.clear();
0358       normalizedTokensMap.clear();
0359       matchesDocFeature = new ArrayList<List<Integer>>();
0360       longAnnot = null;
0361       shortAnnot = null;
0362       tokensLongAnnot = null;
0363       tokensShortAnnot = null;
0364 
0365       //if (log.isDebugEnabled()) OrthoMatcherHelper.saveUsedTable();
0366     }
0367   // run()
0368 
0369   protected void matchNameAnnotations() throws ExecutionException{
0370     // go through all the annotation types
0371     Iterator<String> iterAnnotationTypes = annotationTypes.iterator();
0372     while (iterAnnotationTypes.hasNext()) {
0373       String annotationType = iterAnnotationTypes.next();
0374 
0375       AnnotationSet nameAnnots = nameAllAnnots.get(annotationType);
0376 
0377       // continue if no such annotations exist
0378       if (nameAnnots.isEmpty()) continue;
0379 
0380       AnnotationSet tokensNameAS = nameAllAnnots.get(TOKEN_ANNOTATION_TYPE);
0381       if (tokensNameAS.isEmpty()) continue;
0382 
0383       ArrayList<Annotation> sortedNameAnnots = new ArrayList<Annotation>(nameAnnots);
0384       Collections.<Annotation>sort(sortedNameAnnots,new OffsetComparator());
0385       for (int snaIndex = 0;snaIndex < sortedNameAnnots.size();snaIndex++) {
0386         Annotation tempAnnot = sortedNameAnnots.get(snaIndex);
0387         Annotation nameAnnot = nameAllAnnots.get(tempAnnot.getId())// Not sure if this matters
0388 
0389         // get string and value
0390         String annotString = orthoAnnotation.getStringForAnnotation(nameAnnot, document);
0391 
0392         //convert to lower case if we are not doing a case sensitive match
0393         if (!caseSensitive)
0394           annotString = annotString.toLowerCase();
0395 
0396         if (DEBUG) {
0397           if (log.isDebugEnabled()) {
0398             log.debug("Now processing the annotation:  "
0399                     + orthoAnnotation.getStringForAnnotation(nameAnnot, document" Id: " + nameAnnot.getId()
0400                     " Type: " + nameAnnot.getType() " Offset: " + nameAnnot.getStartNode().getOffset());
0401           }
0402         }
0403 
0404         // get the tokens
0405         List<Annotation> tokens = new ArrayList<Annotation>(tokensNameAS.getContained(nameAnnot.getStartNode().getOffset(),
0406                 nameAnnot.getEndNode().getOffset()));
0407 
0408         //if no tokens to match, do nothing
0409         if (tokens.isEmpty()) {
0410           if (log.isDebugEnabled()) {
0411             log.debug("Didn't find any tokens for the following annotation.  We will be unable to perform coref on this annotation.  \n String:  "
0412                     + orthoAnnotation.getStringForAnnotation(nameAnnot, document" Id: " + nameAnnot.getId() " Type: " + nameAnnot.getType());
0413           }
0414           continue;
0415         }
0416         Collections.sort(tokens, new gate.util.OffsetComparator());
0417         //check if these actually do not end after the name
0418         //needed coz new tokeniser conflates
0419         //strings with dashes. So British Gas-style is two tokens
0420         //instead of three. So cannot match properly British Gas
0421         //      tokens = checkTokens(tokens);
0422         tokensMap.put(nameAnnot.getId(), tokens);
0423         normalizedTokensMap.put(nameAnnot.getId()new ArrayList<Annotation>(tokens));
0424 
0425         //first check whether we have not matched such a string already
0426         //if so, just consider it matched, don't bother calling the rules
0427         // Exception:  AB, Spock:
0428         // Note that we require one-token Person annotations to be matched even if an identical string
0429         // has been matched earlier because there could be multiple people named "David", for instance,
0430         // on a page.
0431         if (processedAnnots.containsValue(annotString&&
0432                 ((nameAnnot.getType().equals(personType&& (tokens.size() == 1)))) {
0433           Annotation returnAnnot = orthoAnnotation.updateMatches(nameAnnot, annotString,processedAnnots,nameAllAnnots,matchesDocFeature);
0434           if (returnAnnot != null) {
0435             if (DEBUG) {
0436               if (log.isDebugEnabled()) {
0437                 log.debug("Exact match criteria matched " + annotString + " from (id: " + nameAnnot.getId() ", offset: " + nameAnnot.getStartNode().getOffset() ") to " +
0438                         "(id: " + returnAnnot.getId() ", offset: " + returnAnnot.getStartNode().getOffset() ")");
0439               }
0440             }
0441             processedAnnots.put(nameAnnot.getId(), annotString);
0442             continue;
0443           }
0444         else if (processedAnnots.isEmpty()) {
0445           // System.out.println("First item put in processedAnnots: " + annotString);
0446           processedAnnots.put(nameAnnot.getId(), annotString);
0447           continue;
0448         }
0449 
0450         //if a person, then remove their title before matching
0451         if (nameAnnot.getType().equals(personType)) {
0452           annotString = orthoAnnotation.stripPersonTitle(annotString, nameAnnot,document,tokensMap,normalizedTokensMap,nameAllAnnots);
0453           normalizePersonName(nameAnnot);
0454         }
0455         else if (nameAnnot.getType().equals(organizationType))
0456           annotString = normalizeOrganizationName(annotString, nameAnnot);
0457 
0458         if(null == annotString || "".equals(annotString|| tokens.isEmpty()) {
0459           if (log.isDebugEnabled()) {
0460             log.debug("Annotation ID " + nameAnnot.getId() " of type" + nameAnnot.getType() +
0461             " refers to a null or empty string or one with no tokens after normalization.  Unable to process further.");
0462           }
0463           continue;
0464         }
0465         //otherwise try matching with previous annotations
0466         matchWithPrevious(nameAnnot, annotString,sortedNameAnnots,snaIndex);
0467 
0468         // Out.prln("Putting in previous " + nameAnnot + ": string " + annotString);
0469         //finally add the current annotations to the processed map
0470         processedAnnots.put(nameAnnot.getId(), annotString);
0471       }//while through name annotations
0472       if (matchingUnknowns) {
0473         matchUnknown(sortedNameAnnots);
0474       }
0475     }//while through annotation types
0476 
0477   }
0478 
0479   protected void matchUnknown(ArrayList<Annotation> sortedAnnotationsForATypethrows ExecutionException {
0480     //get all Unknown annotations
0481     AnnotationSet unknownAnnots = nameAllAnnots.get(unknownType);
0482     annots2Remove.clear();
0483     if (unknownAnnots.isEmpty()) return;
0484 
0485     AnnotationSet nameAllTokens = nameAllAnnots.get(TOKEN_ANNOTATION_TYPE);
0486     if (nameAllTokens.isEmpty()) return;
0487 
0488     Iterator<Annotation> iter = unknownAnnots.iterator();
0489     //loop through the unknown annots
0490     while (iter.hasNext()) {
0491       Annotation unknown = iter.next();
0492 
0493       // get string and value
0494       String unknownString = orthoAnnotation.getStringForAnnotation(unknown, document);
0495       //convert to lower case if we are not doing a case sensitive match
0496       if (!caseSensitive)
0497         unknownString = unknownString.toLowerCase();
0498 
0499       // System.out.println("Now trying to match the unknown string: " + unknownString);
0500       //get the tokens
0501       List<Annotation> tokens = new ArrayList<Annotation>(nameAllTokens.getContained(
0502               unknown.getStartNode().getOffset(),
0503               unknown.getEndNode().getOffset()
0504       ));
0505       if (tokens.isEmpty())
0506         continue;
0507       Collections.sort(tokens, new gate.util.OffsetComparator());
0508       tokensMap.put(unknown.getId(), tokens);
0509       normalizedTokensMap.put(unknown.getId(), tokens);
0510 
0511 
0512       //first check whether we have not matched such a string already
0513       //if so, just consider it matched, don't bother calling the rules
0514       if (processedAnnots.containsValue(unknownString)) {
0515         Annotation matchedAnnot = orthoAnnotation.updateMatches(unknown, unknownString,processedAnnots,nameAllAnnots,matchesDocFeature);
0516         if (matchedAnnot == null) {
0517           log.debug("Orthomatcher: Unable to find the annotation: " +
0518                   orthoAnnotation.getStringForAnnotation(unknown, document+
0519           " in matchUnknown");
0520         }
0521         else {
0522           if (matchedAnnot.getType().equals(unknownType)) {
0523             annots2Remove.put(unknown.getId(),
0524                     annots2Remove.get(matchedAnnot.getId()));
0525           }
0526           else
0527             annots2Remove.put(unknown.getId(), matchedAnnot.getType());
0528           processedAnnots.put(unknown.getId(), unknownString);
0529           unknown.getFeatures().put("NMRule", unknownType);
0530           continue;
0531         }
0532       }
0533 
0534       //check if we should do sub-string matching in case it's hyphenated
0535       //for example US-led
0536       if (tokens.size() == 1
0537               && "hyphen".equals(unknown.getFeatures().get(TOKEN_KIND_FEATURE_NAME))) {
0538         if (matchHyphenatedUnknowns(unknown, unknownString, iter))
0539           continue;
0540       }//if
0541 
0542       // TODO:  The below results in a assigning the unknown's to the last annotation that it matches in a document.
0543       // It would probably be better to first start with things which precede the current unknown and then do
0544       // annotations after
0545       matchWithPrevious(unknown, unknownString,sortedAnnotationsForAType,sortedAnnotationsForAType.size());
0546 
0547     //while though unknowns
0548 
0549     if (! annots2Remove.isEmpty()) {
0550       Iterator<Integer> unknownIter = annots2Remove.keySet().iterator();
0551       while (unknownIter.hasNext()) {
0552         Integer unknId = unknownIter.next();
0553         Annotation unknown = nameAllAnnots.get(unknId);
0554         Integer newID = nameAllAnnots.add(
0555                 unknown.getStartNode(),
0556                 unknown.getEndNode(),
0557                 annots2Remove.get(unknId),
0558                 unknown.getFeatures()
0559         );
0560         nameAllAnnots.remove(unknown);
0561 
0562         //change the id in the matches list
0563         @SuppressWarnings("unchecked")
0564         List<Integer> mList = (List<Integer>)unknown.getFeatures().
0565         get(ANNOTATION_COREF_FEATURE_NAME);
0566         mList.remove(unknId);
0567         mList.add(newID);
0568       }//while
0569     }//if
0570   }
0571 
0572   private boolean matchHyphenatedUnknowns(Annotation unknown, String unknownString,
0573           Iterator<Annotation> iter){
0574     boolean matched = false;
0575 
0576     //only take the substring before the hyphen
0577     int stringEnd = unknownString.indexOf("-");
0578     unknownString = unknownString.substring(0, stringEnd);
0579     //check if we've already matched this string
0580     //because only exact match of the substring are considered
0581     if (processedAnnots.containsValue(unknownString)) {
0582       matched = true;
0583       Annotation matchedAnnot = orthoAnnotation.updateMatches(unknown, unknownString,processedAnnots,nameAllAnnots,matchesDocFeature);
0584       //only do the matching if not a person, because we do not match
0585       //those on sub-strings
0586       iter.remove();
0587       String newType;
0588       if (matchedAnnot.getType().equals(unknownType))
0589         newType = annots2Remove.get(matchedAnnot.getId());
0590       else
0591         newType = matchedAnnot.getType();
0592 
0593       Integer newID = new Integer(-1);
0594       try {
0595         newID = nameAllAnnots.add(
0596                 unknown.getStartNode().getOffset(),
0597                 new Long(unknown.getStartNode().getOffset().longValue()
0598                         + stringEnd),
0599                         newType,
0600                         unknown.getFeatures()
0601         );
0602       catch (InvalidOffsetException ex) {
0603         throw new GateRuntimeException(ex.getMessage());
0604       }
0605       nameAllAnnots.remove(unknown);
0606 
0607       //change the id in the matches list
0608       @SuppressWarnings("unchecked")
0609       List<Integer> mList = (List<Integer>)unknown.getFeatures().
0610       get(ANNOTATION_COREF_FEATURE_NAME);
0611       mList.remove(unknown.getId());
0612       mList.add(newID);
0613 
0614     }
0615     return matched;
0616   }
0617 
0618   /**
0619    * Attempt to match nameAnnot against all previous annotations of the same type, which are passed down
0620    * in listOfThisType.  Matches are tested in order from most recent to oldest.
0621    @param nameAnnot    Annotation we are trying to match
0622    @param annotString  Normalized string representation of annotation
0623    @param listOfThisType  ArrayList of Annotations of the same type as nameAnnot
0624    @param startIndex   Index in listOfThisType that we will start from in matching the current annotation
0625    */
0626   protected void matchWithPrevious(Annotation nameAnnot, String annotString,
0627           ArrayList<Annotation> listOfThisType,
0628           int startIndex) {
0629     boolean matchedUnknown = false;
0630     // Out.prln("matchWithPrevious now processing: " + annotString);
0631 
0632     for (int curIndex = startIndex - 1;curIndex >= 0;curIndex--) {
0633       Integer prevId = listOfThisType.get(curIndex).getId();
0634       Annotation prevAnnot = nameAllAnnots.get(prevId);  // Note that this line probably isn't necessary anymore
0635 
0636       //check if the two are from the same type or the new one is unknown
0637       if (prevAnnot == null || (! prevAnnot.getType().equals(nameAnnot.getType())
0638               && ! nameAnnot.getType().equals(unknownType))
0639       )
0640         continue;
0641       //do not compare two unknown annotations either
0642       //they are only matched to those of known types
0643       if (  nameAnnot.getType().equals(unknownType)
0644               && prevAnnot.getType().equals(unknownType))
0645         continue;
0646 
0647       //check if we have already matched this annotation to the new one
0648       if (orthoAnnotation.matchedAlready(nameAnnot, prevAnnot,matchesDocFeature,nameAllAnnots) )
0649         continue;
0650 
0651       //now changed to a rule, here we just match by gender
0652       if (prevAnnot.getType().equals(personType)) {
0653         String prevGender =
0654           (StringprevAnnot.getFeatures().get(PERSON_GENDER_FEATURE_NAME);
0655         String nameGender =
0656           (StringnameAnnot.getFeatures().get(PERSON_GENDER_FEATURE_NAME);
0657         if (   prevGender != null
0658                 && nameGender != null
0659                 && ( (nameGender.equalsIgnoreCase("female")
0660                         &&
0661                         prevGender.equalsIgnoreCase("male")
0662                 )
0663                 ||
0664                 (prevGender.equalsIgnoreCase("female")
0665                         && nameGender.equalsIgnoreCase("male")
0666                 )
0667                 )
0668         //if condition
0669           continue//we don't have a match if the two genders are different
0670 
0671       }//if
0672 
0673       //if the two annotations match
0674       //
0675       // A. Borthwick, Spock:  If the earlier annotation is shorter than the current annotation and it
0676       // has already been matched with a longer annotations, then don't match it with the current annotation.
0677       // Reasoning is that with the sequence David Jones . . . David  . . . David Smith, we don't want to match
0678       // David Smith with David.  However, with the sequence, David  . . . David Jones, it's okay to match the
0679       // shorter version with the longer, because it hasn't already been matched with a longer.
0680       boolean prevAnnotUsedToMatchWithLonger = prevAnnot.getFeatures().containsKey("matchedWithLonger");
0681       if (matchAnnotations(nameAnnot, annotString,  prevAnnot)) {
0682         orthoAnnotation.updateMatches(nameAnnot, prevAnnot,matchesDocFeature,nameAllAnnots);
0683         if (DEBUG) {
0684           log.debug("Just matched nameAnnot " + nameAnnot.getId() " with prevAnnot " + prevAnnot.getId());
0685         }
0686 
0687         if (!prevAnnotUsedToMatchWithLonger && prevAnnot.getFeatures().containsKey("matchedWithLonger")) {
0688           // We have just matched the previous annotation with a longer annotation for the first time.  We need
0689           // to propagate the matchedWithLonger property to all other annotations which coreffed with the previous annotation
0690           // so that we don't match them with a longer annotation
0691           propagatePropertyToExactMatchingMatches(prevAnnot,"matchedWithLonger",true);
0692         }
0693         //if unknown annotation, we need to change to the new type
0694         if (nameAnnot.getType().equals(unknownType)) {
0695           matchedUnknown = true;
0696           if (prevAnnot.getType().equals(unknownType))
0697             annots2Remove.put(nameAnnot.getId(),
0698                     annots2Remove.get(prevAnnot.getId()));
0699           else
0700             annots2Remove.put(nameAnnot.getId(), prevAnnot.getType());
0701           //also put an attribute to indicate that
0702           nameAnnot.getFeatures().put("NMRule", unknownType);
0703         }//if unknown
0704         break//no need to match further
0705       }//if annotations matched
0706 
0707     }//while through previous annotations
0708 
0709     if (matchedUnknown)
0710       processedAnnots.put(nameAnnot.getId(), annotString);
0711 
0712 
0713   }//matchWithPrevious
0714 
0715   protected void propagatePropertyToExactMatchingMatches(Annotation updateAnnot,String featureName,Object value) {
0716     try {
0717       @SuppressWarnings("unchecked")
0718       List<Integer> matchesList = (List<Integer>updateAnnot.getFeatures().get(ANNOTATION_COREF_FEATURE_NAME);
0719       if ((matchesList == null|| matchesList.isEmpty()) {
0720         return;
0721       }
0722       else {
0723         String updateAnnotString = orthoAnnotation.getStringForAnnotation(updateAnnot, document).toLowerCase();
0724         for (Integer nextId : matchesList) {
0725           Annotation a = nameAllAnnots.get(nextId);
0726 
0727           if (orthoAnnotation.fuzzyMatch(orthoAnnotation.getStringForAnnotation(a, document),updateAnnotString)) {
0728             if (DEBUG) {
0729               log.debug("propogateProperty: " + featureName + " " + value + " from: " + updateAnnot.getId() " to: " + a.getId());
0730             }
0731             a.getFeatures().put(featureName, value);
0732           }
0733         }
0734       }
0735     }
0736     catch (Exception e) {
0737       log.error("Error in propogatePropertyToExactMatchingMatches", e);
0738     }
0739   }
0740 
0741   protected boolean matchAnnotations(Annotation newAnnot, String annotString,
0742           Annotation prevAnnot) {
0743     //do not match two annotations that overlap
0744     if (newAnnot.overlaps(prevAnnot))
0745       return false;
0746 
0747     // find which annotation string of the two is longer
0748     //  this is useful for some of the matching rules
0749     String prevAnnotString = processedAnnots.get(prevAnnot.getId());
0750     // Out.prln("matchAnnotations processing " + annotString + " and " + prevAnnotString);
0751     if (prevAnnotString == null) {
0752       //    Out.prln("We discovered that the following string is null!:  " + prevAnnot.getId() +
0753       //    " For the previous annotation " + getStringForAnnotation(prevAnnot, document) +
0754       //    " which has annotation type " + prevAnnot.getType() +
0755       //    " Tried to compared it to the annotation string " + annotString);
0756       return false;
0757     }
0758 
0759     String longName = prevAnnotString;
0760     String shortName = annotString;
0761     longAnnot = prevAnnot;
0762     shortAnnot = newAnnot;
0763     boolean longerPrevious = true;
0764 
0765     if (shortName.length()>longName.length()) {
0766       String temp = longName;
0767       longName = shortName;
0768       shortName = temp;
0769       Annotation tempAnn = longAnnot;
0770       longAnnot = shortAnnot;
0771       shortAnnot = tempAnn;
0772       longerPrevious = false;
0773     }//if
0774 
0775     tokensLongAnnot = (ArrayList<Annotation>tokensMap.get(longAnnot.getId());
0776     normalizedTokensLongAnnot = (ArrayList<Annotation>normalizedTokensMap.get(longAnnot.getId());
0777     tokensShortAnnot = (ArrayList<Annotation>tokensMap.get(shortAnnot.getId());
0778     normalizedTokensShortAnnot = (ArrayList<Annotation>normalizedTokensMap.get(shortAnnot.getId());
0779 
0780     @SuppressWarnings("unchecked")
0781     List<Integer> matchesList = (List<Integer>prevAnnot.getFeatures().
0782     get(ANNOTATION_COREF_FEATURE_NAME);
0783     if (matchesList == null || matchesList.isEmpty())
0784       return apply_rules_namematch(prevAnnot.getType(), shortName,longName,
0785               prevAnnot,newAnnot,longerPrevious);
0786 
0787     //if these two match, then let's see if all the other matching one will too
0788     //that's needed, because sometimes names can share a token (e.g., first or
0789     //last but not be the same
0790     if (apply_rules_namematch(prevAnnot.getType(), shortName,longName,prevAnnot,newAnnot,
0791             longerPrevious)) {
0792       /**
0793        * Check whether we need to ensure that there is a match with the rest
0794        * of the matching annotations, because the rule requires that
0795        * transtivity is not assummed.
0796        */
0797       if (allMatchingNeeded) {
0798         allMatchingNeeded = false;
0799 
0800         List<Integer> toMatchList = new ArrayList<Integer>(matchesList);
0801         //      if (newAnnot.getType().equals(unknownType))
0802         //        Out.prln("Matching new " + annotString + " with annots " + toMatchList);
0803         toMatchList.remove(prevAnnot.getId());
0804 
0805         return matchOtherAnnots(toMatchList, newAnnot, annotString);
0806       else
0807         return true;
0808     }
0809     return false;
0810   }
0811 
0812   /** This method checkes whether the new annotation matches
0813    *  all annotations given in the toMatchList (it contains ids)
0814    *  The idea is that the new annotation needs to match all those,
0815    *  because assuming transitivity does not always work, when
0816    *  two different entities share a common token: e.g., BT Cellnet
0817    *  and BT and British Telecom.
0818    */
0819   protected boolean matchOtherAnnotsList<Integer> toMatchList, Annotation newAnnot,
0820           String annotString) {
0821 
0822     //if the list is empty, then we're matching all right :-)
0823     if (toMatchList.isEmpty())
0824       return true;
0825 
0826     boolean matchedAll = true;
0827     int i = 0;
0828 
0829     while (matchedAll && i < toMatchList.size()) {
0830       Annotation prevAnnot = nameAllAnnots.get(toMatchList.get(i));
0831 
0832       // find which annotation string of the two is longer
0833       //  this is useful for some of the matching rules
0834       String prevAnnotString = processedAnnots.get(prevAnnot.getId());
0835       if (prevAnnotString == null)
0836         try {
0837           prevAnnotString = document.getContent().getContent(
0838                   prevAnnot.getStartNode().getOffset(),
0839                   prevAnnot.getEndNode().getOffset()
0840           ).toString();
0841         catch (InvalidOffsetException ioe) {
0842           return false;
0843         }//try
0844 
0845 
0846         String longName = prevAnnotString;
0847         String shortName = annotString;
0848         longAnnot = prevAnnot;
0849         shortAnnot = newAnnot;
0850         boolean longerPrevious = true;
0851         if (shortName.length()>=longName.length()) {
0852           String temp = longName;
0853           longName = shortName;
0854           shortName = temp;
0855           Annotation tempAnn = longAnnot;
0856           longAnnot = shortAnnot;
0857           shortAnnot = tempAnn;
0858           longerPrevious = false;
0859         }//if
0860 
0861         tokensLongAnnot = (ArrayList<Annotation>tokensMap.get(longAnnot.getId());
0862         normalizedTokensLongAnnot = (ArrayList<Annotation>normalizedTokensMap.get(longAnnot.getId());
0863         tokensShortAnnot = (ArrayList<Annotation>tokensMap.get(shortAnnot.getId());
0864         normalizedTokensShortAnnot = (ArrayList<Annotation>normalizedTokensMap.get(shortAnnot.getId());
0865 
0866         matchedAll = apply_rules_namematch(prevAnnot.getType(), shortName,longName,prevAnnot,newAnnot,
0867                 longerPrevious);
0868         //      if (newAnnot.getType().equals(unknownType))
0869         //      Out.prln("Loop: " + shortName + " and " + longName + ": result: " + matchedAll);
0870 
0871         i++;
0872     }//while
0873     return matchedAll;
0874   }
0875 
0876   @SuppressWarnings("unchecked")
0877   protected void docCleanup() {
0878     Object matchesValue = document.getFeatures().get(DOCUMENT_COREF_FEATURE_NAME);
0879     if (matchesValue != null && (matchesValue instanceof Map))
0880       ((Map<String,List<List<Integer>>>)matchesValue).remove(nameAllAnnots.getName());
0881     else if (matchesValue != null) {
0882       document.getFeatures().put(DOCUMENT_COREF_FEATURE_NAME, new HashMap<String,List<List<Integer>>>());
0883     }
0884 
0885     //get all annotations that have a matches feature
0886     HashSet<String> fNames = new HashSet<String>();
0887     fNames.add(ANNOTATION_COREF_FEATURE_NAME);
0888     AnnotationSet annots =
0889       nameAllAnnots.get(null, fNames);
0890 
0891     //  Out.prln("Annots to cleanup" + annots);
0892 
0893     if (annots == null || annots.isEmpty())
0894       return;
0895 
0896     Iterator<Annotation> iter = annots.iterator();
0897     while (iter.hasNext()) {
0898       while (iter.hasNext())
0899         iter.next().getFeatures().remove(ANNOTATION_COREF_FEATURE_NAME);
0900     //while
0901   }//cleanup
0902 
0903 
0904   static Pattern periodPat = Pattern.compile("[\\.]+");
0905 
0906   protected void normalizePersonName (Annotation annotthrows ExecutionException {
0907     ArrayList<Annotation> tokens = (ArrayList<Annotation>normalizedTokensMap.get(annot.getId());
0908     for (int i = tokens.size() 1; i >= 0; i--) {
0909       String tokenString = ((Stringtokens.get(i).getFeatures().get(TOKEN_STRING_FEATURE_NAME));
0910       String kind = (Stringtokens.get(i).getFeatures().get(TOKEN_KIND_FEATURE_NAME);
0911       //String category = (String) tokens.get(i).getFeatures().get(TOKEN_CATEGORY_FEATURE_NAME);
0912       if (!caseSensitive)  {
0913         tokenString = tokenString.toLowerCase();
0914       }
0915       // log.debug("tokenString: " + tokenString + " kind: " + kind + " category: " + category);
0916       if (kind.equals(PUNCTUATION_VALUE) ) {
0917         // log.debug("Now tagging it!");
0918         tokens.get(i).getFeatures().put("ortho_stop"true);
0919       }
0920     }
0921 
0922     ArrayList<Annotation> normalizedTokens = new ArrayList<Annotation>(tokens);
0923     for (int j = normalizedTokens.size() 1; j >=  0;j--) {
0924       if (normalizedTokens.get(j).getFeatures().containsKey("ortho_stop")) {
0925         // log.debug("Now removing " + normalizedTokens.get(j).getFeatures().get(TOKEN_STRING_FEATURE_NAME));
0926         normalizedTokens.remove(j);
0927       }
0928     }
0929     // log.debug("normalizedTokens size is: " + normalizedTokens.size());
0930     normalizedTokensMap.put(annot.getId(), normalizedTokens);
0931   }
0932 
0933   /** return an organization  without a designator and starting The*/
0934   protected String normalizeOrganizationName (String annotString, Annotation annot){
0935 
0936     ArrayList<Annotation> tokens = (ArrayList<Annotation>tokensMap.get(annot.getId());
0937 
0938     //strip starting The first
0939     if ( ((Stringtokens.get(0).getFeatures().get(TOKEN_STRING_FEATURE_NAME))
0940     .equalsIgnoreCase(THE_VALUE))
0941       tokens.remove(0);
0942 
0943     if (tokens.size() 0) {
0944 
0945       // New code by A. Borthwick of Spock Networks
0946       // June 13, 2008
0947       // Strip everything on the cdg list, which now encompasses not just cdg's, but also other stopwords
0948       // Start from the right side so we don't mess up the arraylist
0949       for (int i = tokens.size() 1; i >= 0; i--) {
0950         String tokenString = ((Stringtokens.get(i).getFeatures().get(TOKEN_STRING_FEATURE_NAME));
0951         String kind = (Stringtokens.get(i).getFeatures().get(TOKEN_KIND_FEATURE_NAME);
0952         String category = (Stringtokens.get(i).getFeatures().get(TOKEN_CATEGORY_FEATURE_NAME);
0953         if (!caseSensitive)  {
0954           tokenString = tokenString.toLowerCase();
0955         }
0956         // Out.prln("tokenString: " + tokenString + " kind: " + kind + " category: " + category);
0957         if (kind.equals(PUNCTUATION_VALUE||
0958       ( (category != null&& (category.equals("DT"|| category.equals("IN")) )
0959       || cdg.contains(tokenString)) {
0960           // Out.prln("Now tagging it!");
0961           tokens.get(i).getFeatures().put("ortho_stop"true);
0962         }
0963       }
0964 
0965       // AB, Spock:  Need to check for CDG even for 1 token so we don't automatically match
0966       // a one-token annotation called "Company", for instance
0967       String compareString = (Stringtokens.get(tokens.size()-1).getFeatures().get(TOKEN_STRING_FEATURE_NAME);
0968       if (!caseSensitive) {
0969         compareString = compareString.toLowerCase();
0970       }
0971       if (cdg.contains(compareString)) {
0972         tokens.remove(tokens.size()-1);
0973       }
0974 
0975     }
0976 
0977     ArrayList<Annotation> normalizedTokens = new ArrayList<Annotation>(tokens);
0978     for (int j = normalizedTokens.size() 1; j >=  0;j--) {
0979       if (normalizedTokens.get(j).getFeatures().containsKey("ortho_stop")) {
0980         normalizedTokens.remove(j);
0981       }
0982     }
0983 
0984     normalizedTokensMap.put(annot.getId(), normalizedTokens);
0985 
0986     StringBuffer newString = new StringBuffer(50);
0987     for (int i = 0; i < tokens.size(); i++){
0988       newString.append((Stringtokens.get(i).getFeatures().get(TOKEN_STRING_FEATURE_NAME) );
0989       if (i != tokens.size()-1)
0990         newString.append(" ");
0991     }
0992     // Out.prln("Strip CDG returned: " + newString + "for string " + annotString);
0993 
0994     if (caseSensitive)
0995       return newString.toString();
0996 
0997     return newString.toString().toLowerCase();
0998   }
0999 
1000   /** creates the lookup tables */
1001   protected void createAnnotList(String nameFile, String nameList)
1002           throws IOException {
1003     // create the relative URL
1004     URL fileURL = new URL(definitionFileURL, nameFile);
1005     BufferedReader bufferedReader = null;
1006     try {
1007       bufferedReader =
1008               new BomStrippingInputStreamReader(fileURL.openStream(), encoding);
1009 
1010       String lineRead = null;
1011       while((lineRead = bufferedReader.readLine()) != null) {
1012         if(nameList.compareTo(CDGLISTNAME== 0) {
1013           Matcher matcher = punctPat.matcher(lineRead.toLowerCase().trim());
1014           lineRead = matcher.replaceAll(" ").trim();
1015           if(caseSensitive)
1016             cdg.add(lineRead);
1017           else cdg.add(lineRead.toLowerCase());
1018         }// if
1019         else {
1020           int index = lineRead.indexOf("£");
1021           if(index != -1) {
1022             String expr = lineRead.substring(0, index);
1023             // if not case-sensitive, we need to downcase all strings
1024             if(!caseSensitiveexpr = expr.toLowerCase();
1025             String code = lineRead.substring(index + 1, lineRead.length());
1026             if(nameList.equals(ALIASLISTNAME)) {
1027               alias.put(expr, code);
1028             else if(nameList.equals(ARTLISTNAME)) {
1029               def_art.put(expr, code);
1030             else if(nameList.equals(PREPLISTNAME)) {
1031               prepos.put(expr, code);
1032             else if(nameList.equals(CONNECTORLISTNAME)) {
1033               connector.put(expr, code);
1034             else if(nameList.equals(SPURLISTNAME)) {
1035               spur_match.put(expr, code);
1036             }
1037           }// if
1038         }// else
1039 
1040       }// while
1041     finally {
1042       IOUtils.closeQuietly(bufferedReader);
1043     }
1044   }// createAnnotList
1045 
1046 
1047   /**
1048    * This is the skeleton of a function which should be available in OrthoMatcher to allow a pairwise comparison of two name strings
1049    * It should eventually be made public.  It is private here (and thus non-functional) because OrthoMatcher is currently reliant
1050    * on the tokenization of the names, which are held in the global variables tokensShortAnnot and tokensLongAnnot
1051    *
1052    @param name1
1053    @param name2
1054    @return  true if the two names indicate the same person
1055    */
1056   @SuppressWarnings("unused")
1057   private boolean pairwise_person_name_match(String name1, String name2) {
1058     String shortName,longName;
1059     if (name1.length() > name2.length()) {
1060       longName = name1;
1061       shortName = name2;
1062     }
1063     else {
1064       longName = name2;
1065       shortName = name1;
1066     }
1067     if (rules.get(0).value(longName,shortName)) {//matchRule0(longName,shortName)
1068       return false;
1069     }
1070     else {
1071       if (longName.equals(shortName|| rules.get(2).value(longName, shortName||
1072               rules.get(3).value(longName, shortName)) {
1073         return true;
1074       }
1075       else {
1076         return (rules.get(0).value(longName, shortName));
1077         // boolean throwAway[] = new boolean[17];
1078         // return basic_person_match_criteria(shortName,longName,throwAway);
1079         // The above doesn't work because basic_person_match_criteria is reliant on the global
1080         // variables tokensShortAnnot and tokensLongAnnot so I just call what I can directly
1081       }
1082     }
1083   }
1084 
1085   /**
1086    * basic_person_match_criteria
1087    * Note that this function relies on various global variables in some other match rules.
1088    */
1089   private boolean basic_person_match_criteria(String shortName,
1090           String longName, boolean mr[]) {
1091 
1092     if // For 4, 5, 14, and 15, need to mark shorter annot
1093             //kalina: added 16, so it matches names when contain more than one first and one last name
1094             OrthoMatcherHelper.executeDisjunction(rules, new int[] {1,5,6,13,15,16},longName,shortName,mr)
1095     ) {
1096       return true;
1097     }
1098     return false;
1099   }
1100 
1101 
1102   /** apply_rules_namematch: apply rules similarly to lasie1.5's namematch */
1103   private boolean apply_rules_namematch(String annotationType, String shortName,
1104           String longName,Annotation prevAnnot,
1105           Annotation followAnnot,
1106           boolean longerPrevious) {
1107     boolean mr[] new boolean[rules.size()];
1108     // first apply rule for spurious matches i.e. rule0
1109     if (DEBUG) {
1110       log.debug("Now matching " + longName + "(id: " + longAnnot.getId() ") to "
1111               + shortName + "(id: " + shortAnnot.getId() ")");
1112     }
1113 
1114     if (rules.get(0).value(longName,shortName))
1115       return false;
1116     if (
1117             (// rules for all annotations
1118                     //no longer use rule1, coz I do the check for same string via the hash table
1119                     OrthoMatcherHelper.executeDisjunction(rules, new int[] {2,3},longName,shortName,mr)
1120 
1121             // rules for all annotations
1122             ||
1123             (// rules for organisation annotations
1124                     (annotationType.equals(organizationType)
1125                             //ACE addition
1126                             || annotationType.equals("Facility")
1127                     )
1128                     &&
1129                     // Should basically only match when you have a match of all tokens other than
1130                     // CDG's and function words
1131                     (
1132                             (!highPrecisionOrgs && OrthoMatcherHelper.executeDisjunction(rules,new int[] {4,6,7,8,9,10,11,12,14},longName,shortName,mr))
1133                             ||
1134                             (highPrecisionOrgs && OrthoMatcherHelper.executeDisjunction(rules,new int[] {7,8,10,11,17},longName,shortName,mr))
1135                     )
1136             )
1137     ) {// rules for organisation annotations
1138       return true;
1139     }
1140 
1141     if  (// rules for person annotations
1142             (    annotationType.equals(personType))) {
1143       if (noMatchRule1(longName, shortName,prevAnnot, longerPrevious||
1144               noMatchRule2(longName, shortName)) {
1145         // Out.prln("noMatchRule1 rejected match between " + longName + " and " + shortName);
1146         return false;
1147       }
1148       else {
1149         if (  basic_person_match_criteria(shortName,longName,mr))
1150         {
1151           if ((longName.length() != shortName.length()) && (mr[4|| mr[5|| mr[14|| mr[15])) {
1152             if (longerPrevious) {
1153               followAnnot.getFeatures().put("matchedWithLonger"true);
1154             }
1155             else {
1156               prevAnnot.getFeatures().put("matchedWithLonger"true);
1157             }
1158           }
1159           else if ((longName.length() == shortName.length()) && (mr[1])) {
1160             if (prevAnnot.getFeatures().containsKey("matchedWithLonger")) {
1161               followAnnot.getFeatures().put("matchedWithLonger"true);
1162             }
1163           }
1164           return true;
1165         }
1166         return false;
1167       }
1168     }
1169     return false;
1170   }//apply_rules
1171 
1172 
1173   /** set the extLists flag */
1174   @Optional
1175   @CreoleParameter(comment="External lists otherwise internal", defaultValue="true")
1176   public void setExtLists(Boolean newExtLists) {
1177     extLists = newExtLists.booleanValue();
1178   }//setextLists
1179 
1180   /** set the caseSensitive flag */
1181   @Optional
1182   @CreoleParameter(comment="Should this resource diferentiate on case?",defaultValue="false")
1183   public void setCaseSensitive(Boolean newCase) {
1184     caseSensitive = newCase.booleanValue();
1185   }//setextLists
1186 
1187   /** set the annotation set name*/
1188   @RunTime
1189   @Optional
1190   @CreoleParameter(comment="Annotation set name where are the annotation types (annotationTypes)")
1191   public void setAnnotationSetName(String newAnnotationSetName) {
1192     annotationSetName = newAnnotationSetName;
1193   }//setAnnotationSetName
1194 
1195   /** set the types of the annotations*/
1196   @RunTime
1197   @Optional
1198   @CreoleParameter(comment="Name of the annotation types to use", defaultValue="Organization;Person;Location;Date")
1199   public void setAnnotationTypes(List<String> newType) {
1200     annotationTypes = newType;
1201   }//setAnnotationTypes
1202 
1203   /** set whether to process the Unknown annotations*/
1204   @Optional
1205   @CreoleParameter(comment="Should we process 'Unknown' annotations?", defaultValue="true")
1206   public void setProcessUnknown(Boolean processOrNot) {
1207     this.matchingUnknowns = processOrNot.booleanValue();
1208   }//setAnnotationTypes
1209 
1210   @Optional
1211   @CreoleParameter(comment="Annotation name for the organizations", defaultValue="Organization")
1212   public void setOrganizationType(String newOrganizationType) {
1213     organizationType = newOrganizationType;
1214   }//setOrganizationType
1215 
1216   @Optional
1217   @CreoleParameter(comment="Annotation name for the persons", defaultValue="Person")
1218   public void setPersonType(String newPersonType) {
1219     personType = newPersonType;
1220   }//setPersonType
1221 
1222   /**get the name of the annotation set*/
1223   public String getAnnotationSetName() {
1224     return annotationSetName;
1225   }//getAnnotationSetName
1226 
1227   /** get the types of the annotation*/
1228   public List<String> getAnnotationTypes() {
1229     return annotationTypes;
1230   }//getAnnotationTypes
1231 
1232   public String getOrganizationType() {
1233     return organizationType;
1234   }
1235 
1236   public String getPersonType() {
1237     return personType;
1238   }
1239 
1240   public Boolean getExtLists() {
1241     return new Boolean(extLists);
1242   }
1243 
1244   /** Are we running in a case-sensitive mode?*/
1245   public Boolean getCaseSensitive() {
1246     return new Boolean(caseSensitive);
1247   }
1248 
1249   /** Return whether or not we're processing the Unknown annots*/
1250   public Boolean getProcessUnknown() {
1251     return new Boolean(matchingUnknowns);
1252   }
1253 
1254 
1255 
1256   /**
1257   No Match Rule 1:
1258   Avoids the problem of matching
1259   David Jones ...
1260   David ...
1261   David Smith
1262   Since "David" was matched with David Jones, we don't match David with David Smith.
1263    */
1264   public boolean noMatchRule1(String s1,
1265           String s2,Annotation previousAnnot, boolean longerPrevious) {
1266     //    if (DEBUG) {
1267     //      try {
1268     //        String annotString = getStringForAnnotation(previousAnnot, document );
1269 
1270     //        log.debug("Previous annotation was " + annotString +  "(id: " + previousAnnot.getId() + ")" + " features are " + previousAnnot.getFeatures());
1271     //      }
1272     //      catch (ExecutionException e) {}
1273     //    }
1274 
1275     if (longerPrevious || !previousAnnot.getFeatures().containsKey("matchedWithLonger")) {
1276       return false;
1277     }
1278     else {
1279       return true;
1280     }
1281   }//noMatchRule1
1282 
1283   /***
1284    * returns true if it detects a middle name which indicates that the name string contains a nickname or a
1285    * compound last name
1286    */
1287   private boolean detectBadMiddleTokens(ArrayList<Annotation> tokArray) {
1288     for (int j = 1;j < tokArray.size() 1;j++) {
1289       String currentToken = (StringtokArray.get(j).getFeatures().get(TOKEN_STRING_FEATURE_NAME);
1290       Matcher matcher = badMiddleTokens.matcher(currentToken.toLowerCase().trim());
1291       if (matcher.find()) {
1292         // We have found a case of a ", ',
1293         return true;
1294       }
1295     }
1296     return false;
1297   }
1298 
1299   /**
1300    * NoMatch Rule #2: Do we have a mismatch of middle initial?
1301    * Condition(s):  Only applies to person names with more than two tokens in the name
1302    *
1303    * Want George W. Bush != George H. W. Bush and George Walker Bush != George Herbert Walker Bush
1304    * and
1305    * John T. Smith != John Q. Smith
1306    * however
1307    * John T. Smith == John Thomas Smith
1308    * be careful about
1309    * Hillary Rodham Clinton == Hillary Rodham-Clinton
1310    * be careful about
1311    * Carlos Bueno de Lopez == Bueno de Lopez
1312    * and
1313    * Cynthia Morgan de Rothschild == Cynthia de Rothschild
1314    */
1315   @SuppressWarnings("unused")
1316   public boolean noMatchRule2(String s1,String s2) {
1317     if (normalizedTokensLongAnnot.size()>&& normalizedTokensShortAnnot.size()>2) {
1318       boolean retval = false;
1319       if (normalizedTokensLongAnnot.size() != normalizedTokensShortAnnot.size()) {
1320         String firstNameLong = (StringnormalizedTokensLongAnnot.get(0).getFeatures().get(TOKEN_STRING_FEATURE_NAME);
1321         String firstNameShort = (StringnormalizedTokensShortAnnot.get(0).getFeatures().get(TOKEN_STRING_FEATURE_NAME);
1322         String lastNameLong = (StringnormalizedTokensLongAnnot.get(normalizedTokensLongAnnot.size() 1).
1323         getFeatures().get(TOKEN_STRING_FEATURE_NAME);
1324         String lastNameShort = (StringnormalizedTokensShortAnnot.get(normalizedTokensShortAnnot.size() 1).
1325         getFeatures().get(TOKEN_STRING_FEATURE_NAME);
1326         if (rules.get(1).value(firstNameLong,firstNameShort&&
1327                 (rules.get(1).value(lastNameLong,lastNameShort))) {
1328           // Must have a match on first and last name for this non-match rule to take effect when the number of tokens differs
1329           if (detectBadMiddleTokens(tokensLongAnnot|| detectBadMiddleTokens(tokensShortAnnot)) {
1330             // Exclude the William (Bill) H. Gates vs. William H. Gates case and the
1331             // Cynthia Morgan de Rothschild vs. Cynthia de Rothschild case
1332             if (DEBUG && log.isDebugEnabled()) {
1333               log.debug("noMatchRule2Name did not non-match because of bad middle tokens " + s1 + "(id: " + longAnnot.getId() ") to "
1334                       + s2+ "(id: " + shortAnnot.getId() ")");
1335             }
1336             return false;
1337           }
1338           else {
1339             // Covers the George W. Bush vs George H. W. Bush and George Walker Bush vs. George Herbert Walker Bush cases
1340             retval = true;
1341           }
1342         }
1343       }
1344       else {
1345         for (int i = 1; i < normalizedTokensLongAnnot.size() 1;i++) {
1346           String s1_middle = (StringnormalizedTokensLongAnnot.get(i).getFeatures().get(TOKEN_STRING_FEATURE_NAME);
1347           String s2_middle = (StringnormalizedTokensShortAnnot.get(i).getFeatures().get(TOKEN_STRING_FEATURE_NAME);
1348           if (!caseSensitive) {
1349             s1_middle = s1_middle.toLowerCase();
1350             s2_middle = s2_middle.toLowerCase();
1351           }
1352           //          log.debug("noMatchRule2 comparing substring " + s1_middle + " to " + s2_middle);
1353           if (!(rules.get(1).value(s1_middle,s2_middle||
1354                   OrthoMatcherHelper.initialMatch(s1_middle, s2_middle))) {
1355             // We found a mismatching middle name
1356             retval = true;
1357             break;
1358           }
1359         }
1360       }
1361       if (retval && log.isDebugEnabled() && DEBUG)  {
1362         log.debug("noMatchRule2Name non-matched  " + s1 + "(id: " + longAnnot.getId() ") to "
1363                 + s2+ "(id: " + shortAnnot.getId() ")");
1364       }
1365       return retval;
1366     // if (normalizedTokensLongAnnot.size()>2 && normalizedTokensShortAnnot.size()>2)
1367     return false;
1368   }//noMatchRule2
1369 
1370   @CreoleParameter(comment="The URL to the definition file", defaultValue="resources/othomatcher/listsNM.def", suffixes="def")
1371   public void setDefinitionFileURL(java.net.URL definitionFileURL) {
1372     this.definitionFileURL = definitionFileURL;
1373   }
1374 
1375   public java.net.URL getDefinitionFileURL() {
1376     return definitionFileURL;
1377   }
1378   
1379   @CreoleParameter(comment="The encoding used for reading the definition file", defaultValue="UTF-8")
1380   public void setEncoding(String encoding) {
1381     this.encoding = encoding;
1382   }
1383   public String getEncoding() {
1384     return encoding;
1385   }
1386 
1387 
1388   public Double getMinimumNicknameLikelihood() {
1389     return minimumNicknameLikelihood;
1390   }
1391 
1392   @CreoleParameter(comment="Minimum likelihood that a name is a nickname", defaultValue="0.50")
1393   public void setMinimumNicknameLikelihood(Double minimumNicknameLikelihood) {
1394     this.minimumNicknameLikelihood = minimumNicknameLikelihood;
1395   }
1396 
1397   /**
1398    @return the highPrecisionOrgs
1399    */
1400   public Boolean getHighPrecisionOrgs() {
1401     return highPrecisionOrgs;
1402   }
1403 
1404   /**
1405    @param highPrecisionOrgs the highPrecisionOrgs to set
1406    */
1407   @Optional
1408   @CreoleParameter(comment="Use very safe features for matching orgs, such as ACME = ACME, Inc.", defaultValue="false")  
1409   public void setHighPrecisionOrgs(Boolean highPrecisionOrgs) {
1410     this.highPrecisionOrgs = highPrecisionOrgs;
1411   }
1412 
1413   public void setOrthography(AnnotationOrthography orthography) {
1414     this.orthoAnnotation = orthography;
1415   }
1416 
1417   public AnnotationOrthography getOrthography() {
1418     return orthoAnnotation;
1419   }
1420 
1421   static Pattern punctPat = Pattern.compile("[\\p{Punct}]+");
1422   // The UTF characters are right and left double and single curly quotes
1423   static Pattern badMiddleTokens = Pattern.compile("[\u201c\u201d\u2018\u2019\'\\(\\)\"]+|^de$|^von$");
1424 }