001 /* Copyright 2000, 2001, Compaq Computer Corporation */ 002 003 package javafe.tc; 004 005 import javafe.ast.*; 006 import javafe.genericfile.*; 007 008 import javafe.reader.StandardTypeReader; // for debugging only 009 import javafe.reader.TypeReader; 010 011 import javafe.util.Location; 012 import javafe.util.Assert; 013 import javafe.util.ErrorSet; 014 015 import java.util.ArrayList; 016 import java.util.Iterator; 017 import java.io.File; 018 019 /** 020 * <code>OutsideEnv</code> implements the top-level environment 021 * consisting of only the package-member types. 022 * 023 * <p> This is the environment outside of any compilation unit (e.g., 024 * no import declarations are in effect). It is used to lookup the 025 * {@link TypeSig} for a given fully-qualified package-member name 026 * (P.T). Class-member types are obtained by using the lookup methods 027 * of the {@link TypeSig} that contains them as members. </p> 028 * 029 * <h3> Initialization </h3> 030 * 031 * <p> In order to greatly simplify the front end, there can be at 032 * most one such environment during front-end execution. All of 033 * <code>OutsideEnv</code>'s lookup methods are accordingly static 034 * methods. <code>OutsideEnv</code> must be initialized before any of 035 * its lookup methods can be called. </p> 036 * 037 * <p> At initialization time <code>OutsideEnv</code> is passed a way 038 * to determine which fully-qualified package-member-type names exist 039 * and a means to read in and parse the files of those types into 040 * {@link CompilationUnit}s. This is done by passing 041 * <code>OutsideEnv</code> a {@link TypeReader}, which contains 042 * exactly this information. </p> 043 * 044 * <p> <code>OutsideEnv</code> uses this information to determine 045 * which package-member types exist and to create 046 * <code>TypeSig</code>s for them when needed by loading their 047 * underlying {@link TypeDecl}s in from the filesystem. (Each 048 * java file contains a <code>CompilationUnit</code>, which is a set 049 * of <code>TypeDecl</code>s.) </p> 050 * 051 * <h3> Loading <code>CompilationUnit</code>s </h3> 052 * 053 * <p> Loading is actually done lazily for efficiency reasons(*). 054 * When a fully-qualified package-member-type name is looked up for 055 * the first time, <code>OutsideEnv</code> first checks to see if it 056 * exists. If it exists, then a new unloaded <code>TypeSig</code> is 057 * returned. Otherwise, <code>null</code> is returned. Future 058 * lookups of the same name return the same result, except that for a 059 * local package-member-type (see next section), a null result may 060 * change to a non-null result. </p> 061 * 062 * <p> Only when the new <code>TypeSig</code>'s <code>TypeDecl</code> 063 * is touched (via {@link TypeSig#getTypeDecl}) for the first time does 064 * <code>OutsideEnv</code> load in the <code>CompilationUnit</code> 065 * that should contain that type. Errors may be reported via {@link 066 * ErrorSet} at this time (e.g., I/O error, syntax error, file fails 067 * to contain the type, etc.). This loading is otherwise transparent 068 * to the users of <code>TypeSig</code>. An special version of lookup 069 * is available that defers testing for type existence until loading 070 * time; this is useful for dealing with types that are required to 071 * exist by the Java language specification. </p> 072 * 073 * <p> (*) - Exception: if {@link #eagerRead} is set (not the 074 * default), all loading is done non-lazily. </p> 075 * 076 * <p> The {@link #avoidSpec} flag is used when 077 * <code>CompilationUnit</code>s are read in to determine if a spec or 078 * a non-spec should be read. (Note that non-specs are not always 079 * available.) </p> 080 * 081 * <p> When <code>CompilationUnit</code>s are loaded in, TypeSigs are 082 * automatically created for each of their <code>TypeDecl</code>s 083 * (including recursively). </p> 084 * 085 * <h3> Local package-member types </h3> 086 * 087 * <p> A package-member type named <i>T</i> that is contained in a 088 * file <i>V</i><code>.java</code>, <i>T</i> != <i>V</i>, is called a 089 * <em>local package-member type</em>. Such types are accessible only 090 * from within in the same file. <code>OutsideEnv</code> handles such 091 * types as follows: 092 * 093 * <ul> 094 * <li> Before the file containing local package-member type 095 * <i>P.T</i> is loaded in, looking up <i>P.T</i> returns 096 * <code>null</code>. (Aka, it is considered not to exist.) 097 * </li> 098 * 099 * <li> Afterwards, the lookup returns a <code>TypeSig</code> that 100 * has been preloaded with the correct <code>TypeDecl</code> 101 * from the file. It is the caller's responsibility to check 102 * whether the returned type is accessible or not. </li> 103 * </ul> 104 * 105 * <p> The existence of local package-member types opens up the 106 * possibility of duplicate package-member-type definitions. Should 107 * <code>OutsideEnv</code> load two different package-member types 108 * with the same name, a fatal error will be reported via 109 * <code>ErrorSet</code>. Because files are loaded lazily, some 110 * duplicate type errors may not be detected. </p> 111 * 112 * <h3> Additional source files </h3> 113 * 114 * <p> A client of <code>OutsideEnv</code> may add additional 115 * package-member types to those defined by the information provided 116 * at initialization time by using the method <code>addSource</code>. 117 * <code>addSource</code> is called with a source file; it attempts to 118 * load the <code>CompilationUnit</code> contained in that file. If 119 * successful, it adds the package-member types contained in that file 120 * to the package-member-type environment and returns the loaded 121 * <code>CompilationUnit</code> to the caller. </p> 122 * 123 * <p> {@link #addSource(GenericFile)} is intended primarily for use in handling 124 * source files given to a tool as command-line arguments. It can be 125 * called only before the first lookup is done. The filenames of the 126 * source files passed to <code>addSource</code> are ignored. </p> 127 * 128 * <h3> Notification </h3> 129 * 130 * <p> Whenever <code>OutsideEnv</code> successfully loads a 131 * <code>CompilationUnit</code>, it notifies the current {@link 132 * Listener}, if any. Only one <code>Listener</code> at a time is 133 * currently supported; {@link #setListener} is used to set the 134 * current <code>Listener</code>. </p> 135 * 136 * <p> Because this notification is "asynchronous" (it can occur in 137 * the middle of any code that touches a <code>TypeSig</code>'s 138 * <code>TypeDecl</code>), it is strongly recommended that 139 * <code>Listener</code>s take no action other then storing 140 * information for later use. </p> 141 * 142 * <h3> Implementation </h3> 143 * 144 * <p> Note that the implementation of the functionality described 145 * here is spread between this class and that of 146 * <code>TypeSig</code>. </p> 147 * 148 * @see TypeSig 149 * @see javafe.reader.TypeReader 150 * @see javafe.ast.CompilationUnit 151 * @see Listener 152 */ 153 154 public final class OutsideEnv { 155 // Class Variables 156 157 /** 158 * The {@link TypeReader} for our underlying Java file space. 159 */ 160 public static/*@ non_null @*/TypeReader reader; 161 162 /** 163 * When we load in types, do we prefer to read specs or non-specs? 164 * Defaults to preferring non-specs. 165 */ 166 public static boolean avoidSpec = true; 167 168 /** 169 * If true, files are read eagerly, as soon as we look them up. 170 * Defaults to false. 171 */ 172 public static boolean eagerRead = false; 173 174 /** Count of files read so far. */ 175 //@ private invariant filesRead >= 0; 176 //@ spec_public 177 private static int filesRead = 0; 178 179 /** 180 * The {@link Listener} to notify when a {@link CompilationUnit} 181 * is loaded. May be <code>null</code> if there is no current 182 * <code>Listener</code> (the initial state). 183 */ 184 //@ spec_public 185 private static Listener listener = null; 186 187 /** Return count of files read so far. */ 188 //@ ensures \result == filesRead; 189 //@ ensures \result >= 0; 190 public static int filesRead() { 191 return filesRead; 192 } 193 194 // Initialization 195 196 //@ public static ghost boolean initialized; 197 198 //* No constructors available: 199 //@ requires false; 200 private OutsideEnv() { 201 Assert.fail("No instances!"); 202 } 203 204 /** 205 * Initialize ourselves to use <code>TypeReader</code> 206 * <code>R</code> for our underlying Java file space. 207 * 208 * @requires <code>R</code> is not <code>null</code>, no 209 * <code>init</code> method for this class has previously been 210 * called. 211 */ 212 //@ requires R != null; 213 //@ requires !initialized; 214 //@ requires reader == null; 215 //@ ensures reader == R; 216 public static void init(/*@ non_null @*/TypeReader R) { 217 Assert.precondition(R != null); 218 Assert.precondition(reader == null); //@ nowarn Pre; 219 220 reader = R; 221 //@ set initialized=true; 222 } 223 224 //@ ensures reader == null; 225 //@ ensures filesRead == 0; 226 //@ ensures listener == null; 227 //@ ensures !eagerRead; 228 //@ ensures avoidSpec; 229 // @todo should we also add "ensures !initialized"? 230 public static void clear() { 231 reader = null; 232 filesRead = 0; 233 listener = null; 234 eagerRead = false; 235 avoidSpec = true; 236 javafe.tc.Types.remakeTypes(); 237 if (reader instanceof javafe.reader.StandardTypeReader) 238 ((javafe.reader.StandardTypeReader)reader).clear(); 239 TypeSig.clear(); 240 } 241 242 // Looking up TypeSig's 243 244 /** 245 * Get the <code>TypeSig</code> for fully-qualified 246 * package-member name <code>P.T</code>. Returns null if no such 247 * type exists. 248 * 249 * <p> This function never results in 250 * <code>CompilationUnit</code>s being loaded unless eagerRead is 251 * set. </p> 252 * 253 * <p> Calling this function twice with the same arguments is 254 * guaranteed to give back the same answer, except that a 255 * <code>null</code> answer may later change to a 256 * non-<code>null</code> answer. </p> 257 * 258 * @requires an init method has already been called 259 */ 260 //@ requires \nonnullelements(P); 261 //@ requires initialized; 262 public static TypeSig lookup(String[] P, /*@ non_null @*/String T) { 263 TypeSig result = TypeSig.lookup(P, T); 264 if (result == null && reader.exists(P, T)) result = TypeSig.get(P, T); 265 266 if (result != null && eagerRead) result.getTypeDecl(); 267 268 return result; 269 } 270 271 /** 272 * Like <code>lookup</code> except that checking the existence of 273 * the type is deferred until it's <code>TypeDecl</code> is touched 274 * for the first time. If eagerRead is set, existence is always 275 * checked, with non-existance resulting in an error. 276 * 277 * <p> This routine never returns <code>null</code>: if 278 * <code>P</code> does not exist in our Java file space, then an 279 * unloaded <code>TypeSig</code> is returned; when its 280 * <code>TypeDecl</code> is first referenced, an error will be 281 * reported. </p> 282 * 283 * <p> This function is intended to be used only to load types 284 * required to be present by the language specification (e.g., 285 * {@link java.lang.Object}). </p> 286 * 287 * @requires an init method has already been called. 288 */ 289 //@ requires \nonnullelements(P); 290 //@ requires T != null; 291 //@ requires initialized; 292 //@ ensures \result != null; 293 public static TypeSig lookupDeferred(String[] P, String T) { 294 TypeSig result = TypeSig.get(P, T); 295 296 if (eagerRead) result.getTypeDecl(); 297 298 return result; 299 } 300 301 // Loading CompilationUnits 302 303 /** 304 * Attempt to add the package-member types contained in a source 305 * file to the package-member-types environment, returning the 306 * <code>CompilationUnit</code>, if any, found in that file. 307 * 308 * <p> If an error occurs, it will be reported via 309 * <code>ErrorSet</code> and <code>null</code> will be returned. </p> 310 * 311 * <p> <code>null</code> may also be returned if a file is 312 * repeated on the command line. </p> 313 * 314 * @requires no lookup has been done yet using this class. 315 * 316 * @note Calling <code>addSource</code> twice on the same file may 317 * or may not produce a duplicate-type error. 318 */ 319 //@ requires source != null; 320 public static CompilationUnit addSource(GenericFile source) { 321 filesRead++; 322 CompilationUnit cu = reader.read(source, avoidSpec); 323 if (cu != null) { 324 setSigs(cu); 325 notify(cu); 326 } 327 return cu; 328 } 329 330 /** 331 * Adds all relevant files from the given package; 'relevant' is 332 * defined by the 'findFiles' method of the current reader. 333 */ 334 //@ requires sources.elementType <: \type(GenericFile); 335 //@ ensures \result.elementType <: \type(CompilationUnit); 336 public static ArrayList addSources(ArrayList sources) { 337 ArrayList out = new ArrayList(sources.size()); 338 Iterator i = sources.iterator(); 339 while (i.hasNext()) { 340 GenericFile gf = (GenericFile)i.next(); 341 out.add(addSource(gf)); 342 } 343 return out; 344 } 345 346 //@ ensures \result.elementType <: \type(GenericFile); 347 public static ArrayList resolveSources(String[] pname) { 348 ArrayList a = reader.findFiles(pname); 349 if (a == null) { 350 ErrorSet.caution("Could not locate package: " 351 + javafe.parser.ParseUtil.arrayToString(pname, ".")); 352 return null; 353 } 354 if (a.isEmpty()) { 355 ErrorSet.caution("Package has no files: " 356 + javafe.parser.ParseUtil.arrayToString(pname, ".")); 357 } 358 return a; 359 } 360 361 /** 362 * Attempt to add the package-member types contained in a named 363 * source file to the package-member-types environment, returning 364 * the <code>CompilationUnit</code>, if any, found in that 365 * file. 366 * 367 * <p> If an error occurs, it will be reported via 368 * <code>ErrorSet</code> and <code>null</code> will be 369 * returned. </p> 370 * 371 * @note Calling <code>addSource</code> twice on the same file may 372 * or may not produce a duplicate-type error. 373 * 374 * @requires no lookup has been done yet using this class. 375 */ 376 //@ requires sourceName != null; 377 public static CompilationUnit addSource(String sourceName) { 378 GenericFile source = new NormalGenericFile(sourceName); 379 return addSource(source); 380 } 381 382 // Output is an ArrayList of GenericFiles. 383 //@ ensures \result == null || \result.elementType <: \type(GenericFile); 384 public static ArrayList resolveDirSources(String dirname) { 385 File f = new File(dirname); 386 if (!f.exists()) { 387 ErrorSet.caution("Directory does not exist: " + dirname); 388 return null; 389 } 390 File[] names = f.listFiles(reader.filter()); 391 if (names.length == 0) { 392 ErrorSet.caution("Directory has no files: " + dirname); 393 } 394 ArrayList a = new ArrayList(names.length); 395 for (int i = 0; i < names.length; ++i) { 396 a.add(new NormalGenericFile(names[i])); 397 } 398 return a; 399 } 400 401 /** 402 * This routine creates TypeSigs for each TypeDecl member of 403 * <code>cu</code>. 404 * 405 * <p> As a side effect, this sets the sig fields of 406 * <code>cu</code>'s direct TypeDecl members (aka, the TypeDecls 407 * for the package-member types cu contains) to point to TypeSigs 408 * that have been loaded with the TypeDecls that point to 409 * them. </p> 410 * 411 * @requires <code>cu</code> must be non-null. 412 */ 413 //@ requires cu != null; 414 private static void setSigs(CompilationUnit cu) { 415 // Get package name from cu (may be null): 416 String[] P = new String[0]; 417 if (cu.pkgName != null) P = cu.pkgName.toStrings(); 418 419 // Iterate over all the TypeDecls representing package-member 420 // types in cu: 421 TypeDeclVec elems = cu.elems; 422 for (int i = 0, sz = elems.size(); i < sz; i++) { 423 TypeDecl decl = elems.elementAt(i); 424 String T = decl.id.toString(); // decl's typename 425 426 TypeSig sig = TypeSig.get(P, T); 427 sig.load(decl, cu); 428 } 429 } 430 431 /** 432 * Attempt to load the TypeDecl of TypeSig sig. 433 * 434 * <p> This method should be called only from 435 * TypeSig.preload. </p> 436 * 437 * <p> Tries to load the file that should contain sig. Reports 438 * any errors encountered to ErrorSet. If successful, calls 439 * sig.load with its TypeDecl. </p> 440 * 441 * <p> It is a fatal error if this routine cannot load sig. Later 442 * the error may be made non-fatal; in that case TypeSig.preload 443 * will be responsible for substituting a wildcard TypeDecl. </p> 444 * 445 * @requires an init method has already been called. 446 */ 447 //@ requires initialized; 448 //@ ensures sig.myTypeDecl != null; 449 /*package*/static void load(/*@ non_null @*/TypeSig sig) { 450 // Do nothing if sig is already loaded: 451 if (sig.isPreloaded()) return; 452 453 filesRead++; 454 // Read in the CompilationUnit that should have sig in it: 455 CompilationUnit cu = reader 456 .read(sig.packageName, sig.simpleName, avoidSpec); 457 if (cu == null) { 458 ErrorSet.fatal("unable to load type " + sig.getExternalName()); 459 return; 460 } 461 462 /* 463 * Get cu's package name in the same format as 464 * TypeSig.getPackageName uses: 465 */ 466 String actualPkg = (cu.pkgName == null) ? TypeSig.THE_UNNAMED_PACKAGE 467 : cu.pkgName.printName(); 468 469 // Check that cu is in the correct package: 470 if (sig.getPackageName().equals(actualPkg)) { 471 /* 472 * Only load the types in cu if it is in the correct 473 * package: 474 */ 475 setSigs(cu); 476 notify(cu); 477 } else { 478 // Get the location of the package declaration in cu if 479 // present, otherwise get a location for the entire cu: 480 int pkgDeclLoc = (cu.pkgName != null) ? cu.pkgName.getStartLoc() 481 : Location.createWholeFileLoc(Location.toFile(cu.loc)); 482 483 ErrorSet.error(pkgDeclLoc, "file declared to be in package " + actualPkg 484 + " rather than in the correct package " + sig.getPackageName()); 485 } 486 487 // Make sure the CompilationUnit actually contains sig: 488 if (!sig.isPreloaded()) { 489 int fileLoc = Location.createWholeFileLoc(Location.toFile(cu.loc)); 490 ErrorSet.fatal(fileLoc, "file does not contain the type " 491 + sig.getExternalName() + " as expected"); 492 } 493 } 494 495 // Notification 496 497 /** 498 * Set the <code>Listener</code> to be notified about 499 * <code>CompilationUnit</code> loading. 500 * 501 * <p> <code>l</code> may be <code>null</code> if no notification 502 * is desired (the initial default). The previous current 503 * <code>Listener</code> is replaced. (I.e., only 1 504 * <code>Listener</code> may be in effect at a time.) </p> 505 */ 506 public static void setListener(Listener l) { 507 listener = l; 508 } 509 510 /** 511 * Send a CompilationUnit-loaded notification event to the current 512 * Listener (if any). 513 * 514 * @requires justLoaded != null, justLoaded must already have 515 * the <code>sig</code> fields of its direct 516 * <code>TypeDecl</code>s adjusted. 517 */ 518 //@ requires justLoaded != null; 519 private static void notify(CompilationUnit justLoaded) { 520 if (listener != null) listener.notify(justLoaded); 521 } 522 523 // Test methods 524 525 /** 526 * A debugging harness that allows describing the results of 527 * calling <code>lookup</code> on a series of package-member-type 528 * names. 529 */ 530 //@ requires args != null; 531 /*@ requires (\forall int i; (0<=i && i<args.length) 532 @ ==> args[i] != null); 533 @*/ 534 public static void main(String[] args) { 535 // Check argument usage: 536 if (args.length == 0) { 537 System.err 538 .println("OutsideEnv: <fully-qualified package-member-type name>..."); 539 System.exit(1); 540 } 541 542 init(StandardTypeReader.make()); // Use default classpath... 543 544 // Test each package-member-type name: 545 for (int i = 0; i < args.length; i++) 546 describeLookup(args[i]); 547 } 548 549 /** 550 * Call lookup on N then describe the results. 551 * 552 */ 553 //@ requires initialized; // that is, an init method has alreaady been called 554 private static void describeLookup(/*@ non_null @*/String N) { 555 // Convert N to a list of its components: 556 String[] components = javafe.filespace.StringUtil.parseList(N, '.'); 557 if (components.length == 0) { 558 System.out.println("Error: `' is an illegal type name"); 559 return; 560 } 561 562 // Split components into P and T: 563 String[] P = new String[components.length - 1]; 564 for (int i = 0; i < P.length; i++) 565 P[i] = components[i]; 566 String T = components[components.length - 1]; 567 568 TypeSig result = lookup(P, T); 569 if (result == null) { 570 System.out.println("no such type " + N); 571 return; 572 } 573 574 System.out.println("Sig = " + result + "; " 575 + (result.isPreloaded() ? " " : "not ") + "preloaded"); 576 577 System.out.println(" represents package-member-type " 578 + result.getExternalName()); 579 580 System.out.println(" it's TypeDecl is:"); 581 PrettyPrint.inst.print(System.out, 0, result.getTypeDecl()); 582 } 583 }