001// DtracePartitioner.java
002package daikon.tools;
003
004import java.io.BufferedReader;
005import java.io.Closeable;
006import java.io.IOException;
007import java.io.UncheckedIOException;
008import java.util.ArrayList;
009import java.util.HashMap;
010import java.util.HashSet;
011import java.util.Iterator;
012import java.util.List;
013import java.util.Set;
014import java.util.StringTokenizer;
015import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods;
016import org.checkerframework.checker.lock.qual.GuardSatisfied;
017import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor;
018import org.checkerframework.checker.mustcall.qual.Owning;
019import org.plumelib.util.FilesPlume;
020import org.plumelib.util.Partitioner;
021
022/**
023 * This class partitions Daikon trace files so that invocations of the same program point are
024 * grouped together for use with random selection.
025 */
026public class DtracePartitioner implements Closeable, Partitioner<String, String>, Iterator<String> {
027
028  /** The system-specific line separator. */
029  private static final String lineSep = System.lineSeparator();
030
031  /** reading from the file as a lazy iterator */
032  private @Owning BufferedReader br;
033
034  /** the name of the Daikon trace file */
035  private String filename;
036
037  /**
038   * @param filename the Daikon trace file to be partitioned
039   */
040  public DtracePartitioner(String filename) {
041    try {
042      this.filename = filename;
043      // System.out.printf("trying with file %s%n", filename);
044      br = FilesPlume.newBufferedFileReader(filename);
045
046    } catch (IOException e) {
047      e.printStackTrace();
048      throw new Error(e);
049    }
050  }
051
052  /** Releases resources held by this. */
053  @EnsuresCalledMethods(value = "br", methods = "close")
054  @Override
055  public void close(@GuardSatisfied DtracePartitioner this) throws IOException {
056    br.close();
057  }
058
059  @Override
060  public boolean hasNext(@GuardSatisfied DtracePartitioner this) {
061    try {
062      return br.ready();
063    } catch (IOException e) {
064      e.printStackTrace();
065      return false;
066    }
067  }
068
069  /** Not implemented, because this class does not modify the underlying trace file. */
070  @Override
071  public void remove(@GuardSatisfied DtracePartitioner this) {
072    throw new UnsupportedOperationException("Can not remove");
073  }
074
075  @Override
076  public String next(@GuardSatisfied DtracePartitioner this) {
077    try {
078      String ret = grabNextInvocation();
079      if (ret.indexOf("EXIT") != -1) {
080        if (!br.ready()) {
081          return "";
082        }
083        return next();
084      } else {
085        return ret;
086      }
087    } catch (IOException e) {
088      throw new UncheckedIOException(e);
089    }
090  }
091
092  /**
093   * Grabs the next invocation in the Daikon trace file by interpreting a blank line as the
094   * invocation delimter. Note that multiple blank lines between invocations might occur, so the
095   * callee is responsible for checking if the returned String is a blank line.
096   */
097  private String grabNextInvocation(@GuardSatisfied DtracePartitioner this) throws IOException {
098    StringBuilder sb = new StringBuilder();
099    while (br.ready()) {
100      String line = br.readLine();
101      assert line != null; // because br.ready() = true
102      line = line.trim();
103      if (line.equals("")) {
104        break;
105      }
106      sb.append(line).append(lineSep);
107    }
108    return sb.toString();
109  }
110
111  /** Returns the program point name given by the input invocation. */
112  @Override
113  public String assignToBucket(String invocation) {
114    if (invocation.indexOf(lineSep) == -1) {
115      // was: return null;
116      throw new Error("No lineSep: " + invocation);
117    }
118    return invocation.substring(0, invocation.indexOf(lineSep));
119  }
120
121  /**
122   * Same as {@link #patchValues(List, boolean)} with second arg=false.
123   *
124   * @param enters a list of program point names
125   * @return an ArrayList containing all of the elements of 'enters'. The original order is NOT
126   *     guaranteed.
127   */
128  @CreatesMustCallFor("this")
129  public List<String> patchValues(List<String> enters) {
130    return patchValues(enters, false);
131  }
132
133  /**
134   * Finds the exits that correspond to Enters.
135   *
136   * <p>Modifies: none
137   *
138   * @param enters a list of program point names
139   * @param includeUnreturnedEnters ensures that any ENTER ppt invocations will definitely have a
140   *     corresponding EXIT ppt invocation following them
141   * @return an ArrayList containing all of the elements of 'enters'. The original order is NOT
142   *     guaranteed.
143   */
144  @CreatesMustCallFor("this")
145  public List<String> patchValues(List<String> enters, boolean includeUnreturnedEnters) {
146    try {
147      System.out.println("Entering patchValues");
148      // Keep a list of enters that are so far unmatched
149      Set<String> unreturned = new HashSet<>(enters);
150
151      // Build a hashmap of values to watch
152      HashMap<Object /*String or Integer*/, String> nonceMap = new HashMap<>();
153      for (String enterStr : enters) {
154        // it could be an OBJECT or CLASS invocation ppt, ignore those
155        // by putting them in the HashMap to themselves, they'll
156        // be reaped up later
157        if (enterStr.indexOf("ENTER") == -1) {
158          nonceMap.put(enterStr, enterStr);
159          // no way for OBJECT or CLASS to be unresolved
160          unreturned.remove(enterStr);
161          continue;
162        }
163
164        // get the nonce of this invocation and use it
165        // as the key in the nonceMap, which maps
166        // nonces --> ENTER half of invocation
167        int theNonce = calcNonce(enterStr);
168        nonceMap.put(theNonce, enterStr);
169      }
170
171      br.close();
172      // look for EXIT half of invocations and augment
173      // the values of nonceMap so that the map eventually
174      // maps nonces --> full invocations with ENTER / EXIT
175      br = FilesPlume.newBufferedFileReader(filename);
176      while (br.ready()) {
177        String nextInvo = grabNextInvocation();
178        if (nextInvo.indexOf("EXIT") == -1) {
179          continue;
180        }
181        int invoNonce = calcNonce(nextInvo);
182        Integer key = invoNonce;
183        String enterInvo = nonceMap.get(key);
184        if (enterInvo != null) {
185          nonceMap.put(key, enterInvo + lineSep + nextInvo);
186          unreturned.remove(enterInvo);
187        }
188      }
189
190      // Return a list of all the invocations where matching ENTER and
191      // EXIT points were found as well as the OBJECT and CLASS
192      // invocations.
193      ArrayList<String> al = new ArrayList<>();
194      for (String s : nonceMap.values()) {
195        al.add(s);
196      }
197      // add in the invocations that were never resolved because no
198      // matching EXIT invocation exists.
199      if (!includeUnreturnedEnters) {
200        al.removeAll(unreturned);
201      }
202      return al;
203
204    } catch (IOException e) {
205      throw new UncheckedIOException(e);
206    }
207  }
208
209  private int calcNonce(String invocation) {
210    StringTokenizer st = new StringTokenizer(invocation, lineSep);
211    while (st.hasMoreTokens()) {
212      String line = st.nextToken();
213      if (line.equals("this_invocation_nonce")) {
214        return Integer.parseInt(st.nextToken());
215      }
216    }
217    throw new RuntimeException("This invocation didn't contain a nonce: " + invocation);
218  }
219}