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 }