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