ClassFinder.java revision 2571:10fc81ac75b4
1/* 2 * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.tools.javac.code; 27 28import java.io.*; 29import java.util.EnumSet; 30import java.util.Set; 31import javax.lang.model.SourceVersion; 32import javax.tools.JavaFileObject; 33import javax.tools.JavaFileManager; 34import javax.tools.JavaFileManager.Location; 35import javax.tools.StandardJavaFileManager; 36 37import static javax.tools.StandardLocation.*; 38 39import com.sun.tools.javac.comp.Annotate; 40import com.sun.tools.javac.code.Scope.WriteableScope; 41import com.sun.tools.javac.code.Symbol.*; 42import com.sun.tools.javac.jvm.ClassReader; 43import com.sun.tools.javac.util.*; 44 45import static com.sun.tools.javac.code.Flags.*; 46import static com.sun.tools.javac.code.Kinds.*; 47 48import static com.sun.tools.javac.main.Option.*; 49 50/** 51 * This class provides operations to locate class definitions 52 * from the source and class files on the paths provided to javac. 53 * 54 * <p><b>This is NOT part of any supported API. 55 * If you write code that depends on this, you do so at your own risk. 56 * This code and its internal interfaces are subject to change or 57 * deletion without notice.</b> 58 */ 59public class ClassFinder { 60 /** The context key for the class finder. */ 61 protected static final Context.Key<ClassFinder> classFinderKey = new Context.Key<>(); 62 63 ClassReader reader; 64 65 Annotate annotate; 66 67 /** Switch: verbose output. 68 */ 69 boolean verbose; 70 71 /** 72 * Switch: cache completion failures unless -XDdev is used 73 */ 74 private boolean cacheCompletionFailure; 75 76 /** 77 * Switch: prefer source files instead of newer when both source 78 * and class are available 79 **/ 80 protected boolean preferSource; 81 82 /** 83 * Switch: Search classpath and sourcepath for classes before the 84 * bootclasspath 85 */ 86 protected boolean userPathsFirst; 87 88 /** The log to use for verbose output 89 */ 90 final Log log; 91 92 /** The symbol table. */ 93 Symtab syms; 94 95 /** The name table. */ 96 final Names names; 97 98 /** Force a completion failure on this name 99 */ 100 final Name completionFailureName; 101 102 /** Access to files 103 */ 104 private final JavaFileManager fileManager; 105 106 /** Dependency tracker 107 */ 108 private final Dependencies dependencies; 109 110 /** Factory for diagnostics 111 */ 112 JCDiagnostic.Factory diagFactory; 113 114 /** Can be reassigned from outside: 115 * the completer to be used for ".java" files. If this remains unassigned 116 * ".java" files will not be loaded. 117 */ 118 public Completer sourceCompleter = null; 119 120 /** The path name of the class file currently being read. 121 */ 122 protected JavaFileObject currentClassFile = null; 123 124 /** The class or method currently being read. 125 */ 126 protected Symbol currentOwner = null; 127 128 /** 129 * Completer that delegates to the complete-method of this class. 130 */ 131 private final Completer thisCompleter = new Completer() { 132 @Override 133 public void complete(Symbol sym) throws CompletionFailure { 134 ClassFinder.this.complete(sym); 135 } 136 }; 137 138 public Completer getCompleter() { 139 return thisCompleter; 140 } 141 142 /** Get the ClassFinder instance for this invocation. */ 143 public static ClassFinder instance(Context context) { 144 ClassFinder instance = context.get(classFinderKey); 145 if (instance == null) 146 instance = new ClassFinder(context); 147 return instance; 148 } 149 150 /** Construct a new class reader. */ 151 protected ClassFinder(Context context) { 152 context.put(classFinderKey, this); 153 reader = ClassReader.instance(context); 154 names = Names.instance(context); 155 syms = Symtab.instance(context); 156 fileManager = context.get(JavaFileManager.class); 157 dependencies = Dependencies.instance(context); 158 if (fileManager == null) 159 throw new AssertionError("FileManager initialization error"); 160 diagFactory = JCDiagnostic.Factory.instance(context); 161 162 log = Log.instance(context); 163 annotate = Annotate.instance(context); 164 165 Options options = Options.instance(context); 166 verbose = options.isSet(VERBOSE); 167 cacheCompletionFailure = options.isUnset("dev"); 168 preferSource = "source".equals(options.get("-Xprefer")); 169 userPathsFirst = options.isSet(XXUSERPATHSFIRST); 170 171 172 completionFailureName = 173 options.isSet("failcomplete") 174 ? names.fromString(options.get("failcomplete")) 175 : null; 176 } 177 178/************************************************************************ 179 * Loading Classes 180 ***********************************************************************/ 181 182 /** Completion for classes to be loaded. Before a class is loaded 183 * we make sure its enclosing class (if any) is loaded. 184 */ 185 private void complete(Symbol sym) throws CompletionFailure { 186 if (sym.kind == TYP) { 187 try { 188 ClassSymbol c = (ClassSymbol) sym; 189 dependencies.push(c); 190 c.members_field = new Scope.ErrorScope(c); // make sure it's always defined 191 annotate.enterStart(); 192 try { 193 completeOwners(c.owner); 194 completeEnclosing(c); 195 } finally { 196 // The flush needs to happen only after annotations 197 // are filled in. 198 annotate.enterDoneWithoutFlush(); 199 } 200 fillIn(c); 201 } finally { 202 dependencies.pop(); 203 } 204 } else if (sym.kind == PCK) { 205 PackageSymbol p = (PackageSymbol)sym; 206 try { 207 fillIn(p); 208 } catch (IOException ex) { 209 throw new CompletionFailure(sym, ex.getLocalizedMessage()).initCause(ex); 210 } 211 } 212 if (!reader.filling) 213 annotate.flush(); // finish attaching annotations 214 } 215 216 /** complete up through the enclosing package. */ 217 private void completeOwners(Symbol o) { 218 if (o.kind != PCK) completeOwners(o.owner); 219 o.complete(); 220 } 221 222 /** 223 * Tries to complete lexically enclosing classes if c looks like a 224 * nested class. This is similar to completeOwners but handles 225 * the situation when a nested class is accessed directly as it is 226 * possible with the Tree API or javax.lang.model.*. 227 */ 228 private void completeEnclosing(ClassSymbol c) { 229 if (c.owner.kind == PCK) { 230 Symbol owner = c.owner; 231 for (Name name : Convert.enclosingCandidates(Convert.shortName(c.name))) { 232 Symbol encl = owner.members().findFirst(name); 233 if (encl == null) 234 encl = syms.classes.get(TypeSymbol.formFlatName(name, owner)); 235 if (encl != null) 236 encl.complete(); 237 } 238 } 239 } 240 241 /** Fill in definition of class `c' from corresponding class or 242 * source file. 243 */ 244 private void fillIn(ClassSymbol c) { 245 if (completionFailureName == c.fullname) { 246 throw new CompletionFailure(c, "user-selected completion failure by class name"); 247 } 248 currentOwner = c; 249 JavaFileObject classfile = c.classfile; 250 if (classfile != null) { 251 JavaFileObject previousClassFile = currentClassFile; 252 try { 253 if (reader.filling) { 254 Assert.error("Filling " + classfile.toUri() + " during " + previousClassFile); 255 } 256 currentClassFile = classfile; 257 if (verbose) { 258 log.printVerbose("loading", currentClassFile.toString()); 259 } 260 if (classfile.getKind() == JavaFileObject.Kind.CLASS) { 261 reader.readClassFile(c); 262 } else { 263 if (sourceCompleter != null) { 264 sourceCompleter.complete(c); 265 } else { 266 throw new IllegalStateException("Source completer required to read " 267 + classfile.toUri()); 268 } 269 } 270 } finally { 271 currentClassFile = previousClassFile; 272 } 273 } else { 274 JCDiagnostic diag = 275 diagFactory.fragment("class.file.not.found", c.flatname); 276 throw 277 newCompletionFailure(c, diag); 278 } 279 } 280 // where 281 /** Static factory for CompletionFailure objects. 282 * In practice, only one can be used at a time, so we share one 283 * to reduce the expense of allocating new exception objects. 284 */ 285 private CompletionFailure newCompletionFailure(TypeSymbol c, 286 JCDiagnostic diag) { 287 if (!cacheCompletionFailure) { 288 // log.warning("proc.messager", 289 // Log.getLocalizedString("class.file.not.found", c.flatname)); 290 // c.debug.printStackTrace(); 291 return new CompletionFailure(c, diag); 292 } else { 293 CompletionFailure result = cachedCompletionFailure; 294 result.sym = c; 295 result.diag = diag; 296 return result; 297 } 298 } 299 private CompletionFailure cachedCompletionFailure = 300 new CompletionFailure(null, (JCDiagnostic) null); 301 { 302 cachedCompletionFailure.setStackTrace(new StackTraceElement[0]); 303 } 304 305 306 /** Load a toplevel class with given fully qualified name 307 * The class is entered into `classes' only if load was successful. 308 */ 309 public ClassSymbol loadClass(Name flatname) throws CompletionFailure { 310 boolean absent = syms.classes.get(flatname) == null; 311 ClassSymbol c = syms.enterClass(flatname); 312 if (c.members_field == null && c.completer != null) { 313 try { 314 c.complete(); 315 } catch (CompletionFailure ex) { 316 if (absent) syms.classes.remove(flatname); 317 throw ex; 318 } 319 } 320 return c; 321 } 322 323/************************************************************************ 324 * Loading Packages 325 ***********************************************************************/ 326 327 /** Include class corresponding to given class file in package, 328 * unless (1) we already have one the same kind (.class or .java), or 329 * (2) we have one of the other kind, and the given class file 330 * is older. 331 */ 332 protected void includeClassFile(PackageSymbol p, JavaFileObject file) { 333 if ((p.flags_field & EXISTS) == 0) 334 for (Symbol q = p; q != null && q.kind == PCK; q = q.owner) 335 q.flags_field |= EXISTS; 336 JavaFileObject.Kind kind = file.getKind(); 337 int seen; 338 if (kind == JavaFileObject.Kind.CLASS) 339 seen = CLASS_SEEN; 340 else 341 seen = SOURCE_SEEN; 342 String binaryName = fileManager.inferBinaryName(currentLoc, file); 343 int lastDot = binaryName.lastIndexOf("."); 344 Name classname = names.fromString(binaryName.substring(lastDot + 1)); 345 boolean isPkgInfo = classname == names.package_info; 346 ClassSymbol c = isPkgInfo 347 ? p.package_info 348 : (ClassSymbol) p.members_field.findFirst(classname); 349 if (c == null) { 350 c = syms.enterClass(classname, p); 351 if (c.classfile == null) // only update the file if's it's newly created 352 c.classfile = file; 353 if (isPkgInfo) { 354 p.package_info = c; 355 } else { 356 if (c.owner == p) // it might be an inner class 357 p.members_field.enter(c); 358 } 359 } else if (!preferCurrent && c.classfile != null && (c.flags_field & seen) == 0) { 360 // if c.classfile == null, we are currently compiling this class 361 // and no further action is necessary. 362 // if (c.flags_field & seen) != 0, we have already encountered 363 // a file of the same kind; again no further action is necessary. 364 if ((c.flags_field & (CLASS_SEEN | SOURCE_SEEN)) != 0) 365 c.classfile = preferredFileObject(file, c.classfile); 366 } 367 c.flags_field |= seen; 368 } 369 370 /** Implement policy to choose to derive information from a source 371 * file or a class file when both are present. May be overridden 372 * by subclasses. 373 */ 374 protected JavaFileObject preferredFileObject(JavaFileObject a, 375 JavaFileObject b) { 376 377 if (preferSource) 378 return (a.getKind() == JavaFileObject.Kind.SOURCE) ? a : b; 379 else { 380 long adate = a.getLastModified(); 381 long bdate = b.getLastModified(); 382 // 6449326: policy for bad lastModifiedTime in ClassReader 383 //assert adate >= 0 && bdate >= 0; 384 return (adate > bdate) ? a : b; 385 } 386 } 387 388 /** 389 * specifies types of files to be read when filling in a package symbol 390 */ 391 protected EnumSet<JavaFileObject.Kind> getPackageFileKinds() { 392 return EnumSet.of(JavaFileObject.Kind.CLASS, JavaFileObject.Kind.SOURCE); 393 } 394 395 /** 396 * this is used to support javadoc 397 */ 398 protected void extraFileActions(PackageSymbol pack, JavaFileObject fe) { 399 } 400 401 protected Location currentLoc; // FIXME 402 403 private boolean verbosePath = true; 404 405 // Set to true when the currently selected file should be kept 406 private boolean preferCurrent; 407 408 /** Load directory of package into members scope. 409 */ 410 private void fillIn(PackageSymbol p) throws IOException { 411 if (p.members_field == null) 412 p.members_field = WriteableScope.create(p); 413 414 preferCurrent = false; 415 if (userPathsFirst) { 416 scanUserPaths(p); 417 preferCurrent = true; 418 scanPlatformPath(p); 419 } else { 420 scanPlatformPath(p); 421 scanUserPaths(p); 422 } 423 verbosePath = false; 424 } 425 426 /** 427 * Scans class path and source path for files in given package. 428 */ 429 private void scanUserPaths(PackageSymbol p) throws IOException { 430 Set<JavaFileObject.Kind> kinds = getPackageFileKinds(); 431 432 Set<JavaFileObject.Kind> classKinds = EnumSet.copyOf(kinds); 433 classKinds.remove(JavaFileObject.Kind.SOURCE); 434 boolean wantClassFiles = !classKinds.isEmpty(); 435 436 Set<JavaFileObject.Kind> sourceKinds = EnumSet.copyOf(kinds); 437 sourceKinds.remove(JavaFileObject.Kind.CLASS); 438 boolean wantSourceFiles = !sourceKinds.isEmpty(); 439 440 boolean haveSourcePath = fileManager.hasLocation(SOURCE_PATH); 441 442 if (verbose && verbosePath) { 443 if (fileManager instanceof StandardJavaFileManager) { 444 StandardJavaFileManager fm = (StandardJavaFileManager)fileManager; 445 if (haveSourcePath && wantSourceFiles) { 446 List<File> path = List.nil(); 447 for (File file : fm.getLocation(SOURCE_PATH)) { 448 path = path.prepend(file); 449 } 450 log.printVerbose("sourcepath", path.reverse().toString()); 451 } else if (wantSourceFiles) { 452 List<File> path = List.nil(); 453 for (File file : fm.getLocation(CLASS_PATH)) { 454 path = path.prepend(file); 455 } 456 log.printVerbose("sourcepath", path.reverse().toString()); 457 } 458 if (wantClassFiles) { 459 List<File> path = List.nil(); 460 for (File file : fm.getLocation(PLATFORM_CLASS_PATH)) { 461 path = path.prepend(file); 462 } 463 for (File file : fm.getLocation(CLASS_PATH)) { 464 path = path.prepend(file); 465 } 466 log.printVerbose("classpath", path.reverse().toString()); 467 } 468 } 469 } 470 471 String packageName = p.fullname.toString(); 472 if (wantSourceFiles && !haveSourcePath) { 473 fillIn(p, CLASS_PATH, 474 fileManager.list(CLASS_PATH, 475 packageName, 476 kinds, 477 false)); 478 } else { 479 if (wantClassFiles) 480 fillIn(p, CLASS_PATH, 481 fileManager.list(CLASS_PATH, 482 packageName, 483 classKinds, 484 false)); 485 if (wantSourceFiles) 486 fillIn(p, SOURCE_PATH, 487 fileManager.list(SOURCE_PATH, 488 packageName, 489 sourceKinds, 490 false)); 491 } 492 } 493 494 /** 495 * Scans platform class path for files in given package. 496 */ 497 private void scanPlatformPath(PackageSymbol p) throws IOException { 498 fillIn(p, PLATFORM_CLASS_PATH, 499 fileManager.list(PLATFORM_CLASS_PATH, 500 p.fullname.toString(), 501 EnumSet.of(JavaFileObject.Kind.CLASS), 502 false)); 503 } 504 // where 505 private void fillIn(PackageSymbol p, 506 Location location, 507 Iterable<JavaFileObject> files) 508 { 509 currentLoc = location; 510 for (JavaFileObject fo : files) { 511 switch (fo.getKind()) { 512 case CLASS: 513 case SOURCE: { 514 // TODO pass binaryName to includeClassFile 515 String binaryName = fileManager.inferBinaryName(currentLoc, fo); 516 String simpleName = binaryName.substring(binaryName.lastIndexOf(".") + 1); 517 if (SourceVersion.isIdentifier(simpleName) || 518 simpleName.equals("package-info")) 519 includeClassFile(p, fo); 520 break; 521 } 522 default: 523 extraFileActions(p, fo); 524 } 525 } 526 } 527 528 /** 529 * Used for bad class definition files, such as bad .class files or 530 * for .java files with unexpected package or class names. 531 */ 532 public static class BadClassFile extends CompletionFailure { 533 private static final long serialVersionUID = 0; 534 535 public BadClassFile(TypeSymbol sym, JavaFileObject file, JCDiagnostic diag, 536 JCDiagnostic.Factory diagFactory) { 537 super(sym, createBadClassFileDiagnostic(file, diag, diagFactory)); 538 } 539 // where 540 private static JCDiagnostic createBadClassFileDiagnostic( 541 JavaFileObject file, JCDiagnostic diag, JCDiagnostic.Factory diagFactory) { 542 String key = (file.getKind() == JavaFileObject.Kind.SOURCE 543 ? "bad.source.file.header" : "bad.class.file.header"); 544 return diagFactory.fragment(key, file, diag); 545 } 546 } 547} 548