You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sa...@apache.org on 2002/10/25 01:05:34 UTC

cvs commit: jakarta-commons-sandbox/clazz/src/java/org/apache/commons/clazz MethodUtils.java

sanders     2002/10/24 16:05:34

  Added:       clazz    PROPOSAL.html RELEASE-NOTES.txt STATUS.html
                        build.properties.sample build.xml
               clazz/src/conf MANIFEST.MF
               clazz/src/java/org/apache/commons/clazz MethodUtils.java
  Log:
  Bootstrap of clazz project, get the ball rolling
  
  Revision  Changes    Path
  1.1                  jakarta-commons-sandbox/clazz/PROPOSAL.html
  
  Index: PROPOSAL.html
  ===================================================================
  <html>
  <head>
  <title>Proposal for clazz Package</title>
  </head>
  <body bgcolor="white">
  
  <div align="center">
  <h1>Proposal for <em>clazz</em> Package</h1>
  </div>
  
  <h3>(0) Rationale</h3>
  <p>
  The Java Reflection Framework provides a set of classes for accessing
  and calling classes, methods and fields dynamically at runtime. In
  addition, the beans Introspector class provides for examination of
  java beans. Together these offer very useful functionality that is
  widely used by todays applications.
  </p>
  <p>
  There is a need for reflection and introspection code in a great many of
  the Commons, Jakarta and Apache projects.  However, the standard Java
  classes have proved inadequate, causing each project to write their own
  reflection helper classes. In addition, many projects also require metadata
  to be stored against classes and properties. This is not supported by the
  current Java APIs.
  </p>
  <p>
  The <em>Clazz</em> package will focus on introspection and class
  manipulation.
  Reflection helper code is located in the [lang] package.
  </p>
  
  <h3>(1) Scope of the Package</h3>
  <p>
  The <em>Clazz</em> package shall create and maintain a package that provides
  introspection and class manipulation handling built upon the Java Reflection
  Framework and other providers, such as byte and source code
  readers/generators.
  </p>
  
  <p>
  The package should:
  </p>
  <ul>
  <li>handle all classes, not just beans</li>
  <li>support extensible metadata (not just for GUI builders)</li>
  <li>handle normal (today) bean conventions (get/set/add/put methods)</li>
  <li>handle future conventions that are not yet standard</li>
  <li>support method overloading</li>
  <li>provide a complete alternative to using java.beans.Introspector</li>
  <li>be simple to learn</li>
  </ul>
  </p>
  
  In terms of code, [clazz] would begin by having classes/interfaces that
  represent a class, method and field like reflection. It would then provide
  mechanisms to build the structure via reflection and BCEL, and ideally to
  generate classes from the structure using BCEL. Note: One possibility would
  be to say that the BCEL dependency belongs in BCEL, and the commons version
  just uses reflection.
  
  [clazz] would also provide mechanisms for identifying methods as being
  property methods, coping with modern conventions such as addXxx(), lists and
  maps that the beans introspector doesn't. This would allow [clazz] to be
  used by betwixt and digester to add flexibility. (I remember reading a case
  where some swedish company had insisted on localising the words get and set
  in bean methods - [clazz] could cope with that)
  
  
  <h3>(1.5) Interaction With Other Packages</h3>
  
  <p><em>clazz</em> relies only on standard JDK 1.2 (or later) APIs for
  production deployment.  It utilizes the JUnit unit testing framework for
  developing and executing unit tests, but this is of interest only to
  developers of the component.</p>
  
  <p>No external configuration files are utilized.</p>
  
  
  <h3>(2) Initial Source of the Package</h3>
  
  <p>These types of classes are splashed around various Apache
  subprojects.  We intend to seek them out and integrate them.</p>
  
  <p>The proposed package name for the new component is
  <code>org.apache.commons.clazz</code>.</p>
  
  
  <h3>(3)  Required Jakarta-Commons Resources</h3>
  
  <ul>
  <li>CVS Repository - New directory <code>clazz</code> in the
      <code>jakarta-commons</code> CVS repository.</li>
  <li>Mailing List - Discussions will take place on the general
      <em>commons-dev@jakarta.apache.org</em> mailing list.  To help
      list subscribers identify messages of interest, it is suggested that
      the message subject of messages about this component be prefixed with
      [clazz].</li>
  <li>Bugzilla - New component "clazz" under the "Commons" product
      category, with appropriate version identifiers as needed.</li>
  <li>Jyve FAQ - New category "commons-clazz" (when available).</li>
  </ul>
  
  
  <h3>(4) Initial Committers</h3>
  
  <p>The initial committers on the IO component shall be:
  <ul>
    <li><a href="mailto:sanders@apache.org">Scott Sanders</a></li>
    <li>Your name here</li>
  </ul>
  </p>
  
  
  
  </body>
  </html>
  
  
  
  1.1                  jakarta-commons-sandbox/clazz/RELEASE-NOTES.txt
  
  Index: RELEASE-NOTES.txt
  ===================================================================
  $Id: RELEASE-NOTES.txt,v 1.1 2002/10/24 23:05:34 sanders Exp $
  
  			 Commons clazz Package
  			   Version 1.0-dev
  			    Release Notes
  
  
  INTRODUCTION:
  
  This document contains the release notes for this version of the Commons
  clazz package, and highlights changes since the previous version.  The
  current release adds new features and bug fixes, and is being done now to
  follow the release early/release often mentality.
  
  
  NEW FEATURES:
  
  * 
  
  
  BUG FIXES:
  
  
  
  
  
  1.1                  jakarta-commons-sandbox/clazz/STATUS.html
  
  Index: STATUS.html
  ===================================================================
  <html>
  <head>
  <title>Status File for Jakarta Commons "clazz" Component</title>
  </head>
  <body bgcolor="white">
  
  
  <div align="center">
  <h1>The Jakarta Commons <em>clazz</em> Component</h1>
  $Id: STATUS.html,v 1.1 2002/10/24 23:05:34 sanders Exp $<br />
  <a href="#Introduction">[Introduction]</a>
  <a href="#Dependencies">[Dependencies]</a>
  <a href="#Release Info">[Release Info]</a>
  <a href="#Committers">[Committers]</a>
  <a href="#Action Items">[Action Items]</a>
  <br /><br />
  </div>
  
  
  <a name="Introduction"></a>
  <h3>1.  INTRODUCTION</h3>
  
  <p>The <em>clazz</em> Component provides a set of classes for accessing
  and calling classes, methods and fields dynamically at runtime. In
  addition, the beans Introspector class provides for examination of
  java beans. Together these offer very useful functionality that is
  widely used by todays applications. The following classes are included:</p>
  <ul>
  <li><strong>TBD</strong> - TBD.</li>
  </ul>
  
  
  <a name="Dependencies"></a>
  <h3>2.  DEPENDENCIES</h3>
  
  <p>The <em>clazz</em> component is dependent upon the following external
  components for development and use:</p>
  <ul>
  <li><a href="http://java.sun.com/j2se">Java Development Kit</a>
      (Version 1.2 or later)</li>
  <li><a href="http://www.junit.org">JUnit Testing Framework</a>
      (Version 3.7 or later) - for unit tests only, not required
      for deployment</li>
  </ul>
  
  
  <a name="Release Info"></a>
  <h3>3.  RELEASE INFO</h3>
  
  <p>Current Release: clazz is yet to be released.  We hope it will be RSN.</p>
  
  <p>Planned Next Release:  Real Soon Now :)  See the
  <a href="#Action Items">Action Items</a> list for tasks that need to be
  completed prior to this release.</p>
  
  
  <a name="Committers"></a>
  <h3>4.  COMMITTERS</h3>
  
  <p>The following individuals are the primary developers and maintainers of this
  component.  Developers who plan to use <em>clazz</em> in their own
  projects are encouraged to collaborate on the future development of this
  component to ensure that it continues to meet a variety of needs.</p>
  <ul>
  <li><a href="mailto:sanders@apache.org">Scott Sanders</a></li>
  <li>Fancy volunteering?  We need you!</li>
  </ul>
  
  
  <a name="Action Items"></a>
  <h3>5.  ACTION ITEMS</h3>
  
  <p>The following action items need to be completed prior to a Version 1.3
  release of this component:</p>
  
  <table border="1">
  
    <tr>
      <th width="80%">Action Item</th>
      <th width="20%">Volunteer</th>
    </tr>
  
    <tr>
      <td><strong>CODE!</strong>.  Add code to make this package useful.</td>
      <td align="center">Everyone</td>
    </tr>
  
  </table>
  
  </body>
  </html>
  
  
  
  1.1                  jakarta-commons-sandbox/clazz/build.properties.sample
  
  Index: build.properties.sample
  ===================================================================
  # The directory containing your binary distribution of JUnit, 
  # version 3.7 or later
  junit.home = /usr/local/junit3.7
  
  # The pathname of the "junit.jar" JAR file
  junit.jar = ${junit.home}/junit.jar
  
  
  
  1.1                  jakarta-commons-sandbox/clazz/build.xml
  
  Index: build.xml
  ===================================================================
  <project name="clazz" default="compile" basedir=".">
  
  
  <!--
          "clazz" component of the Jakarta Commons Subproject
          $Id: build.xml,v 1.1 2002/10/24 23:05:34 sanders Exp $
  -->
  
  
  <!-- ========== Initialize Properties ===================================== -->
  
  
    <property file="build.properties"/>                <!-- Component local   -->
    <property file="../build.properties"/>             <!-- Commons local     -->
    <property file="${user.home}/build.properties"/>   <!-- User local        -->
  
  
  <!-- ========== External Dependencies ===================================== -->
  
  
    <!-- The directory containing your binary distribution of JUnit,
         version 3.7 or later -->
    <property name="junit.home"              value="/usr/local/junit3.7"/>
  
    <!-- The home directory for the Commons collection classes distribution -->
    <property name="commons-logging.home" value="../../jakarta-commons/logging/dist"/>
  
  
  <!-- ========== Derived Values ============================================ -->
  
  
    <!-- The pathname of the "junit.jar" JAR file -->
    <property name="junit.jar"               value="${junit.home}/junit.jar"/>
  
    <!-- The pathname of the logging classes JAR file -->
    <property name="commons-logging.jar" value="${commons-logging.home}/commons-logging.jar"/>
  
  <!-- ========== Component Declarations ==================================== -->
  
  
    <!-- The name of this component -->
    <property name="component.name"          value="clazz"/>
  
    <!-- The primary package name of this component -->
    <property name="component.package"       value="org.apache.commons.clazz"/>
  
    <!-- The title of this component -->
    <property name="component.title"         value="clazz Utilities"/>
  
    <!-- The current version number of this component -->
    <property name="component.version"       value="1.0-dev"/>
  
    <!-- The base directory for compilation targets -->
    <property name="build.home"              value="target"/>
  
    <!-- The base directory for component configuration files -->
    <property name="conf.home"               value="src/conf"/>
  
    <!-- The base directory for distribution targets -->
    <property name="dist.home"               value="dist"/>
  
    <!-- The base directory for component sources -->
    <property name="source.home"             value="src/java"/>
  
    <!-- The base directory for unit test sources -->
    <property name="test.home"               value="src/test"/>
  
  
  <!-- ========== Compiler Defaults ========================================= -->
  
  
    <!-- Should Java compilations set the 'debug' compiler option? -->
    <property name="compile.debug"           value="true"/>
  
    <!-- Should Java compilations set the 'deprecation' compiler option? -->
    <property name="compile.deprecation"     value="true"/>
  
    <!-- Should Java compilations set the 'optimize' compiler option? -->
    <property name="compile.optimize"        value="true"/>
  
    <!-- Construct compile classpath -->
    <path id="compile.classpath">
      <pathelement location="${build.home}/classes"/>
      <pathelement location="${commons-logging.jar}"/>
    </path>
  
  
  <!-- ========== Test Execution Defaults =================================== -->
  
  
    <!-- Construct unit test classpath -->
    <path id="test.classpath">
      <pathelement location="${build.home}/classes"/>
      <pathelement location="${build.home}/tests"/>
      <pathelement location="${commons.logging.jar}"/>
      <pathelement location="${junit.jar}"/>
    </path>
  
    <!-- Should all tests fail if one does? -->
    <property name="test.failonerror"        value="true"/>
  
    <!-- The test runner to execute -->
    <property name="test.runner"             value="junit.textui.TestRunner"/>
  
  
  <!-- ========== Executable Targets ======================================== -->
  
  
    <target name="init"
     description="Initialize and evaluate conditionals">
      <echo message="-------- ${component.name} ${component.version} --------"/>
      <filter  token="name"                  value="${component.name}"/>
      <filter  token="package"               value="${component.package}"/>
      <filter  token="version"               value="${component.version}"/>
    </target>
  
  
    <target name="prepare" depends="init"
     description="Prepare build directory">
      <mkdir dir="${build.home}"/>
      <mkdir dir="${build.home}/classes"/>
      <mkdir dir="${build.home}/conf"/>
      <mkdir dir="${build.home}/tests"/>
    </target>
  
  
    <target name="static" depends="prepare"
     description="Copy static files to build directory">
      <tstamp/>
      <copy  todir="${build.home}/conf" filtering="on">
        <fileset dir="${conf.home}" includes="*.MF"/>
      </copy>
    </target>
  
  
    <target name="compile" depends="static"
     description="Compile shareable components">
      <javac  srcdir="${source.home}"
             destdir="${build.home}/classes"
               debug="${compile.debug}"
         deprecation="${compile.deprecation}"
            optimize="${compile.optimize}">
        <classpath refid="compile.classpath"/>
      </javac>
      <copy    todir="${build.home}/classes" filtering="on">
        <fileset dir="${source.home}" excludes="**/*.java"/>
      </copy>
    </target>
  
  
    <target name="compile.tests" depends="compile"
     description="Compile unit test cases">
      <javac  srcdir="${test.home}"
             destdir="${build.home}/tests"
               debug="${compile.debug}"
         deprecation="${compile.deprecation}"
            optimize="${compile.optimize}">
        <classpath refid="test.classpath"/>
      </javac>
      <copy    todir="${build.home}/tests" filtering="on">
        <fileset dir="${test.home}" excludes="**/*.java"/>
      </copy>
    </target>
  
  
    <target name="clean"
     description="Clean build and distribution directories">
      <delete    dir="${build.home}"/>
      <delete    dir="${dist.home}"/>
    </target>
  
  
    <target name="all" depends="clean,compile"
     description="Clean and compile all components"/>
  
  
    <target name="javadoc" depends="compile"
     description="Create component Javadoc documentation">
      <mkdir      dir="${dist.home}"/>
      <mkdir      dir="${dist.home}/docs"/>
      <mkdir      dir="${dist.home}/docs/api"/>
      <javadoc sourcepath="${source.home}"
                  destdir="${dist.home}/docs/api"
             packagenames="org.apache.commons.*"
                   author="true"
                  private="true"
                  version="true"
                 doctitle="&lt;h1&gt;${component.title}&lt;/h1&gt;"
              windowtitle="${component.title} (Version ${component.version})"
                   bottom="Copyright (c) 2001-2002 - Apache Software Foundation">
        <classpath refid="compile.classpath"/>
      </javadoc>
    </target>
  
  
    <target name="dist" depends="compile,javadoc"
     description="Create binary distribution">
      <mkdir      dir="${dist.home}"/>
      <copy      file="../LICENSE"
                todir="${dist.home}"/>
      <copy      file="RELEASE-NOTES.txt"
                todir="${dist.home}"/>
      <antcall target="jar"/>
    </target>
  
  
    <target name="jar" depends="compile"
     description="Create jar">
      <mkdir      dir="${dist.home}"/>
      <mkdir      dir="${build.home}/classes/META-INF"/>
      <copy      file="../LICENSE"
               tofile="${build.home}/classes/META-INF/LICENSE.txt"/>
      <jar    jarfile="${dist.home}/commons-${component.name}.jar"
              basedir="${build.home}/classes"
             manifest="${build.home}/conf/MANIFEST.MF"/>
    </target>
  
  
    <target name="install-jar" depends="jar"
     description="--> Installs jar file in ${lib.repo}">
      <copy todir="${lib.repo}" filtering="no">
        <fileset dir="${dist.home}">
          <include name="commons-${component.name}.jar"/>
        </fileset>
      </copy>
    </target>
  
  
  <!-- ========== Unit Test Targets ========================================= -->
  
  
    <target name="test"  depends="compile.tests,
                                  test.clazz"
     description="Run all unit test cases">
    </target>
  
  
    <target name="test.clazz" depends="compile.tests">
      <echo message="Running clazz tests ..."/>
      <java classname="${test.runner}" fork="yes"
          failonerror="${test.failonerror}">
        <arg value="org.apache.commons.clazz.SomeTestCase"/>
        <classpath refid="test.classpath"/>
      </java>
    </target>
  
  </project>
  
  
  
  1.1                  jakarta-commons-sandbox/clazz/src/conf/MANIFEST.MF
  
  Index: MANIFEST.MF
  ===================================================================
  Extension-Name: @package@
  Specification-Vendor: Apache Software Foundation
  Specification-Version: 1.0
  Implementation-Vendor: Apache Software Foundation
  Implementation-Version: @version@
  
  
  
  1.1                  jakarta-commons-sandbox/clazz/src/java/org/apache/commons/clazz/MethodUtils.java
  
  Index: MethodUtils.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
  
  
  package org.apache.commons.clazz;
  
  
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  
  
  /**
   * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p>
   *
   * <h3>Known Limitations</h3>
   * <h4>Accessing Public Methods In A Default Access Superclass</h4>
   * <p>There is an issue when invoking public methods contained in a default access superclass.
   * Reflection locates these methods fine and correctly assigns them as public.
   * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
   *
   * <p><code>MethodUtils</code> contains a workaround for this situation. 
   * It will attempt to call <code>setAccessible</code> on this method.
   * If this call succeeds, then the method can be invoked as normal.
   * This call will only succeed when the application has sufficient security privilages. 
   * If this call fails then a warning will be logged and the method may fail.</p>
   *
   * This code originated in the Jakarta Commons BeanUtils project.
   *
   * @author Craig R. McClanahan
   * @author Ralph Schaer
   * @author Chris Audley
   * @author Rey Fran�ois
   * @author Gregor Ra�man
   * @author Jan Sorensen
   * @author Robert Burrell Donkin
   * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
   */
  
  public class MethodUtils {
  
      // --------------------------------------------------------- Private Methods
      
      /**
       * All logging goes through this logger
       */
      private static Log log = LogFactory.getLog(MethodUtils.class);
  
      /** An empty class array */
      private static final Class[] emptyClassArray = new Class[0];
      /** An empty object array */
      private static final Object[] emptyObjectArray = new Object[0];
      
      // --------------------------------------------------------- Public Methods
      
      /**
       * <p>Invoke a named method whose parameter type matches the object type.</p>
       *
       * <p>The behaviour of this method is less deterministic 
       * than {@link #invokeExactMethod}. 
       * It loops through all methods with names that match
       * and then executes the first it finds with compatable parameters.</p>
       *
       * <p>This method supports calls to methods taking primitive parameters 
       * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
       * would match a <code>boolean</code> primitive.</p>
       *
       * <p> This is a convenient wrapper for
       * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
       * </p>
       *
       * @param object invoke method on this object
       * @param methodName get method with this name
       * @param arg use this argument
       *
       * @throws NoSuchMethodException if there is no such accessible method
       * @throws InvocationTargetException wraps an exception thrown by the
       *  method invoked
       * @throws IllegalAccessException if the requested method is not accessible
       *  via reflection
       */
      public static Object invokeMethod(
              Object object,
              String methodName,
              Object arg)
              throws
              NoSuchMethodException,
              IllegalAccessException,
              InvocationTargetException {
  
          Object[] args = {arg};
          return invokeMethod(object, methodName, args);
  
      }
  
  
      /**
       * <p>Invoke a named method whose parameter type matches the object type.</p>
       *
       * <p>The behaviour of this method is less deterministic 
       * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. 
       * It loops through all methods with names that match
       * and then executes the first it finds with compatable parameters.</p>
       *
       * <p>This method supports calls to methods taking primitive parameters 
       * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
       * would match a <code>boolean</code> primitive.</p>
       *
       * <p> This is a convenient wrapper for
       * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
       * </p>
       *
       * @param object invoke method on this object
       * @param methodName get method with this name
       * @param args use these arguments - treat null as empty array
       *
       * @throws NoSuchMethodException if there is no such accessible method
       * @throws InvocationTargetException wraps an exception thrown by the
       *  method invoked
       * @throws IllegalAccessException if the requested method is not accessible
       *  via reflection
       */
      public static Object invokeMethod(
              Object object,
              String methodName,
              Object[] args)
              throws
              NoSuchMethodException,
              IllegalAccessException,
              InvocationTargetException {
          
          if (args == null) {
              args = emptyObjectArray;
          }  
          int arguments = args.length;
          Class parameterTypes [] = new Class[arguments];
          for (int i = 0; i < arguments; i++) {
              parameterTypes[i] = args[i].getClass();
          }
          return invokeMethod(object, methodName, args, parameterTypes);
  
      }
  
  
      /**
       * <p>Invoke a named method whose parameter type matches the object type.</p>
       *
       * <p>The behaviour of this method is less deterministic 
       * than {@link 
       * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. 
       * It loops through all methods with names that match
       * and then executes the first it finds with compatable parameters.</p>
       *
       * <p>This method supports calls to methods taking primitive parameters 
       * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
       * would match a <code>boolean</code> primitive.</p>
       *
       *
       * @param object invoke method on this object
       * @param methodName get method with this name
       * @param args use these arguments - treat null as empty array
       * @param parameterTypes match these parameters - treat null as empty array
       *
       * @throws NoSuchMethodException if there is no such accessible method
       * @throws InvocationTargetException wraps an exception thrown by the
       *  method invoked
       * @throws IllegalAccessException if the requested method is not accessible
       *  via reflection
       */
      public static Object invokeMethod(
              Object object,
              String methodName,
              Object[] args,
              Class[] parameterTypes)
                  throws
                      NoSuchMethodException,
                      IllegalAccessException,
                      InvocationTargetException {
                      
          if (parameterTypes == null) {
              parameterTypes = emptyClassArray;
          }        
          if (args == null) {
              args = emptyObjectArray;
          }  
  
          Method method = getMatchingAccessibleMethod(
                  object.getClass(),
                  methodName,
                  parameterTypes);
          if (method == null)
              throw new NoSuchMethodException("No such accessible method: " +
                      methodName + "() on object: " + object.getClass().getName());
          return method.invoke(object, args);
      }
  
  
      /**
       * <p>Invoke a method whose parameter type matches exactly the object
       * type.</p>
       *
       * <p> This is a convenient wrapper for
       * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
       * </p>
       *
       * @param object invoke method on this object
       * @param methodName get method with this name
       * @param arg use this argument
       *
       * @throws NoSuchMethodException if there is no such accessible method
       * @throws InvocationTargetException wraps an exception thrown by the
       *  method invoked
       * @throws IllegalAccessException if the requested method is not accessible
       *  via reflection
       */
      public static Object invokeExactMethod(
              Object object,
              String methodName,
              Object arg)
              throws
              NoSuchMethodException,
              IllegalAccessException,
              InvocationTargetException {
  
          Object[] args = {arg};
          return invokeExactMethod(object, methodName, args);
  
      }
  
  
      /**
       * <p>Invoke a method whose parameter types match exactly the object
       * types.</p>
       *
       * <p> This uses reflection to invoke the method obtained from a call to
       * {@link #getAccessibleMethod}.</p>
       *
       * @param object invoke method on this object
       * @param methodName get method with this name
       * @param args use these arguments - treat null as empty array
       *
       * @throws NoSuchMethodException if there is no such accessible method
       * @throws InvocationTargetException wraps an exception thrown by the
       *  method invoked
       * @throws IllegalAccessException if the requested method is not accessible
       *  via reflection
       */
      public static Object invokeExactMethod(
              Object object,
              String methodName,
              Object[] args)
              throws
              NoSuchMethodException,
              IllegalAccessException,
              InvocationTargetException {
          if (args == null) {
              args = emptyObjectArray;
          }  
          int arguments = args.length;
          Class parameterTypes [] = new Class[arguments];
          for (int i = 0; i < arguments; i++) {
              parameterTypes[i] = args[i].getClass();
          }
          return invokeExactMethod(object, methodName, args, parameterTypes);
  
      }
  
  
      /**
       * <p>Invoke a method whose parameter types match exactly the parameter
       * types given.</p>
       *
       * <p>This uses reflection to invoke the method obtained from a call to
       * {@link #getAccessibleMethod}.</p>
       *
       * @param object invoke method on this object
       * @param methodName get method with this name
       * @param args use these arguments - treat null as empty array
       * @param parameterTypes match these parameters - treat null as empty array
       *
       * @throws NoSuchMethodException if there is no such accessible method
       * @throws InvocationTargetException wraps an exception thrown by the
       *  method invoked
       * @throws IllegalAccessException if the requested method is not accessible
       *  via reflection
       */
      public static Object invokeExactMethod(
              Object object,
              String methodName,
              Object[] args,
              Class[] parameterTypes)
              throws
              NoSuchMethodException,
              IllegalAccessException,
              InvocationTargetException {
          
          if (args == null) {
              args = emptyObjectArray;
          }  
                  
          if (parameterTypes == null) {
              parameterTypes = emptyClassArray;
          }
  
          Method method = getAccessibleMethod(
                  object.getClass(),
                  methodName,
                  parameterTypes);
          if (method == null)
              throw new NoSuchMethodException("No such accessible method: " +
                      methodName + "() on object: " + object.getClass().getName());
          return method.invoke(object, args);
  
      }
  
  
      /**
       * <p>Return an accessible method (that is, one that can be invoked via
       * reflection) with given name and a single parameter.  If no such method
       * can be found, return <code>null</code>.
       * Basically, a convenience wrapper that constructs a <code>Class</code>
       * array for you.</p>
       *
       * @param clazz get method from this class
       * @param methodName get method with this name
       * @param parameterType taking this type of parameter
       */
      public static Method getAccessibleMethod(
              Class clazz,
              String methodName,
              Class parameterType) {
  
          Class[] parameterTypes = {parameterType};
          return getAccessibleMethod(clazz, methodName, parameterTypes);
  
      }
  
  
      /**
       * <p>Return an accessible method (that is, one that can be invoked via
       * reflection) with given name and parameters.  If no such method
       * can be found, return <code>null</code>.
       * This is just a convenient wrapper for
       * {@link #getAccessibleMethod(Method method)}.</p>
       *
       * @param clazz get method from this class
       * @param methodName get method with this name
       * @param parameterTypes with these parameters types
       */
      public static Method getAccessibleMethod(
              Class clazz,
              String methodName,
              Class[] parameterTypes) {
  
          try {
              return getAccessibleMethod
                      (clazz.getMethod(methodName, parameterTypes));
          } catch (NoSuchMethodException e) {
              return (null);
          }
  
      }
  
  
      /**
       * <p>Return an accessible method (that is, one that can be invoked via
       * reflection) that implements the specified Method.  If no such method
       * can be found, return <code>null</code>.</p>
       *
       * @param method The method that we wish to call
       */
      public static Method getAccessibleMethod(Method method) {
  
          // Make sure we have a method to check
          if (method == null) {
              return (null);
          }
  
          // If the requested method is not public we cannot call it
          if (!Modifier.isPublic(method.getModifiers())) {
              return (null);
          }
  
          // If the declaring class is public, we are done
          Class clazz = method.getDeclaringClass();
          if (Modifier.isPublic(clazz.getModifiers())) {
              return (method);
          }
  
          // Check the implemented interfaces and subinterfaces
          String methodName = method.getName();
          Class[] parameterTypes = method.getParameterTypes();
          method =
                  getAccessibleMethodFromInterfaceNest(clazz,
                          method.getName(),
                          method.getParameterTypes());
          return (method);
  
      }
  
  
      // -------------------------------------------------------- Private Methods
  
      /**
       * <p>Return an accessible method (that is, one that can be invoked via
       * reflection) that implements the specified method, by scanning through
       * all implemented interfaces and subinterfaces.  If no such method
       * can be found, return <code>null</code>.</p>
       *
       * <p> There isn't any good reason why this method must be private.
       * It is because there doesn't seem any reason why other classes should
       * call this rather than the higher level methods.</p>
       *
       * @param clazz Parent class for the interfaces to be checked
       * @param methodName Method name of the method we wish to call
       * @param parameterTypes The parameter type signatures
       */
      private static Method getAccessibleMethodFromInterfaceNest
              (Class clazz, String methodName, Class parameterTypes[]) {
  
          Method method = null;
  
          // Search up the superclass chain
          for (; clazz != null; clazz = clazz.getSuperclass()) {
  
              // Check the implemented interfaces of the parent class
              Class interfaces[] = clazz.getInterfaces();
              for (int i = 0; i < interfaces.length; i++) {
  
                  // Is this interface public?
                  if (!Modifier.isPublic(interfaces[i].getModifiers()))
                      continue;
  
                  // Does the method exist on this interface?
                  try {
                      method = interfaces[i].getDeclaredMethod(methodName,
                              parameterTypes);
                  } catch (NoSuchMethodException e) {
                      ;
                  }
                  if (method != null)
                      break;
  
                  // Recursively check our parent interfaces
                  method =
                          getAccessibleMethodFromInterfaceNest(interfaces[i],
                                  methodName,
                                  parameterTypes);
                  if (method != null)
                      break;
  
              }
  
          }
  
          // If we found a method return it
          if (method != null)
              return (method);
  
          // We did not find anything
          return (null);
  
      }
  
      /**
       * <p>Find an accessible method that matches the given name and has compatible parameters.
       * Compatible parameters mean that every method parameter is assignable from 
       * the given parameters.
       * In other words, it finds a method with the given name 
       * that will take the parameters given.<p>
       *
       * <p>This method is slightly undeterminstic since it loops 
       * through methods names and return the first matching method.</p>
       * 
       * <p>This method is used by 
       * {@link 
       * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
       *
       * <p>This method can match primitive parameter by passing in wrapper classes.
       * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
       * parameter.
       *
       * @param clazz find method in this class
       * @param methodName find method with this name
       * @param parameterTypes find method with compatible parameters 
       */
      private static Method getMatchingAccessibleMethod(
                                                  Class clazz,
                                                  String methodName,
                                                  Class[] parameterTypes) {
          // trace logging
          if (log.isTraceEnabled()) {
              log.trace("Matching name=" + methodName + " on " + clazz);
          }
          
          // see if we can find the method directly
          // most of the time this works and it's much faster
          try {
              Method method = clazz.getMethod(methodName, parameterTypes);
              if (log.isTraceEnabled()) {
                  log.trace("Found straight match: " + method);
                  log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
              }
              
              try {
                  //
                  // XXX Default access superclass workaround
                  //
                  // When a public class has a default access superclass
                  // with public methods, these methods are accessible.
                  // Calling them from compiled code works fine.
                  //
                  // Unfortunately, using reflection to invoke these methods
                  // seems to (wrongly) to prevent access even when the method
                  // modifer is public.
                  //
                  // The following workaround solves the problem but will only
                  // work from sufficiently privilages code. 
                  //
                  // Better workarounds would be greatfully accepted.
                  //
                  method.setAccessible(true);
                  
              } catch (SecurityException se) {
                  // log but continue just in case the method.invoke works anyway
                  log.warn(
                  "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", 
                  se);
              }
              return method;
              
          } catch (NoSuchMethodException e) { /* SWALLOW */ }
          
          // search through all methods 
          int paramSize = parameterTypes.length;
          Method[] methods = clazz.getMethods();
          for (int i = 0, size = methods.length; i < size ; i++) {
              if (methods[i].getName().equals(methodName)) {	
                  // log some trace information
                  if (log.isTraceEnabled()) {
                      log.trace("Found matching name:");
                      log.trace(methods[i]);
                  }                
                  
                  // compare parameters
                  Class[] methodsParams = methods[i].getParameterTypes();
                  int methodParamSize = methodsParams.length;
                  if (methodParamSize == paramSize) {          
                      boolean match = true;
                      for (int n = 0 ; n < methodParamSize; n++) {
                          if (log.isTraceEnabled()) {
                              log.trace("Param=" + parameterTypes[n].getName());
                              log.trace("Method=" + methodsParams[n].getName());
                          }
                          if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
                              if (log.isTraceEnabled()) {
                                  log.trace(methodsParams[n] + " is not assignable from " 
                                              + parameterTypes[n]);
                              }    
                              match = false;
                              break;
                          }
                      }
                      
                      if (match) {
                          // get accessible version of method
                          Method method = getAccessibleMethod(methods[i]);
                          if (method != null) {
                              if (log.isTraceEnabled()) {
                                  log.trace(method + " accessible version of " 
                                              + methods[i]);
                              }
                              try {
                                  //
                                  // XXX Default access superclass workaround
                                  // (See above for more details.)
                                  //
                                  method.setAccessible(true);
                                  
                              } catch (SecurityException se) {
                                  // log but continue just in case the method.invoke works anyway
                                  log.warn(
                                  "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", 
                                  se);
                              }
                              return method;
                          }
                          
                          log.trace("Couldn't find accessible method.");
                      }
                  }
              }
          }
          
          // didn't find a match
          log.trace("No match found.");
          return null;                                        
      }
  
      /**
       * <p>Determine whether a type can be used as a parameter in a method invocation.
       * This method handles primitive conversions correctly.</p>
       *
       * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
       * a <code>Long</code> to a <code>long</code>,
       * a <code>Float</code> to a <code>float</code>,
       * a <code>Integer</code> to a <code>int</code>,
       * and a <code>Double</code> to a <code>double</code>.
       * Now logic widening matches are allowed.
       * For example, a <code>Long</code> will not match a <code>int</code>.
       *
       * @param parameterType the type of parameter accepted by the method
       * @param parameterization the type of parameter being tested 
       *
       * @return true if the assignement is compatible.
       */
      private static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) {
          // try plain assignment
          if (parameterType.isAssignableFrom(parameterization)) {
              return true;
          }
          
          if (parameterType.isPrimitive()) {
              // does anyone know a better strategy than comparing names?
              // also, this method does *not* do widening - you must specify exactly
              // is this the right behaviour?
              if (boolean.class.equals(parameterType)) {
                  return Boolean.class.equals(parameterization);
              }         
              if (float.class.equals(parameterType)) {
                  return Float.class.equals(parameterization);
              }     
              if (long.class.equals(parameterType)) {
                  return Long.class.equals(parameterization);
              }     
              if (int.class.equals(parameterType)) {
                  return Integer.class.equals(parameterization);
              }                
              if (double.class.equals(parameterType)) {
                  return Double.class.equals(parameterization);
              }               
          }
          
          return false;
      }
  }
  
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>