001package daikon.split; 002 003import java.io.File; 004import java.io.FileNotFoundException; 005import java.io.IOException; 006import java.io.LineNumberReader; 007import java.util.ArrayList; 008import java.util.List; 009import jtb.ParseException; 010import org.checkerframework.checker.initialization.qual.UnknownInitialization; 011import org.checkerframework.checker.nullness.qual.EnsuresNonNull; 012import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; 013import org.checkerframework.checker.nullness.qual.Nullable; 014import org.checkerframework.checker.nullness.qual.RequiresNonNull; 015import org.checkerframework.dataflow.qual.Pure; 016import org.plumelib.util.FilesPlume; 017 018/** 019 * SpinfoFile stores information parsed from a {@code .spinfo} file. The constructor parses the 020 * file; then clients can make calls to retrieve the parsed information. 021 * 022 * <p>This file uses the term "ppt section" and "replace section" to describe what is refered to as 023 * a "Program Point Section" and "replacement sections" in the Daikon User Manual, respectively. A 024 * "ppt statement" is a single line from a "ppt section". 025 */ 026public class SpinfoFile { 027 028 /** The path of the file being parsed. This is used only for debugging output. */ 029 private String spinfoFileName; 030 031 /** The directory in which the java files for the splitters are kept. */ 032 private String tempDir; 033 034 /** A Replacer constructed out of the replace statements from the spinfo file. */ 035 // It is set by parseFile(), which is called by the constructor. 036 private StatementReplacer statementReplacer; 037 038 /** 039 * splitterObjects is an array of arrays of SplitterObjects where each array of SplitterObjects 040 * contains all the SplitterObjects for a single Ppt. 041 */ 042 // It is set by parseFile(), which is called by the constructor. 043 // Invariant: for all i, j, and k: 044 // splitterObject[i][j].getPptName() == splitterObject[i][k].getPptName() 045 // splitterObject[i][k].getPptName() != splitterObject[j][k].getPptName() || i = j 046 private SplitterObject[][] splitterObjects; 047 048 private static String lineSep = System.lineSeparator(); 049 050 /** 051 * Parses file spinfoFile. 052 * 053 * @param spinfoFile the file to be parsed 054 * @param tempDir the directory in which the splitters' java files are created 055 */ 056 SpinfoFile(File spinfoFile, String tempDir) { 057 this.tempDir = tempDir; 058 this.spinfoFileName = spinfoFile.toString(); 059 try (LineNumberReader reader = FilesPlume.newLineNumberFileReader(spinfoFile)) { 060 parseFile(reader); 061 } catch (FileNotFoundException e) { 062 throw new RuntimeException(e); 063 } catch (IOException e) { 064 throw new RuntimeException(e); 065 } 066 } 067 068 /** 069 * Gets the StatementReplacer for the replace statements in the file parsed. 070 * 071 * @return the Replacer for the replace statements in the file parsed 072 */ 073 public StatementReplacer getReplacer() { 074 return statementReplacer; 075 } 076 077 /** 078 * Gets the SplitterObjects for the file parsed. For each Ppt in the spinfo file, one array of 079 * SplitterObjects is created. This method returns an array of those arrays. 080 * 081 * @return the SplitterObjects for the file parsed 082 */ 083 public SplitterObject[][] getSplitterObjects() { 084 return splitterObjects; 085 } 086 087 /** Return the number of splitters (SplitterObject objects) represented by this file. */ 088 public int numSplittterObjects() { 089 int result = 0; 090 for (SplitterObject[] spa : splitterObjects) { 091 result += spa.length; 092 } 093 return result; 094 } 095 096 /** 097 * Return the number of splitters (SplitterObject objects) represented by all the files in the 098 * list. 099 */ 100 public static int numSplittterObjects(List<SpinfoFile> spinfoFiles) { 101 int result = 0; 102 for (SpinfoFile spf : spinfoFiles) { 103 result += spf.numSplittterObjects(); 104 } 105 return result; 106 } 107 108 /** 109 * parseFile sets the member fields statementReplacer and splitterObjects, from the spinfoFile. 110 * 111 * @param spinfoFile a LineNumberReader for the spinfo file being parsed 112 * @throws IOException if an I/O error occurs 113 */ 114 @RequiresNonNull("tempDir") 115 @EnsuresNonNull({"statementReplacer", "splitterObjects"}) 116 public void parseFile(@UnknownInitialization SpinfoFile this, LineNumberReader spinfoFile) 117 throws IOException { 118 List<ReplaceStatement> replaceStatements = new ArrayList<>(); 119 List<List<String>> pptSections = new ArrayList<>(); 120 try { 121 String line = spinfoFile.readLine(); 122 while (line != null) { 123 line = line.trim(); 124 if (isComment(line) || line.equals("")) { 125 // nothing to do 126 } else if (line.equals("REPLACE")) { 127 readReplaceStatements(spinfoFile, replaceStatements); 128 } else if (line.startsWith("PPT_NAME")) { 129 line = line.substring("PPT_NAME".length()).trim(); 130 readPptStatements(spinfoFile, pptSections, line); 131 } else { 132 throw new RuntimeException( 133 "Illegal file format in: " 134 + spinfoFileName 135 + lineSep 136 + "at: " 137 + spinfoFile.getLineNumber() 138 + lineSep 139 + line); 140 } 141 line = spinfoFile.readLine(); 142 } 143 } catch (IOException ioe) { 144 // System.err.println(ioe); 145 System.err.println("Error in " + spinfoFileName); 146 System.err.println(" at line number " + spinfoFile.getLineNumber() + " of .spinfo file"); 147 throw new RuntimeException(ioe); 148 } catch (ParseException e) { 149 // System.err.println(ioe); 150 System.err.println("Error in " + spinfoFileName); 151 System.err.println(" at line number " + spinfoFile.getLineNumber() + " of .spinfo file"); 152 throw new RuntimeException(e); 153 } 154 statementReplacer = new StatementReplacer(replaceStatements); 155 splitterObjects = createSplitterObjects(pptSections); 156 } 157 158 /** 159 * Reads a group of replace statement lines. The method declaration and the return statement of a 160 * replace statement is placed in a ReplaceStatement. The ReplaceStatements are then placed into 161 * replaceStatements. 162 * 163 * @param spinfoFile a LineNumberReader for the spinfo file being parsed 164 * @param replaceStatements the List into which the ReplaceStatements are added 165 * @throws IOException if there is a problem reading the file 166 * @throws ParseException if there is a problem parsing 167 */ 168 private void readReplaceStatements( 169 @UnknownInitialization SpinfoFile this, 170 LineNumberReader spinfoFile, 171 List<ReplaceStatement> replaceStatements) 172 throws IOException, ParseException { 173 String methodDeclaration = spinfoFile.readLine(); 174 while (!isBlank(methodDeclaration)) { 175 String returnStatement = spinfoFile.readLine(); 176 if (isBlank(returnStatement)) { 177 throw new RuntimeException( 178 "MalFormed .spinfo file in: " 179 + spinfoFileName 180 + lineSep 181 + (spinfoFile.getLineNumber() - 1) 182 + lineSep 183 + methodDeclaration 184 + lineSep 185 + "Each replace statement must be a pair of lines."); 186 } 187 ReplaceStatement replaceStatement = 188 new ReplaceStatement(methodDeclaration.trim(), returnStatement.trim()); 189 replaceStatements.add(replaceStatement); 190 methodDeclaration = spinfoFile.readLine(); 191 } 192 } 193 194 /** 195 * Reads a group of Ppt statements (statements following the line "PPT_NAME..."). Makes a list 196 * whose first element is the name on the PPT_NAME line (but without the "PPT_NAME" prefix), and 197 * whose additional elements are all the non-empty lines up to the next empty line. Puts this list 198 * in pptSections. 199 * 200 * @param spinfoFile a LineNumberReader for the spinfo file being parsed 201 * @param pptSections the List into which the List of lines for this pptSection are to be added 202 * @param pptName name of the ppt 203 * @throws IOException if an I/O error occurs 204 */ 205 private void readPptStatements( 206 @UnknownInitialization SpinfoFile this, 207 LineNumberReader spinfoFile, 208 List<List<String>> pptSections, 209 String pptName) 210 throws IOException { 211 List<String> pptSection = new ArrayList<>(); 212 pptSection.add(pptName); 213 String line = spinfoFile.readLine(); 214 while ((line != null) && !line.trim().equals("")) { 215 pptSection.add(line); 216 line = spinfoFile.readLine(); 217 } 218 pptSections.add(pptSection); 219 } 220 221 /** 222 * Creates the SplitterObjects for the Ppt section in pptSections. 223 * 224 * @param pptSections is a List of Lists of Strings. Each list should include all the lines from a 225 * single Ppt Section. This includes the first line, but without the prefix "PPT_NAME" 226 * @return an array of arrays with each array containing the SplitterObjects for one of lists of 227 * ppt statements found in pptSections 228 */ 229 @RequiresNonNull("tempDir") 230 private SplitterObject[][] createSplitterObjects( 231 @UnknownInitialization SpinfoFile this, List<List<String>> pptSections) { 232 List<SplitterObject[]> splittersForAllPpts = new ArrayList<>(); 233 for (List<String> pptSection : pptSections) { 234 List<SplitterObject> splittersForThisPpt = new ArrayList<>(); 235 if (pptSection.size() > 0) { 236 String pptName = pptSection.get(0).trim(); 237 SplitterObject splitObj = null; 238 for (int j = 1; j < pptSection.size(); j++) { 239 String pptStatement = pptSection.get(j); 240 if (isComment(pptStatement)) { 241 // nothing to do 242 } else if (isFormatting(pptStatement)) { 243 if (splitObj == null) { 244 throw new RuntimeException( 245 "Malformed Spinfo file: " 246 + spinfoFileName 247 + lineSep 248 + "Indented format specification, " 249 + pptStatement 250 + ", must follow an unindented condition" 251 + lineSep 252 + "For details, see the Daikon manual, section \"Splitter info file\""); 253 } else { 254 setFormatting(splitObj, pptStatement.trim()); 255 } 256 } else { 257 splitObj = new SplitterObject(pptName, pptStatement.trim(), tempDir); 258 splittersForThisPpt.add(splitObj); 259 } 260 } 261 } 262 splittersForAllPpts.add(splittersForThisPpt.toArray(new SplitterObject[0])); 263 } 264 return splittersForAllPpts.toArray(new SplitterObject[0][0]); 265 } 266 267 /** 268 * Updates obj's fields to take in account the formatting command given by command. If the command 269 * is invalid an error message is given. Extra white space is ignored. 270 * 271 * @param obj the splitterObject for which command is intended 272 * @param command the formatting command to be applied to obj 273 */ 274 private static void setFormatting(SplitterObject obj, String command) { 275 command = command.trim(); 276 if (command.startsWith("DAIKON_FORMAT")) { 277 obj.daikonFormat = command.substring("DAIKON_FORMAT".length()).trim(); 278 obj.dummyDesired = true; 279 } else if (command.startsWith("JAVA_FORMAT")) { 280 obj.javaFormat = command.substring("JAVA_FORMAT".length()).trim(); 281 obj.dummyDesired = true; 282 } else if (command.startsWith("ESC_FORMAT")) { 283 obj.escFormat = command.substring("ESC_FORMAT".length()).trim(); 284 obj.dummyDesired = true; 285 } else if (command.startsWith("SIMPLIFY_FORMAT")) { 286 obj.simplifyFormat = command.substring("SIMPLIFY_FORMAT".length()).trim(); 287 obj.dummyDesired = true; 288 } else if (command.startsWith("CSHARPCONTRACT_FORMAT")) { 289 obj.csharpFormat = command.substring("CSHARPCONTRACT_FORMAT".length()).trim(); 290 obj.dummyDesired = true; 291 } else { 292 System.err.println("Unrecognized format spec in .spinfo: " + command); 293 } 294 } 295 296 /** Returns whether the line is blank (or null). */ 297 @EnsuresNonNullIf(result = false, expression = "#1") 298 @Pure 299 private static boolean isBlank(@Nullable String line) { 300 return (line == null) || line.trim().equals(""); 301 } 302 303 /** 304 * Returns whether the line is a spinfo file comment line. A line is a comment if it starts with a 305 * (possibly indented) "#". 306 */ 307 @Pure 308 private static boolean isComment(String line) { 309 return line.trim().startsWith("#"); 310 } 311 312 /** 313 * Returns whether the line is a spinfo file formatting command. A line is a formatting command if 314 * line is indented with a tab ("\t") or spaces (" "). 315 */ 316 @Pure 317 private static boolean isFormatting(String line) { 318 return line.startsWith("\t") || line.startsWith(" "); 319 } 320}