001package daikon.split; 002 003import daikon.Global; 004import daikon.PptTopLevel; 005import java.io.BufferedWriter; 006import java.io.File; 007import java.io.FileNotFoundException; 008import java.io.IOException; 009import java.util.ArrayList; 010import java.util.List; 011import java.util.logging.Logger; 012import java.util.regex.Matcher; 013import java.util.regex.Pattern; 014import jtb.ParseException; 015import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 016import org.checkerframework.checker.nullness.qual.NonNull; 017import org.checkerframework.checker.nullness.qual.RequiresNonNull; 018import org.checkerframework.checker.regex.qual.Regex; 019import org.checkerframework.checker.signature.qual.BinaryName; 020import org.plumelib.util.FilesPlume; 021 022/** 023 * This class contains static methods {@link #parse_spinfofile(File)} which creates Splitterss from 024 * a {@code .spinfo} file, and {@link #load_splitters} which loads the splitters for a given Ppt. 025 */ 026public class SplitterFactory { 027 private SplitterFactory() { 028 throw new Error("do not instantiate"); 029 } 030 031 public static final Logger debug = Logger.getLogger("daikon.split.SplitterFactory"); 032 033 /** The directory in which the Java files for the splitter will be made. */ 034 // This must *not* be set in a static block, which happens before the 035 // Configuration object has had a chance to possibly set 036 // dkconfig_delete_splitters_on_exit. 037 private static @MonotonicNonNull String tempdir; 038 039 /** 040 * Boolean. If true, the temporary Splitter files are deleted on exit. Set it to "false" if you 041 * are debugging splitters. 042 */ 043 public static boolean dkconfig_delete_splitters_on_exit = true; 044 045 /** 046 * String. Specifies which Java compiler is used to compile Splitters. This can be the full path 047 * name or whatever is used on the command line. Uses the current classpath. 048 */ 049 public static String dkconfig_compiler 050 // "-source 8 -target 8" is a hack for when using a Java 9+ compiler but 051 // a Java 8 runtime. A better solution would be to add 052 // these command-line arguments only when running 053 // SplitterFactoryTestUpdater, but that program does not support that. 054 = "javac -nowarn -source 8 -target 8 -classpath " + System.getProperty("java.class.path"); 055 056 /** 057 * Positive integer. Specifies the Splitter compilation timeout, in seconds, after which the 058 * compilation process is terminated and retried, on the assumption that it has hung. 059 */ 060 public static int dkconfig_compile_timeout = 20; 061 062 private static @MonotonicNonNull FileCompiler fileCompiler; // lazily initialized 063 064 /** 065 * guid is a counter that increments every time a file is written. It is used to ensure that every 066 * file written has a unique name. 067 */ 068 private static int guid = 0; 069 070 /// Methods 071 072 /** 073 * Parses the Splitter info. 074 * 075 * @param infofile filename.spinfo 076 * @return a SpinfoFile encapsulating the parsed splitter info file 077 */ 078 public static SpinfoFile parse_spinfofile(File infofile) 079 throws IOException, FileNotFoundException { 080 if (tempdir == null) { 081 tempdir = createTempDir(); 082 } 083 if (!dkconfig_delete_splitters_on_exit) { 084 System.out.println("\rSplitters for this run created in " + tempdir); 085 } 086 return new SpinfoFile(infofile, tempdir); 087 } 088 089 /** 090 * Finds the splitters that apply to a given Ppt and loads them (that is, it populates 091 * SplitterList). 092 * 093 * @param ppt the Ppt 094 * @param spfiles a list of SpinfoFiles 095 */ 096 @RequiresNonNull("tempdir") 097 public static void load_splitters(PptTopLevel ppt, List<SpinfoFile> spfiles) { 098 Global.debugSplit.fine("<<enter>> load_splitters"); 099 100 for (SpinfoFile spfile : spfiles) { 101 SplitterObject[][] splitterObjects = spfile.getSplitterObjects(); 102 StatementReplacer statementReplacer = spfile.getReplacer(); 103 for (int i = 0; i < splitterObjects.length; i++) { 104 int numsplitters = splitterObjects[i].length; 105 if (numsplitters != 0) { 106 String ppt_name = splitterObjects[i][0].getPptName(); 107 Global.debugSplit.fine( 108 " load_splitters: " 109 + ppt_name 110 + ", " 111 + ppt 112 + "; match=" 113 + matchPpt(ppt_name, ppt)); 114 if (matchPpt(ppt_name, ppt)) { 115 int numGood = 0; 116 // Writes, compiles, and loads the splitter .java files. 117 loadSplitters(splitterObjects[i], ppt, statementReplacer); 118 List<Splitter> sp = new ArrayList<>(); 119 for (int k = 0; k < numsplitters; k++) { 120 if (splitterObjects[i][k].splitterExists()) { 121 @SuppressWarnings("nullness") // dependent: because splitterExists() = true 122 @NonNull Splitter splitter = splitterObjects[i][k].getSplitter(); 123 sp.add(splitter); 124 numGood++; 125 } else { 126 // UNDONE: We should only output the load error if the 127 // compile was successful. 128 System.out.println(splitterObjects[i][k].getError()); 129 } 130 } 131 System.out.printf( 132 "%s: %d of %d splitters successful%n", ppt_name, numGood, numsplitters); 133 if (sp.size() >= 1) { 134 SplitterList.put(ppt_name, sp.toArray(new Splitter[0])); 135 } 136 // delete this entry in the splitter array to prevent it from 137 // matching any other Ppts, since the documented behavior is that 138 // it only matches one. 139 splitterObjects[i] = new SplitterObject[0]; 140 } 141 } 142 } 143 } 144 Global.debugSplit.fine("<<exit>> load_splitters"); 145 } 146 147 // Accessible for the purpose of testing. 148 public static String getTempDir() { 149 if (tempdir == null) { 150 tempdir = createTempDir(); 151 } 152 return tempdir; 153 } 154 155 /** 156 * Writes, compiles, and loads the splitter {@code .java} files for each splitterObject in 157 * splitterObjects. 158 * 159 * @param splitterObjects are the splitterObjects for ppt 160 * @param ppt the Ppt for these splitterObjects 161 * @param statementReplacer a StatementReplacer for the replace statements to be used in these 162 * splitterObjects 163 */ 164 @RequiresNonNull("tempdir") 165 private static void loadSplitters( 166 SplitterObject[] splitterObjects, PptTopLevel ppt, StatementReplacer statementReplacer) { 167 Global.debugSplit.fine("<<enter>> loadSplitters - count: " + splitterObjects.length); 168 169 // System.out.println("loadSplitters for " + ppt.name); 170 if (splitterObjects.length == 0) { 171 return; 172 } 173 for (int i = 0; i < splitterObjects.length; i++) { 174 SplitterObject splitObj = splitterObjects[i]; 175 String fileName = getFileName(splitObj.getPptName()); 176 StringBuilder fileContents; 177 try { 178 SplitterJavaSource splitterWriter = 179 new SplitterJavaSource( 180 splitObj, splitObj.getPptName(), fileName, ppt.var_infos, statementReplacer); 181 fileContents = splitterWriter.getFileText(); 182 } catch (ParseException e) { 183 System.out.println("Error in SplitterFactory while writing splitter java file for: "); 184 System.out.println(splitObj.condition() + " cannot be parsed."); 185 continue; 186 } 187 String fileAddress = tempdir + fileName; 188 @SuppressWarnings("signature") // safe, has been quoted 189 @BinaryName String fileName_bn = fileName; 190 splitObj.setClassName(fileName_bn); 191 try (BufferedWriter writer = FilesPlume.newBufferedFileWriter(fileAddress + ".java")) { 192 if (dkconfig_delete_splitters_on_exit) { 193 new File(fileAddress + ".java").deleteOnExit(); 194 new File(fileAddress + ".class").deleteOnExit(); 195 } 196 writer.write(fileContents.toString()); 197 writer.flush(); 198 } catch (IOException ioe) { 199 System.out.println("Error while writing Splitter file: " + fileAddress); 200 debug.fine(ioe.toString()); 201 } 202 } 203 List<String> fileNames = new ArrayList<>(); 204 for (int i = 0; i < splitterObjects.length; i++) { 205 fileNames.add(splitterObjects[i].getFullSourcePath()); 206 } 207 String errorOutput = null; 208 try { 209 errorOutput = compileFiles(fileNames); 210 } catch (IOException ioe) { 211 System.out.println("Error while compiling Splitter files (Daikon will continue):"); 212 debug.fine(ioe.toString()); 213 } 214 boolean errorOutputExists = errorOutput != null && !errorOutput.equals(""); 215 if (errorOutputExists && !PptSplitter.dkconfig_suppressSplitterErrors) { 216 System.out.println(); 217 System.out.println( 218 "Errors while compiling Splitter files (Daikon will use non-erroneous splitters):"); 219 System.out.println(errorOutput); 220 } 221 for (int i = 0; i < splitterObjects.length; i++) { 222 splitterObjects[i].load(); 223 } 224 225 Global.debugSplit.fine("<<exit>> loadSplitters"); 226 } 227 228 /** 229 * Compiles the files given by fileNames. Return the error output. 230 * 231 * @return the error output from compiling the files 232 * @param fileNames paths to the files to be compiled as Strings 233 * @throws IOException if there is a problem reading a file 234 */ 235 private static String compileFiles(List<String> fileNames) throws IOException { 236 // We delay setting fileCompiler until now because we want to permit 237 // the user to set the dkconfig_compiler variable. Note that our 238 // timeout is specified in seconds, but the parameter to FileCompiler 239 // is specified in milliseconds. 240 if (fileCompiler == null) { 241 fileCompiler = new FileCompiler(dkconfig_compiler, 1000 * (long) dkconfig_compile_timeout); 242 } 243 return fileCompiler.compileFiles(fileNames); 244 } 245 246 /** Determine whether a Ppt's name matches the given pattern. */ 247 private static boolean matchPpt(String ppt_name, PptTopLevel ppt) { 248 if (ppt.name.equals(ppt_name)) { 249 return true; 250 } 251 if (ppt_name.endsWith(":::EXIT")) { 252 String regex = Pattern.quote(ppt_name) + "[0-9]+"; 253 if (matchPptRegex(regex, ppt)) { 254 return true; 255 } 256 } 257 258 // Look for corresponding EXIT ppt. This is because the exit ppt usually has 259 // more relevant variables in scope (eg. return, hashcodes) than the enter. 260 String regex; 261 int index = ppt_name.indexOf("OBJECT"); 262 if (index == -1) { 263 // Didn't find "OBJECT" suffix; add ".*EXIT". 264 regex = Pattern.quote(ppt_name) + ".*EXIT"; 265 } else { 266 // Found "OBJECT" suffix. 267 if (ppt_name.length() > 6) { 268 regex = Pattern.quote(ppt_name.substring(0, index - 1)) + ":::OBJECT"; 269 } else { 270 regex = Pattern.quote(ppt_name); 271 } 272 } 273 return matchPptRegex(regex, ppt); 274 } 275 276 private static boolean matchPptRegex(@Regex String ppt_regex, PptTopLevel ppt) { 277 // System.out.println("matchPptRegex: " + ppt_regex); 278 Pattern pattern = Pattern.compile(ppt_regex); 279 String name = ppt.name; 280 Matcher matcher = pattern.matcher(name); 281 // System.out.println(" considering " + name); 282 return matcher.find(); 283 } 284 285 /** 286 * Returns a file name for a splitter file to be used with a Ppt with the name, ppt_name. The file 287 * name is ppt_name with all characters which are invalid for use in a java file name (such as 288 * ".") replaced with "_". Then "_guid" is append to the end. For example if ppt_name is 289 * "myPackage.myClass.someMethod" and guid = 12, then the following would be returned: 290 * "myPackage_myClass_someMethod_12". 291 * 292 * @param ppt_name the name of the Ppt that the splitter Java file wil be used with 293 */ 294 private static String getFileName(String ppt_name) { 295 String splitterName = clean(ppt_name); 296 splitterName = splitterName + "_" + guid; 297 guid++; 298 return splitterName; 299 } 300 301 /** 302 * Cleans str by replacing all characters that are not valid java indentifier parts with "_". 303 * 304 * @param str the string to be cleaned 305 * @return str with all non-Java-indentifier parts replaced with "_" 306 */ 307 private static String clean(String str) { 308 char[] cleaned = str.toCharArray(); 309 for (int i = 0; i < cleaned.length; i++) { 310 char c = cleaned[i]; 311 if (!Character.isJavaIdentifierPart(c)) { 312 cleaned[i] = '_'; 313 } 314 } 315 return new String(cleaned); 316 } 317 318 /** 319 * Creates the temporary directory in which splitter files will be stored. The return value 320 * includes a trailing file separtor (e.g., "/"), unless the return value is "". 321 * 322 * @return the name of the temporary directory. This is where the Splitters are created. 323 */ 324 private static String createTempDir() { 325 try { 326 File tmpDir = FilesPlume.createTempDir("daikon", "split"); 327 if (dkconfig_delete_splitters_on_exit) { 328 tmpDir.deleteOnExit(); 329 } 330 return tmpDir.getPath() + File.separator; 331 } catch (IOException e) { 332 debug.fine(e.toString()); 333 } 334 return ""; // Use current directory 335 } 336}