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 }