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    }