1 /* 2 * Copyright (c) 2001, 2018, 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 26 package com.sun.tools.javadoc.main; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.EnumSet; 33 import java.util.HashSet; 34 import java.util.LinkedHashMap; 35 import java.util.LinkedHashSet; 36 import java.util.Map; 37 import java.util.Set; 38 39 import javax.tools.JavaFileManager; 40 import javax.tools.JavaFileManager.Location; 41 import javax.tools.JavaFileObject; 42 import javax.tools.StandardJavaFileManager; 43 import javax.tools.StandardLocation; 44 45 import com.sun.tools.javac.code.ClassFinder; 46 import com.sun.tools.javac.code.Symbol.Completer; 47 import com.sun.tools.javac.code.Symbol.ModuleSymbol; 48 import com.sun.tools.javac.code.Symbol.PackageSymbol; 49 import com.sun.tools.javac.comp.Enter; 50 import com.sun.tools.javac.tree.JCTree; 51 import com.sun.tools.javac.tree.JCTree.JCClassDecl; 52 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 53 import com.sun.tools.javac.util.Abort; 54 import com.sun.tools.javac.util.Context; 55 import com.sun.tools.javac.util.List; 56 import com.sun.tools.javac.util.ListBuffer; 57 import 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(since="9", forRemoval=true) 74 @SuppressWarnings("removal") 75 public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler { 76 DocEnv docenv; 77 78 final Messager messager; 79 final ClassFinder javadocFinder; 80 final Enter javadocEnter; 81 final Set<JavaFileObject> uniquefiles; 82 83 /** 84 * Construct a new JavaCompiler processor, using appropriately 85 * extended phases of the underlying compiler. 86 */ JavadocTool(Context context)87 protected JavadocTool(Context context) { 88 super(context); 89 messager = Messager.instance0(context); 90 javadocFinder = JavadocClassFinder.instance(context); 91 javadocEnter = JavadocEnter.instance(context); 92 uniquefiles = new HashSet<>(); 93 } 94 95 /** 96 * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler. 97 */ 98 @Override keepComments()99 protected boolean keepComments() { 100 return true; 101 } 102 103 /** 104 * Construct a new javadoc tool. 105 */ make0(Context context)106 public static JavadocTool make0(Context context) { 107 // force the use of Javadoc's class finder 108 JavadocClassFinder.preRegister(context); 109 110 // force the use of Javadoc's own enter phase 111 JavadocEnter.preRegister(context); 112 113 // force the use of Javadoc's own member enter phase 114 JavadocMemberEnter.preRegister(context); 115 116 // force the use of Javadoc's own todo phase 117 JavadocTodo.preRegister(context); 118 119 // force the use of Messager as a Log 120 Messager.instance0(context); 121 122 return new JavadocTool(context); 123 } 124 getRootDocImpl(String doclocale, String encoding, ModifierFilter filter, List<String> args, List<String[]> options, Iterable<? extends JavaFileObject> fileObjects, boolean breakiterator, List<String> subPackages, List<String> excludedPackages, boolean docClasses, boolean legacyDoclet, boolean quiet)125 public RootDocImpl getRootDocImpl(String doclocale, 126 String encoding, 127 ModifierFilter filter, 128 List<String> args, 129 List<String[]> options, 130 Iterable<? extends JavaFileObject> fileObjects, 131 boolean breakiterator, 132 List<String> subPackages, 133 List<String> excludedPackages, 134 boolean docClasses, 135 boolean legacyDoclet, 136 boolean quiet) throws IOException { 137 docenv = DocEnv.instance(context); 138 docenv.showAccess = filter; 139 docenv.quiet = quiet; 140 docenv.breakiterator = breakiterator; 141 docenv.setLocale(doclocale); 142 docenv.setEncoding(encoding); 143 docenv.docClasses = docClasses; 144 docenv.legacyDoclet = legacyDoclet; 145 146 javadocFinder.sourceCompleter = docClasses ? Completer.NULL_COMPLETER : sourceCompleter; 147 148 if (docClasses) { 149 // If -Xclasses is set, the args should be a series of class names 150 for (String arg: args) { 151 if (!isValidPackageName(arg)) // checks 152 docenv.error(null, "main.illegal_class_name", arg); 153 } 154 if (messager.nerrors() != 0) { 155 return null; 156 } 157 return new RootDocImpl(docenv, args, options); 158 } 159 160 ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<>(); 161 Set<String> includedPackages = new LinkedHashSet<>(); 162 163 try { 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(null, "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 191 modules.initModules(classTrees.toList()); 192 193 // Build up the complete list of any packages to be documented 194 Location location = modules.multiModuleMode ? StandardLocation.MODULE_SOURCE_PATH 195 : docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) ? StandardLocation.SOURCE_PATH 196 : StandardLocation.CLASS_PATH; 197 198 PackageTable t = new PackageTable(docenv.fileManager, location) 199 .packages(packageNames) 200 .subpackages(subPackages, excludedPackages); 201 202 includedPackages = t.getIncludedPackages(); 203 204 // Parse the files in the packages to be documented 205 ListBuffer<JCCompilationUnit> packageTrees = new ListBuffer<>(); 206 for (String packageName: includedPackages) { 207 List<JavaFileObject> files = t.getFiles(packageName); 208 docenv.notice("main.Loading_source_files_for_package", packageName); 209 210 if (files.isEmpty()) 211 messager.warning(Messager.NOPOS, "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 } 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? */ isValidPackageName(String s)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 parse(Iterable<? extends JavaFileObject> files, ListBuffer<JCCompilationUnit> trees, boolean trace)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(); 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 */ isValidClassName(String s)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 */ listClasses(List<JCCompilationUnit> trees)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 */ PackageTable(JavaFileManager fm, Location locn)322 PackageTable(JavaFileManager fm, Location locn) { 323 this.fm = fm; 324 this.location = locn; 325 getEntry("").excluded = false; 326 } 327 packages(Collection<String> packageNames)328 PackageTable packages(Collection<String> packageNames) { 329 includedPackages.addAll(packageNames); 330 return this; 331 } 332 subpackages(Collection<String> packageNames, Collection<String> excludePackageNames)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 */ getIncludedPackages()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 */ getFiles(String packageName)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 getLocation(String packageName)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.getLocationForModule(location, msym.name.toString()); 397 } 398 } 399 400 return null; 401 } else { 402 return location; 403 } 404 } 405 getEntry(String name)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 getPackageName(String name)413 private String getPackageName(String name) { 414 int lastDot = name.lastIndexOf("."); 415 return (lastDot == -1 ? "" : name.substring(0, lastDot)); 416 } 417 getSimpleName(String name)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 Entry(String name)428 Entry(String name) { 429 this.name = name; 430 } 431 isExcluded()432 boolean isExcluded() { 433 if (excluded == null) 434 excluded = getEntry(getPackageName(name)).isExcluded(); 435 return excluded; 436 } 437 } 438 } 439 440 } 441