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