JavadocTool.java revision 3552:467ad69d5948
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.code.Symbol.PackageSymbol; 53import com.sun.tools.javac.comp.Enter; 54import com.sun.tools.javac.tree.JCTree; 55import com.sun.tools.javac.tree.JCTree.JCClassDecl; 56import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 57import com.sun.tools.javac.util.Abort; 58import com.sun.tools.javac.util.Context; 59import com.sun.tools.javac.util.ListBuffer; 60import com.sun.tools.javac.util.Name; 61import com.sun.tools.javac.util.Position; 62import jdk.javadoc.doclet.DocletEnvironment; 63 64 65/** 66 * This class could be the main entry point for Javadoc when Javadoc is used as a 67 * component in a larger software system. It provides operations to 68 * construct a new javadoc processor, and to run it on a set of source 69 * files. 70 * 71 * <p><b>This is NOT part of any supported API. 72 * If you write code that depends on this, you do so at your own risk. 73 * This code and its internal interfaces are subject to change or 74 * deletion without notice.</b> 75 * 76 * @author Neal Gafter 77 */ 78public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler { 79 ToolEnvironment toolEnv; 80 81 final Messager messager; 82 final ClassFinder javadocFinder; 83 final Enter javadocEnter; 84 final Set<JavaFileObject> uniquefiles; 85 86 /** 87 * Construct a new JavaCompiler processor, using appropriately 88 * extended phases of the underlying compiler. 89 */ 90 protected JavadocTool(Context context) { 91 super(context); 92 messager = Messager.instance0(context); 93 javadocFinder = JavadocClassFinder.instance(context); 94 javadocEnter = JavadocEnter.instance(context); 95 uniquefiles = new HashSet<>(); 96 } 97 98 /** 99 * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler. 100 */ 101 @Override 102 protected boolean keepComments() { 103 return true; 104 } 105 106 /** 107 * Construct a new javadoc tool. 108 */ 109 public static JavadocTool make0(Context context) { 110 Messager messager = null; 111 try { 112 // force the use of Javadoc's class finder 113 JavadocClassFinder.preRegister(context); 114 115 // force the use of Javadoc's own enter phase 116 JavadocEnter.preRegister(context); 117 118 // force the use of Javadoc's own member enter phase 119 JavadocMemberEnter.preRegister(context); 120 121 // force the use of Javadoc's own todo phase 122 JavadocTodo.preRegister(context); 123 124 // force the use of Messager as a Log 125 messager = Messager.instance0(context); 126 127 return new JavadocTool(context); 128 } catch (CompletionFailure ex) { 129 messager.error(Position.NOPOS, ex.getMessage()); 130 return null; 131 } 132 } 133 134 public DocletEnvironment getEnvironment(String encoding, 135 String showAccess, 136 String overviewpath, 137 List<String> args, 138 Iterable<? extends JavaFileObject> fileObjects, 139 List<String> subPackages, 140 List<String> excludedPackages, 141 boolean docClasses, 142 boolean quiet) throws IOException { 143 toolEnv = ToolEnvironment.instance(context); 144 toolEnv.intialize(encoding, showAccess, overviewpath, args, fileObjects, 145 subPackages, excludedPackages, docClasses, quiet); 146 147 javadocFinder.sourceCompleter = docClasses ? Completer.NULL_COMPLETER : sourceCompleter; 148 149 if (docClasses) { 150 // If -Xclasses is set, the args should be a series of class names 151 for (String arg: args) { 152 if (!isValidPackageName(arg)) // checks 153 toolEnv.error(null, "main.illegal_class_name", arg); 154 } 155 if (messager.nerrors() != 0) { 156 return null; 157 } 158 return new DocEnvImpl(toolEnv, args); 159 } 160 161 ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<>(); 162 Set<String> includedPackages = new LinkedHashSet<>(); 163 164 try { 165 166 StandardJavaFileManager fm = toolEnv.fileManager instanceof StandardJavaFileManager 167 ? (StandardJavaFileManager) toolEnv.fileManager : null; 168 Set<String> packageNames = new LinkedHashSet<>(); 169 // Normally, the args should be a series of package names or file names. 170 // Parse the files and collect the package names. 171 for (String arg: args) { 172 if (fm != null && arg.endsWith(".java") && new File(arg).exists()) { 173 if (new File(arg).getName().equals("module-info.java")) { 174 toolEnv.warning("main.file_ignored", arg); 175 } else { 176 parse(fm.getJavaFileObjects(arg), classTrees, true); 177 } 178 } else if (isValidPackageName(arg)) { 179 packageNames.add(arg); 180 } else if (arg.endsWith(".java")) { 181 if (fm == null) 182 throw new IllegalArgumentException(); 183 else 184 toolEnv.error(null, "main.file_not_found", arg); 185 } else { 186 toolEnv.error(null, "main.illegal_package_name", arg); 187 } 188 } 189 190 // Parse file objects provide via the DocumentationTool API 191 parse(fileObjects, classTrees, true); 192 193 modules.initModules(classTrees.toList(), Collections.emptySet(), Collections.emptySet()); 194 195 // Build up the complete list of any packages to be documented 196 Location location = modules.multiModuleMode ? StandardLocation.MODULE_SOURCE_PATH 197 : toolEnv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) ? StandardLocation.SOURCE_PATH 198 : StandardLocation.CLASS_PATH; 199 200 PackageTable t = new PackageTable(toolEnv.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 toolEnv.notice("main.Loading_source_files_for_package", packageName); 211 if (files.isEmpty()) 212 toolEnv.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 toolEnv.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 toolEnv.docEnv = new DocEnvImpl(toolEnv, listClasses(classTrees.toList()), 231 new ArrayList<>(includedPackages)); 232 return toolEnv.docEnv; 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 toolEnv.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 Name pack = names.fromString(packageName); 399 400 for (ModuleSymbol msym : modules.allModules()) { 401 PackageSymbol p = syms.getPackage(msym, pack); 402 if (p != null && !p.members().isEmpty()) { 403 return fm.getModuleLocation(location, msym.name.toString()); 404 } 405 } 406 407 return null; 408 } else { 409 return location; 410 } 411 } 412 413 private Entry getEntry(String name) { 414 Entry e = entries.get(name); 415 if (e == null) 416 entries.put(name, e = new Entry(name)); 417 return e; 418 } 419 420 private String getPackageName(String name) { 421 int lastDot = name.lastIndexOf("."); 422 return (lastDot == -1 ? "" : name.substring(0, lastDot)); 423 } 424 425 private String getSimpleName(String name) { 426 int lastDot = name.lastIndexOf("."); 427 return (lastDot == -1 ? name : name.substring(lastDot + 1)); 428 } 429 430 class Entry { 431 final String name; 432 Boolean excluded; 433 com.sun.tools.javac.util.List<JavaFileObject> files; 434 435 Entry(String name) { 436 this.name = name; 437 } 438 439 boolean isExcluded() { 440 if (excluded == null) 441 excluded = getEntry(getPackageName(name)).isExcluded(); 442 return excluded; 443 } 444 } 445 } 446 447} 448