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