    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    }