JavadocTool.java revision 3294:9adfb22ff08f
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 = 196 modules.multiModuleMode && !modules.noModules ? StandardLocation.MODULE_SOURCE_PATH 197 : docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) ? StandardLocation.SOURCE_PATH 198 : StandardLocation.CLASS_PATH; 199 200 PackageTable t = new PackageTable(docenv.fileManager, location) 201 .packages(packageNames) 202 .subpackages(subPackages, excludedPackages); 203 204 includedPackages = t.getIncludedPackages(); 205 206 // Parse the files in the packages to be documented 207 ListBuffer<JCCompilationUnit> packageTrees = new ListBuffer<>(); 208 for (String packageName: includedPackages) { 209 List<JavaFileObject> files = t.getFiles(packageName); 210 docenv.notice("main.Loading_source_files_for_package", packageName); 211 if (files.isEmpty()) 212 docenv.warning("main.no_source_files_for_package", packageName); 213 parse(files, packageTrees, false); 214 } 215 modules.enter(packageTrees.toList(), null); 216 217 if (messager.nerrors() != 0) { 218 return null; 219 } 220 221 // Enter symbols for all files 222 docenv.notice("main.Building_tree"); 223 javadocEnter.main(classTrees.toList().appendList(packageTrees.toList())); 224 225 enterDone = true; 226 } catch (Abort ex) {} 227 228 if (messager.nerrors() != 0) 229 return null; 230 docenv.root = new RootDocImpl(docenv, listClasses(classTrees.toList()), 231 new ArrayList<>(includedPackages)); 232 return docenv.root; 233 } 234 235 /** Is the given string a valid package name? */ 236 boolean isValidPackageName(String s) { 237 int index; 238 while ((index = s.indexOf('.')) != -1) { 239 if (!isValidClassName(s.substring(0, index))) return false; 240 s = s.substring(index+1); 241 } 242 return isValidClassName(s); 243 } 244 245 private void parse(Iterable<? extends JavaFileObject> files, ListBuffer<JCCompilationUnit> trees, 246 boolean trace) { 247 for (JavaFileObject fo: files) { 248 if (uniquefiles.add(fo)) { // ignore duplicates 249 if (trace) 250 docenv.notice("main.Loading_source_file", fo.getName()); 251 trees.append(parse(fo)); 252 } 253 } 254 } 255 256 /** Are surrogates supported? 257 */ 258 final static boolean surrogatesSupported = surrogatesSupported(); 259 private static boolean surrogatesSupported() { 260 try { 261 boolean b = Character.isHighSurrogate('a'); 262 return true; 263 } catch (NoSuchMethodError ex) { 264 return false; 265 } 266 } 267 268 /** 269 * Return true if given file name is a valid class name 270 * (including "package-info"). 271 * @param s the name of the class to check. 272 * @return true if given class name is a valid class name 273 * and false otherwise. 274 */ 275 public static boolean isValidClassName(String s) { 276 if (s.length() < 1) return false; 277 if (s.equals("package-info")) return true; 278 if (surrogatesSupported) { 279 int cp = s.codePointAt(0); 280 if (!Character.isJavaIdentifierStart(cp)) 281 return false; 282 for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) { 283 cp = s.codePointAt(j); 284 if (!Character.isJavaIdentifierPart(cp)) 285 return false; 286 } 287 } else { 288 if (!Character.isJavaIdentifierStart(s.charAt(0))) 289 return false; 290 for (int j=1; j<s.length(); j++) 291 if (!Character.isJavaIdentifierPart(s.charAt(j))) 292 return false; 293 } 294 return true; 295 } 296 297 /** 298 * From a list of top level trees, return the list of contained class definitions 299 */ 300 List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) { 301 List<JCClassDecl> result = new ArrayList<>(); 302 for (JCCompilationUnit t : trees) { 303 for (JCTree def : t.defs) { 304 if (def.hasTag(JCTree.Tag.CLASSDEF)) 305 result.add((JCClassDecl)def); 306 } 307 } 308 return result; 309 } 310 311 /** 312 * A table to manage included and excluded packages. 313 */ 314 class PackageTable { 315 private final Map<String, Entry> entries = new LinkedHashMap<>(); 316 private final Set<String> includedPackages = new LinkedHashSet<>(); 317 private final JavaFileManager fm; 318 private final Location location; 319 private final Set<JavaFileObject.Kind> sourceKinds = EnumSet.of(JavaFileObject.Kind.SOURCE); 320 321 /** 322 * Creates a table to manage included and excluded packages. 323 * @param fm The file manager used to locate source files 324 * @param locn the location used to locate source files 325 */ 326 PackageTable(JavaFileManager fm, Location locn) { 327 this.fm = fm; 328 this.location = locn; 329 getEntry("").excluded = false; 330 } 331 332 PackageTable packages(Collection<String> packageNames) { 333 includedPackages.addAll(packageNames); 334 return this; 335 } 336 337 PackageTable subpackages(Collection<String> packageNames, Collection<String> excludePackageNames) 338 throws IOException { 339 for (String p: excludePackageNames) { 340 getEntry(p).excluded = true; 341 } 342 343 for (String packageName: packageNames) { 344 for (JavaFileObject fo: fm.list(location, packageName, sourceKinds, true)) { 345 String binaryName = fm.inferBinaryName(location, fo); 346 String pn = getPackageName(binaryName); 347 String simpleName = getSimpleName(binaryName); 348 Entry e = getEntry(pn); 349 if (!e.isExcluded() && isValidClassName(simpleName)) { 350 includedPackages.add(pn); 351 e.files = (e.files == null 352 ? com.sun.tools.javac.util.List.of(fo) 353 : e.files.prepend(fo)); 354 } 355 } 356 } 357 return this; 358 } 359 360 /** 361 * Returns the aggregate set of included packages. 362 * @return the aggregate set of included packages 363 */ 364 Set<String> getIncludedPackages() { 365 return includedPackages; 366 } 367 368 /** 369 * Returns the set of source files for a package. 370 * @param packageName the specified package 371 * @return the set of file objects for the specified package 372 * @throws IOException if an error occurs while accessing the files 373 */ 374 List<JavaFileObject> getFiles(String packageName) throws IOException { 375 Entry e = getEntry(packageName); 376 // The files may have been found as a side effect of searching for subpackages 377 if (e.files != null) 378 return e.files; 379 380 ListBuffer<JavaFileObject> lb = new ListBuffer<>(); 381 Location packageLocn = getLocation(packageName); 382 if (packageLocn == null) 383 return Collections.emptyList(); 384 for (JavaFileObject fo: fm.list(packageLocn, packageName, sourceKinds, false)) { 385 String binaryName = fm.inferBinaryName(packageLocn, fo); 386 String simpleName = getSimpleName(binaryName); 387 if (isValidClassName(simpleName)) { 388 lb.append(fo); 389 } 390 } 391 392 return lb.toList(); 393 } 394 395 private Location getLocation(String packageName) throws IOException { 396 if (location == StandardLocation.MODULE_SOURCE_PATH) { 397 // TODO: handle invalid results better. 398 ModuleSymbol msym = syms.inferModule(names.fromString(packageName)); 399 if (msym == null) { 400 return null; 401 } 402 return fm.getModuleLocation(location, msym.name.toString()); 403 } else { 404 return location; 405 } 406 } 407 408 private Entry getEntry(String name) { 409 Entry e = entries.get(name); 410 if (e == null) 411 entries.put(name, e = new Entry(name)); 412 return e; 413 } 414 415 private String getPackageName(String name) { 416 int lastDot = name.lastIndexOf("."); 417 return (lastDot == -1 ? "" : name.substring(0, lastDot)); 418 } 419 420 private String getSimpleName(String name) { 421 int lastDot = name.lastIndexOf("."); 422 return (lastDot == -1 ? name : name.substring(lastDot + 1)); 423 } 424 425 class Entry { 426 final String name; 427 Boolean excluded; 428 com.sun.tools.javac.util.List<JavaFileObject> files; 429 430 Entry(String name) { 431 this.name = name; 432 } 433 434 boolean isExcluded() { 435 if (excluded == null) 436 excluded = getEntry(getPackageName(name)).isExcluded(); 437 return excluded; 438 } 439 } 440 } 441 442} 443