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