code: package caevo.sieves;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import caevo.SieveDocument;
  import caevo.SieveDocuments;
  import caevo.SieveSentence;
  import caevo.TextEvent;
  import caevo.Timex;
  import caevo.tlink.EventTimeLink;
  import caevo.tlink.TLink;
  import caevo.util.CaevoProperties;
  import caevo.util.TreeOperator;
  import edu.stanford.nlp.ling.CoreLabel;
  import edu.stanford.nlp.trees.GrammaticalRelation;
  import edu.stanford.nlp.trees.Tree;
  import edu.stanford.nlp.trees.TypedDependency;
  
  /**
   * 
   * AdjacentVerbTimex intends to classify event/time pairs from the same sentence
   * in which the two are "adjacent" in some sense, and the event is a verb. Two
   * notions of adjacency implemented thus far are based on surface distance and
   * relative position in a dependency parse graph.
   * 
   * 
   * Parameters These parameters are booleans. If true, classify the corresponding
   * pairs AdjacentVerbTimex.EVENT_BEFORE_TIMEX - classify a pair if the event
   * comes before the timex in the text AdjacentVerbTimex.TIMEX_BEFORE_EVENT
   * AdjacentVerbTimex.EVENT_GOVERNS_TIMEX - classidy a pair if the event governs
   * the timex AdjacentVerbTimex.TIMEX_GOVERNS_EVENT
   * 
   * This parameter specifies the maximum number of words that may intervene
   * between the verb and timex such that they are still considered "adjacent".
   * AdjacentVerbTimex.numInterWords
   * 
   * timex before event - true p=0.64 89 of 139 Non-VAGUE: p=0.75 89 of 119 timex
   * before event - false p=0.70 74 of 106 Non-VAGUE: p=0.86 74 of 86
   *
   * The biggest problem is when the system says IS_INCLUDED and the correct
   * answer is VAGUE. I attribute this to annotation difficulties; I think in many
   * such cases the system is correct. More analysis to come.
   * 
   * EVENT_BEFORE_TIMEX uses preposition-based rules (default is_included)
   * TIMEX_BEFORE_EVENT default is_included; vague for event "now"; vague when
   * "said" precedes timex TODO: generalize the third rule to reporting verbs
   * TODO: check if that reporting verb governs the event after the timex
   * EVENT_GOVERNS_TIMEX just returns is_included TODO: add more rules; this is
   * hard because many errors are with event = "now", or vague
   * 
   * @author cassidy
   */
  public class AdjacentVerbTimex implements Sieve {
  
    public boolean debug = false;
    private boolean EVENT_BEFORE_TIMEX = true;
    private boolean TIMEX_BEFORE_EVENT = true;
    private boolean EVENT_GOVERNS_TIMEX = true;
    private boolean TIMEX_GOVERNS_EVENT = true;
    private int numInterWords = 0;
  
    // Exclude timex that refer to "quarters" using this regex to be
    // applied to timex.value, since such a timex usually modifies an
    // argument of the event verb, as opposed to serving as a stand-alone
    // temporal argument of the verb.
    private String valQuarterRegex = "\\d{4}-Q\\d";
    private Pattern valQuarter = Pattern.compile(valQuarterRegex);
  
    /**
     * The main function. All sieves must have this.
     */
    public List<TLink> annotate(SieveDocument doc, List<TLink> currentTLinks) {
      // Parameter Value Code
      try {
        EVENT_BEFORE_TIMEX = CaevoProperties
            .getBoolean("AdjacentVerbTimex.EVENT_BEFORE_TIMEX", true);
        TIMEX_BEFORE_EVENT = CaevoProperties
            .getBoolean("AdjacentVerbTimex.TIMEX_BEFORE_EVENT", true);
        EVENT_GOVERNS_TIMEX = CaevoProperties
            .getBoolean("AdjacentVerbTimex.EVENT_GOVERNS_TIMEX", true);
        TIMEX_GOVERNS_EVENT = CaevoProperties
            .getBoolean("AdjacentVerbTimex.TIMEX_GOVERNS_EVENT", true);
        numInterWords = CaevoProperties.getInt("AdjacentVerbTimex.numInterWords",
            0);
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      // SIEVE CODE
      // List of proposed links
      // List of sentences in doc
      List<TLink> proposed = new ArrayList<TLink>();
      List<SieveSentence> sentList = doc.getSentences();
  
      // Iterate over sentences in doc and classify verb/timex pairs
      for (SieveSentence sent : sentList) {
        // Get a list of all dependencies in the sentence
        // We'll need the parse tree from each sentence to calculate a word's POS
        List<TypedDependency> deps = sent.getDeps();
        Tree tree = null; // initialize to null so we don't end up loading it
                          // unless timexes are in the sentence
  
        if (debug == true) {
          System.out.println("DEBUG: adding tlinks from " + doc.getDocname()
              + " sentence " + sent.sentence());
        }
  
        // Check timex/event pairs against our criteria
        // Iterate over timexes
        for (Timex timex : sent.timexes()) {
          // Ensure timex passes eligibility criteria specified in validateTimex
          if (!validateTimex(timex))
            continue;
  
          // Iterate over events for fixed timex...
          for (TextEvent event : sent.events()) {
            // Ensure event passes eligibility criteria specified in validateEvent
            if (!validateEvent(event, tree, sent))
              continue;
  
            // Get parameters used for "flat" notion of adjacency
            // Distance from event to timex (positive if event is before timex)
            int eventToTimexDist = timex.getTokenOffset() - event.getIndex();
            // booleans for ordering
            boolean verbIsBeforeTimex = (eventToTimexDist <= (numInterWords + 1)
                && eventToTimexDist >= 1);
            boolean timexIsBeforeVerb = (eventToTimexDist
                * (-1) <= (numInterWords + 1) && eventToTimexDist <= -1);
  
            // Get parameters used for "structured" notion of adjacency
            // Dependency relation between event and time if applicable
            // Initialize booleans for dependency relation direction, updated
            // below
            // Dependency relation if applicable
            boolean eventDoesGovernTimex = false;
            boolean timexDoesGovernEvent = false;
            GrammaticalRelation depRel = null;
            TypedDependency eventTimeDep = null;
            // Update above booleans
            // Check if event governs timex, and if so save the dependency
            // relation depRel
            eventTimeDep = getDepSentIndexPair(deps, event.getIndex(),
                timex.getTokenOffset());
            if (eventTimeDep != null) {
              depRel = eventTimeDep.reln();
              eventDoesGovernTimex = true;
            }
            // If not, check if timex governs event, and if so save the dependency
            // relation depRel
            else {
              eventTimeDep = getDepSentIndexPair(deps, timex.getTokenOffset(),
                  event.getIndex());
              if (eventTimeDep != null) {
                depRel = eventTimeDep.reln();
                timexDoesGovernEvent = true;
              }
            }
  
            /*
             * At this point, if there's a dependency relationship between the
             * event and the time We know what it is (depRel) and the direction
             * (eventGovernsTimex vs timexGovernsEvent)
             */
  
            // Now, we determine what TLink (if any) to add to proposed for the
            // event/timex pair
            TLink tlink = null;
            TLink flatTlink_et = null;
            TLink depTlink_et = null;
            TLink flatTlink_te = null;
            TLink depTlink_te = null;
            // Now, classify pairs for various parameter settings
  
            if (EVENT_BEFORE_TIMEX && verbIsBeforeTimex) {
              flatTlink_et = eventBeforeTimex(eventToTimexDist, event, timex,
                  sent, tree);
            }
            // if timex is before verb, use these rules...
            else if (TIMEX_BEFORE_EVENT && timexIsBeforeVerb) {
              if (eventDoesGovernTimex == false) {
                // If event governs a timex that comes before it, label it vague -
                // 8/13
                // probably can be fixed with lexical features
                // flatTlink_te = null;
                flatTlink_te = new EventTimeLink(event.getEiid(), timex.getTid(),
                    TLink.Type.VAGUE);
              } else {
                flatTlink_te = timexBeforeEvent(eventToTimexDist, event, timex,
                    sent, tree); // 8/11
                if (debug == true) {
                  System.out.printf("E GOV T: %s(%s) %s(%s)\n%s",
                      event.getString(), event.getId(), timex.getText(),
                      timex.getTid(), sent.sentence());
                }
              }
            }
  
            // if verb governs timex, use these rules
            else if (EVENT_GOVERNS_TIMEX && eventDoesGovernTimex) {
              depTlink_et = eventGovernsTimex(eventToTimexDist, event, timex,
                  sent, tree, depRel);
  
            }
            // if timex governs verb, use these rules
            else if (TIMEX_GOVERNS_EVENT && timexDoesGovernEvent) {
              // TIMEX_GOVERNS_EVENT is never true in the data!
              depTlink_te = timexGovernsEvent(eventToTimexDist, event, timex,
                  sent, tree, depRel);
            }
  
            // heriarchy of preference is already determined in the above
            // conditional blocks
            // take flat unless the event and timex are not within the
            // numInterWords range,
            // in which case back off to dep. By setting numInterWords low we only
            // use the
            // flat method when the event and timex are very close together. This
            // seems
            // to work best.
            if (depTlink_te != null)
              tlink = depTlink_te;
            if (flatTlink_te != null)
              tlink = flatTlink_te;
            if (flatTlink_et != null)
              tlink = flatTlink_et;
            if (depTlink_et != null)
              tlink = depTlink_et;
  
            // Finally add tlink (as long as there is one)
            if (tlink != null) {
              proposed.add(tlink);
            }
          }
        }
      }
  
      if (debug == true) {
        System.out.println("TLINKS: " + proposed);
      }
      return proposed;
    }
  
    /**
     * 
     * @param eventToTimexDist
     * @param event
     * @param timex
     * @param sent
     * @param tree
     * @param depRel
     * @criteria is_included no matter what!
     * @return a non-null tlink just in case the implemented criteria are
     *         satisfied
     */
    private TLink timexGovernsEvent(int eventToTimexDist, TextEvent event,
        Timex timex, SieveSentence sent, Tree tree, GrammaticalRelation depRel) {
      TLink tlink = null;
      tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
          TLink.Type.IS_INCLUDED);
      if (debug == true) {
        System.out.printf("Timex-Governs-Event: %s(%s) <%s> %s(%s)\n%s\n",
            event.getString(), event.getId(), depRel.getShortName(),
            timex.getText(), timex.getTokenOffset(), sent.sentence());
      }
      return tlink;
    }
  
    /**
     * 
     * @param eventToTimexDist
     * @param event
     * @param timex
     * @param sent
     * @param tree
     * @param depRel
     * @criteria is_included no matter what!
     * @return a non-null tlink just in case the implemented criteria are
     *         satisfied
     */
    private TLink eventGovernsTimex(int eventToTimexDist, TextEvent event,
        Timex timex, SieveSentence sent, Tree tree, GrammaticalRelation depRel) {
      TLink tlink = null;
      if (timex.getText().toLowerCase().equals("now")) {
        if (event.getAspect() == TextEvent.Aspect.PROGRESSIVE)
          tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
              TLink.Type.INCLUDES);
        else
          tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
              TLink.Type.VAGUE);
      } else
        tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      // tlink = eventBeforeTimex(eventToTimexDist,event,timex,sent,tree);
      if (debug == true) {
        System.out.printf("Event-Governs-Timex: %s(%s) <%s> %s(%s)\n%s\n",
            event.getString(), event.getId(), depRel.getShortName(),
            timex.getText(), timex.getTokenOffset(), sent.sentence());
      }
  
      return tlink;
  
    }
  
    /**
     * 
     * @param eventToTimexDist
     * @param event
     * @param timex
     * @param sent
     * @param tree
     * @criteria generally is_included, with two special cases yielding vague
     * @return a non-null tlink just in case the implemented criteria are
     *         satisfied
     */
    private TLink timexBeforeEvent(int eventToTimexDist, TextEvent event,
        Timex timex, SieveSentence sent, Tree tree) {
      TLink tlink = null;
      // if the timex is "now", return a vague tlink unless the event is in the
      // progressive tense,
      // in which case we return includes.
      if (timex.getText().toLowerCase().equals("now")) {
        if (event.getAspect() == TextEvent.Aspect.PROGRESSIVE)
          tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
              TLink.Type.INCLUDES);
        else
          tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
              TLink.Type.VAGUE);
  
      }
      // if the words are directly adjecent then is_included
      else if (eventToTimexDist == -1) {
        tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      }
      // otherwise, return is_included unless the word directly preceding the
      // timex is "said".
      // this is the naive (flat) way to cover a common case that looks like this:
      //
      else {
        if (timex.getTokenOffset() > 1
            && getTextAtIndex(timex.getTokenOffset() - 1, sent).equals("said")) {
          tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
              TLink.Type.VAGUE);
        } else
          // return eventBeforeTimex(eventToTimexDist,event,timex,sent,tree);
          tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
              TLink.Type.IS_INCLUDED);
      }
      if (debug == true) {
        System.out.printf("TimexVerb: %s\n%s | %s", sent.sentence(),
            event.getString(), timex.getText());
      }
      return tlink;
    }
  
    private TLink eventBeforeTimex(int eventToTimexDist, TextEvent event,
        Timex timex, SieveSentence sent, Tree tree) {
      // If there are no intervening words, label is_included
      TLink tlink = null;
  
      if (eventToTimexDist == 1) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      } else {
        // First, determine if there is a preposition immediately
        // preceding the timex
        int precTimexIndex = timex.getTokenOffset() - 1;
        String precTimexPos = posTagFromTree(tree, sent, precTimexIndex);
        // IN is the tag for prepositions and suborinating conjunctions
        if (precTimexPos != null && precTimexPos.equals("IN")) {
          // Different rules for different prepositions
          // As of now the rules are solely based on preposition string
          // TODO: add more details to rules based on properties of event,
          // timex, and surrounding context.
          String prepText = getTextAtIndex(precTimexIndex, sent);
          tlink = applyPrepVerbTimexRules(event, timex, prepText);
        }
        // If the word right before the timex is not IN
        else {
          tlink = new EventTimeLink(event.getEiid(), timex.getTid(),
              TLink.Type.IS_INCLUDED);
        }
      }
      return tlink;
    }
  
    /**
     * 
     * @param deps
     * @param sentIndex1
     * @param sentIndex2
     * @return null unless there is a dependency in which the item at sentIndex1
     *         governs the item at sentIndex2
     */
    private TypedDependency getDepSentIndexPair(List<TypedDependency> deps,
        int sentIndex1, int sentIndex2) {
      // sentIndex_i conforms to the convention that index starts at 1!
      TypedDependency foundDep = null;
      for (TypedDependency dep : deps) {
        if (sentIndex1 == dep.gov().index() && sentIndex2 == dep.dep().index()) {
          foundDep = dep;
        }
      }
      return foundDep;
    }
  
    // validateTime ensures that timex value meets criteria
    private Boolean validateTimex(Timex timex) {
      String val = timex.getValue();
      // Return false if timex value is not a date or is a quarter
      Matcher m = valQuarter.matcher(val);
      if (!m.matches())
        return true;
      else
        return false;
    }
  
    /**
     * 
     * @param event
     * @param tree
     * @param sent
     * @criteria only allow verbal events
     * @return true just in case event criteria are satisfied
     */
    private boolean validateEvent(TextEvent event, Tree tree,
        SieveSentence sent) {
      String eventPos = posTagFromTree(tree, sent, event.getIndex());
      if (!eventPos.startsWith("VB"))
        return false;
      else
        return true;
    }
  
    // Given a sentence parse tree and an (sentence) index, return
    // the pos of the corresponding word.
    private String posTagFromTree(Tree tree, SieveSentence sent, int index) {
      // tree might be null; we keep it null until we need a pos for the first
      // time for a given sentence
      if (tree == null)
        tree = sent.getParseTree();
      String pos = TreeOperator.indexToPOSTag(tree, index);
      return pos;
    }
  
    private TLink applyPrepVerbTimexRules(TextEvent event, Timex timex,
        String prepText) {
      if (prepText.equals("in")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      } else if (prepText.equals("on")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      } else if (prepText.equals("for")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.SIMULTANEOUS);
      } else if (prepText.equals("at")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.SIMULTANEOUS);
      } else if (prepText.equals("by")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.VAGUE);
      } else if (prepText.equals("over")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      } else if (prepText.equals("during")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      } else if (prepText.equals("throughout")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.SIMULTANEOUS);
      } else if (prepText.equals("within")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      } else if (prepText.equals("until")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.BEFORE);
      } else if (prepText.equals("from")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.AFTER);
      } else if (prepText.equals("after")) {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.AFTER);
      }
      // If we encounter a different IN (prep/sub conj)
      else {
        return new EventTimeLink(event.getEiid(), timex.getTid(),
            TLink.Type.IS_INCLUDED);
      }
    }
  
    // Return the text of the token at a given index
    // Here, the index given to the function assumes that the first
    // index is 1. sent.tokens() does not count this way, so we
    // need to subtract 1 from index when retrieving our core label.
    private String getTextAtIndex(int index, SieveSentence sent) {
      CoreLabel cl = sent.tokens().get(index - 1);
      String text = cl.originalText();
      return text;
    }
  
    // public void checkTLink(TLink tlink, List<TLink> proposed) throws
    // IllegalStateException{
    // int numTLinks = proposed.size();
    // for (int t = 0; t < numTLinks; t++) {
    // if (tlink.coversSamePair(proposed.get(t))) {
    // throw new IllegalStateException("Cannot add a tlink between a pair of
    // events for which there is already a tlink in proposed");
    // }
    // }
    // }
  
    /**
     * No training. Just rule-based.
     */
    public void train(SieveDocuments trainingInfo) {
      // no training
    }
  
  }
  