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 TypeReader reader;
161 //@ public static model boolean initialized;
162 //@ public static represents initialized <- reader!=null;
163
164 /**
165 * When we load in types, do we prefer to read specs or non-specs?
166 * Defaults to preferring non-specs.
167 */
168 public static boolean avoidSpec = true;
169
170 /**
171 * If true, files are read eagerly, as soon as we look them up.
172 * Defaults to false.
173 */
174 public static boolean eagerRead = false;
175
176 /** Count of files read so far. */
177 //@ private invariant filesRead >= 0;
178 //@ spec_public
179 private static int filesRead = 0;
180
181 /**
182 * The {@link Listener} to notify when a {@link CompilationUnit}
183 * is loaded. May be <code>null</code> if there is no current
184 * <code>Listener</code> (the initial state).
185 */
186 //@ spec_public
187 private static Listener listener = null;
188
189 /** Return count of files read so far. */
190 //@ ensures \result == filesRead;
191 //@ ensures \result >= 0;
192 public static int filesRead() {
193 return filesRead;
194 }
195
196 // Initialization
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 !initialized;
213 //@ ensures reader == R;
214 //@ ensures_redundantly initialized;
215 public static void init(/*@ non_null @*/TypeReader R) {
216 reader = R;
217 }
218
219 //@ ensures !initialized;
220 //@ ensures filesRead == 0;
221 //@ ensures listener == null;
222 //@ ensures !eagerRead;
223 //@ ensures avoidSpec;
224 public static void clear() {
225 reader = null;
226 filesRead = 0;
227 listener = null;
228 eagerRead = false;
229 avoidSpec = true;
230 javafe.tc.Types.remakeTypes();
231 if (reader instanceof javafe.reader.StandardTypeReader)
232 ((javafe.reader.StandardTypeReader)reader).clear();
233 TypeSig.clear();
234 }
235
236 // Looking up TypeSig's
237
238 /**
239 * Get the <code>TypeSig</code> for fully-qualified
240 * package-member name <code>P.T</code>. Returns null if no such
241 * type exists.
242 *
243 * <p> This function never results in
244 * <code>CompilationUnit</code>s being loaded unless eagerRead is
245 * set. </p>
246 *
247 * <p> Calling this function twice with the same arguments is
248 * guaranteed to give back the same answer, except that a
249 * <code>null</code> answer may later change to a
250 * non-<code>null</code> answer. </p>
251 *
252 * @requires an init method has already been called
253 */
254 //@ requires \nonnullelements(P);
255 //@ requires initialized;
256 public static TypeSig lookup(String[] P, /*@ non_null @*/String T) {
257 TypeSig result = TypeSig.lookup(P, T);
258 if (result == null && reader.exists(P, T)) result = TypeSig.get(P, T);
259
260 if (result != null && eagerRead) result.getTypeDecl();
261
262 return result;
263 }
264
265 /**
266 * Like <code>lookup</code> except that checking the existence of
267 * the type is deferred until it's <code>TypeDecl</code> is touched
268 * for the first time. If eagerRead is set, existence is always
269 * checked, with non-existance resulting in an error.
270 *
271 * <p> This routine never returns <code>null</code>: if
272 * <code>P</code> does not exist in our Java file space, then an
273 * unloaded <code>TypeSig</code> is returned; when its
274 * <code>TypeDecl</code> is first referenced, an error will be
275 * reported. </p>
276 *
277 * <p> This function is intended to be used only to load types
278 * required to be present by the language specification (e.g.,
279 * {@link java.lang.Object}). </p>
280 *
281 * @requires an init method has already been called.
282 */
283 //@ requires \nonnullelements(P);
284 //@ requires T != null;
285 //@ requires initialized;
286 //@ ensures \result != null;
287 public static TypeSig lookupDeferred(String[] P, String T) {
288 TypeSig result = TypeSig.get(P, T);
289
290 if (eagerRead) result.getTypeDecl();
291
292 return result;
293 }
294
295 // Loading CompilationUnits
296
297 /**
298 * Attempt to add the package-member types contained in a source
299 * file to the package-member-types environment, returning the
300 * <code>CompilationUnit</code>, if any, found in that file.
301 *
302 * <p> If an error occurs, it will be reported via
303 * <code>ErrorSet</code> and <code>null</code> will be returned. </p>
304 *
305 * <p> <code>null</code> may also be returned if a file is
306 * repeated on the command line. </p>
307 *
308 * @requires no lookup has been done yet using this class.
309 *
310 * @note Calling <code>addSource</code> twice on the same file may
311 * or may not produce a duplicate-type error.
312 */
313 //@ requires source != null;
314 public static CompilationUnit addSource(GenericFile source) {
315 filesRead++;
316 CompilationUnit cu = reader.read(source, avoidSpec);
317 if (cu != null) {
318 setSigs(cu);
319 notify(cu);
320 }
321 return cu;
322 }
323
324 /**
325 * Adds all relevant files from the given package; 'relevant' is
326 * defined by the 'findFiles' method of the current reader.
327 */
328 //@ requires sources.elementType <: \type(GenericFile);
329 //@ ensures \result.elementType <: \type(CompilationUnit);
330 public static ArrayList addSources(ArrayList sources) {
331 ArrayList out = new ArrayList(sources.size());
332 Iterator i = sources.iterator();
333 while (i.hasNext()) {
334 GenericFile gf = (GenericFile)i.next();
335 out.add(addSource(gf));
336 }
337 return out;
338 }
339
340 //@ ensures \result.elementType <: \type(GenericFile);
341 public static ArrayList resolveSources(String[] pname) {
342 ArrayList a = reader.findFiles(pname);
343 if (a == null) {
344 ErrorSet.caution("Could not locate package: "
345 + javafe.parser.ParseUtil.arrayToString(pname, "."));
346 return null;
347 }
348 if (a.isEmpty()) {
349 ErrorSet.caution("Package has no files: "
350 + javafe.parser.ParseUtil.arrayToString(pname, "."));
351 }
352 return a;
353 }
354
355 /**
356 * Attempt to add the package-member types contained in a named
357 * source file to the package-member-types environment, returning
358 * the <code>CompilationUnit</code>, if any, found in that
359 * file.
360 *
361 * <p> If an error occurs, it will be reported via
362 * <code>ErrorSet</code> and <code>null</code> will be
363 * returned. </p>
364 *
365 * @note Calling <code>addSource</code> twice on the same file may
366 * or may not produce a duplicate-type error.
367 *
368 * @requires no lookup has been done yet using this class.
369 */
370 //@ requires sourceName != null;
371 public static CompilationUnit addSource(String sourceName) {
372 GenericFile source = new NormalGenericFile(sourceName);
373 return addSource(source);
374 }
375
376 // Output is an ArrayList of GenericFiles.
377 //@ ensures \result == null || \result.elementType <: \type(GenericFile);
378 public static ArrayList resolveDirSources(String dirname) {
379 File f = new File(dirname);
380 if (!f.exists()) {
381 ErrorSet.caution("Directory does not exist: " + dirname);
382 return null;
383 }
384 File[] names = f.listFiles(reader.filter());
385 if (names.length == 0) {
386 ErrorSet.caution("Directory has no files: " + dirname);
387 }
388 ArrayList a = new ArrayList(names.length);
389 for (int i = 0; i < names.length; ++i) {
390 a.add(new NormalGenericFile(names[i]));
391 }
392 return a;
393 }
394
395 /**
396 * This routine creates TypeSigs for each TypeDecl member of
397 * <code>cu</code>.
398 *
399 * <p> As a side effect, this sets the sig fields of
400 * <code>cu</code>'s direct TypeDecl members (aka, the TypeDecls
401 * for the package-member types cu contains) to point to TypeSigs
402 * that have been loaded with the TypeDecls that point to
403 * them. </p>
404 *
405 * @requires <code>cu</code> must be non-null.
406 */
407 //@ requires cu != null;
408 private static void setSigs(CompilationUnit cu) {
409 // Get package name from cu (may be null):
410 String[] P = new String[0];
411 if (cu.pkgName != null) P = cu.pkgName.toStrings();
412
413 // Iterate over all the TypeDecls representing package-member
414 // types in cu:
415 TypeDeclVec elems = cu.elems;
416 for (int i = 0, sz = elems.size(); i < sz; i++) {
417 TypeDecl decl = elems.elementAt(i);
418 String T = decl.id.toString(); // decl's typename
419
420 TypeSig sig = TypeSig.get(P, T);
421 sig.load(decl, cu);
422 }
423 }
424
425 /**
426 * Attempt to load the TypeDecl of TypeSig sig.
427 *
428 * <p> This method should be called only from
429 * TypeSig.preload. </p>
430 *
431 * <p> Tries to load the file that should contain sig. Reports
432 * any errors encountered to ErrorSet. If successful, calls
433 * sig.load with its TypeDecl. </p>
434 *
435 * <p> It is a fatal error if this routine cannot load sig. Later
436 * the error may be made non-fatal; in that case TypeSig.preload
437 * will be responsible for substituting a wildcard TypeDecl. </p>
438 *
439 * @requires an init method has already been called.
440 */
441 //@ requires initialized;
442 //@ ensures sig.myTypeDecl != null;
443 /*package*/static void load(/*@ non_null @*/TypeSig sig) {
444 // Do nothing if sig is already loaded:
445 if (sig.isPreloaded()) return;
446
447 filesRead++;
448 // Read in the CompilationUnit that should have sig in it:
449 CompilationUnit cu = reader
450 .read(sig.packageName, sig.simpleName, avoidSpec);
451 if (cu == null) {
452 ErrorSet.fatal("unable to load type " + sig.getExternalName());
453 return;
454 }
455
456 /*
457 * Get cu's package name in the same format as
458 * TypeSig.getPackageName uses:
459 */
460 String actualPkg = (cu.pkgName == null) ? TypeSig.THE_UNNAMED_PACKAGE
461 : cu.pkgName.printName();
462
463 // Check that cu is in the correct package:
464 if (sig.getPackageName().equals(actualPkg)) {
465 /*
466 * Only load the types in cu if it is in the correct
467 * package:
468 */
469 setSigs(cu);
470 notify(cu);
471 } else {
472 // Get the location of the package declaration in cu if
473 // present, otherwise get a location for the entire cu:
474 int pkgDeclLoc = (cu.pkgName != null) ? cu.pkgName.getStartLoc()
475 : Location.createWholeFileLoc(Location.toFile(cu.loc));
476
477 ErrorSet.error(pkgDeclLoc, "file declared to be in package " + actualPkg
478 + " rather than in the correct package " + sig.getPackageName());
479 }
480
481 // Make sure the CompilationUnit actually contains sig:
482 if (!sig.isPreloaded()) {
483 int fileLoc = Location.createWholeFileLoc(Location.toFile(cu.loc));
484 ErrorSet.fatal(fileLoc, "file does not contain the type "
485 + sig.getExternalName() + " as expected");
486 }
487 }
488
489 // Notification
490
491 /**
492 * Set the <code>Listener</code> to be notified about
493 * <code>CompilationUnit</code> loading.
494 *
495 * <p> <code>l</code> may be <code>null</code> if no notification
496 * is desired (the initial default). The previous current
497 * <code>Listener</code> is replaced. (I.e., only 1
498 * <code>Listener</code> may be in effect at a time.) </p>
499 */
500 public static void setListener(Listener l) {
501 listener = l;
502 }
503
504 /**
505 * Send a CompilationUnit-loaded notification event to the current
506 * Listener (if any).
507 *
508 * @requires justLoaded != null, justLoaded must already have
509 * the <code>sig</code> fields of its direct
510 * <code>TypeDecl</code>s adjusted.
511 */
512 //@ requires justLoaded != null;
513 private static void notify(CompilationUnit justLoaded) {
514 if (listener != null) listener.notify(justLoaded);
515 }
516
517 // Test methods
518
519 /**
520 * A debugging harness that allows describing the results of
521 * calling <code>lookup</code> on a series of package-member-type
522 * names.
523 */
524 //@ requires args != null;
525 /*@ requires (\forall int i; (0<=i && i<args.length)
526 @ ==> args[i] != null);
527 @*/
528 public static void main(String[] args) {
529 // Check argument usage:
530 if (args.length == 0) {
531 System.err
532 .println("OutsideEnv: <fully-qualified package-member-type name>...");
533 System.exit(1);
534 }
535
536 init(StandardTypeReader.make()); // Use default classpath...
537
538 // Test each package-member-type name:
539 for (int i = 0; i < args.length; i++)
540 describeLookup(args[i]);
541 }
542
543 /**
544 * Call lookup on N then describe the results.
545 *
546 */
547 //@ requires initialized; // that is, an init method has alreaady been called
548 private static void describeLookup(/*@ non_null @*/String N) {
549 // Convert N to a list of its components:
550 String[] components = javafe.filespace.StringUtil.parseList(N, '.');
551 if (components.length == 0) {
552 System.out.println("Error: `' is an illegal type name");
553 return;
554 }
555
556 // Split components into P and T:
557 String[] P = new String[components.length - 1];
558 for (int i = 0; i < P.length; i++)
559 P[i] = components[i];
560 String T = components[components.length - 1];
561
562 TypeSig result = lookup(P, T);
563 if (result == null) {
564 System.out.println("no such type " + N);
565 return;
566 }
567
568 System.out.println("Sig = " + result + "; "
569 + (result.isPreloaded() ? " " : "not ") + "preloaded");
570
571 System.out.println(" represents package-member-type "
572 + result.getExternalName());
573
574 System.out.println(" it's TypeDecl is:");
575 PrettyPrint.inst.print(System.out, 0, result.getTypeDecl());
576 }
577 }