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    }