JavadocTool.java revision 3447:2fa4e0cc6e60
1/* 2 * Copyright (c) 2001, 2016, 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 jdk.javadoc.internal.tool; 27 28 29import java.io.File; 30import java.io.IOException; 31import java.util.ArrayList; 32import java.util.Collection; 33import java.util.Collections; 34import java.util.EnumSet; 35import java.util.HashSet; 36import java.util.LinkedHashMap; 37import java.util.LinkedHashSet; 38import java.util.List; 39import java.util.Map; 40import java.util.Set; 41 42import javax.tools.JavaFileManager; 43import javax.tools.JavaFileManager.Location; 44import javax.tools.JavaFileObject; 45import javax.tools.StandardJavaFileManager; 46import javax.tools.StandardLocation; 47 48import com.sun.tools.javac.code.ClassFinder; 49import com.sun.tools.javac.code.Symbol.Completer; 50import com.sun.tools.javac.code.Symbol.CompletionFailure; 51import com.sun.tools.javac.code.Symbol.ModuleSymbol; 52import com.sun.tools.javac.comp.Enter; 53import com.sun.tools.javac.tree.JCTree; 54import com.sun.tools.javac.tree.JCTree.JCClassDecl; 55import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 56import com.sun.tools.javac.util.Abort; 57import com.sun.tools.javac.util.Context; 58import com.sun.tools.javac.util.ListBuffer; 59import com.sun.tools.javac.util.Position; 60import jdk.javadoc.doclet.DocletEnvironment; 61 62 63/** 64 * This class could be the main entry point for Javadoc when Javadoc is used as a 65 * component in a larger software system. It provides operations to 66 * construct a new javadoc processor, and to run it on a set of source 67 * files. 68 * 69 * <p><b>This is NOT part of any supported API. 70 * If you write code that depends on this, you do so at your own risk. 71 * This code and its internal interfaces are subject to change or 72 * deletion without notice.</b> 73 * 74 * @author Neal Gafter 75 */ 76public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler { 77 DocEnv docenv; 78 79 final Messager messager; 80 final ClassFinder javadocFinder; 81 final Enter javadocEnter; 82 final Set<JavaFileObject> uniquefiles; 83 84 /** 85 * Construct a new JavaCompiler processor, using appropriately 86 * extended phases of the underlying compiler. 87 */ 88 protected JavadocTool(Context context) { 89 super(context); 90 messager = Messager.instance0(context); 91 javadocFinder = JavadocClassFinder.instance(context); 92 javadocEnter = JavadocEnter.instance(context); 93 uniquefiles = new HashSet<>(); 94 } 95 96 /** 97 * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler. 98 */ 99 @Override 100 protected boolean keepComments() { 101 return true; 102 } 103 104 /** 105 * Construct a new javadoc tool. 106 */ 107 public static JavadocTool make0(Context context) { 108 Messager messager = null; 109 try { 110 // force the use of Javadoc's class finder 111 JavadocClassFinder.preRegister(context); 112 113 // force the use of Javadoc's own enter phase 114 JavadocEnter.preRegister(context); 115 116 // force the use of Javadoc's own member enter phase 117 JavadocMemberEnter.preRegister(context); 118 119 // force the use of Javadoc's own todo phase 120 JavadocTodo.preRegister(context); 121 122 // force the use of Messager as a Log 123 messager = Messager.instance0(context); 124 125 return new JavadocTool(context); 126 } catch (CompletionFailure ex) { 127 messager.error(Position.NOPOS, ex.getMessage()); 128 return null; 129 } 130 } 131 132 public DocletEnvironment getEnvironment(String encoding, 133 String showAccess, 134 String overviewpath, 135 List<String> args, 136 Iterable<? extends JavaFileObject> fileObjects, 137 List<String> subPackages, 138 List<String> excludedPackages, 139 boolean docClasses, 140 boolean quiet) throws IOException { 141 docenv = DocEnv.instance(context); 142 docenv.intialize(encoding, showAccess, overviewpath, args, fileObjects, 143 subPackages, excludedPackages, docClasses, quiet); 144 145 javadocFinder.sourceCompleter = docClasses ? Completer.NULL_COMPLETER : sourceCompleter; 146 147 if (docClasses) { 148 // If -Xclasses is set, the args should be a series of class names 149 for (String arg: args) { 150 if (!isValidPackageName(arg)) // checks 151 docenv.error(null, "main.illegal_class_name", arg); 152 } 153 if (messager.nerrors() != 0) { 154 return null; 155 } 156 return new RootDocImpl(docenv, args); 157 } 158 159 ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<>(); 160 Set<String> includedPackages = new LinkedHashSet<>(); 161 162 try { 163 164 StandardJavaFileManager fm = docenv.fileManager instanceof StandardJavaFileManager 165 ? (StandardJavaFileManager) docenv.fileManager : null; 166 Set<String> packageNames = new LinkedHashSet<>(); 167 // Normally, the args should be a series of package names or file names. 168 // Parse the files and collect the package names. 169 for (String arg: args) { 170 if (fm != null && arg.endsWith(".java") && new File(arg).exists()) { 171 if (new File(arg).getName().equals("module-info.java")) { 172 docenv.warning("main.file_ignored", arg); 173 } else { 174 parse(fm.getJavaFileObjects(arg), classTrees, true); 175 } 176 } else if (isValidPackageName(arg)) { 177 packageNames.add(arg); 178 } else if (arg.endsWith(".java")) { 179 if (fm == null) 180 throw new IllegalArgumentException(); 181 else 182 docenv.error(null, "main.file_not_found", arg); 183 } else { 184 docenv.error(null, "main.illegal_package_name", arg); 185 } 186 } 187 188 // Parse file objects provide via the DocumentationTool API 189 parse(fileObjects, classTrees, true); 190 modules.enter(classTrees.toList(), null); 191 192 syms.unnamedModule.complete(); // TEMP to force reading all named modules 193 194 // Build up the complete list of any packages to be documented 195 Location location = modules.multiModuleMode ? StandardLocation.MODULE_SOURCE_PATH 196 : docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) ? StandardLocation.SOURCE_PATH 197 : StandardLocation.CLASS_PATH; 198 199 PackageTable t = new PackageTable(docenv.fileManager, location) 200 .packages(packageNames) 201 .subpackages(subPackages, excludedPackages); 202 203 includedPackages = t.getIncludedPackages(); 204 205 // Parse the files in the packages to be documented 206 ListBuffer<JCCompilationUnit> packageTrees = new ListBuffer<>(); 207 for (String packageName: includedPackages) { 208 List<JavaFileObject> files = t.getFiles(packageName); 209 docenv.notice("main.Loading_source_files_for_package", packageName); 210 if (files.isEmpty()) 211 docenv.warning("main.no_source_files_for_package", packageName); 212 parse(files, packageTrees, false); 213 } 214 modules.enter(packageTrees.toList(), null); 215 216 if (messager.nerrors() != 0) { 217 return null; 218 } 219 220 // Enter symbols for all files 221 docenv.notice("main.Building_tree"); 222 javadocEnter.main(classTrees.toList().appendList(packageTrees.toList())); 223 224 enterDone = true; 225 } catch (Abort ex) {} 226 227 if (messager.nerrors() != 0) 228 return null; 229 docenv.root = new RootDocImpl(docenv, listClasses(classTrees.toList()), 230 new ArrayList<>(includedPackages)); 231 return docenv.root; 232 } 233 234 /** Is the given string a valid package name? */ 235 boolean isValidPackageName(String s) { 236 int index; 237 while ((index = s.indexOf('.')) != -1) { 238 if (!isValidClassName(s.substring(0, index))) return false; 239 s = s.substring(index+1); 240 } 241 return isValidClassName(s); 242 } 243 244 private void parse(Iterable<? extends JavaFileObject> files, ListBuffer<JCCompilationUnit> trees, 245 boolean trace) { 246 for (JavaFileObject fo: files) { 247 if (uniquefiles.add(fo)) { // ignore duplicates 248 if (trace) 249 docenv.notice("main.Loading_source_file", fo.getName()); 250 trees.append(parse(fo)); 251 } 252 } 253 } 254 255 /** Are surrogates supported? 256 */ 257 final static boolean surrogatesSupported = surrogatesSupported(); 258 private static boolean surrogatesSupported() { 259 try { 260 boolean b = Character.isHighSurrogate('a'); 261 return true; 262 } catch (NoSuchMethodError ex) { 263 return false; 264 } 265 } 266 267 /** 268 * Return true if given file name is a valid class name 269 * (including "package-info"). 270 * @param s the name of the class to check. 271 * @return true if given class name is a valid class name 272 * and false otherwise. 273 */ 274 public static boolean isValidClassName(String s) { 275 if (s.length() < 1) return false; 276 if (s.equals("package-info")) return true; 277 if (surrogatesSupported) { 278 int cp = s.codePointAt(0); 279 if (!Character.isJavaIdentifierStart(cp)) 280 return false; 281 for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) { 282 cp = s.codePointAt(j); 283 if (!Character.isJavaIdentifierPart(cp)) 284 return false; 285 } 286 } else { 287 if (!Character.isJavaIdentifierStart(s.charAt(0))) 288 return false; 289 for (int j=1; j<s.length(); j++) 290 if (!Character.isJavaIdentifierPart(s.charAt(j))) 291 return false; 292 } 293 return true; 294 } 295 296 /** 297 * From a list of top level trees, return the list of contained class definitions 298 */ 299 List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) { 300 List<JCClassDecl> result = new ArrayList<>(); 301 for (JCCompilationUnit t : trees) { 302 for (JCTree def : t.defs) { 303 if (def.hasTag(JCTree.Tag.CLASSDEF)) 304 result.add((JCClassDecl)def); 305 } 306 } 307 return result; 308 } 309 310 /** 311 * A table to manage included and excluded packages. 312 */ 313 class PackageTable { 314 private final Map<String, Entry> entries = new LinkedHashMap<>(); 315 private final Set<String> includedPackages = new LinkedHashSet<>(); 316 private final JavaFileManager fm; 317 private final Location location; 318 private final Set<JavaFileObject.Kind> sourceKinds = EnumSet.of(JavaFileObject.Kind.SOURCE); 319 320 /** 321 * Creates a table to manage included and excluded packages. 322 * @param fm The file manager used to locate source files 323 * @param locn the location used to locate source files 324 */ 325 PackageTable(JavaFileManager fm, Location locn) { 326 this.fm = fm; 327 this.location = locn; 328 getEntry("").excluded = false; 329 } 330 331 PackageTable packages(Collection<String> packageNames) { 332 includedPackages.addAll(packageNames); 333 return this; 334 } 335 336 PackageTable subpackages(Collection<String> packageNames, Collection<String> excludePackageNames) 337 throws IOException { 338 for (String p: excludePackageNames) { 339 getEntry(p).excluded = true; 340 } 341 342 for (String packageName: packageNames) { 343 for (JavaFileObject fo: fm.list(location, packageName, sourceKinds, true)) { 344 String binaryName = fm.inferBinaryName(location, fo); 345 String pn = getPackageName(binaryName); 346 String simpleName = getSimpleName(binaryName); 347 Entry e = getEntry(pn); 348 if (!e.isExcluded() && isValidClassName(simpleName)) { 349 includedPackages.add(pn); 350 e.files = (e.files == null 351 ? com.sun.tools.javac.util.List.of(fo) 352 : e.files.prepend(fo)); 353 } 354 } 355 } 356 return this; 357 } 358 359 /** 360 * Returns the aggregate set of included packages. 361 * @return the aggregate set of included packages 362 */ 363 Set<String> getIncludedPackages() { 364 return includedPackages; 365 } 366 367 /** 368 * Returns the set of source files for a package. 369 * @param packageName the specified package 370 * @return the set of file objects for the specified package 371 * @throws IOException if an error occurs while accessing the files 372 */ 373 List<JavaFileObject> getFiles(String packageName) throws IOException { 374 Entry e = getEntry(packageName); 375 // The files may have been found as a side effect of searching for subpackages 376 if (e.files != null) 377 return e.files; 378 379 ListBuffer<JavaFileObject> lb = new ListBuffer<>(); 380 Location packageLocn = getLocation(packageName); 381 if (packageLocn == null) 382 return Collections.emptyList(); 383 for (JavaFileObject fo: fm.list(packageLocn, packageName, sourceKinds, false)) { 384 String binaryName = fm.inferBinaryName(packageLocn, fo); 385 String simpleName = getSimpleName(binaryName); 386 if (isValidClassName(simpleName)) { 387 lb.append(fo); 388 } 389 } 390 391 return lb.toList(); 392 } 393 394 private Location getLocation(String packageName) throws IOException { 395 if (location == StandardLocation.MODULE_SOURCE_PATH) { 396 // TODO: handle invalid results better. 397 ModuleSymbol msym = syms.inferModule(names.fromString(packageName)); 398 if (msym == null) { 399 return null; 400 } 401 return fm.getModuleLocation(location, msym.name.toString()); 402 } else { 403 return location; 404 } 405 } 406 407 private Entry getEntry(String name) { 408 Entry e = entries.get(name); 409 if (e == null) 410 entries.put(name, e = new Entry(name)); 411 return e; 412 } 413 414 private String getPackageName(String name) { 415 int lastDot = name.lastIndexOf("."); 416 return (lastDot == -1 ? "" : name.substring(0, lastDot)); 417 } 418 419 private String getSimpleName(String name) { 420 int lastDot = name.lastIndexOf("."); 421 return (lastDot == -1 ? name : name.substring(lastDot + 1)); 422 } 423 424 class Entry { 425 final String name; 426 Boolean excluded; 427 com.sun.tools.javac.util.List<JavaFileObject> files; 428 429 Entry(String name) { 430 this.name = name; 431 } 432 433 boolean isExcluded() { 434 if (excluded == null) 435 excluded = getEntry(getPackageName(name)).isExcluded(); 436 return excluded; 437 } 438 } 439 } 440 441} 442