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