    1   /*
    2    * Copyright 1999-2004 The Apache Software Foundation
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.jxpath;
    17
    18  import java.lang.reflect.Constructor;
    19  import java.lang.reflect.Method;
    20  import java.util.Collection;
    21  import java.util.Collections;
    22  import java.util.Iterator;
    23  import java.util.Set;
    24
    25  import org.apache.commons.jxpath.functions.ConstructorFunction;
    26  import org.apache.commons.jxpath.functions.MethodFunction;
    27  import org.apache.commons.jxpath.util.MethodLookupUtils;
    28  import org.apache.commons.jxpath.util.TypeUtils;
    29
    30  /***
    31   * Extension functions provided by Java classes.  The class prefix specified
    32   * in the constructor is used when a constructor or a static method is called.
    33   * Usually, a class prefix is a package name (hence the name of this class).
    34   *
    35   * Let's say, we declared a PackageFunction like this:
    36   * <blockquote><pre>
    37   *     new PackageFunctions("java.util.", "util")
    38   * </pre></blockquote>
    39   *
    40   * We can now use XPaths like:
    41   * <dl>
    42   *  <dt><code>"util:Date.new()"</code></dt>
    43   *  <dd>Equivalent to <code>new java.util.Date()</code></dd>
    44   *  <dt><code>"util:Collections.singleton('foo')"</code></dt>
    45   *  <dd>Equivalent to <code>java.util.Collections.singleton("foo")</code></dd>
    46   *  <dt><code>"util:substring('foo', 1, 2)"</code></dt>
    47   *  <dd>Equivalent to <code>"foo".substring(1, 2)</code>.  Note that in
    48   *  this case, the class prefix is not used. JXPath does not check that
    49   *  the first parameter of the function (the method target) is in fact
    50   *  a member of the package described by this PackageFunctions object.</dd>
    51   * </dl>
    52   *
    53   * <p>
    54   * If the first argument of a method or constructor is ExpressionContext, the
    55   * expression context in which the function is evaluated is passed to
    56   * the method.
    57   * </p>
    58   * <p>
    59   * There is one PackageFunctions object registered by default with each
    60   * JXPathContext.  It does not have a namespace and uses no class prefix.
    61   * The existence of this object allows us to use XPaths like:
    62   * <code>"java.util.Date.new()"</code> and <code>"length('foo')"</code>
    63   * without the explicit registration of any extension functions.
    64   * </p>
    65
    66   *
    67   * @author Dmitri Plotnikov
    68   * @version $Revision: 1.14 $ $Date: 2004/04/04 23:16:23 $
    69   */
    70  public class PackageFunctions implements Functions {
    71      private String classPrefix;
    72      private String namespace;
    73      private static final Object[] EMPTY_ARRAY = new Object[0];
    74
    75      public PackageFunctions(String classPrefix, String namespace) {
    76          this.classPrefix = classPrefix;
    77          this.namespace = namespace;
    78      }
    79
    80      /***
    81       * Returns the namespace specified in the constructor
    82       */
    83      public Set getUsedNamespaces() {
    84          return Collections.singleton(namespace);
    85      }
    86
    87      /***
    88       * Returns a Function, if any, for the specified namespace,
    89       * name and parameter types.
    90       * <p>
    91       * @param  namespace - if it is not the same as specified in the
    92       * construction, this method returns null
    93       * @param name - name of the method, which can one these forms:
    94       * <ul>
    95       * <li><b>methodname</b>, if invoking a method on an object passed as the
    96       * first parameter</li>
    97       * <li><b>Classname.new</b>, if looking for a constructor</li>
    98       * <li><b>subpackage.subpackage.Classname.new</b>, if looking for a
    99       * constructor in a subpackage</li>
    100      * <li><b>Classname.methodname</b>, if looking for a static method</li>
    101      * <li><b>subpackage.subpackage.Classname.methodname</b>, if looking for a
    102      * static method of a class in a subpackage</li>
    103      * </ul>
    104      *
    105      * @return  a MethodFunction, a ConstructorFunction or null if no function
    106      * is found
    107      */
    108     public Function getFunction(
    109         String namespace,
    110         String name,
    111         Object[] parameters)
    112     {
    113         if ((namespace == null && this.namespace != null)
    114             || (namespace != null && !namespace.equals(this.namespace))) {
    115             return null;
    116         }
    117
    118         if (parameters == null) {
    119             parameters = EMPTY_ARRAY;
    120         }
    121
    122         if (parameters.length >= 1) {
    123             Object target = TypeUtils.convert(parameters[0], Object.class);
    124             if (target != null) {
    125                 Method method =
    126                     MethodLookupUtils.lookupMethod(
    127                         target.getClass(),
    128                         name,
    129                         parameters);
    130                 if (method != null) {
    131                     return new MethodFunction(method);
    132                 }
    133
    134                 if (target instanceof NodeSet) {
    135                     target = ((NodeSet) target).getPointers();
    136                 }
    137
    138                 method =
    139                     MethodLookupUtils.lookupMethod(
    140                         target.getClass(),
    141                         name,
    142                         parameters);
    143                 if (method != null) {
    144                     return new MethodFunction(method);
    145                 }
    146
    147                 if (target instanceof Collection) {
    148                     Iterator iter = ((Collection) target).iterator();
    149                     if (iter.hasNext()) {
    150                         target = iter.next();
    151                         if (target instanceof Pointer) {
    152                             target = ((Pointer) target).getValue();
    153                         }
    154                     }
    155                     else {
    156                         target = null;
    157                     }
    158                 }
    159             }
    160             if (target != null) {
    161                 Method method =
    162                     MethodLookupUtils.lookupMethod(
    163                         target.getClass(),
    164                         name,
    165                         parameters);
    166                 if (method != null) {
    167                     return new MethodFunction(method);
    168                 }
    169             }
    170         }
    171
    172         String fullName = classPrefix + name;
    173         int inx = fullName.lastIndexOf('.');
    174         if (inx == -1) {
    175             return null;
    176         }
    177
    178         String className = fullName.substring(0, inx);
    179         String methodName = fullName.substring(inx + 1);
    180
    181         Class functionClass;
    182         try {
    183             functionClass = Class.forName(className);
    184         }
    185         catch (ClassNotFoundException ex) {
    186             throw new JXPathException(
    187                 "Cannot invoke extension function "
    188                     + (namespace != null ? namespace + ":" + name : name),
    189                 ex);
    190         }
    191
    192         if (methodName.equals("new")) {
    193             Constructor constructor =
    194                 MethodLookupUtils.lookupConstructor(functionClass, parameters);
    195             if (constructor != null) {
    196                 return new ConstructorFunction(constructor);
    197             }
    198         }
    199         else {
    200             Method method =
    201                 MethodLookupUtils.lookupStaticMethod(
    202                     functionClass,
    203                     methodName,
    204                     parameters);
    205             if (method != null) {
    206                 return new MethodFunction(method);
    207             }
    208         }
    209         return null;
    210     }
    211 }