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 }