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    }