    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jxpath;
    019
    020import java.lang.reflect.Constructor;
    021import java.lang.reflect.Method;
    022import java.util.Collection;
    023import java.util.Collections;
    024import java.util.Iterator;
    025import java.util.Objects;
    026import java.util.Set;
    027
    028import org.apache.commons.jxpath.functions.ConstructorFunction;
    029import org.apache.commons.jxpath.functions.MethodFunction;
    030import org.apache.commons.jxpath.util.ClassLoaderUtil;
    031import org.apache.commons.jxpath.util.MethodLookupUtils;
    032import org.apache.commons.jxpath.util.TypeUtils;
    033
    034/**
    035 * Extension functions provided by Java classes. The class prefix specified in the constructor is used when a constructor or a static method is called. Usually,
    036 * a class prefix is a package name (hence the name of this class).
    037 *
    038 * Let's say, we declared a PackageFunction like this: <blockquote>
    039 *
    040 * <pre>
    041 * new PackageFunctions("java.util.", "util")
    042 * </pre>
    043 *
    044 * </blockquote>
    045 *
    046 * We can now use XPaths like:
    047 * <dl>
    048 * <dt>{@code "util:Date.new()"}</dt>
    049 * <dd>Equivalent to {@code new java.util.Date()}</dd>
    050 * <dt>{@code "util:Collections.singleton('foo')"}</dt>
    051 * <dd>Equivalent to {@code java.util.Collections.singleton("foo")}</dd>
    052 * <dt>{@code "util:substring('foo', 1, 2)"}</dt>
    053 * <dd>Equivalent to {@code "foo".substring(1, 2)}. Note that in this case, the class prefix is not used. JXPath does not check that the first parameter of the
    054 * function (the method target) is in fact a member of the package described by this PackageFunctions object.</dd>
    055 * </dl>
    056 *
    057 * <p>
    058 * If the first argument of a method or constructor is {@link ExpressionContext}, the expression context in which the function is evaluated is passed to the
    059 * method.
    060 * </p>
    061 * <p>
    062 * There is one PackageFunctions object registered by default with each JXPathContext. It does not have a namespace and uses no class prefix. The existence of
    063 * this object allows us to use XPaths like: {@code "java.util.Date.new()"} and {@code "length('foo')"} without the explicit registration of any extension
    064 * functions.
    065 * </p>
    066 */
    067public class PackageFunctions implements Functions {
    068
    069    private static final Object[] EMPTY_ARRAY = {};
    070    private final String classPrefix;
    071    private final String namespace;
    072
    073    /**
    074     * Constructs a new PackageFunctions.
    075     *
    076     * @param classPrefix class prefix
    077     * @param namespace   namespace String
    078     */
    079    public PackageFunctions(final String classPrefix, final String namespace) {
    080        this.classPrefix = classPrefix;
    081        this.namespace = namespace;
    082    }
    083
    084    /**
    085     * Returns a {@link Function}, if found, for the specified namespace, name and parameter types.
    086     *
    087     * @param namespace  - if it is not the same as specified in the construction, this method returns null
    088     * @param name       - name of the method, which can one these forms:
    089     *                   <ul>
    090     *                   <li><strong>methodname</strong>, if invoking a method on an object passed as the first parameter</li>
    091     *                   <li><strong>Classname.new</strong>, if looking for a constructor</li>
    092     *                   <li><strong>subpackage.subpackage.Classname.new</strong>, if looking for a constructor in a subpackage</li>
    093     *                   <li><strong>Classname.methodname</strong>, if looking for a static method</li>
    094     *                   <li><strong>subpackage.subpackage.Classname.methodname</strong>, if looking for a static method of a class in a subpackage</li>
    095     *                   </ul>
    096     * @param parameters Object[] of parameters
    097     * @return a MethodFunction, a ConstructorFunction or null if no function is found
    098     */
    099    @Override
    100    public Function getFunction(final String namespace, final String name, Object[] parameters) {
    101        if (!Objects.equals(this.namespace, namespace)) {
    102            return null;
    103        }
    104        if (parameters == null) {
    105            parameters = EMPTY_ARRAY;
    106        }
    107        if (parameters.length >= 1) {
    108            Object target = TypeUtils.convert(parameters[0], Object.class);
    109            if (target != null) {
    110                Method method = MethodLookupUtils.lookupMethod(target.getClass(), name, parameters);
    111                if (method != null) {
    112                    return new MethodFunction(method);
    113                }
    114                if (target instanceof NodeSet) {
    115                    target = ((NodeSet) target).getPointers();
    116                }
    117                method = MethodLookupUtils.lookupMethod(target.getClass(), name, parameters);
    118                if (method != null) {
    119                    return new MethodFunction(method);
    120                }
    121                if (target instanceof Collection) {
    122                    final Iterator iter = ((Collection) target).iterator();
    123                    if (iter.hasNext()) {
    124                        target = iter.next();
    125                        if (target instanceof Pointer) {
    126                            target = ((Pointer) target).getValue();
    127                        }
    128                    } else {
    129                        target = null;
    130                    }
    131                }
    132            }
    133            if (target != null) {
    134                final Method method = MethodLookupUtils.lookupMethod(target.getClass(), name, parameters);
    135                if (method != null) {
    136                    return new MethodFunction(method);
    137                }
    138            }
    139        }
    140        final String fullName = classPrefix + name;
    141        final int inx = fullName.lastIndexOf('.');
    142        if (inx == -1) {
    143            return null;
    144        }
    145        final String className = fullName.substring(0, inx);
    146        final String methodName = fullName.substring(inx + 1);
    147        Class<?> functionClass;
    148        try {
    149            functionClass = ClassLoaderUtil.getClass(className, true);
    150        } catch (final ClassNotFoundException ex) {
    151            throw new JXPathException("Cannot invoke extension function " + (namespace != null ? namespace + ":" + name : name), ex);
    152        }
    153        if (methodName.equals("new")) {
    154            final Constructor constructor = MethodLookupUtils.lookupConstructor(functionClass, parameters);
    155            if (constructor != null) {
    156                return new ConstructorFunction(constructor);
    157            }
    158        } else {
    159            final Method method = MethodLookupUtils.lookupStaticMethod(functionClass, methodName, parameters);
    160            if (method != null) {
    161                return new MethodFunction(method);
    162            }
    163        }
    164        return null;
    165    }
    166
    167    /**
    168     * Returns the namespace specified in the constructor
    169     *
    170     * @return (singleton) namespace Set
    171     */
    172    @Override
    173    public Set<String> getUsedNamespaces() {
    174        return Collections.singleton(namespace);
    175    }
    176}