    001    /* Copyright 2000, 2001, Compaq Computer Corporation */
    002
    003    package javafe.reader;
    004
    005    import javafe.ast.CompilationUnit;
    006    import javafe.ast.PrettyPrint;
    007
    008    import javafe.genericfile.*;
    009    import javafe.parser.PragmaParser;
    010
    011    import javafe.filespace.Query;
    012    import javafe.filespace.SlowQuery;
    013    import javafe.filespace.Tree;
    014
    015    import javafe.Options;
    016    import javafe.Tool;
    017
    018    import javafe.util.Assert;
    019    import javafe.util.ErrorSet;
    020
    021    import java.util.Enumeration;
    022    import java.util.ArrayList;
    023    import java.io.File;
    024    import java.io.FilenameFilter;
    025
    026    /**
    027     * A StandardTypeReader is a {@link TypeReader} that uses {@link
    028     * javafe.filespace.SlowQuery} to find type files, and user-supplied
    029     * {@link Reader}s to read source and binary files.
    030     */
    031
    032    public class StandardTypeReader extends TypeReader
    033    {
    034      /**
    035       * Our (non-null) {@link Query} engine for determining the {@link
    036       * GenericFile}s for files that belong to Java packages.
    037       */
    038      public /*@ non_null @*/ Query javaFileSpace;
    039
    040      public /*@ non_null @*/ Query javaSrcFileSpace;
    041
    042      /**
    043       * Our (non-null) reader for use in reading in source files.
    044       */
    045      public /*@ non_null @*/ Reader sourceReader;
    046
    047      /**
    048       * Our (non-null) reader for use in reading in binary (.class) files.
    049       */
    050      public /*@ non_null @*/ Reader binaryReader;
    051
    052      /**
    053       * Create a <code>StandardTypeReader</code> from a query engine, a
    054       * source reader, and a binary reader.  All arguments must be
    055       * non-null.
    056       */
    057      protected StandardTypeReader(/*@ non_null @*/ Query engine,
    058                                   /*@ non_null @*/ Query srcEngine,
    059                                   /*@ non_null @*/ CachedReader srcReader,
    060                                   /*@ non_null @*/ CachedReader binReader) {
    061        javaFileSpace = engine;
    062        javaSrcFileSpace = srcEngine;
    063
    064        // The sourceReader must be cached to meet TypeReader's spec:
    065        sourceReader = srcReader;
    066
    067        /*
    068         * The binaryReader is cached only for efficiency reasons.
    069         *  (this prevents duplicate reads of binaries when useSrcPtr is
    070         *   used)
    071         */
    072        binaryReader = binReader;
    073      }
    074
    075      public void clear() {
    076        ((CachedReader)sourceReader).flushAll();
    077        ((CachedReader)binaryReader).flushAll();
    078      }
    079
    080      /**
    081       * Create a <code>StandardTypeReader</code> from a query engine, a
    082       * source reader, and a binary reader.  All arguments must be
    083       * non-null.
    084       */
    085      public static /*@ non_null @*/ StandardTypeReader
    086        make(/*@ non_null @*/ Query engine,
    087             /*@ non_null @*/ Query srcEngine,
    088             /*@ non_null @*/ Reader srcReader,
    089             /*@ non_null @*/ Reader binReader) {
    090        return new StandardTypeReader(engine, srcEngine,
    091                                      new CachedReader(srcReader),
    092                                      new CachedReader(binReader));
    093      }
    094
    095      /**
    096       * Create a <code>StandardTypeReader</code> from a query engine and
    097       * a pragma parser.  The pragma parser may be null.
    098       */
    099      public static /*@ non_null @*/ StandardTypeReader make(/*@ non_null @*/ Query Q,
    100                                                             Query sourceQ,
    101                                                             PragmaParser pragmaP) {
    102        Assert.precondition(Q != null);
    103
    104        return make(Q, sourceQ, new SrcReader(pragmaP), new BinReader());
    105      }
    106
    107      /**
    108       * Create a {@link Query} for use in creating a
    109       * <code>StandardTypeReader</code> from a Java classpath.
    110       *
    111       * <p> A fatal error will be reported via {@link ErrorSet} if an I/O
    112       * error occurs while initially scanning the filesystem.
    113       */
    114      public static /*@ non_null @*/ Query queryFromClasspath(/*@ non_null @*/ String path) {
    115        try {
    116          return new SlowQuery(path);
    117        } catch (java.io.IOException e) {
    118          ErrorSet.fatal("unable to initialize Java filespace due to"
    119                         + " I/O error: " + e.getMessage());
    120        }
    121
    122        //@ unreachable;
    123        return null;        // make compiler happy
    124      }
    125
    126      /**
    127       * Create a <code>StandardTypeReader</code> using a given Java
    128       * classpath for our underlying Java file space and a given pragma
    129       * parser.  If the given path is null, the default Java classpath is
    130       * used.
    131       *
    132       * <p> A fatal error will be reported via {@link ErrorSet} if an
    133       * I/O error occurs while initially scanning the filesystem.
    134       */
    135      public static /*@ non_null @*/ StandardTypeReader make(String path,
    136                                                             String sourcePath,
    137                                                             PragmaParser pragmaP) {
    138        if (path==null)
    139          path = javafe.filespace.ClassPath.current();
    140
    141        Query q = queryFromClasspath(path);
    142        Query srcq = sourcePath == null ? q : queryFromClasspath(sourcePath);
    143        return make(q, srcq, pragmaP);
    144      }
    145
    146      /**
    147       * Create a <code>StandardTypeReader</code> using a the default Java
    148       * classpath for our underlying Java file space and a given pragma
    149       * parser.
    150       *
    151       * <p> A fatal error will be reported via {@link ErrorSet} if
    152       * an I/O error occurs while initially scanning the filesystem.
    153       */
    154      public static /*@ non_null @*/ StandardTypeReader make(PragmaParser pragmaP) {
    155        return make((String)null, (String)null, pragmaP);
    156      }
    157
    158      /**
    159       * Create a <code>StandardTypeReader</code> using the default Java
    160       * classpath for our underlying Java file space and no pragma
    161       * parser.
    162       *
    163       * <p> A fatal error will be reported via {@link ErrorSet} if an I/O
    164       * error occurs while initially scanning the filesystem.
    165       */
    166      public static /*@ non_null @*/ StandardTypeReader make() {
    167        return make((PragmaParser) null);
    168      }
    169
    170      /**
    171       * Return true iff the package <code>P</code> is "accessible".
    172       *
    173       * <p> Warning: the definition of accessible is host system
    174       * dependent and may in fact be defined as always true.
    175       */
    176      public boolean accessable(/*@ non_null @*/ String[] P) {
    177        return javaSrcFileSpace.accessable(P) || javaFileSpace.accessable(P);
    178      }
    179
    180      /**
    181       * Return true iff the fully-qualified outside type <code>P.T</code>
    182       * exists.
    183       */
    184      public boolean exists(/*@ non_null @*/ String[] P, /*@ non_null @*/ String T) {
    185        return (javaSrcFileSpace.findFile(P, T, "java") != null) ||
    186          (javaFileSpace.findFile(P, T, "class") != null);
    187      }
    188
    189      public GenericFile findType(/*@ non_null @*/ String[] P, /*@ non_null @*/ String T) {
    190        GenericFile gf = javaSrcFileSpace.findFile(P, T, "java");
    191        if (gf == null) gf = javaFileSpace.findFile(P, T, "class");
    192        return gf;
    193      }
    194
    195      /**
    196       * If a binary exists for the exact fully-qualified type P.N (e.g.,
    197       * no inheritance required), then return a {@link GenericFile}
    198       * representing that file.  Otherwise, return null.
    199       *
    200       * <p> WARNING: if N is not a simple name, then a non-null return
    201       * result does *not* imply that P.N actually exists.  The binary may
    202       * be left over from a previous compilation.  Only if P.N can be
    203       * reached from its containing clases, is it considered to exist.
    204       */
    205      //@ requires \nonnullelements(P) && \nonnullelements(N);
    206      public GenericFile locateBinary(/*@ non_null @*/ String[] P, /*@ non_null @*/ String[] N) {
    207        String typename = "";
    208
    209        for (int i=0; i<N.length; i++) {
    210          if (i != 0)
    211            typename += "$";
    212          typename += N[i];
    213        }
    214
    215        return javaFileSpace.findFile(P, typename, "class");
    216      }
    217
    218      /**
    219       * If a source exists for the fully-qualified outside type
    220       * <code>P.T</code>, then return a {@link GenericFile} representing
    221       * that file.  Otherwise, return null.
    222       *
    223       * <p> Exception: If <code>P.T</code>'s source file is not called
    224       * T.java, and no T.class file exists for <code>P.T</code>, then
    225       * null will also be returned.  If useSrcPtr is not set, then null
    226       * will be returned when <code>P.T</code>'s source file is not
    227       * called T.java, regardless of whether or not there is a T.class
    228       * file for <code>P.T</code>.
    229       *
    230       * <p> Note: iff <code>useSrcPtr</code> is set, then
    231       * <code>P.T</code>'s binary may be read in in order to obtain it's
    232       * source pointer.
    233       */
    234      //@ requires \nonnullelements(P);
    235      // can return null
    236      public GenericFile locateSource(/*@ non_null @*/ String[] P,
    237                                      /*@ non_null @*/ String T,
    238                                      boolean useSrcPtr) {
    239        // First try the .java file with name T.java:
    240        GenericFile file = javaSrcFileSpace.findFile(P, T, "java");
    241        if (file != null || !useSrcPtr)
    242          return file;
    243
    244        // Try and fetch the source pointer from T.class:
    245        String[] N = { T };
    246        file = locateBinary(P, N);
    247        if (file==null)
    248          return null;
    249        CompilationUnit binary = binaryReader.read(file, false);
    250        if (binary==null)
    251          return null;
    252        String srcPtr = "srcptr.java";      // !!!! FIXME
    253
    254        // Try and locate that file if a valid srcPtr is present:
    255        if (srcPtr==null || !srcPtr.endsWith(".java"))
    256          return null;
    257        return javaSrcFileSpace.findFile(P, srcPtr.substring(0,srcPtr.length()-5),
    258                                         "java");
    259      }
    260
    261      // Finds source files
    262      public /*@ non_null @*/ ArrayList findFiles(/*@ non_null @*/ String[] P) {
    263        FilenameFilter ff = filter();
    264        ArrayList a = new ArrayList();
    265        Enumeration e = javaSrcFileSpace.findFiles(P);
    266        while (e.hasMoreElements()) {
    267          Tree t = (Tree)e.nextElement();
    268          String s = t.getLabel();
    269          if (ff.accept(new File(s),s)) { a.add(t.data); }
    270        }
    271        return a;
    272      }
    273
    274      public /*@ non_null @*/ FilenameFilter filter() {
    275        return new FilenameFilter() {
    276            public boolean accept(File f, String n) {
    277              if (!f.isFile()) return false;
    278              if (n.endsWith(".java")) return true;
    279              return false;
    280            }
    281          };
    282      }
    283
    284      /**
    285       * Attempt to read and parse a {@link CompilationUnit} from
    286       * <emph>source file</emph> target.  Any errors encountered are
    287       * reported via {@link ErrorSet}.  Null is returned iff an error
    288       * was encountered.
    289       *
    290       *
    291       * <p> By default, we attempt to read only a spec (e.g.,
    292       * <code>specOnly</code> is set in the resulting {@link
    293       * CompilationUnit}) to save time.  If <code>avoidSpec</code> is
    294       * true, we return a non-spec, except in the case where we have
    295       * previously read in the same source file with
    296       * <code>avoidSpec</code> false.  (See notes on caching below.)
    297       *
    298       * <p> There are 2 safe ways to ensure source files yield non-spec
    299       * files: (1) always use <code>avoidSpec</code>, or (2) read all
    300       * desired non-spec's at the beginning with <code>avoidSpec</code>
    301       * set.  [these instructions apply to both versions of read.]
    302       *
    303       *
    304       * <p> The result of this function is cached.  Note that {@link
    305       * #read(String[], String, boolean)} may implicitly call this
    306       * function, resulting in caching of source files.
    307       *
    308       * <p> Only the value of <code>avoidSpec</code> used the first time
    309       * a given file is read is used (including implicit calls).  This
    310       * may result in a spec being returned unnecessarily when
    311       * <code>avoidSpec</code> is true.
    312       *
    313       * <p> Target must be non-null.
    314       */
    315      public CompilationUnit read(/*@ non_null @*/ GenericFile target,
    316                                  boolean avoidSpec) {
    317        return sourceReader.read(target, avoidSpec);
    318      }
    319
    320      /**
    321       * Attempt to read and parse a {@link CompilationUnit} from the
    322       * source for the fully-qualified outside type <code>P.T</code>.
    323       * Null is returned if no source can be found for <code>P.T</code>
    324       * or if an error is encountered.  Errors are reported via {@link
    325       * ErrorSet}.
    326       *
    327       * <p> If <code>P.T</code>'s source is not named <tt>T.java</tt> and
    328       * there is no <tt>T.class</tt> file for <code>P.T</code>., then no
    329       * source for <code>P.T</code> will be found.
    330       *
    331       * <p> (This is a convenience function.)
    332       */
    333      //@ requires \nonnullelements(P);
    334      public CompilationUnit readTypeSrc(/*@ non_null @*/ String[] P,
    335                                         /*@ non_null @*/ String T,
    336                                         boolean avoidSpec) {
    337        GenericFile source = locateSource(P, T, true);
    338        if (source==null)
    339          return null;
    340
    341        return read(source, avoidSpec);
    342      }
    343
    344      /**
    345       * Attempt to read and parse a complete (i.e., no stubs) {@link
    346       * CompilationUnit} from the binaries for the fully-qualified
    347       * outside type <code>P.T</code>.
    348       *
    349       * <p> Null is returned if:
    350       * <ul>
    351       *   <li> no <tt>T.class</tt> file exists, </li>
    352       *   <li> the <tt>T.class</tt> file is known to predate the last
    353       *        modified time <code>after</code> and <code>after</code>
    354       *        is not <code>0L</code>, or </li>
    355       *   <li> an error occurs. </li>
    356       * </ul>
    357       *
    358       * <p> Errors are reported via {@link ErrorSet}.  An incomplete set
    359       * of binaries (one or more inner classes missing or not up-to-date
    360       * WRT after) is considered an error.
    361       */
    362      //@ requires \nonnullelements(P);
    363      public CompilationUnit readTypeBinaries(/*@ non_null @*/ String[] P,
    364                                              /*@ non_null @*/ String T,
    365                                              long after) {
    366        // Check for an up-to-date T.class file:
    367        String[] N = { T };
    368        GenericFile bin = locateBinary(P, N);
    369        if (bin==null || (after != 0L && bin.lastModified() != 0L
    370                          && bin.lastModified() < after))
    371          return null;
    372
    373        /*
    374         * @bug For now, ignore possibility of inner classes and return only
    375         * the outside class.  This needs to be fixed later to read in
    376         * all the inner classes and stitch them together.  !!!!
    377         */
    378        return binaryReader.read(bin, false);
    379      }
    380
    381      /**
    382       * Attempt to read and parse a {@link CompilationUnit} from either
    383       * the binaries for <code>P.T</code> if they are up to date, or from
    384       * the source for <code>P.T</code>.  If both a source and an
    385       * up-to-date series of binaries are available for <code>P.T</code>,
    386       * preference is given to the source if <code>srcPreferred</code> is
    387       * set, and to the binaries otherwise.
    388       *
    389       * <p> Binaries are considered to exist for <code>P.T</code> iff a
    390       * <tt>T.class</tt> file exists in package <code>P</code>.  The
    391       * last modified date for these binaries as a whole is considered to
    392       * be the <tt>T.class</tt> file's last modified date.
    393       *
    394       * <p> Null is returned if no source or binaries for
    395       * <code>P.T</code> exist or if an error occurs.  Errors are
    396       * reported via {@link ErrorSet}.  An incomplete series of binaries
    397       * (one or more inner classes missing or not up-to-date) generates
    398       * an error when read in.
    399       *
    400       * <p> If the resulting {@link CompilationUnit} is non-null, then it
    401       * is always complete, having no stubs.
    402       */
    403      public CompilationUnit read(/*@ non_null @*/ String[] P,
    404                                  /*@ non_null @*/ String T,
    405                                  boolean avoidSpec) {
    406        int fileOriginOption = Tool.options.fileOrigin;
    407
    408        // Locate source file, if any:
    409        GenericFile source = null;
    410        if (fileOriginOption != Options.NEVER_SOURCE)
    411          source = locateSource(P, T, true);
    412        // FIXME - even with NEVER_SOURCE, shouldn't we read the source
    413        // if avoidSpec is true (that is we need the implementation)???
    414
    415        // Last modification date for source if known (0L if not known):
    416        long after = source==null ? 0L : source.lastModified();
    417
    418        // If try to avoid spec's, read from source if it exists:
    419        if (source != null && (avoidSpec ||
    420                               fileOriginOption == Options.NEVER_BINARY ||
    421                               fileOriginOption == Options.PREFER_SOURCE))
    422          return read(source, avoidSpec);
    423
    424        // Read from the binaries if they're complete and up-to-date:
    425        if (fileOriginOption == Options.PREFER_BINARY) after = 0L;
    426        if (fileOriginOption != Options.NEVER_BINARY) {
    427          CompilationUnit bin = readTypeBinaries(P, T, after);
    428          if (bin != null) return bin;
    429        }
    430
    431        // Finally, fall back on source if it's available:
    432        if (source != null) return read(source, avoidSpec);
    433
    434        return null;
    435      }
    436
    437      //@ requires \nonnullelements(args);
    438      public static void main(/*@ non_null @*/ String[] args)
    439        throws java.io.IOException {
    440        if (args.length != 2) {
    441          System.err.println("StandardTypeReader: <package> <simple name>");
    442          System.exit(1);
    443        }
    444
    445        String[] P = javafe.filespace.StringUtil.parseList(args[0], '.');
    446
    447        StandardTypeReader R = make();
    448
    449        CompilationUnit cu = R.read(P, args[1], false);
    450        if (cu==null) {
    451          System.out.println("Unable to load that type.");
    452          System.exit(1);
    453        }
    454
    455        PrettyPrint.inst.print( System.out, cu );
    456      }
    457    }