|
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.Scope.WriteableScope; |
|
41 import com.sun.tools.javac.code.Symbol.*; |
|
42 import com.sun.tools.javac.jvm.ClassReader; |
|
43 import com.sun.tools.javac.util.*; |
|
44 |
|
45 import static com.sun.tools.javac.code.Flags.*; |
|
46 import static com.sun.tools.javac.code.Kinds.*; |
|
47 |
|
48 import 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 */ |
|
59 public 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 } |