Thu, 18 Apr 2013 20:00:14 -0700
8012656: cache frequently used name strings for DocImpl classes
Reviewed-by: darcy
1 /*
2 * Copyright (c) 1998, 2012, 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 */
26 package com.sun.tools.javadoc;
28 import java.io.File;
29 import java.lang.reflect.InvocationTargetException;
30 import java.lang.reflect.Method;
31 import java.lang.reflect.Modifier;
32 import java.net.URL;
33 import java.net.URLClassLoader;
35 import javax.tools.DocumentationTool;
36 import javax.tools.JavaFileManager;
38 import com.sun.javadoc.*;
39 import com.sun.tools.javac.file.Locations;
40 import com.sun.tools.javac.util.ClientCodeException;
41 import com.sun.tools.javac.util.List;
42 import static com.sun.javadoc.LanguageVersion.*;
45 /**
46 * Class creates, controls and invokes doclets.
47 *
48 * <p><b>This is NOT part of any supported API.
49 * If you write code that depends on this, you do so at your own risk.
50 * This code and its internal interfaces are subject to change or
51 * deletion without notice.</b>
52 *
53 * @author Neal Gafter (rewrite)
54 */
55 public class DocletInvoker {
57 private final Class<?> docletClass;
59 private final String docletClassName;
61 private final ClassLoader appClassLoader;
63 private final Messager messager;
65 /**
66 * In API mode, exceptions thrown while calling the doclet are
67 * propagated using ClientCodeException.
68 */
69 private final boolean apiMode;
71 private static class DocletInvokeException extends Exception {
72 private static final long serialVersionUID = 0;
73 }
75 private String appendPath(String path1, String path2) {
76 if (path1 == null || path1.length() == 0) {
77 return path2 == null ? "." : path2;
78 } else if (path2 == null || path2.length() == 0) {
79 return path1;
80 } else {
81 return path1 + File.pathSeparator + path2;
82 }
83 }
85 public DocletInvoker(Messager messager, Class<?> docletClass, boolean apiMode) {
86 this.messager = messager;
87 this.docletClass = docletClass;
88 docletClassName = docletClass.getName();
89 appClassLoader = null;
90 this.apiMode = apiMode;
91 }
93 public DocletInvoker(Messager messager, JavaFileManager fileManager,
94 String docletClassName, String docletPath,
95 ClassLoader docletParentClassLoader,
96 boolean apiMode) {
97 this.messager = messager;
98 this.docletClassName = docletClassName;
99 this.apiMode = apiMode;
101 if (fileManager != null && fileManager.hasLocation(DocumentationTool.Location.DOCLET_PATH)) {
102 appClassLoader = fileManager.getClassLoader(DocumentationTool.Location.DOCLET_PATH);
103 } else {
104 // construct class loader
105 String cpString = null; // make sure env.class.path defaults to dot
107 // do prepends to get correct ordering
108 cpString = appendPath(System.getProperty("env.class.path"), cpString);
109 cpString = appendPath(System.getProperty("java.class.path"), cpString);
110 cpString = appendPath(docletPath, cpString);
111 URL[] urls = Locations.pathToURLs(cpString);
112 if (docletParentClassLoader == null)
113 appClassLoader = new URLClassLoader(urls, getDelegationClassLoader(docletClassName));
114 else
115 appClassLoader = new URLClassLoader(urls, docletParentClassLoader);
116 }
118 // attempt to find doclet
119 Class<?> dc = null;
120 try {
121 dc = appClassLoader.loadClass(docletClassName);
122 } catch (ClassNotFoundException exc) {
123 messager.error(Messager.NOPOS, "main.doclet_class_not_found", docletClassName);
124 messager.exit();
125 }
126 docletClass = dc;
127 }
129 /*
130 * Returns the delegation class loader to use when creating
131 * appClassLoader (used to load the doclet). The context class
132 * loader is the best choice, but legacy behavior was to use the
133 * default delegation class loader (aka system class loader).
134 *
135 * Here we favor using the context class loader. To ensure
136 * compatibility with existing apps, we revert to legacy
137 * behavior if either or both of the following conditions hold:
138 *
139 * 1) the doclet is loadable from the system class loader but not
140 * from the context class loader,
141 *
142 * 2) this.getClass() is loadable from the system class loader but not
143 * from the context class loader.
144 */
145 private ClassLoader getDelegationClassLoader(String docletClassName) {
146 ClassLoader ctxCL = Thread.currentThread().getContextClassLoader();
147 ClassLoader sysCL = ClassLoader.getSystemClassLoader();
148 if (sysCL == null)
149 return ctxCL;
150 if (ctxCL == null)
151 return sysCL;
153 // Condition 1.
154 try {
155 sysCL.loadClass(docletClassName);
156 try {
157 ctxCL.loadClass(docletClassName);
158 } catch (ClassNotFoundException e) {
159 return sysCL;
160 }
161 } catch (ClassNotFoundException e) {
162 }
164 // Condition 2.
165 try {
166 if (getClass() == sysCL.loadClass(getClass().getName())) {
167 try {
168 if (getClass() != ctxCL.loadClass(getClass().getName()))
169 return sysCL;
170 } catch (ClassNotFoundException e) {
171 return sysCL;
172 }
173 }
174 } catch (ClassNotFoundException e) {
175 }
177 return ctxCL;
178 }
180 /**
181 * Generate documentation here. Return true on success.
182 */
183 public boolean start(RootDoc root) {
184 Object retVal;
185 String methodName = "start";
186 Class<?>[] paramTypes = { RootDoc.class };
187 Object[] params = { root };
188 try {
189 retVal = invoke(methodName, null, paramTypes, params);
190 } catch (DocletInvokeException exc) {
191 return false;
192 }
193 if (retVal instanceof Boolean) {
194 return ((Boolean)retVal).booleanValue();
195 } else {
196 messager.error(Messager.NOPOS, "main.must_return_boolean",
197 docletClassName, methodName);
198 return false;
199 }
200 }
202 /**
203 * Check for doclet added options here. Zero return means
204 * option not known. Positive value indicates number of
205 * arguments to option. Negative value means error occurred.
206 */
207 public int optionLength(String option) {
208 Object retVal;
209 String methodName = "optionLength";
210 Class<?>[] paramTypes = { String.class };
211 Object[] params = { option };
212 try {
213 retVal = invoke(methodName, new Integer(0), paramTypes, params);
214 } catch (DocletInvokeException exc) {
215 return -1;
216 }
217 if (retVal instanceof Integer) {
218 return ((Integer)retVal).intValue();
219 } else {
220 messager.error(Messager.NOPOS, "main.must_return_int",
221 docletClassName, methodName);
222 return -1;
223 }
224 }
226 /**
227 * Let doclet check that all options are OK. Returning true means
228 * options are OK. If method does not exist, assume true.
229 */
230 public boolean validOptions(List<String[]> optlist) {
231 Object retVal;
232 String options[][] = optlist.toArray(new String[optlist.length()][]);
233 String methodName = "validOptions";
234 DocErrorReporter reporter = messager;
235 Class<?>[] paramTypes = { String[][].class, DocErrorReporter.class };
236 Object[] params = { options, reporter };
237 try {
238 retVal = invoke(methodName, Boolean.TRUE, paramTypes, params);
239 } catch (DocletInvokeException exc) {
240 return false;
241 }
242 if (retVal instanceof Boolean) {
243 return ((Boolean)retVal).booleanValue();
244 } else {
245 messager.error(Messager.NOPOS, "main.must_return_boolean",
246 docletClassName, methodName);
247 return false;
248 }
249 }
251 /**
252 * Return the language version supported by this doclet.
253 * If the method does not exist in the doclet, assume version 1.1.
254 */
255 public LanguageVersion languageVersion() {
256 try {
257 Object retVal;
258 String methodName = "languageVersion";
259 Class<?>[] paramTypes = new Class<?>[0];
260 Object[] params = new Object[0];
261 try {
262 retVal = invoke(methodName, JAVA_1_1, paramTypes, params);
263 } catch (DocletInvokeException exc) {
264 return JAVA_1_1;
265 }
266 if (retVal instanceof LanguageVersion) {
267 return (LanguageVersion)retVal;
268 } else {
269 messager.error(Messager.NOPOS, "main.must_return_languageversion",
270 docletClassName, methodName);
271 return JAVA_1_1;
272 }
273 } catch (NoClassDefFoundError ex) { // for boostrapping, no Enum class.
274 return null;
275 }
276 }
278 /**
279 * Utility method for calling doclet functionality
280 */
281 private Object invoke(String methodName, Object returnValueIfNonExistent,
282 Class<?>[] paramTypes, Object[] params)
283 throws DocletInvokeException {
284 Method meth;
285 try {
286 meth = docletClass.getMethod(methodName, paramTypes);
287 } catch (NoSuchMethodException exc) {
288 if (returnValueIfNonExistent == null) {
289 messager.error(Messager.NOPOS, "main.doclet_method_not_found",
290 docletClassName, methodName);
291 throw new DocletInvokeException();
292 } else {
293 return returnValueIfNonExistent;
294 }
295 } catch (SecurityException exc) {
296 messager.error(Messager.NOPOS, "main.doclet_method_not_accessible",
297 docletClassName, methodName);
298 throw new DocletInvokeException();
299 }
300 if (!Modifier.isStatic(meth.getModifiers())) {
301 messager.error(Messager.NOPOS, "main.doclet_method_must_be_static",
302 docletClassName, methodName);
303 throw new DocletInvokeException();
304 }
305 ClassLoader savedCCL =
306 Thread.currentThread().getContextClassLoader();
307 try {
308 if (appClassLoader != null) // will be null if doclet class provided via API
309 Thread.currentThread().setContextClassLoader(appClassLoader);
310 return meth.invoke(null , params);
311 } catch (IllegalArgumentException exc) {
312 messager.error(Messager.NOPOS, "main.internal_error_exception_thrown",
313 docletClassName, methodName, exc.toString());
314 throw new DocletInvokeException();
315 } catch (IllegalAccessException exc) {
316 messager.error(Messager.NOPOS, "main.doclet_method_not_accessible",
317 docletClassName, methodName);
318 throw new DocletInvokeException();
319 } catch (NullPointerException exc) {
320 messager.error(Messager.NOPOS, "main.internal_error_exception_thrown",
321 docletClassName, methodName, exc.toString());
322 throw new DocletInvokeException();
323 } catch (InvocationTargetException exc) {
324 Throwable err = exc.getTargetException();
325 if (apiMode)
326 throw new ClientCodeException(err);
327 if (err instanceof java.lang.OutOfMemoryError) {
328 messager.error(Messager.NOPOS, "main.out.of.memory");
329 } else {
330 messager.error(Messager.NOPOS, "main.exception_thrown",
331 docletClassName, methodName, exc.toString());
332 exc.getTargetException().printStackTrace();
333 }
334 throw new DocletInvokeException();
335 } finally {
336 Thread.currentThread().setContextClassLoader(savedCCL);
337 }
338 }
339 }