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