code: package caevo;
  
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Vector;
  
  import caevo.tlink.EventEventLink;
  import caevo.tlink.EventTimeLink;
  import caevo.tlink.TLink;
  import caevo.tlink.TimeTimeLink;
  
  /**
   * Class to compute closure over a set of temporal relations.
   * 
   * @author Nate Chambers
   */
  public class Closure {
    static boolean report = true;
    static String rulePath = "/closure-sieve.dat";
    HashMap<String, TLink.Type> rules[];
  
    public Closure() throws IOException {
      this(Closure.class.getResource(rulePath));
    }
  
    public Closure(String path) throws IOException {
      this(new File(path).toURI().toURL());
    }
  
    public Closure(URL url) throws IOException {
      loadClosureRules(url);
    }
  
    // 0: A-B A-C
    // 1: A-B C-A
    // 2: B-A A-C
    // 3: B-A C-A
    private TLink.Type closeLinks(TLink.Type relation1, TLink.Type relation2,
        int matchCase) {
      return rules[matchCase].get(relation1 + " " + relation2);
    }
  
    /**
     * Reads the closure rules from a data file
     */
    private void loadClosureRules(URL url) throws IOException {
      int matchCase = 0;
      int numAdded = 0;
      BufferedReader in = new BufferedReader(
          new InputStreamReader(url.openStream()));
      try {
        System.out.println("Loading closure rules from " + url);
  
        rules = new HashMap[4];
        for (int i = 0; i < 4; i++) {
          rules[i] = new HashMap<String, TLink.Type>();
        }
  
        while (in.ready()) {
          String line = in.readLine();
  
          // defines start of new match case
          if (line.matches(".*A-B A-C.*"))
            matchCase = 0;
          else if (line.matches(".*A-B C-A.*"))
            matchCase = 1;
          else if (line.matches(".*B-A A-C.*"))
            matchCase = 2;
          else if (line.matches(".*B-A C-A.*"))
            matchCase = 3;
  
          // line comments
          else if (line.indexOf('/') > -1) {
          }
          // e.g. "TLink.SIMULTANEOUS TLink.ENDS TLink.ENDS"
          else if (line.length() > 5) {
            String parts[] = line.split("\\s+");
            TLink.Type first = TLink.Type.valueOf(parts[0]);
            TLink.Type second = TLink.Type.valueOf(parts[1]);
            TLink.Type closed = TLink.Type.valueOf(parts[2]);
  
            // System.out.println("Adding closure rule: " + first + " " + second +
            // " " + closed);
            rules[matchCase].put(first + " " + second, closed);
            numAdded++;
          }
        }
  
        // Sanity check.
        if (numAdded == 0) {
          System.out.println(
              "WARNING!! Didn't load any closure rules. Was I supposed to?");
        }
      } finally {
        in.close();
      }
    }
  
    /**
     * Returns true if the given link is consistent with the rest of the
     * relations. This function actually performs closure, so the test is pretty
     * expensive to run.
     * 
     * @return True if the link is consistent, false otherwise
     */
    public boolean isConsistent(Collection<TLink> relations, TLink link) {
      // Hash what we've seen already
      HashMap<String, TLink.Type> seen = new HashMap<String, TLink.Type>();
      for (TLink tlink : relations)
        seen.put(tlink.getId1() + tlink.getId2(), tlink.getRelation());
  
      int status = newLinkStatus(seen, link.getId1(), link.getId2(),
          link.getRelation());
  
      if (status == 2)
        return false;
      else
        return true;
    }
  
    /**
     * Compute a larger closed set of relations using transitivity rules of
     * temporal reasoning. Adds the new links directly to the newRelations List.
     * 
     * @param relations
     *          A List of TLinks from which to compute closure.
     * @param newRelations
     *          The List in which to append any new closure links.
     * @return True if the closure is consistent, false if a conflict occurred.
     */
    public boolean computeClosure(List<TLink> relations, List<TLink> newRelations,
        boolean prints) {
      boolean noneAdded = false;
      TLink tlink1 = null, tlink2 = null;
      String eid1, eid2, B, C;
      int matchCase = 0;
      int size = 0, oldsize;
      int start;
      boolean conflict = false;
  
      if (prints)
        report = true;
      else
        report = false;
  
      if (report)
        System.out
            .println("Computing Closure (" + relations.size() + " relations)");
  
      // Save what we've seen already
      HashMap<String, TLink.Type> seen = new HashMap<String, TLink.Type>();
      for (TLink tlink : relations)
        seen.put(tlink.getId1() + tlink.getId2(), tlink.getRelation());
  
      while (!noneAdded) {
        // System.out.println("iter = " + iter);
        oldsize = size;
        size = relations.size();
        for (int i = 0; i < size; i++) {
          if (i >= oldsize)
            start = i + 1;
          else
            start = oldsize;
          tlink1 = relations.get(i);
          TLink.Type rel1 = tlink1.getRelation();
          eid1 = tlink1.getId1();
          eid2 = tlink1.getId2();
          if (report)
            System.out.println("Starting with tlink1 = " + tlink1);
  
          for (int j = start; j < size; j++) {
            tlink2 = relations.get(j);
            TLink.Type rel2 = tlink2.getRelation();
            B = null;
            C = null;
            matchCase = -1;
            if (report)
              System.out.println("\ttlink2 = " + tlink2);
  
            // Find which out of 4 transitive patterns to use
  
            // A-B-Rel, A-C-Rel
            if (eid1.equals(tlink2.getId1()) && !eid2.equals(tlink2.getId2())) {
              matchCase = 0;
              B = eid2;
              C = tlink2.getId2();
            }
            // A-B-Rel, C-A-Rel
            else if (eid1.equals(tlink2.getId2())
                && !eid2.equals(tlink2.getId1())) {
              matchCase = 1;
              B = eid2;
              C = tlink2.getId1();
            }
            // B-A-Rel, A-C-Rel
            else if (eid2.equals(tlink2.getId1())
                && !eid1.equals(tlink2.getId2())) {
              matchCase = 2;
              B = eid1;
              C = tlink2.getId2();
            }
            // B-A-Rel, C-A-Rel
            else if (eid2.equals(tlink2.getId2())
                && !eid1.equals(tlink2.getId1())) {
              matchCase = 3;
              B = eid1;
              C = tlink2.getId1();
            }
  
            // System.out.println("\tB = " + B + " C = " + C + " matchCase = " +
            // matchCase);
  
            // Ignore closing trivial relations such as A-A-INCL, A-A-SIMUL
            if (eid1.equals(eid2) && (rel1 == TLink.Type.SIMULTANEOUS
                || rel1 == TLink.Type.INCLUDES)) {
              matchCase = -1;
            } else if (tlink2.getId2().equals(tlink2.getId1())
                && (rel2 == TLink.Type.SIMULTANEOUS
                    || rel2 == TLink.Type.INCLUDES)) {
              matchCase = -1;
            }
  
            if (B != null && C != null && matchCase != -1) {
              // System.out.println("Checking B=" + B + " C=" + C + " case=" +
              // matchCase);
              // Find the relation to close it
              TLink.Type newrel = closeLinks(rel1, rel2, matchCase);
              // System.out.println(rel1 + " " + rel2 + " newrel=" + newrel);
              if (newrel != null) {
                if (report)
                  System.out.println("New link! " + newrel + "(from B=" + B
                      + " C=" + C + " matchCase=" + matchCase + ")");
                TLink newLink = addlink(seen, relations, B, C, newrel);
                // If this new link conflicts, remember that
                if (newLink == null)
                  conflict = true;
                else
                  newRelations.add(newLink);
              }
            }
          }
        }
        noneAdded = (relations.size() == size);
      }
  
      return conflict;
    }
  
    /**
     * Computes closure over the given relations, and returns a list of new links
     * created by the closure rules.
     * 
     * @param relations
     *          List of known relations.
     * @return
     */
    public List<TLink> computeClosure(List<TLink> relations) {
      return computeClosure(relations, false);
    }
  
    /**
     * Computes closure over the given relations, keeps the given List unchanged,
     * and returns a new list of closed relations.
     * 
     * @param relations
     *          The list of relations from which we compute closure.
     * @param debug
     * @return A list of new relations computed from transitivity rules.
     */
    public List<TLink> computeClosure(List<TLink> relations, boolean debug) {
      List<TLink> cloned = new ArrayList<TLink>(relations);
      List<TLink> newRelations = new ArrayList<TLink>();
      computeClosure(cloned, newRelations, debug);
      return newRelations;
    }
  
    private TLink.Type getClosed(int matchCase, TLink.Type relation,
        TLink.Type relation2) {
      // System.out.println("closing..." + relation + " " + relation2);
      TLink.Type rel = closeLinks(relation, relation2, matchCase);
      // System.out.println("closed..." + rel);
      return rel;
    }
  
    /**
     * Creates the appropriate type of TLink, based on the string from of A and B.
     * If it is e30 or ei12 then it is an event, whereas t14 is a time.
     * 
     * @param relations
     *          A vector of TLinks to which to add a new link
     * @param A
     *          The id of the first event/time
     * @param B
     *          The id of the second event/time
     * @param relation
     *          The type of relation between A and B
     * @return The new link that was added to the relations list. null if the link
     *         was duplicate, or conflicted.
     */
    private TLink addlink(HashMap<String, TLink.Type> seen, List<TLink> relations,
        String A, String B, TLink.Type rel) {
      int status = newLinkStatus(seen, A, B, rel);
  
      if (status == 0) {
        TLink link;
        int times = 0;
  
        // See what type of relation we are adding (e.g. event-time)
        // YES, this depends on making sure all time variables start with 't'
        if (A.charAt(0) == 't')
          times++;
        if (B.charAt(0) == 't')
          times++;
  
        // Create the appropriate TLink
        if (times == 2)
          link = new TimeTimeLink(A, B, rel, true);
        else if (times == 1)
          link = new EventTimeLink(A, B, rel, true);
        else
          link = new EventEventLink(A, B, rel, true);
  
        relations.add(link);
        seen.put(A + B, rel);
  
        // System.out.println("Added link " + A + " " + relation + " " + B);
        return link;
      } else
        return null;
    }
  
    /**
     * Tells you if a new proposed link A-B is ok with the current relations. 0:
     * doesn't exist. 1: already exists, or is consistent with existing relation
     * A-B 2: conflicts with existing relation between A-B
     */
    private int newLinkStatus(HashMap<String, TLink.Type> seen, String A,
        String B, TLink.Type rel) {
      // Make sure we don't already have a relation
      if (seen.containsKey(A + B)) {
        TLink.Type current = seen.get(A + B);
        if (current != rel) {
  
          // some relation clashes are ok
          if ((current == TLink.Type.BEFORE && rel == TLink.Type.IBEFORE)
              || (current == TLink.Type.IBEFORE && rel == TLink.Type.BEFORE))
            return 1;
  
          if (report) {
            System.err.println("Closure conflict: " + A + " " + B);
            System.err.println("...old relation " + A + " " + seen.get(A + B)
                + " " + B + " adding new relation " + A + " " + rel + " " + B);
          }
          return 2;
        } else
          return 1; // exact same relation already exists
      }
      // Make sure the inverse relation doesn't exist
      else if (seen.containsKey(B + A)) {
        TLink.Type reverse = seen.get(B + A);
        TLink.Type relReversed = TLink.invertRelation(rel);
        // inverse simultaneous relations are harmless, just ignore
        if (reverse == relReversed ||
        // INCLUDES and BEGINS/ENDS is ok
            (rel == TLink.Type.INCLUDES
                && (reverse == TLink.Type.BEGINS || reverse == TLink.Type.ENDS))
            ||
            // BEGINS/ENDS and INCLUDES is ok
            (reverse == TLink.Type.INCLUDES
                && (rel == TLink.Type.BEGINS || rel == TLink.Type.ENDS)))
          return 1;
  
        if (report) {
          System.err.println("Closure conflict: " + A + " " + B);
          System.err.println("...old relation " + B + " " + seen.get(B + A) + " "
              + A + " adding new relation " + A + " " + rel + " " + B);
        }
        return 2;
      }
      // Else, you can add the new relation.
      else
        return 0;
    }
  
    /**
     * Generates a complete set of NONE tlinks between all pairs of events that
     * are not already tlinks. One pair A-B or B-A, not both A-B and B-A.
     * 
     * @param tlinks
     *          The current TLinks in the document.
     * @param events
     *          All of the events in the document.
     * @param eiidToID
     *          A mapping from the eiid's to id's in TimeBank. Use null if no
     *          mapping needed (TempEval).
     * @return Vector of NONE tlinks
     */
    public Vector<TLink> addNoneLinks(List<TLink> tlinks, List<TextEvent> events,
        Map<String, String> eiidToID) {
      Vector<TLink> newlinks = new Vector<TLink>();
      HashMap<String, HashSet<String>> map = new HashMap<String, HashSet<String>>();
  
      // Save the tlinks for quick access
      for (TLink link : tlinks) {
        HashSet<String> set = map.get(link.getId1());
        if (set == null)
          set = new HashSet<String>();
        set.add(link.getId2());
        map.put(link.getId1(), set);
      }
  
      // Generate all pairs of NONE links
      for (TextEvent event1 : events) {
        for (TextEvent event2 : events) {
          if (!event1.equals(event2)) {
            // only event pairs 1 sentence away
            if (Math.abs(event1.getSid() - event2.getSid()) < 2) {
              String id1 = event1.getEiid();
              String id2 = event2.getEiid();
              if (eiidToID != null && eiidToID.containsKey(id1))
                id1 = eiidToID.get(id1);
              if (eiidToID != null && eiidToID.containsKey(id2))
                id2 = eiidToID.get(id2);
  
              // if this pair is not yet linked
              if (!containsLink(map, id1, id2)) {
                // randomly choose order
                String first = id1;
                String second = id2;
                if (Math.random() < 0.5) {
                  first = second;
                  second = event1.getEiid();
                }
  
                // Add to the map
                HashSet<String> set = map.get(first);
                if (set == null)
                  set = new HashSet<String>();
                set.add(second);
                map.put(first, set);
  
                // Create TLink
                newlinks.add(new EventEventLink(first, second, "none"));
              }
            }
          }
        }
      }
      return newlinks;
    }
  
    /**
     * @return True if the relation event1-event2 or event2-event1 exists in the
     *         hashmap
     */
    private boolean containsLink(HashMap<String, HashSet<String>> map,
        String event1, String event2) {
      if (map.containsKey(event1) && map.get(event1).contains(event2))
        return true;
      else if (map.containsKey(event2) && map.get(event2).contains(event1))
        return true;
      else
        return false;
    }
  
    public void printRules() {
      for (int i = 0; i < rules.length; i++) {
        System.out.println("i=" + i);
        System.out.println(rules[i]);
      }
    }
  }
  