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 */ 017package org.apache.commons.jxpath; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.Method; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Iterator; 024import java.util.Set; 025 026import org.apache.commons.jxpath.functions.ConstructorFunction; 027import org.apache.commons.jxpath.functions.MethodFunction; 028import org.apache.commons.jxpath.util.ClassLoaderUtil; 029import org.apache.commons.jxpath.util.MethodLookupUtils; 030import org.apache.commons.jxpath.util.TypeUtils; 031 032/** 033 * Extension functions provided by Java classes. The class prefix specified 034 * in the constructor is used when a constructor or a static method is called. 035 * Usually, a class prefix is a package name (hence the name of this class). 036 * 037 * Let's say, we declared a PackageFunction like this: 038 * <blockquote><pre> 039 * new PackageFunctions("java.util.", "util") 040 * </pre></blockquote> 041 * 042 * We can now use XPaths like: 043 * <dl> 044 * <dt><code>"util:Date.new()"</code></dt> 045 * <dd>Equivalent to <code>new java.util.Date()</code></dd> 046 * <dt><code>"util:Collections.singleton('foo')"</code></dt> 047 * <dd>Equivalent to <code>java.util.Collections.singleton("foo")</code></dd> 048 * <dt><code>"util:substring('foo', 1, 2)"</code></dt> 049 * <dd>Equivalent to <code>"foo".substring(1, 2)</code>. Note that in 050 * this case, the class prefix is not used. JXPath does not check that 051 * the first parameter of the function (the method target) is in fact 052 * a member of the package described by this PackageFunctions object.</dd> 053 * </dl> 054 * 055 * <p> 056 * If the first argument of a method or constructor is {@link ExpressionContext}, 057 * the expression context in which the function is evaluated is passed to 058 * the method. 059 * </p> 060 * <p> 061 * There is one PackageFunctions object registered by default with each 062 * JXPathContext. It does not have a namespace and uses no class prefix. 063 * The existence of this object allows us to use XPaths like: 064 * <code>"java.util.Date.new()"</code> and <code>"length('foo')"</code> 065 * without the explicit registration of any extension functions. 066 * </p> 067 * 068 * @author Dmitri Plotnikov 069 * @version $Revision: 916559 $ $Date: 2010-02-26 04:55:46 +0100 (Fr, 26 Feb 2010) $ 070 */ 071public class PackageFunctions implements Functions { 072 private String classPrefix; 073 private String namespace; 074 private static final Object[] EMPTY_ARRAY = new Object[0]; 075 076 /** 077 * Create a new PackageFunctions. 078 * @param classPrefix class prefix 079 * @param namespace namespace String 080 */ 081 public PackageFunctions(String classPrefix, String namespace) { 082 this.classPrefix = classPrefix; 083 this.namespace = namespace; 084 } 085 086 /** 087 * Returns the namespace specified in the constructor 088 * @return (singleton) namespace Set 089 */ 090 public Set getUsedNamespaces() { 091 return Collections.singleton(namespace); 092 } 093 094 /** 095 * Returns a {@link Function}, if found, for the specified namespace, 096 * name and parameter types. 097 * <p> 098 * @param namespace - if it is not the same as specified in the 099 * construction, this method returns null 100 * @param name - name of the method, which can one these forms: 101 * <ul> 102 * <li><b>methodname</b>, if invoking a method on an object passed as the 103 * first parameter</li> 104 * <li><b>Classname.new</b>, if looking for a constructor</li> 105 * <li><b>subpackage.subpackage.Classname.new</b>, if looking for a 106 * constructor in a subpackage</li> 107 * <li><b>Classname.methodname</b>, if looking for a static method</li> 108 * <li><b>subpackage.subpackage.Classname.methodname</b>, if looking for a 109 * static method of a class in a subpackage</li> 110 * </ul> 111 * @param parameters Object[] of parameters 112 * @return a MethodFunction, a ConstructorFunction or null if no function 113 * is found 114 */ 115 public Function getFunction( 116 String namespace, 117 String name, 118 Object[] parameters) { 119 if ((namespace == null && this.namespace != null) //NOPMD 120 || (namespace != null && !namespace.equals(this.namespace))) { 121 return null; 122 } 123 124 if (parameters == null) { 125 parameters = EMPTY_ARRAY; 126 } 127 128 if (parameters.length >= 1) { 129 Object target = TypeUtils.convert(parameters[0], Object.class); 130 if (target != null) { 131 Method method = 132 MethodLookupUtils.lookupMethod( 133 target.getClass(), 134 name, 135 parameters); 136 if (method != null) { 137 return new MethodFunction(method); 138 } 139 140 if (target instanceof NodeSet) { 141 target = ((NodeSet) target).getPointers(); 142 } 143 144 method = 145 MethodLookupUtils.lookupMethod( 146 target.getClass(), 147 name, 148 parameters); 149 if (method != null) { 150 return new MethodFunction(method); 151 } 152 153 if (target instanceof Collection) { 154 Iterator iter = ((Collection) target).iterator(); 155 if (iter.hasNext()) { 156 target = iter.next(); 157 if (target instanceof Pointer) { 158 target = ((Pointer) target).getValue(); 159 } 160 } 161 else { 162 target = null; 163 } 164 } 165 } 166 if (target != null) { 167 Method method = 168 MethodLookupUtils.lookupMethod( 169 target.getClass(), 170 name, 171 parameters); 172 if (method != null) { 173 return new MethodFunction(method); 174 } 175 } 176 } 177 178 String fullName = classPrefix + name; 179 int inx = fullName.lastIndexOf('.'); 180 if (inx == -1) { 181 return null; 182 } 183 184 String className = fullName.substring(0, inx); 185 String methodName = fullName.substring(inx + 1); 186 187 Class functionClass; 188 try { 189 functionClass = ClassLoaderUtil.getClass(className, true); 190 } 191 catch (ClassNotFoundException ex) { 192 throw new JXPathException( 193 "Cannot invoke extension function " 194 + (namespace != null ? namespace + ":" + name : name), 195 ex); 196 } 197 198 if (methodName.equals("new")) { 199 Constructor constructor = 200 MethodLookupUtils.lookupConstructor(functionClass, parameters); 201 if (constructor != null) { 202 return new ConstructorFunction(constructor); 203 } 204 } 205 else { 206 Method method = 207 MethodLookupUtils.lookupStaticMethod( 208 functionClass, 209 methodName, 210 parameters); 211 if (method != null) { 212 return new MethodFunction(method); 213 } 214 } 215 return null; 216 } 217}