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