1 /* 2 * Copyright (c) 1998, 2018, 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.main; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.lang.reflect.InvocationTargetException; 31 import java.lang.reflect.Method; 32 import java.lang.reflect.Modifier; 33 import java.net.MalformedURLException; 34 import java.net.URL; 35 import java.net.URLClassLoader; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.util.ArrayList; 39 import java.util.regex.Pattern; 40 41 import javax.tools.DocumentationTool; 42 import javax.tools.JavaFileManager; 43 44 import com.sun.javadoc.*; 45 import com.sun.tools.javac.util.ClientCodeException; 46 import com.sun.tools.javac.util.List; 47 48 /** 49 * Class creates, controls and invokes doclets. 50 * 51 * <p><b>This is NOT part of any supported API. 52 * If you write code that depends on this, you do so at your own risk. 53 * This code and its internal interfaces are subject to change or 54 * deletion without notice.</b> 55 * 56 * @author Neal Gafter (rewrite) 57 */ 58 @Deprecated(since="9", forRemoval=true) 59 @SuppressWarnings("removal") 60 public class DocletInvoker { 61 62 private final Class<?> docletClass; 63 64 private final String docletClassName; 65 66 private final ClassLoader appClassLoader; 67 68 private final Messager messager; 69 70 /** 71 * In API mode, exceptions thrown while calling the doclet are 72 * propagated using ClientCodeException. 73 */ 74 private final boolean apiMode; 75 76 /** 77 * Whether javadoc internal API should be exported to doclets 78 * and (indirectly) to taglets 79 */ 80 private final boolean exportInternalAPI; 81 82 private static class DocletInvokeException extends Exception { 83 private static final long serialVersionUID = 0; 84 } 85 appendPath(String path1, String path2)86 private String appendPath(String path1, String path2) { 87 if (path1 == null || path1.length() == 0) { 88 return path2 == null ? "." : path2; 89 } else if (path2 == null || path2.length() == 0) { 90 return path1; 91 } else { 92 return path1 + File.pathSeparator + path2; 93 } 94 } 95 DocletInvoker(Messager messager, Class<?> docletClass, boolean apiMode, boolean exportInternalAPI)96 public DocletInvoker(Messager messager, Class<?> docletClass, boolean apiMode, boolean exportInternalAPI) { 97 this.messager = messager; 98 this.docletClass = docletClass; 99 docletClassName = docletClass.getName(); 100 appClassLoader = null; 101 this.apiMode = apiMode; 102 this.exportInternalAPI = exportInternalAPI; // for backdoor use by standard doclet for taglets 103 104 // this may not be soon enough if the class has already been loaded 105 if (exportInternalAPI) { 106 exportInternalAPI(docletClass.getClassLoader()); 107 } 108 } 109 DocletInvoker(Messager messager, JavaFileManager fileManager, String docletClassName, String docletPath, ClassLoader docletParentClassLoader, boolean apiMode, boolean exportInternalAPI)110 public DocletInvoker(Messager messager, JavaFileManager fileManager, 111 String docletClassName, String docletPath, 112 ClassLoader docletParentClassLoader, 113 boolean apiMode, 114 boolean exportInternalAPI) { 115 this.messager = messager; 116 this.docletClassName = docletClassName; 117 this.apiMode = apiMode; 118 this.exportInternalAPI = exportInternalAPI; // for backdoor use by standard doclet for taglets 119 120 if (fileManager != null && fileManager.hasLocation(DocumentationTool.Location.DOCLET_PATH)) { 121 appClassLoader = fileManager.getClassLoader(DocumentationTool.Location.DOCLET_PATH); 122 } else { 123 // construct class loader 124 String cpString = null; // make sure env.class.path defaults to dot 125 126 // do prepends to get correct ordering 127 cpString = appendPath(System.getProperty("env.class.path"), cpString); 128 cpString = appendPath(System.getProperty("java.class.path"), cpString); 129 cpString = appendPath(docletPath, cpString); 130 URL[] urls = pathToURLs(cpString); 131 if (docletParentClassLoader == null) 132 appClassLoader = new URLClassLoader(urls, getDelegationClassLoader(docletClassName)); 133 else 134 appClassLoader = new URLClassLoader(urls, docletParentClassLoader); 135 } 136 137 if (exportInternalAPI) { 138 exportInternalAPI(appClassLoader); 139 } 140 141 // attempt to find doclet 142 Class<?> dc = null; 143 try { 144 dc = appClassLoader.loadClass(docletClassName); 145 } catch (ClassNotFoundException exc) { 146 messager.error(Messager.NOPOS, "main.doclet_class_not_found", docletClassName); 147 messager.exit(); 148 } 149 docletClass = dc; 150 } 151 152 /* 153 * Returns the delegation class loader to use when creating 154 * appClassLoader (used to load the doclet). The context class 155 * loader is the best choice, but legacy behavior was to use the 156 * default delegation class loader (aka system class loader). 157 * 158 * Here we favor using the context class loader. To ensure 159 * compatibility with existing apps, we revert to legacy 160 * behavior if either or both of the following conditions hold: 161 * 162 * 1) the doclet is loadable from the system class loader but not 163 * from the context class loader, 164 * 165 * 2) this.getClass() is loadable from the system class loader but not 166 * from the context class loader. 167 */ getDelegationClassLoader(String docletClassName)168 private ClassLoader getDelegationClassLoader(String docletClassName) { 169 ClassLoader ctxCL = Thread.currentThread().getContextClassLoader(); 170 ClassLoader sysCL = ClassLoader.getSystemClassLoader(); 171 if (sysCL == null) 172 return ctxCL; 173 if (ctxCL == null) 174 return sysCL; 175 176 // Condition 1. 177 try { 178 sysCL.loadClass(docletClassName); 179 try { 180 ctxCL.loadClass(docletClassName); 181 } catch (ClassNotFoundException e) { 182 return sysCL; 183 } 184 } catch (ClassNotFoundException e) { 185 } 186 187 // Condition 2. 188 try { 189 if (getClass() == sysCL.loadClass(getClass().getName())) { 190 try { 191 if (getClass() != ctxCL.loadClass(getClass().getName())) 192 return sysCL; 193 } catch (ClassNotFoundException e) { 194 return sysCL; 195 } 196 } 197 } catch (ClassNotFoundException e) { 198 } 199 200 return ctxCL; 201 } 202 203 /** 204 * Generate documentation here. Return true on success. 205 */ start(RootDoc root)206 public boolean start(RootDoc root) { 207 Object retVal; 208 String methodName = "start"; 209 Class<?>[] paramTypes = { RootDoc.class }; 210 Object[] params = { root }; 211 try { 212 retVal = invoke(methodName, null, paramTypes, params); 213 } catch (DocletInvokeException exc) { 214 return false; 215 } 216 if (retVal instanceof Boolean) { 217 return ((Boolean)retVal); 218 } else { 219 messager.error(Messager.NOPOS, "main.must_return_boolean", 220 docletClassName, methodName); 221 return false; 222 } 223 } 224 225 /** 226 * Check for doclet added options here. Zero return means 227 * option not known. Positive value indicates number of 228 * arguments to option. Negative value means error occurred. 229 */ optionLength(String option)230 public int optionLength(String option) { 231 Object retVal; 232 String methodName = "optionLength"; 233 Class<?>[] paramTypes = { String.class }; 234 Object[] params = { option }; 235 try { 236 retVal = invoke(methodName, 0, paramTypes, params); 237 } catch (DocletInvokeException exc) { 238 return -1; 239 } 240 if (retVal instanceof Integer) { 241 return ((Integer)retVal); 242 } else { 243 messager.error(Messager.NOPOS, "main.must_return_int", 244 docletClassName, methodName); 245 return -1; 246 } 247 } 248 249 /** 250 * Let doclet check that all options are OK. Returning true means 251 * options are OK. If method does not exist, assume true. 252 */ validOptions(List<String[]> optlist)253 public boolean validOptions(List<String[]> optlist) { 254 Object retVal; 255 String options[][] = optlist.toArray(new String[optlist.length()][]); 256 String methodName = "validOptions"; 257 DocErrorReporter reporter = messager; 258 Class<?>[] paramTypes = { String[][].class, DocErrorReporter.class }; 259 Object[] params = { options, reporter }; 260 try { 261 retVal = invoke(methodName, Boolean.TRUE, paramTypes, params); 262 } catch (DocletInvokeException exc) { 263 return false; 264 } 265 if (retVal instanceof Boolean) { 266 return ((Boolean)retVal); 267 } else { 268 messager.error(Messager.NOPOS, "main.must_return_boolean", 269 docletClassName, methodName); 270 return false; 271 } 272 } 273 274 /** 275 * Return the language version supported by this doclet. 276 * If the method does not exist in the doclet, assume version 1.1. 277 */ languageVersion()278 public LanguageVersion languageVersion() { 279 try { 280 Object retVal; 281 String methodName = "languageVersion"; 282 Class<?>[] paramTypes = new Class<?>[0]; 283 Object[] params = new Object[0]; 284 try { 285 retVal = invoke(methodName, LanguageVersion.JAVA_1_1, paramTypes, params); 286 } catch (DocletInvokeException exc) { 287 return LanguageVersion.JAVA_1_1; 288 } 289 if (retVal instanceof LanguageVersion) { 290 return (LanguageVersion)retVal; 291 } else { 292 messager.error(Messager.NOPOS, "main.must_return_languageversion", 293 docletClassName, methodName); 294 return LanguageVersion.JAVA_1_1; 295 } 296 } catch (NoClassDefFoundError ex) { // for boostrapping, no Enum class. 297 return null; 298 } 299 } 300 301 /** 302 * Utility method for calling doclet functionality 303 */ invoke(String methodName, Object returnValueIfNonExistent, Class<?>[] paramTypes, Object[] params)304 private Object invoke(String methodName, Object returnValueIfNonExistent, 305 Class<?>[] paramTypes, Object[] params) 306 throws DocletInvokeException { 307 Method meth; 308 try { 309 meth = docletClass.getMethod(methodName, paramTypes); 310 } catch (NoSuchMethodException exc) { 311 if (returnValueIfNonExistent == null) { 312 messager.error(Messager.NOPOS, "main.doclet_method_not_found", 313 docletClassName, methodName); 314 throw new DocletInvokeException(); 315 } else { 316 return returnValueIfNonExistent; 317 } 318 } catch (SecurityException exc) { 319 messager.error(Messager.NOPOS, "main.doclet_method_not_accessible", 320 docletClassName, methodName); 321 throw new DocletInvokeException(); 322 } 323 if (!Modifier.isStatic(meth.getModifiers())) { 324 messager.error(Messager.NOPOS, "main.doclet_method_must_be_static", 325 docletClassName, methodName); 326 throw new DocletInvokeException(); 327 } 328 ClassLoader savedCCL = 329 Thread.currentThread().getContextClassLoader(); 330 try { 331 if (appClassLoader != null) // will be null if doclet class provided via API 332 Thread.currentThread().setContextClassLoader(appClassLoader); 333 return meth.invoke(null , params); 334 } catch (IllegalArgumentException | NullPointerException exc) { 335 messager.error(Messager.NOPOS, "main.internal_error_exception_thrown", 336 docletClassName, methodName, exc.toString()); 337 throw new DocletInvokeException(); 338 } catch (IllegalAccessException exc) { 339 messager.error(Messager.NOPOS, "main.doclet_method_not_accessible", 340 docletClassName, methodName); 341 throw new DocletInvokeException(); 342 } 343 catch (InvocationTargetException exc) { 344 Throwable err = exc.getTargetException(); 345 if (apiMode) 346 throw new ClientCodeException(err); 347 if (err instanceof java.lang.OutOfMemoryError) { 348 messager.error(Messager.NOPOS, "main.out.of.memory"); 349 } else { 350 messager.error(Messager.NOPOS, "main.exception_thrown", 351 docletClassName, methodName, exc.toString()); 352 exc.getTargetException().printStackTrace(System.err); 353 } 354 throw new DocletInvokeException(); 355 } finally { 356 Thread.currentThread().setContextClassLoader(savedCCL); 357 } 358 } 359 360 /** 361 * Export javadoc internal API to the unnamed module for a classloader. 362 * This is to support continued use of existing non-standard doclets that 363 * use the internal toolkit API and related classes. 364 * @param cl the classloader 365 */ exportInternalAPI(ClassLoader cl)366 private void exportInternalAPI(ClassLoader cl) { 367 String[] packages = { 368 "com.sun.tools.doclets", 369 "com.sun.tools.doclets.standard", 370 "com.sun.tools.doclets.internal.toolkit", 371 "com.sun.tools.doclets.internal.toolkit.taglets", 372 "com.sun.tools.doclets.internal.toolkit.builders", 373 "com.sun.tools.doclets.internal.toolkit.util", 374 "com.sun.tools.doclets.internal.toolkit.util.links", 375 "com.sun.tools.doclets.formats.html", 376 "com.sun.tools.doclets.formats.html.markup" 377 }; 378 379 try { 380 Method getModuleMethod = Class.class.getDeclaredMethod("getModule"); 381 Object thisModule = getModuleMethod.invoke(getClass()); 382 383 Class<?> moduleClass = Class.forName("java.lang.Module"); 384 Method addExportsMethod = moduleClass.getDeclaredMethod("addExports", String.class, moduleClass); 385 386 Method getUnnamedModuleMethod = ClassLoader.class.getDeclaredMethod("getUnnamedModule"); 387 Object target = getUnnamedModuleMethod.invoke(cl); 388 389 for (String pack : packages) { 390 addExportsMethod.invoke(thisModule, pack, target); 391 } 392 } catch (Exception e) { 393 // do nothing 394 } 395 } 396 397 /** 398 * Utility method for converting a search path string to an array of directory and JAR file 399 * URLs. 400 * 401 * Note that this method is called by the DocletInvoker. 402 * 403 * @param path the search path string 404 * @return the resulting array of directory and JAR file URLs 405 */ pathToURLs(String path)406 private static URL[] pathToURLs(String path) { 407 java.util.List<URL> urls = new ArrayList<>(); 408 for (String s: path.split(Pattern.quote(File.pathSeparator))) { 409 if (!s.isEmpty()) { 410 URL url = fileToURL(Paths.get(s)); 411 if (url != null) { 412 urls.add(url); 413 } 414 } 415 } 416 return urls.toArray(new URL[urls.size()]); 417 } 418 419 /** 420 * Returns the directory or JAR file URL corresponding to the specified local file name. 421 * 422 * @param file the Path object 423 * @return the resulting directory or JAR file URL, or null if unknown 424 */ fileToURL(Path file)425 private static URL fileToURL(Path file) { 426 Path p; 427 try { 428 p = file.toRealPath(); 429 } catch (IOException e) { 430 p = file.toAbsolutePath(); 431 } 432 try { 433 return p.normalize().toUri().toURL(); 434 } catch (MalformedURLException e) { 435 return null; 436 } 437 } 438 } 439