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}