001    package daikon.split;
002    
003    import java.io.*;
004    import java.util.*;
005    import daikon.split.misc.*;
006    import plume.*;
007    import java.util.logging.Logger;
008    
009    /**
010     * This factory creates Splitters from map files.  The splitters
011     * partition the data based upon the the caller (i.e., which static
012     * callgraph edge was taken).
013     **/
014    public class ContextSplitterFactory
015    {
016      /** Debug tracer. **/
017      public static final Logger debug = Logger.getLogger("daikon.split.ContextSplitterFactory");
018    
019      /** Callsite granularity at the line level. */
020      public static final int GRAIN_LINE = 0;
021      /** Callsite granularity at the method level. */
022      public static final int GRAIN_METHOD = 1;
023      /** Callsite granularity at the class level. */
024      public static final int GRAIN_CLASS = 2;
025    
026      // Variables starting with dkconfig_ should only be set via the
027      // daikon.config.Configuration interface.
028      /**
029       * Enumeration (integer).  Specifies the granularity to use for
030       * callsite splitter processing.  0 is line-level granularity; 1 is
031       * method-level granularity; 2 is class-level granularity.
032       **/
033      public static int dkconfig_granularity = GRAIN_METHOD;
034    
035      /**
036       * @param files set of File objects to read from
037       * @param grain one of the GRAIN constants defined in this class
038       *
039       * Read all the map files in the given collection, create callsite
040       * splitters from them, and put the splitters into SplitterList.
041       **/
042      public static void load_mapfiles_into_splitterlist(Collection<File> files,
043                                                         int grain
044                                                         ) {
045        for (File file : files) {
046          String filename = file.getName();
047    
048          System.out.print(".");  // show progress
049          debug.fine ("Reading mapfile " + filename);
050    
051          PptNameAndSplitters[] splitters;
052          try {
053            MapfileEntry[] entries = parse_mapfile(file);
054            splitters = make_context_splitters(entries, grain);
055          } catch (IOException e) {
056            throw new Error(e);
057          }
058    
059          for (int j=0; j < splitters.length; j++) {
060            PptNameAndSplitters nas = splitters[j];
061            SplitterList.put(nas.ppt_name, nas.splitters);
062          }
063        }
064      }
065    
066      /**
067       * Simple record type to store a map file entry.
068       **/
069      public static final class MapfileEntry
070      {
071        public final long id;
072        public final String fromclass;
073        public final String frommeth;
074        public final String fromfile;
075        public final long fromline;
076        public final long fromcol;
077        public final String toexpr;
078        public final String toargs;
079        public final String toclass;
080        public final String tometh;
081    
082        public MapfileEntry(long id,
083                            String fromclass,
084                            String frommeth,
085                            String fromfile,
086                            long fromline,
087                            long fromcol,
088                            String toexpr,
089                            String toargs,
090                            String toclass,
091                            String tometh)
092        {
093          this.id = id;
094          this.fromclass = fromclass;
095          this.frommeth = frommeth;
096          this.fromfile = fromfile;
097          this.fromline = fromline;
098          this.fromcol = fromcol;
099          this.toexpr = toexpr;
100          this.toargs = toargs;
101          this.toclass = toclass;
102          this.tometh = tometh;
103        }
104      }
105    
106      /**
107       * Read and parse a map file.
108       **/
109      public static MapfileEntry[] parse_mapfile(File mapfile)
110        throws IOException
111      {
112        ArrayList<MapfileEntry> result = new ArrayList<MapfileEntry>();
113    
114        try {
115          for (String reader_line : new EntryReader(mapfile.toString())) {
116            String line = reader_line;
117            // Remove comments, skip blank lines
118            {
119              int hash = line.indexOf('#');
120              if (hash >= 0) {
121                line = line.substring(0, hash);
122              }
123              line = line.trim();
124              if (line.length() == 0) {
125                continue;
126              }
127            }
128    
129            // Example line:
130            //   0x85c2e8c PC.RPStack get [PC/RPStack.java:156:29] -> "getCons" [(I)LPC/Cons;] PC.RP meth
131            // where this ^ is a tab and the rest are single spaces
132            long id;
133            String fromclass, frommeth, fromfile; long fromline, fromcol;
134            String toexpr, toargs, toclass, tometh;
135    
136            int tab = line.indexOf('\t');
137            int arrow = line.indexOf(" -> ");
138            assert tab >= 0;
139            assert arrow >= tab;
140    
141            id = Long.decode(line.substring(0, tab)).longValue();
142    
143            // parse "called from" data
144            {
145              StringTokenizer tok = new StringTokenizer(line.substring(tab+1,arrow));
146              fromclass = tok.nextToken();
147              frommeth = tok.nextToken();
148              String temp = tok.nextToken();
149              assert temp.startsWith("[");
150              assert temp.endsWith("]");
151              temp = temp.substring(1, temp.length()-1);
152              int one = temp.indexOf(':');
153              int two = temp.lastIndexOf(':');
154              fromfile = temp.substring(0, one);
155              fromline = Integer.parseInt(temp.substring(one+1,two));
156              fromcol = Integer.parseInt(temp.substring(two+1));
157              assert ! tok.hasMoreTokens();
158            }
159    
160            // parse "call into" data
161            {
162              String to = line.substring(arrow + 4); // 4: " -> "
163              assert to.startsWith("\"") : to;
164              int endquote = to.indexOf("\" ", 1);
165              toexpr = line.substring(1, endquote);
166              StringTokenizer tok = new StringTokenizer(to.substring(endquote+1));
167              toargs = tok.nextToken();
168              toclass = tok.nextToken();
169              tometh = tok.nextToken();
170              assert ! tok.hasMoreTokens();
171            }
172    
173            MapfileEntry entry = new MapfileEntry
174              (id, fromclass, frommeth, fromfile, fromline, fromcol,
175               toexpr, toargs, toclass, tometh);
176    
177            result.add(entry);
178          }
179        } catch (NumberFormatException e) {
180          throw (IOException)(new IOException("Malformed number").initCause(e));
181        }
182    
183        return result.toArray(new MapfileEntry[result.size()]);
184      }
185    
186      /**
187       * @param grain one of the GRAIN constants defined in this class
188       *
189       * Given map file data, create splitters given the requested
190       * granularity.
191       **/
192      public static PptNameAndSplitters[] make_context_splitters(MapfileEntry[] entries,
193                                                                 int grain) {
194        // Use a 2-deep map structure.  First key is an identifier
195        // (~pptname) for the callee.  Second key is an idenfier for the
196        // caller (based on granularity).  The value is a set of Integers
197        // giving the ids that are associated with that callgraph edge.
198        Map<String,Map<String,Set<Long>>> callee2caller2ids = new HashMap<String,Map<String,Set<Long>>>();
199    
200        // For each entry
201        for (int i=0; i < entries.length; i++) {
202          MapfileEntry entry = entries[i];
203          String callee_ppt_name = entry.toclass + "." + entry.tometh;
204    
205          // Compute the caller based on granularity
206          String caller_condition;
207          switch (grain) {
208          case GRAIN_LINE:
209            caller_condition = "<Called from "
210              + entry.fromclass + "." + entry.frommeth
211              + ":" + entry.fromline + ":" + entry.fromcol + ">";
212            break;
213          case GRAIN_METHOD:
214            caller_condition = "<Called from "
215              + entry.fromclass + "." + entry.frommeth + ">";
216            break;
217          case GRAIN_CLASS:
218            caller_condition = "<Called from "
219              + entry.fromclass + ">";
220            break;
221          default:
222            throw new UnsupportedOperationException("Unknown grain " + grain);
223          }
224    
225          // Place the ID into the mapping
226          Map<String,Set<Long>> caller2ids = callee2caller2ids.get(callee_ppt_name);
227          if (caller2ids == null) {
228            caller2ids = new HashMap<String,Set<Long>>();
229            callee2caller2ids.put(callee_ppt_name, caller2ids);
230          }
231          Set<Long> ids = caller2ids.get(caller_condition);
232          if (ids == null) {
233            ids = new TreeSet<Long>();
234            caller2ids.put(caller_condition, ids);
235          }
236          ids.add(new Long(entry.id));
237        } // for all entries
238    
239        ArrayList<PptNameAndSplitters> result = new ArrayList<PptNameAndSplitters>();
240    
241        // For each callee
242        for (Map.Entry<String,Map<String,Set<Long>>> ipair : callee2caller2ids.entrySet()) {
243          String callee_ppt_name = ipair.getKey();
244          Map<String,Set<Long>> caller2ids = ipair.getValue();
245    
246          // 'splitters' collects all splitters for one callee_ppt_name
247          Collection<Splitter> splitters = new ArrayList<Splitter>();
248    
249          // For each caller of that callee
250          for (Map.Entry<String,Set<Long>> jpair : caller2ids.entrySet()) {
251            String caller_condition = jpair.getKey();
252            List<Long> ids = new ArrayList<Long>(jpair.getValue());
253    
254            // Make a splitter
255            long[] ids_array = new long[ids.size()];
256            for (int k=0; k < ids_array.length; k++) {
257              ids_array[k] = ids.get(k).longValue();
258            }
259    
260            debug.fine ("Creating splitter for " + callee_ppt_name
261                        + " with ids " +  ids
262                        + " named " + caller_condition);
263    
264            Splitter splitter = new CallerContextSplitter(ids_array, caller_condition);
265            splitters.add(splitter);
266          }
267    
268          // Collect all splitters for one callee_ppt_name
269          Splitter[] splitters_array =
270            splitters.toArray(new Splitter[splitters.size()]);
271          result.add(new PptNameAndSplitters(callee_ppt_name, splitters_array));
272        }
273    
274        return
275          result.toArray(new PptNameAndSplitters[result.size()]);
276      }
277    
278      /**
279       * Simple record type to store a PptName and Splitter array.
280       **/
281      public static final class PptNameAndSplitters
282      {
283        public final String ppt_name; // really more like a regexp
284        public final Splitter[] splitters;
285    
286        public PptNameAndSplitters(String ppt_name, Splitter[] splitters) {
287          this.ppt_name = ppt_name;
288          this.splitters = splitters;
289        }
290    
291        public String toString() {
292          return "PptNameAndSplitters<" + ppt_name + ","
293            + Arrays.asList(splitters).toString() + ">";
294        }
295    
296      }
297    
298    }