Fri, 14 Feb 2014 17:28:07 -0800
8029145: javadoc fails with java.lang.IllegalStateException: endPosTable already set
Reviewed-by: jjg
1 /*
2 * Copyright (c) 2001, 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 */
26 package com.sun.tools.javadoc;
28 import java.io.File;
29 import java.io.IOException;
30 import java.util.Collection;
31 import java.util.EnumSet;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Map;
35 import java.util.Set;
36 import javax.tools.JavaFileManager.Location;
37 import javax.tools.JavaFileObject;
38 import javax.tools.StandardJavaFileManager;
39 import javax.tools.StandardLocation;
41 import com.sun.tools.javac.code.Symbol.CompletionFailure;
42 import com.sun.tools.javac.tree.JCTree;
43 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
44 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
45 import com.sun.tools.javac.util.Abort;
46 import com.sun.tools.javac.util.Context;
47 import com.sun.tools.javac.util.List;
48 import com.sun.tools.javac.util.ListBuffer;
49 import com.sun.tools.javac.util.Position;
52 /**
53 * This class could be the main entry point for Javadoc when Javadoc is used as a
54 * component in a larger software system. It provides operations to
55 * construct a new javadoc processor, and to run it on a set of source
56 * files.
57 *
58 * <p><b>This is NOT part of any supported API.
59 * If you write code that depends on this, you do so at your own risk.
60 * This code and its internal interfaces are subject to change or
61 * deletion without notice.</b>
62 *
63 * @author Neal Gafter
64 */
65 public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler {
66 DocEnv docenv;
68 final Messager messager;
69 final JavadocClassReader javadocReader;
70 final JavadocEnter javadocEnter;
72 /**
73 * Construct a new JavaCompiler processor, using appropriately
74 * extended phases of the underlying compiler.
75 */
76 protected JavadocTool(Context context) {
77 super(context);
78 messager = Messager.instance0(context);
79 javadocReader = JavadocClassReader.instance0(context);
80 javadocEnter = JavadocEnter.instance0(context);
81 }
83 /**
84 * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler.
85 */
86 protected boolean keepComments() {
87 return true;
88 }
90 /**
91 * Construct a new javadoc tool.
92 */
93 public static JavadocTool make0(Context context) {
94 Messager messager = null;
95 try {
96 // force the use of Javadoc's class reader
97 JavadocClassReader.preRegister(context);
99 // force the use of Javadoc's own enter phase
100 JavadocEnter.preRegister(context);
102 // force the use of Javadoc's own member enter phase
103 JavadocMemberEnter.preRegister(context);
105 // force the use of Javadoc's own todo phase
106 JavadocTodo.preRegister(context);
108 // force the use of Messager as a Log
109 messager = Messager.instance0(context);
111 return new JavadocTool(context);
112 } catch (CompletionFailure ex) {
113 messager.error(Position.NOPOS, ex.getMessage());
114 return null;
115 }
116 }
118 public RootDocImpl getRootDocImpl(String doclocale,
119 String encoding,
120 ModifierFilter filter,
121 List<String> javaNames,
122 List<String[]> options,
123 Iterable<? extends JavaFileObject> fileObjects,
124 boolean breakiterator,
125 List<String> subPackages,
126 List<String> excludedPackages,
127 boolean docClasses,
128 boolean legacyDoclet,
129 boolean quiet) throws IOException {
130 docenv = DocEnv.instance(context);
131 docenv.showAccess = filter;
132 docenv.quiet = quiet;
133 docenv.breakiterator = breakiterator;
134 docenv.setLocale(doclocale);
135 docenv.setEncoding(encoding);
136 docenv.docClasses = docClasses;
137 docenv.legacyDoclet = legacyDoclet;
138 javadocReader.sourceCompleter = docClasses ? null : thisCompleter;
140 ListBuffer<String> names = new ListBuffer<String>();
141 ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<JCCompilationUnit>();
142 ListBuffer<JCCompilationUnit> packTrees = new ListBuffer<JCCompilationUnit>();
144 try {
145 StandardJavaFileManager fm = docenv.fileManager instanceof StandardJavaFileManager
146 ? (StandardJavaFileManager) docenv.fileManager : null;
147 for (List<String> it = javaNames; it.nonEmpty(); it = it.tail) {
148 String name = it.head;
149 if (!docClasses && fm != null && name.endsWith(".java") && new File(name).exists()) {
150 JavaFileObject fo = fm.getJavaFileObjects(name).iterator().next();
151 docenv.notice("main.Loading_source_file", name);
152 JCCompilationUnit tree = parse(fo);
153 classTrees.append(tree);
154 } else if (isValidPackageName(name)) {
155 names = names.append(name);
156 } else if (name.endsWith(".java")) {
157 if (fm == null)
158 throw new IllegalArgumentException();
159 else
160 docenv.error(null, "main.file_not_found", name);
161 } else {
162 docenv.error(null, "main.illegal_package_name", name);
163 }
164 }
165 for (JavaFileObject fo: fileObjects) {
166 docenv.notice("main.Loading_source_file", fo.getName());
167 JCCompilationUnit tree = parse(fo);
168 classTrees.append(tree);
169 }
171 if (!docClasses) {
172 // Recursively search given subpackages. If any packages
173 //are found, add them to the list.
174 Map<String,List<JavaFileObject>> packageFiles =
175 searchSubPackages(subPackages, names, excludedPackages);
177 // Parse the packages
178 for (List<String> packs = names.toList(); packs.nonEmpty(); packs = packs.tail) {
179 // Parse sources ostensibly belonging to package.
180 String packageName = packs.head;
181 parsePackageClasses(packageName, packageFiles.get(packageName), packTrees, excludedPackages);
182 }
184 if (messager.nerrors() != 0) return null;
186 // Enter symbols for all files
187 docenv.notice("main.Building_tree");
188 javadocEnter.main(classTrees.toList().appendList(packTrees.toList()));
189 }
190 } catch (Abort ex) {}
192 if (messager.nerrors() != 0)
193 return null;
195 if (docClasses)
196 return new RootDocImpl(docenv, javaNames, options);
197 else
198 return new RootDocImpl(docenv, listClasses(classTrees.toList()), names.toList(), options);
199 }
201 /** Is the given string a valid package name? */
202 boolean isValidPackageName(String s) {
203 int index;
204 while ((index = s.indexOf('.')) != -1) {
205 if (!isValidClassName(s.substring(0, index))) return false;
206 s = s.substring(index+1);
207 }
208 return isValidClassName(s);
209 }
211 /**
212 * search all directories in path for subdirectory name. Add all
213 * .java files found in such a directory to args.
214 */
215 private void parsePackageClasses(String name,
216 Iterable<JavaFileObject> files,
217 ListBuffer<JCCompilationUnit> trees,
218 List<String> excludedPackages)
219 throws IOException {
220 if (excludedPackages.contains(name)) {
221 return;
222 }
224 boolean hasFiles = false;
225 docenv.notice("main.Loading_source_files_for_package", name);
227 if (files == null) {
228 Location location = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
229 ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
230 ListBuffer<JavaFileObject> lb = new ListBuffer<JavaFileObject>();
231 for (JavaFileObject fo: docenv.fileManager.list(
232 location, name, EnumSet.of(JavaFileObject.Kind.SOURCE), false)) {
233 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
234 String simpleName = getSimpleName(binaryName);
235 if (isValidClassName(simpleName)) {
236 lb.append(fo);
237 }
238 }
239 files = lb.toList();
240 }
242 Set<JavaFileObject> ufiles = new HashSet<>();
243 for (JavaFileObject fo : files) {
244 if (ufiles.add(fo)) { // ignore duplicates
245 // messager.notice("main.Loading_source_file", fn);
246 trees.append(parse(fo));
247 hasFiles = true;
248 }
249 }
251 if (!hasFiles) {
252 messager.warning(Messager.NOPOS, "main.no_source_files_for_package",
253 name.replace(File.separatorChar, '.'));
254 }
255 }
257 /**
258 * Recursively search all directories in path for subdirectory name.
259 * Add all packages found in such a directory to packages list.
260 */
261 private Map<String,List<JavaFileObject>> searchSubPackages(
262 List<String> subPackages,
263 ListBuffer<String> packages,
264 List<String> excludedPackages)
265 throws IOException {
266 Map<String,List<JavaFileObject>> packageFiles =
267 new HashMap<String,List<JavaFileObject>>();
269 Map<String,Boolean> includedPackages = new HashMap<String,Boolean>();
270 includedPackages.put("", true);
271 for (String p: excludedPackages)
272 includedPackages.put(p, false);
274 StandardLocation path = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
275 ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
277 searchSubPackages(subPackages,
278 includedPackages,
279 packages, packageFiles,
280 path,
281 EnumSet.of(JavaFileObject.Kind.SOURCE));
283 return packageFiles;
284 }
286 private void searchSubPackages(List<String> subPackages,
287 Map<String,Boolean> includedPackages,
288 ListBuffer<String> packages,
289 Map<String, List<JavaFileObject>> packageFiles,
290 StandardLocation location, Set<JavaFileObject.Kind> kinds)
291 throws IOException {
292 for (String subPackage: subPackages) {
293 if (!isIncluded(subPackage, includedPackages))
294 continue;
296 for (JavaFileObject fo: docenv.fileManager.list(location, subPackage, kinds, true)) {
297 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
298 String packageName = getPackageName(binaryName);
299 String simpleName = getSimpleName(binaryName);
300 if (isIncluded(packageName, includedPackages) && isValidClassName(simpleName)) {
301 List<JavaFileObject> list = packageFiles.get(packageName);
302 list = (list == null ? List.of(fo) : list.prepend(fo));
303 packageFiles.put(packageName, list);
304 if (!packages.contains(packageName))
305 packages.add(packageName);
306 }
307 }
308 }
309 }
311 private String getPackageName(String name) {
312 int lastDot = name.lastIndexOf(".");
313 return (lastDot == -1 ? "" : name.substring(0, lastDot));
314 }
316 private String getSimpleName(String name) {
317 int lastDot = name.lastIndexOf(".");
318 return (lastDot == -1 ? name : name.substring(lastDot + 1));
319 }
321 private boolean isIncluded(String packageName, Map<String,Boolean> includedPackages) {
322 Boolean b = includedPackages.get(packageName);
323 if (b == null) {
324 b = isIncluded(getPackageName(packageName), includedPackages);
325 includedPackages.put(packageName, b);
326 }
327 return b;
328 }
330 /**
331 * Recursively search all directories in path for subdirectory name.
332 * Add all packages found in such a directory to packages list.
333 */
334 private void searchSubPackage(String packageName,
335 ListBuffer<String> packages,
336 List<String> excludedPackages,
337 Collection<File> pathnames) {
338 if (excludedPackages.contains(packageName))
339 return;
341 String packageFilename = packageName.replace('.', File.separatorChar);
342 boolean addedPackage = false;
343 for (File pathname : pathnames) {
344 File f = new File(pathname, packageFilename);
345 String filenames[] = f.list();
346 // if filenames not null, then found directory
347 if (filenames != null) {
348 for (String filename : filenames) {
349 if (!addedPackage
350 && (isValidJavaSourceFile(filename) ||
351 isValidJavaClassFile(filename))
352 && !packages.contains(packageName)) {
353 packages.append(packageName);
354 addedPackage = true;
355 } else if (isValidClassName(filename) &&
356 (new File(f, filename)).isDirectory()) {
357 searchSubPackage(packageName + "." + filename,
358 packages, excludedPackages, pathnames);
359 }
360 }
361 }
362 }
363 }
365 /**
366 * Return true if given file name is a valid class file name.
367 * @param file the name of the file to check.
368 * @return true if given file name is a valid class file name
369 * and false otherwise.
370 */
371 private static boolean isValidJavaClassFile(String file) {
372 if (!file.endsWith(".class")) return false;
373 String clazzName = file.substring(0, file.length() - ".class".length());
374 return isValidClassName(clazzName);
375 }
377 /**
378 * Return true if given file name is a valid Java source file name.
379 * @param file the name of the file to check.
380 * @return true if given file name is a valid Java source file name
381 * and false otherwise.
382 */
383 private static boolean isValidJavaSourceFile(String file) {
384 if (!file.endsWith(".java")) return false;
385 String clazzName = file.substring(0, file.length() - ".java".length());
386 return isValidClassName(clazzName);
387 }
389 /** Are surrogates supported?
390 */
391 final static boolean surrogatesSupported = surrogatesSupported();
392 private static boolean surrogatesSupported() {
393 try {
394 boolean b = Character.isHighSurrogate('a');
395 return true;
396 } catch (NoSuchMethodError ex) {
397 return false;
398 }
399 }
401 /**
402 * Return true if given file name is a valid class name
403 * (including "package-info").
404 * @param s the name of the class to check.
405 * @return true if given class name is a valid class name
406 * and false otherwise.
407 */
408 public static boolean isValidClassName(String s) {
409 if (s.length() < 1) return false;
410 if (s.equals("package-info")) return true;
411 if (surrogatesSupported) {
412 int cp = s.codePointAt(0);
413 if (!Character.isJavaIdentifierStart(cp))
414 return false;
415 for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) {
416 cp = s.codePointAt(j);
417 if (!Character.isJavaIdentifierPart(cp))
418 return false;
419 }
420 } else {
421 if (!Character.isJavaIdentifierStart(s.charAt(0)))
422 return false;
423 for (int j=1; j<s.length(); j++)
424 if (!Character.isJavaIdentifierPart(s.charAt(j)))
425 return false;
426 }
427 return true;
428 }
430 /**
431 * From a list of top level trees, return the list of contained class definitions
432 */
433 List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) {
434 ListBuffer<JCClassDecl> result = new ListBuffer<JCClassDecl>();
435 for (JCCompilationUnit t : trees) {
436 for (JCTree def : t.defs) {
437 if (def.hasTag(JCTree.Tag.CLASSDEF))
438 result.append((JCClassDecl)def);
439 }
440 }
441 return result.toList();
442 }
444 }