1 /* 2 * Copyright 1998-2009 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 26 package com.sun.tools.javadoc; 27 28 import com.sun.javadoc.*; 29 30 import static com.sun.javadoc.LanguageVersion.*; 31 32 import com.sun.tools.javac.util.List; 33 34 import java.net.*; 35 import java.lang.reflect.Method; 36 import java.lang.reflect.Modifier; 37 import java.lang.reflect.InvocationTargetException; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.util.StringTokenizer; 42 43 /** 44 * Class creates, controls and invokes doclets. 45 * @author Neal Gafter (rewrite) 46 */ 47 public class DocletInvoker { 48 49 private final Class<?> docletClass; 50 51 private final String docletClassName; 52 53 private final ClassLoader appClassLoader; 54 55 private final Messager messager; 56 57 private static class DocletInvokeException extends Exception { 58 private static final long serialVersionUID = 0; 59 } 60 61 private String appendPath(String path1, String path2) { 62 if (path1 == null || path1.length() == 0) { 63 return path2 == null ? "." : path2; 64 } else if (path2 == null || path2.length() == 0) { 65 return path1; 66 } else { 67 return path1 + File.pathSeparator + path2; 68 } 69 } 70 71 public DocletInvoker(Messager messager, 72 String docletClassName, String docletPath, 73 ClassLoader docletParentClassLoader) { 74 this.messager = messager; 75 this.docletClassName = docletClassName; 76 77 // construct class loader 78 String cpString = null; // make sure env.class.path defaults to dot 79 80 // do prepends to get correct ordering 81 cpString = appendPath(System.getProperty("env.class.path"), cpString); 82 cpString = appendPath(System.getProperty("java.class.path"), cpString); 83 cpString = appendPath(docletPath, cpString); 84 URL[] urls = com.sun.tools.javac.file.Paths.pathToURLs(cpString); 85 if (docletParentClassLoader == null) 86 appClassLoader = new URLClassLoader(urls, getDelegationClassLoader(docletClassName)); 87 else 88 appClassLoader = new URLClassLoader(urls, docletParentClassLoader); 89 90 // attempt to find doclet 91 Class<?> dc = null; 92 try { 93 dc = appClassLoader.loadClass(docletClassName); 94 } catch (ClassNotFoundException exc) { 95 messager.error(null, "main.doclet_class_not_found", docletClassName); 96 messager.exit(); 97 } 98 docletClass = dc; 99 } 100 101 /* 102 * Returns the delegation class loader to use when creating 103 * appClassLoader (used to load the doclet). The context class 104 * loader is the best choice, but legacy behavior was to use the 105 * default delegation class loader (aka system class loader). 106 * 107 * Here we favor using the context class loader. To ensure 108 * compatibility with existing apps, we revert to legacy 109 * behavior if either or both of the following conditions hold: 110 * 111 * 1) the doclet is loadable from the system class loader but not 112 * from the context class loader, 113 * 114 * 2) this.getClass() is loadable from the system class loader but not 115 * from the context class loader. 116 */ 117 private ClassLoader getDelegationClassLoader(String docletClassName) { 118 ClassLoader ctxCL = Thread.currentThread().getContextClassLoader(); 119 ClassLoader sysCL = ClassLoader.getSystemClassLoader(); 120 if (sysCL == null) 121 return ctxCL; 122 if (ctxCL == null) 123 return sysCL; 124 125 // Condition 1. 126 try { 127 sysCL.loadClass(docletClassName); 128 try { 129 ctxCL.loadClass(docletClassName); 130 } catch (ClassNotFoundException e) { 131 return sysCL; 132 } 133 } catch (ClassNotFoundException e) { 134 } 135 136 // Condition 2. 137 try { 138 if (getClass() == sysCL.loadClass(getClass().getName())) { 139 try { 140 if (getClass() != ctxCL.loadClass(getClass().getName())) 141 return sysCL; 142 } catch (ClassNotFoundException e) { 143 return sysCL; 144 } 145 } 146 } catch (ClassNotFoundException e) { 147 } 148 149 return ctxCL; 150 } 151 152 /** 153 * Generate documentation here. Return true on success. 154 */ 155 public boolean start(RootDoc root) { 156 Object retVal; 157 String methodName = "start"; 158 Class<?>[] paramTypes = new Class<?>[1]; 159 Object[] params = new Object[1]; 160 paramTypes[0] = RootDoc.class; 161 params[0] = root; 162 try { 163 retVal = invoke(methodName, null, paramTypes, params); 164 } catch (DocletInvokeException exc) { 165 return false; 166 } 167 if (retVal instanceof Boolean) { 168 return ((Boolean)retVal).booleanValue(); 169 } else { 170 messager.error(null, "main.must_return_boolean", 171 docletClassName, methodName); 172 return false; 173 } 174 } 175 176 /** 177 * Check for doclet added options here. Zero return means 178 * option not known. Positive value indicates number of 179 * arguments to option. Negative value means error occurred. 180 */ 181 public int optionLength(String option) { 182 Object retVal; 183 String methodName = "optionLength"; 184 Class<?>[] paramTypes = new Class<?>[1]; 185 Object[] params = new Object[1]; 186 paramTypes[0] = option.getClass(); 187 params[0] = option; 188 try { 189 retVal = invoke(methodName, new Integer(0), paramTypes, params); 190 } catch (DocletInvokeException exc) { 191 return -1; 192 } 193 if (retVal instanceof Integer) { 194 return ((Integer)retVal).intValue(); 195 } else { 196 messager.error(null, "main.must_return_int", 197 docletClassName, methodName); 198 return -1; 199 } 200 } 201 202 /** 203 * Let doclet check that all options are OK. Returning true means 204 * options are OK. If method does not exist, assume true. 205 */ 206 public boolean validOptions(List<String[]> optlist) { 207 Object retVal; 208 String options[][] = optlist.toArray(new String[optlist.length()][]); 209 String methodName = "validOptions"; 210 DocErrorReporter reporter = messager; 211 Class<?>[] paramTypes = new Class<?>[2]; 212 Object[] params = new Object[2]; 213 paramTypes[0] = options.getClass(); 214 paramTypes[1] = DocErrorReporter.class; 215 params[0] = options; 216 params[1] = reporter; 217 try { 218 retVal = invoke(methodName, Boolean.TRUE, paramTypes, params); 219 } catch (DocletInvokeException exc) { 220 return false; 221 } 222 if (retVal instanceof Boolean) { 223 return ((Boolean)retVal).booleanValue(); 224 } else { 225 messager.error(null, "main.must_return_boolean", 226 docletClassName, methodName); 227 return false; 228 } 229 } 230 231 /** 232 * Return the language version supported by this doclet. 233 * If the method does not exist in the doclet, assume version 1.1. 234 */ 235 public LanguageVersion languageVersion() { 236 try { 237 Object retVal; 238 String methodName = "languageVersion"; 239 Class<?>[] paramTypes = new Class<?>[0]; 240 Object[] params = new Object[0]; 241 try { 242 retVal = invoke(methodName, JAVA_1_1, paramTypes, params); 243 } catch (DocletInvokeException exc) { 244 return JAVA_1_1; 245 } 246 if (retVal instanceof LanguageVersion) { 247 return (LanguageVersion)retVal; 248 } else { 249 messager.error(null, "main.must_return_languageversion", 250 docletClassName, methodName); 251 return JAVA_1_1; 252 } 253 } catch (NoClassDefFoundError ex) { // for boostrapping, no Enum class. 254 return null; 255 } 256 } 257 258 /** 259 * Utility method for calling doclet functionality 260 */ 261 private Object invoke(String methodName, Object returnValueIfNonExistent, 262 Class<?>[] paramTypes, Object[] params) 263 throws DocletInvokeException { 264 Method meth; 265 try { 266 meth = docletClass.getMethod(methodName, paramTypes); 267 } catch (NoSuchMethodException exc) { 268 if (returnValueIfNonExistent == null) { 269 messager.error(null, "main.doclet_method_not_found", 270 docletClassName, methodName); 271 throw new DocletInvokeException(); 272 } else { 273 return returnValueIfNonExistent; 274 } 275 } catch (SecurityException exc) { 276 messager.error(null, "main.doclet_method_not_accessible", 277 docletClassName, methodName); 278 throw new DocletInvokeException(); 279 } 280 if (!Modifier.isStatic(meth.getModifiers())) { 281 messager.error(null, "main.doclet_method_must_be_static", 282 docletClassName, methodName); 283 throw new DocletInvokeException(); 284 } 285 ClassLoader savedCCL = 286 Thread.currentThread().getContextClassLoader(); 287 try { 288 Thread.currentThread().setContextClassLoader(appClassLoader); 289 return meth.invoke(null , params); 290 } catch (IllegalArgumentException exc) { 291 messager.error(null, "main.internal_error_exception_thrown", 292 docletClassName, methodName, exc.toString()); 293 throw new DocletInvokeException(); 294 } catch (IllegalAccessException exc) { 295 messager.error(null, "main.doclet_method_not_accessible", 296 docletClassName, methodName); 297 throw new DocletInvokeException(); 298 } catch (NullPointerException exc) { 299 messager.error(null, "main.internal_error_exception_thrown", 300 docletClassName, methodName, exc.toString()); 301 throw new DocletInvokeException(); 302 } catch (InvocationTargetException exc) { 303 Throwable err = exc.getTargetException(); 304 if (err instanceof java.lang.OutOfMemoryError) { 305 messager.error(null, "main.out.of.memory"); 306 } else { 307 messager.error(null, "main.exception_thrown", 308 docletClassName, methodName, exc.toString()); 309 exc.getTargetException().printStackTrace(); 310 } 311 throw new DocletInvokeException(); 312 } finally { 313 Thread.currentThread().setContextClassLoader(savedCCL); 314 } 315 } 316 }