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 }