001 /* Copyright 2000, 2001, Compaq Computer Corporation */ 002 003 package escjava.reader; 004 005 import javafe.reader.CachedReader; 006 import javafe.reader.Reader; 007 import javafe.ast.CompilationUnit; 008 import javafe.genericfile.*; 009 import javafe.util.ErrorSet; 010 import javafe.util.Location; 011 import javafe.ast.Modifiers; 012 import javafe.tc.TypeSig; 013 import javafe.tc.OutsideEnv; 014 import javafe.ast.*; 015 import escjava.ast.*; 016 import escjava.AnnotationHandler; 017 import escjava.RefinementSequence; 018 019 import java.util.*; 020 021 /** 022 * RefinementCachedReader caches compilation units that have been read, 023 * as does its super class. However, before doing so, it retrieves 024 * all files of a refinement sequence, combines them and caches the 025 * combined result against all of the file names. Thus if any file in 026 * the refinement sequence is read again, the refinement combination will 027 * be produced. 028 * 029 * <p> Reads from GenericFiles with null canonicalIDs are not cached. 030 */ 031 032 public class RefinementCachedReader extends CachedReader 033 { 034 /*************************************************** 035 * * 036 * Creation: * 037 * * 038 **************************************************/ 039 040 /** 041 * The underlying Reader whose results we are caching. 042 */ 043 //@ invariant underlyingReader != null; 044 045 protected /*@ non_null @*/ AnnotationHandler annotationHandler = new AnnotationHandler(); 046 047 /** 048 * Creating a cached version of a Reader: 049 */ 050 //@ requires reader != null; 051 public RefinementCachedReader(Reader reader) { 052 super(reader); 053 054 //+@ set cache.keyType = \type(String); 055 //+@ set cache.elementType = \type(Object); 056 } 057 058 059 /*************************************************** 060 * * 061 * The Cache itself: * 062 * * 063 **************************************************/ 064 065 // Inherited from the super class 066 067 068 /*************************************************** 069 * * 070 * Caching methods: * 071 * * 072 **************************************************/ 073 074 public CompilationUnit isAlreadyRead(GenericFile target) { 075 return (CompilationUnit)get(target); 076 } 077 078 /*************************************************** 079 * * 080 * Reading: * 081 * * 082 **************************************************/ 083 084 /** 085 * Attempt to read and parse a CompilationUnit from target. 086 * Any errors encountered are reported via javafe.util.ErrorSet. 087 * Null is returned iff an error was encountered.<p> 088 * 089 * 090 * By default, we attempt to read only a spec (e.g., specOnly is set 091 * in the resulting CompilationUnit) to save time. If avoidSpec is 092 * true, we attempt to return a non-spec, although this may not 093 * always be possible.<p> 094 * 095 * 096 * The results of this function (including null results, but not 097 * the action of reporting error messages) are cached. 098 * Only the value of avoidSpec used the first time a given file is 099 * read is used. This may result in a spec being returned 100 * unnecessarily when avoidSpec is true.<p> 101 * 102 * Target must be non-null.<p> 103 */ 104 public CompilationUnit read(GenericFile target, boolean avoidSpec) { 105 // Note - reading has the side effect of caching 106 CompilationUnit cu = (CompilationUnit)get(target); 107 if (cu != null) { 108 cu.duplicate = true; 109 return cu; 110 /* 111 Don't complain, but don't do it twice either. ??? 112 CompilationUnit cu = (CompilationUnit)get(target); 113 if (cu != null) { 114 // If we are processing a package, we do not want to complain 115 // about multiple files from a refinement sequence. 116 Name n = cu.pkgName; 117 if (n != null && escjava.Main.options(). 118 packagesToProcess.contains(n.printName())) 119 return null; 120 } 121 ErrorSet.caution("Duplicate command-line file " + 122 "(or part of a refinement sequence): " + 123 target.getHumanName()); 124 */ 125 126 //return null; 127 } 128 129 // not cached - read and do refinement combination 130 refinementSequence = null; 131 cu = super.read(target,avoidSpec); 132 if (cu == null) return null; 133 CompilationUnit result = readRefinements(cu,avoidSpec); 134 if (result == null) { 135 // Some error occurred (already reported). In particular, this 136 // null return can happen if the type declared in this file has 137 // already been loaded by (declared in) another file. 138 return result; 139 } 140 141 // Do anything to pragmas that must be done before type signatures 142 // are created - includes, for example, handling model imports. 143 if (result != null) annotationHandler.handlePragmas(result); 144 145 // Put the composite CU in the cache for all elements of the RS. 146 Iterator i = refinementSequence.iterator(); 147 while (i.hasNext()) { 148 CompilationUnit rcu = (CompilationUnit)i.next(); 149 put(rcu.sourceFile(),result); 150 } 151 return result; 152 } 153 154 protected ArrayList refinementSequence; 155 156 public CompilationUnit readRefinements(/*@ non_null @*/ CompilationUnit cu, boolean avoidSpec) { 157 158 // Get and parse the package name 159 Name pkgName = cu.pkgName; 160 String pkg = pkgName == null ? "" : pkgName.printName(); 161 String[] pkgStrings = pkgName == null ? new String[0] : 162 pkgName.toStrings(); 163 164 165 // Look through all the types in the compilation unit and 166 // find the public one. That is the one whose name should 167 // be used to find the refinement files. 168 TypeDeclVec types = cu.elems; 169 Identifier type = null; 170 for (int i=0; i<types.size(); ++i) { 171 TypeDecl td = types.elementAt(i); 172 if (Modifiers.isPublic(td.modifiers)) { 173 type = td.id; 174 break; 175 } 176 } 177 178 if (type == null) { 179 String s = cu.sourceFile().getLocalName(); 180 int p = s.indexOf('.'); 181 type = Identifier.intern(s.substring(0,p)); 182 } 183 184 // Check one of the ids to see if the type is already loaded. 185 if (types.size() != 0) { 186 String typeToCheck; 187 if (type != null) typeToCheck = type.toString(); 188 else typeToCheck = types.elementAt(0).id.toString(); 189 TypeSig sig = TypeSig.lookup(pkgStrings,typeToCheck); 190 if (sig != null && sig.isPreloaded()) { 191 CompilationUnit pcu = sig.getCompilationUnit(); 192 ErrorSet.caution("Type " + pkg + 193 (pkgName==null?"":".") + typeToCheck + 194 " in " + cu.sourceFile().getHumanName() + 195 " is already loaded from " + 196 pcu.sourceFile().getHumanName()); 197 return null; 198 } 199 } 200 201 // See if there is a java file for this type. 202 // Note that if there is no public type, then type == null, 203 // and we don't look for a java file. 204 GenericFile javafile = null; 205 if (type == null) { 206 // No public type declaration 207 // So there are no other files to be found 208 // Have to do the refinement processing, because that makes 209 // a (singleton) composite set of pragma modifiers 210 211 String s = cu.sourceFile().getLocalName(); 212 if (s.endsWith(".java")) javafile = cu.sourceFile(); 213 214 } else { 215 javafile = ((EscTypeReader)OutsideEnv.reader). 216 findSrcFile(pkgStrings,type.toString()+".java"); 217 if (javafile == null && 218 cu.sourceFile().getLocalName().endsWith(".java")) { 219 javafile = cu.sourceFile(); 220 ErrorSet.caution(Location.createWholeFileLoc(javafile), 221 "Using given file as the .java file, even though it is not the java file for " + pkg + (pkgName==null?"":".") + type + " on the classpath"); 222 } 223 224 } 225 226 // Now find the refinement sequence belonging to the given type. 227 // If there is none, or if type is null, then a refinement sequence 228 // consisting of just the one compilation unit cu is returned. 229 // Note that this parses each of the files in the RS. 230 // Note also that 'cu' need not be in its own RS if it isn't, 231 // then it is not part of the list returned. 232 refinementSequence = getRefinementSequence(pkgStrings, type, cu, avoidSpec); 233 // Error occurred (already reported) such that we don't 234 // want to add a new compilation unit to the environment 235 if (refinementSequence == null) return null; 236 237 if (javafe.util.Info.on) { 238 java.util.Iterator i = refinementSequence.iterator(); 239 System.out.print("Refinement Sequence: ["); 240 while (i.hasNext()) { 241 System.out.print(" "+ ((CompilationUnit)i.next()). 242 sourceFile().getHumanName()); 243 } 244 System.out.println(" ]"); 245 } 246 247 // Now find the compilation unit for the java file. If it is 248 // already in the RS or is the same as cu, we don't read it again. 249 CompilationUnit javacu = null; 250 if (javafile != null) { 251 if (javafile.getCanonicalID().equals(cu.sourceFile().getCanonicalID())) javacu = cu; 252 else for (int i=0; javacu == null && i<refinementSequence.size(); ++i) { 253 CompilationUnit rcu = (CompilationUnit)refinementSequence.get(i); 254 if (rcu.sourceFile().getCanonicalID().equals(javafile.getCanonicalID())) 255 javacu = rcu; 256 } 257 } 258 //System.out.println("HAVE " + cu.sourceFile().getHumanName()); 259 if (javacu == null) { 260 // We don't already have a CU for the java file (that is, it 261 // is not cu or in the RS) so we need to parse the source or 262 // binary file. 263 264 // Note - if we are not using any implementation or 265 // specs from the source file, why not just read the 266 // binary if it is available? FIXME 267 if (javafile != null) { 268 // We have a .java source file so read from it. 269 javafe.util.Info.out("Reading source file " 270 + javafile.getHumanName()); 271 ErrorSet.caution("The file " + javafile.getHumanName() + 272 " is not in the refinement sequence that begins with " + 273 cu.sourceFile().getHumanName() + 274 "; it is used to generate a class signature, but no refinements within it are used."); 275 javacu = underlyingReader.read(javafile, false); 276 // The false above means only read a signature and not 277 // the implementation or the annotations. 278 } else { 279 javacu = getCombinedBinaries(pkgName,pkgStrings,refinementSequence); 280 } 281 } 282 283 // FIXME - really want a routine that reads binary if up to date otherwise 284 // source, simply to get signature. Read java with bodies if it is one of the 285 // files to be checked. The above should be able to be greatly improved!!!! 286 287 CompilationUnit newcu = new RefinementSequence(refinementSequence, 288 javacu,annotationHandler); 289 290 291 javafe.util.Info.out("Constructed refinement sequence"); 292 return newcu; 293 } 294 295 CompilationUnit getCombinedBinaries(/*null*/ Name pkgName, 296 /*@ non_null @*/ String[] pkg, 297 /*@ non_null @*/ ArrayList rs) 298 { 299 CompilationUnit combination = null; 300 java.util.List failures = new java.util.LinkedList(); 301 Iterator i = rs.iterator(); 302 while (i.hasNext()) { 303 CompilationUnit cu = (CompilationUnit)i.next(); 304 TypeDeclVec tdv = cu.elems; 305 for (int j=0; j<tdv.size(); ++j) { 306 TypeDecl td = tdv.elementAt(j); 307 Identifier id = td.id; 308 boolean found = false; 309 if (combination != null) { 310 for (int k = combination.elems.size()-1; k>=0; --k) { 311 if (combination.elems.elementAt(k).id == id) { 312 found = true; 313 break; 314 } 315 } 316 } 317 if (!found) { 318 GenericFile javafile = ((EscTypeReader)OutsideEnv.reader). 319 findBinFile(pkg,id.toString()+".class"); 320 if (javafile != null) { 321 javafe.util.Info.out("Reading class file " 322 + javafile.getHumanName()); 323 CompilationUnit 324 javacu = ((EscTypeReader)OutsideEnv.reader). 325 binaryReader.read(javafile, false); 326 if (combination == null) 327 combination = javacu; 328 else { 329 TypeDeclVec ntdv = javacu.elems; 330 for (int n=0; n<ntdv.size(); ++n) { 331 combination.elems.addElement(ntdv.elementAt(n)); 332 } 333 } 334 } else { 335 failures.add( 336 (pkgName==null? id.toString() : 337 pkgName.printName() + "." + id)); 338 } 339 } 340 } 341 } 342 if (combination != null && failures.size() != 0) { 343 // FIXME - should marak the source location for these 344 String s = "Failed to find some but not all binary files: "; 345 Iterator ii = failures.iterator(); 346 while (ii.hasNext()) s += ii.next(); 347 ErrorSet.error(s); 348 } 349 return combination; 350 } 351 352 353 // result is a list of CompilationUnits 354 // result will contain something, perhaps just the given cu 355 //@ ensures \result != null; 356 ArrayList getRefinementSequence(/*@ non_null @*/ String[] pkgStrings, 357 Identifier type, 358 /*@ non_null @*/ CompilationUnit cu, 359 boolean avoidSpec) { 360 ArrayList refinements = new ArrayList(); 361 GenericFile mrcufile; 362 GenericFile gf = cu.sourceFile(); 363 String gfid = gf.getCanonicalID(); 364 if (type == null) { 365 mrcufile = gf; 366 } else { 367 mrcufile = 368 ((EscTypeReader)OutsideEnv.reader).findFirst( 369 pkgStrings,type.toString()); 370 } 371 javafe.util.Info.out( mrcufile==null ? "No MRCU found" : 372 "Found MRCU " + mrcufile); 373 // If no MRCU is found in the sourcepath, then we presume that 374 // the file on the command line is that. 375 GenericFile gfile = (mrcufile == null) ? gf : mrcufile ; 376 CompilationUnit ccu; 377 boolean foundCommandLineFileInRS = false; 378 while(gfile != null) { 379 if (gfile.getCanonicalID().equals(gfid)) { 380 // Avoid parsing a file twice 381 ccu = cu; 382 foundCommandLineFileInRS = true; 383 } else { 384 ccu = underlyingReader.read(gfile,avoidSpec); 385 } 386 annotationHandler.parseAllRoutineSpecs(ccu); 387 refinements.add(ccu); 388 gfile = findRefined(pkgStrings,ccu); 389 if (gfile != null) { 390 if (!gfile.getLocalName().startsWith(type.toString() + ".")){ 391 ErrorSet.caution("The refinement file " + 392 gfile.getHumanName() + 393 " in the sequence beginning with " + 394 mrcufile.getHumanName() + 395 " has a prefix that does not match the type name " 396 + type); 397 } 398 for (int i=0; i<refinements.size(); ++i) { 399 if ( ((CompilationUnit)refinements.get(i)).sourceFile(). 400 getCanonicalID().equals( gfile.getCanonicalID() )) { 401 ErrorSet.error(gfile.getHumanName() + 402 " is circularly referenced in a refinement sequence"); 403 gfile = null; 404 break; 405 } 406 } 407 } 408 } 409 if (!foundCommandLineFileInRS) { 410 String pkg = cu.pkgName == null ? "" : 411 cu.pkgName.printName() + "."; 412 if (refinements.size() == 0) { 413 // If no refinement sequence was found, we simply use the 414 // file on the command line, even if it is not on the 415 // classpath. 416 refinements.add(cu); 417 } else { 418 StringBuffer err = new StringBuffer( 419 "The command-line argument " 420 + cu.sourceFile().getHumanName() 421 + " was not in the refinement sequence for type " 422 + pkg + type.toString() + ":"); 423 for (int k = 0; k<refinements.size(); ++k) { 424 err.append(" "); 425 err.append(((CompilationUnit)refinements.get(k)). 426 sourceFile().getHumanName()); 427 } 428 // If the command-line file is not in the refinement 429 // sequence, we use the refinement sequence, since, 430 // if the type was referenced from another class it 431 // is the refinement sequence that would be found. 432 // 433 ErrorSet.error(err.toString()); 434 } 435 } 436 javafe.util.Info.out("Found refinement sequence files"); 437 return refinements; 438 } 439 440 public static GenericFile findRefined(/*@ non_null @*/ String[] pkgStrings, 441 /*@ non_null @*/ CompilationUnit cu) 442 { 443 LexicalPragmaVec v = cu.lexicalPragmas; 444 for (int i=0; i<v.size(); ++i) { 445 if (v.elementAt(i) instanceof RefinePragma) { 446 RefinePragma rp = (RefinePragma)v.elementAt(i); 447 String filename = rp.filename; 448 // FIXME - what if we are refining a class file ??? 449 GenericFile gf = ((EscTypeReader)OutsideEnv.reader).findSrcFile(pkgStrings,filename); 450 if (gf == null) ErrorSet.error(rp.loc, 451 "Could not find file referenced in refine annotation: " + filename); 452 return gf; 453 454 // FIXME - hsould be able to have refined files that are not in regular files as well 455 } 456 } 457 return null; 458 } 459 460 }