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}