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