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 }