duke@1: /* jjg@1357: * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved. duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@1: * duke@1: * This code is free software; you can redistribute it and/or modify it duke@1: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this duke@1: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. duke@1: * duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@1: * version 2 for more details (a copy is included in the LICENSE file that duke@1: * accompanied this code). duke@1: * duke@1: * You should have received a copy of the GNU General Public License version duke@1: * 2 along with this work; if not, write to the Free Software Foundation, duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@1: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. duke@1: */ duke@1: duke@1: package com.sun.tools.javadoc; duke@1: jjg@912: import java.io.File; jjg@1357: import java.lang.reflect.InvocationTargetException; duke@1: import java.lang.reflect.Method; duke@1: import java.lang.reflect.Modifier; jjg@912: import java.net.URL; jjg@912: import java.net.URLClassLoader; duke@1: jjg@1357: import com.sun.javadoc.*; jjg@1357: import com.sun.tools.javac.util.List; jjg@1357: import static com.sun.javadoc.LanguageVersion.*; jjg@1357: duke@1: duke@1: /** duke@1: * Class creates, controls and invokes doclets. duke@1: * @author Neal Gafter (rewrite) duke@1: */ duke@1: public class DocletInvoker { duke@1: jjg@74: private final Class docletClass; duke@1: duke@1: private final String docletClassName; duke@1: duke@1: private final ClassLoader appClassLoader; duke@1: duke@1: private final Messager messager; duke@1: duke@1: private static class DocletInvokeException extends Exception { duke@1: private static final long serialVersionUID = 0; duke@1: } duke@1: duke@1: private String appendPath(String path1, String path2) { duke@1: if (path1 == null || path1.length() == 0) { duke@1: return path2 == null ? "." : path2; duke@1: } else if (path2 == null || path2.length() == 0) { duke@1: return path1; duke@1: } else { duke@1: return path1 + File.pathSeparator + path2; duke@1: } duke@1: } duke@1: duke@1: public DocletInvoker(Messager messager, jjg@129: String docletClassName, String docletPath, jjg@129: ClassLoader docletParentClassLoader) { duke@1: this.messager = messager; duke@1: this.docletClassName = docletClassName; duke@1: duke@1: // construct class loader duke@1: String cpString = null; // make sure env.class.path defaults to dot duke@1: duke@1: // do prepends to get correct ordering duke@1: cpString = appendPath(System.getProperty("env.class.path"), cpString); duke@1: cpString = appendPath(System.getProperty("java.class.path"), cpString); duke@1: cpString = appendPath(docletPath, cpString); jjg@1116: URL[] urls = com.sun.tools.javac.file.Locations.pathToURLs(cpString); jjg@129: if (docletParentClassLoader == null) jjg@209: appClassLoader = new URLClassLoader(urls, getDelegationClassLoader(docletClassName)); jjg@129: else jjg@129: appClassLoader = new URLClassLoader(urls, docletParentClassLoader); duke@1: duke@1: // attempt to find doclet mcimadamore@184: Class dc = null; duke@1: try { duke@1: dc = appClassLoader.loadClass(docletClassName); duke@1: } catch (ClassNotFoundException exc) { duke@1: messager.error(null, "main.doclet_class_not_found", docletClassName); duke@1: messager.exit(); duke@1: } duke@1: docletClass = dc; duke@1: } duke@1: jjg@209: /* jjg@209: * Returns the delegation class loader to use when creating jjg@209: * appClassLoader (used to load the doclet). The context class jjg@209: * loader is the best choice, but legacy behavior was to use the jjg@209: * default delegation class loader (aka system class loader). jjg@209: * jjg@209: * Here we favor using the context class loader. To ensure jjg@209: * compatibility with existing apps, we revert to legacy jjg@209: * behavior if either or both of the following conditions hold: jjg@209: * jjg@209: * 1) the doclet is loadable from the system class loader but not jjg@209: * from the context class loader, jjg@209: * jjg@209: * 2) this.getClass() is loadable from the system class loader but not jjg@209: * from the context class loader. jjg@209: */ jjg@209: private ClassLoader getDelegationClassLoader(String docletClassName) { jjg@209: ClassLoader ctxCL = Thread.currentThread().getContextClassLoader(); jjg@209: ClassLoader sysCL = ClassLoader.getSystemClassLoader(); jjg@209: if (sysCL == null) jjg@209: return ctxCL; jjg@209: if (ctxCL == null) jjg@209: return sysCL; jjg@209: jjg@209: // Condition 1. jjg@209: try { jjg@209: sysCL.loadClass(docletClassName); jjg@209: try { jjg@209: ctxCL.loadClass(docletClassName); jjg@209: } catch (ClassNotFoundException e) { jjg@209: return sysCL; jjg@209: } jjg@209: } catch (ClassNotFoundException e) { jjg@209: } jjg@209: jjg@209: // Condition 2. jjg@209: try { jjg@209: if (getClass() == sysCL.loadClass(getClass().getName())) { jjg@209: try { jjg@209: if (getClass() != ctxCL.loadClass(getClass().getName())) jjg@209: return sysCL; jjg@209: } catch (ClassNotFoundException e) { jjg@209: return sysCL; jjg@209: } jjg@209: } jjg@209: } catch (ClassNotFoundException e) { jjg@209: } jjg@209: jjg@209: return ctxCL; jjg@209: } jjg@209: duke@1: /** duke@1: * Generate documentation here. Return true on success. duke@1: */ duke@1: public boolean start(RootDoc root) { duke@1: Object retVal; duke@1: String methodName = "start"; jjg@584: Class[] paramTypes = { RootDoc.class }; jjg@584: Object[] params = { root }; duke@1: try { duke@1: retVal = invoke(methodName, null, paramTypes, params); duke@1: } catch (DocletInvokeException exc) { duke@1: return false; duke@1: } duke@1: if (retVal instanceof Boolean) { duke@1: return ((Boolean)retVal).booleanValue(); duke@1: } else { duke@1: messager.error(null, "main.must_return_boolean", duke@1: docletClassName, methodName); duke@1: return false; duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Check for doclet added options here. Zero return means duke@1: * option not known. Positive value indicates number of duke@1: * arguments to option. Negative value means error occurred. duke@1: */ duke@1: public int optionLength(String option) { duke@1: Object retVal; duke@1: String methodName = "optionLength"; jjg@584: Class[] paramTypes = { String.class }; jjg@584: Object[] params = { option }; duke@1: try { duke@1: retVal = invoke(methodName, new Integer(0), paramTypes, params); duke@1: } catch (DocletInvokeException exc) { duke@1: return -1; duke@1: } duke@1: if (retVal instanceof Integer) { duke@1: return ((Integer)retVal).intValue(); duke@1: } else { duke@1: messager.error(null, "main.must_return_int", duke@1: docletClassName, methodName); duke@1: return -1; duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Let doclet check that all options are OK. Returning true means duke@1: * options are OK. If method does not exist, assume true. duke@1: */ duke@1: public boolean validOptions(List optlist) { duke@1: Object retVal; duke@1: String options[][] = optlist.toArray(new String[optlist.length()][]); duke@1: String methodName = "validOptions"; duke@1: DocErrorReporter reporter = messager; jjg@584: Class[] paramTypes = { String[][].class, DocErrorReporter.class }; jjg@584: Object[] params = { options, reporter }; duke@1: try { duke@1: retVal = invoke(methodName, Boolean.TRUE, paramTypes, params); duke@1: } catch (DocletInvokeException exc) { duke@1: return false; duke@1: } duke@1: if (retVal instanceof Boolean) { duke@1: return ((Boolean)retVal).booleanValue(); duke@1: } else { duke@1: messager.error(null, "main.must_return_boolean", duke@1: docletClassName, methodName); duke@1: return false; duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Return the language version supported by this doclet. duke@1: * If the method does not exist in the doclet, assume version 1.1. duke@1: */ duke@1: public LanguageVersion languageVersion() { duke@1: try { duke@1: Object retVal; duke@1: String methodName = "languageVersion"; mcimadamore@184: Class[] paramTypes = new Class[0]; duke@1: Object[] params = new Object[0]; duke@1: try { duke@1: retVal = invoke(methodName, JAVA_1_1, paramTypes, params); duke@1: } catch (DocletInvokeException exc) { duke@1: return JAVA_1_1; duke@1: } duke@1: if (retVal instanceof LanguageVersion) { duke@1: return (LanguageVersion)retVal; duke@1: } else { duke@1: messager.error(null, "main.must_return_languageversion", duke@1: docletClassName, methodName); duke@1: return JAVA_1_1; duke@1: } duke@1: } catch (NoClassDefFoundError ex) { // for boostrapping, no Enum class. duke@1: return null; duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Utility method for calling doclet functionality duke@1: */ duke@1: private Object invoke(String methodName, Object returnValueIfNonExistent, mcimadamore@184: Class[] paramTypes, Object[] params) duke@1: throws DocletInvokeException { duke@1: Method meth; duke@1: try { duke@1: meth = docletClass.getMethod(methodName, paramTypes); duke@1: } catch (NoSuchMethodException exc) { duke@1: if (returnValueIfNonExistent == null) { duke@1: messager.error(null, "main.doclet_method_not_found", duke@1: docletClassName, methodName); duke@1: throw new DocletInvokeException(); duke@1: } else { duke@1: return returnValueIfNonExistent; duke@1: } duke@1: } catch (SecurityException exc) { duke@1: messager.error(null, "main.doclet_method_not_accessible", duke@1: docletClassName, methodName); duke@1: throw new DocletInvokeException(); duke@1: } duke@1: if (!Modifier.isStatic(meth.getModifiers())) { duke@1: messager.error(null, "main.doclet_method_must_be_static", duke@1: docletClassName, methodName); duke@1: throw new DocletInvokeException(); duke@1: } jjg@209: ClassLoader savedCCL = jjg@209: Thread.currentThread().getContextClassLoader(); duke@1: try { duke@1: Thread.currentThread().setContextClassLoader(appClassLoader); duke@1: return meth.invoke(null , params); duke@1: } catch (IllegalArgumentException exc) { duke@1: messager.error(null, "main.internal_error_exception_thrown", duke@1: docletClassName, methodName, exc.toString()); duke@1: throw new DocletInvokeException(); duke@1: } catch (IllegalAccessException exc) { duke@1: messager.error(null, "main.doclet_method_not_accessible", duke@1: docletClassName, methodName); duke@1: throw new DocletInvokeException(); duke@1: } catch (NullPointerException exc) { duke@1: messager.error(null, "main.internal_error_exception_thrown", duke@1: docletClassName, methodName, exc.toString()); duke@1: throw new DocletInvokeException(); duke@1: } catch (InvocationTargetException exc) { duke@1: Throwable err = exc.getTargetException(); duke@1: if (err instanceof java.lang.OutOfMemoryError) { duke@1: messager.error(null, "main.out.of.memory"); duke@1: } else { duke@1: messager.error(null, "main.exception_thrown", duke@1: docletClassName, methodName, exc.toString()); duke@1: exc.getTargetException().printStackTrace(); duke@1: } duke@1: throw new DocletInvokeException(); jjg@209: } finally { jjg@209: Thread.currentThread().setContextClassLoader(savedCCL); duke@1: } duke@1: } duke@1: }