1 /*
   2  * Copyright (c) 2001, 2019, 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 import java.nio.file.Files;
  29 import java.nio.file.InvalidPathException;
  30 import java.nio.file.Paths;
  31 import java.util.ArrayList;
  32 import java.util.HashSet;
  33 import java.util.LinkedHashSet;
  34 import java.util.List;
  35 import java.util.Set;
  36 
  37 import javax.lang.model.element.Element;
  38 import javax.lang.model.element.ElementKind;
  39 import javax.tools.JavaFileObject;
  40 import javax.tools.StandardJavaFileManager;
  41 
  42 import com.sun.tools.javac.code.ClassFinder;
  43 import com.sun.tools.javac.code.DeferredCompletionFailureHandler;
  44 import com.sun.tools.javac.code.Symbol.Completer;
  45 import com.sun.tools.javac.code.Symbol.CompletionFailure;
  46 import com.sun.tools.javac.code.Symbol.PackageSymbol;
  47 import com.sun.tools.javac.comp.Enter;
  48 import com.sun.tools.javac.tree.JCTree;
  49 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
  50 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
  51 import com.sun.tools.javac.util.Abort;
  52 import com.sun.tools.javac.util.Context;
  53 import com.sun.tools.javac.util.ListBuffer;
  54 import com.sun.tools.javac.util.Position;
  55 import jdk.javadoc.doclet.DocletEnvironment;
  56 
  57 import static jdk.javadoc.internal.tool.Main.Result.*;
  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 public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler {
  71     ToolEnvironment toolEnv;
  72 
  73     final Messager messager;
  74     final ClassFinder javadocFinder;
  75     final DeferredCompletionFailureHandler dcfh;
  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         dcfh = DeferredCompletionFailureHandler.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     @Override
  96     protected boolean keepComments() {
  97         return true;
  98     }
  99 
 100     /**
 101      *  Construct a new javadoc tool.
 102      */
 103     public static JavadocTool make0(Context context) {
 104         Messager messager = null;
 105         try {
 106             // force the use of Javadoc's class finder
 107             JavadocClassFinder.preRegister(context);
 108 
 109             // force the use of Javadoc's own enter phase
 110             JavadocEnter.preRegister(context);
 111 
 112             // force the use of Javadoc's own member enter phase
 113             JavadocMemberEnter.preRegister(context);
 114 
 115             // force the use of Javadoc's own todo phase
 116             JavadocTodo.preRegister(context);
 117 
 118             // force the use of Messager as a Log
 119             messager = Messager.instance0(context);
 120 
 121             return new JavadocTool(context);
 122         } catch (CompletionFailure ex) {
 123             assert messager != null;
 124             messager.error(Position.NOPOS, ex.getMessage());
 125             return null;
 126         }
 127     }
 128 
 129     public DocletEnvironment getEnvironment(ToolOptions toolOptions,
 130             List<String> javaNames,
 131             Iterable<? extends JavaFileObject> fileObjects) throws ToolException {
 132         toolEnv = ToolEnvironment.instance(context);
 133         toolEnv.initialize(toolOptions);
 134         ElementsTable etable = new ElementsTable(context, toolOptions);
 135         javadocFinder.sourceCompleter = etable.xclasses
 136                 ? Completer.NULL_COMPLETER
 137                 : sourceCompleter;
 138 
 139         if (etable.xclasses) {
 140             // If -Xclasses is set, the args should be a list of class names
 141             for (String arg: javaNames) {
 142                 if (!isValidPackageName(arg)) { // checks
 143                     String text = messager.getText("main.illegal_class_name", arg);
 144                     throw new ToolException(CMDERR, text);
 145                 }
 146             }
 147             if (messager.hasErrors()) {
 148                 return null;
 149             }
 150             etable.setClassArgList(javaNames);
 151             // prepare, force the data structures to be analyzed
 152             etable.analyze();
 153             return new DocEnvImpl(toolEnv, etable);
 154         }
 155 
 156         ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<>();
 157 
 158         try {
 159             StandardJavaFileManager fm = toolEnv.fileManager instanceof StandardJavaFileManager
 160                     ? (StandardJavaFileManager) toolEnv.fileManager
 161                     : 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: javaNames) {
 166                 if (fm != null && arg.endsWith(".java") && isRegularFile(arg)) {
 167                     parse(fm.getJavaFileObjects(arg), classTrees, true);
 168                 } else if (isValidPackageName(arg)) {
 169                     packageNames.add(arg);
 170                 } else if (arg.endsWith(".java")) {
 171                     if (fm == null) {
 172                         String text = messager.getText("main.assertion.error", "fm == null");
 173                         throw new ToolException(ABNORMAL, text);
 174                     } else {
 175                         String text = messager.getText("main.file_not_found", arg);
 176                         throw new ToolException(ERROR, text);
 177                     }
 178                 } else {
 179                     String text = messager.getText("main.illegal_package_name", arg);
 180                     throw new ToolException(CMDERR, text);
 181                 }
 182             }
 183 
 184             // Parse file objects provide via the DocumentationTool API
 185             parse(fileObjects, classTrees, true);
 186 
 187             etable.packages(packageNames)
 188                     .classTrees(classTrees.toList())
 189                     .scanSpecifiedItems();
 190 
 191             // abort, if errors were encountered during modules initialization
 192             if (messager.hasErrors()) {
 193                 return null;
 194             }
 195 
 196             // Parse the files in the packages and subpackages to be documented
 197             ListBuffer<JCCompilationUnit> allTrees = new ListBuffer<>();
 198             allTrees.addAll(classTrees);
 199             parse(etable.getFilesToParse(), allTrees, false);
 200             modules.newRound();
 201             modules.initModules(allTrees.toList());
 202 
 203             if (messager.hasErrors()) {
 204                 return null;
 205             }
 206 
 207             // Enter symbols for all files
 208             toolEnv.notice("main.Building_tree");
 209             javadocEnter.main(allTrees.toList());
 210 
 211             if (messager.hasErrors()) {
 212                 return null;
 213             }
 214 
 215             etable.setClassDeclList(listClasses(classTrees.toList()));
 216 
 217             dcfh.setHandler(dcfh.userCodeHandler);
 218             etable.analyze();
 219 
 220             // Ensure that package-info is read for all included packages
 221             for (Element e : etable.getIncludedElements()) {
 222                 if (e.getKind() == ElementKind.PACKAGE) {
 223                     PackageSymbol p = (PackageSymbol) e;
 224                     if (p.package_info != null) {
 225                         p.package_info.complete();
 226                     }
 227                 }
 228             }
 229 
 230         } catch (CompletionFailure cf) {
 231             throw new ToolException(ABNORMAL, cf.getMessage(), cf);
 232         } catch (Abort abort) {
 233             if (messager.hasErrors()) {
 234                 // presumably a message has been emitted, keep silent
 235                 throw new ToolException(ABNORMAL, "", abort);
 236             } else {
 237                 String text = messager.getText("main.internal.error");
 238                 Throwable t = abort.getCause() == null ? abort : abort.getCause();
 239                 throw new ToolException(ABNORMAL, text, t);
 240             }
 241         }
 242 
 243         if (messager.hasErrors())
 244             return null;
 245 
 246         toolEnv.docEnv = new DocEnvImpl(toolEnv, etable);
 247         return toolEnv.docEnv;
 248     }
 249 
 250     private boolean isRegularFile(String s) {
 251         try {
 252             return Files.isRegularFile(Paths.get(s));
 253         } catch (InvalidPathException e) {
 254             return false;
 255         }
 256     }
 257 
 258     /** Is the given string a valid package name? */
 259     boolean isValidPackageName(String s) {
 260         if (s.contains("/")) {
 261             String[] a = s.split("/");
 262             if (a.length == 2) {
 263                  return isValidPackageName0(a[0]) && isValidPackageName0(a[1]);
 264             }
 265             return false;
 266         }
 267         return isValidPackageName0(s);
 268     }
 269 
 270     private boolean isValidPackageName0(String s) {
 271         for (int index = s.indexOf('.') ; index != -1; index = s.indexOf('.')) {
 272             if (!isValidClassName(s.substring(0, index))) {
 273                 return false;
 274             }
 275             s = s.substring(index + 1);
 276         }
 277         return isValidClassName(s);
 278     }
 279 
 280     private void parse(Iterable<? extends JavaFileObject> files, ListBuffer<JCCompilationUnit> trees,
 281                        boolean trace) {
 282         for (JavaFileObject fo: files) {
 283             if (uniquefiles.add(fo)) { // ignore duplicates
 284                 if (trace)
 285                     toolEnv.notice("main.Loading_source_file", fo.getName());
 286                 trees.append(parse(fo));
 287             }
 288         }
 289     }
 290 
 291     /** Are surrogates supported? */
 292     static final boolean surrogatesSupported = surrogatesSupported();
 293     private static boolean surrogatesSupported() {
 294         try {
 295             boolean b = Character.isHighSurrogate('a');
 296             return true;
 297         } catch (NoSuchMethodError ex) {
 298             return false;
 299         }
 300     }
 301 
 302     /**
 303      * Return true if given file name is a valid class name
 304      * (including "package-info").
 305      * @param s the name of the class to check.
 306      * @return true if given class name is a valid class name
 307      * and false otherwise.
 308      */
 309     public static boolean isValidClassName(String s) {
 310         if (s.length() < 1) return false;
 311         if (s.equals("package-info")) return true;
 312         if (surrogatesSupported) {
 313             int cp = s.codePointAt(0);
 314             if (!Character.isJavaIdentifierStart(cp))
 315                 return false;
 316             for (int j = Character.charCount(cp); j < s.length(); j += Character.charCount(cp)) {
 317                 cp = s.codePointAt(j);
 318                 if (!Character.isJavaIdentifierPart(cp))
 319                     return false;
 320             }
 321         } else {
 322             if (!Character.isJavaIdentifierStart(s.charAt(0)))
 323                 return false;
 324             for (int j = 1; j < s.length(); j++)
 325                 if (!Character.isJavaIdentifierPart(s.charAt(j)))
 326                     return false;
 327         }
 328         return true;
 329     }
 330 
 331     /**
 332      * From a list of top level trees, return the list of contained class definitions
 333      */
 334     List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) {
 335         List<JCClassDecl> result = new ArrayList<>();
 336         for (JCCompilationUnit t : trees) {
 337             for (JCTree def : t.defs) {
 338                 if (def.hasTag(JCTree.Tag.CLASSDEF))
 339                     result.add((JCClassDecl)def);
 340             }
 341         }
 342         return result;
 343     }
 344 }