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.util; 019 020import java.lang.reflect.Constructor; 021import java.lang.reflect.Method; 022import java.lang.reflect.Modifier; 023import java.util.Arrays; 024 025import org.apache.commons.jxpath.ExpressionContext; 026import org.apache.commons.jxpath.JXPathException; 027 028/** 029 * Method lookup utilities, which find static and non-static methods as well as constructors based on a name and list of parameters. 030 */ 031public class MethodLookupUtils { 032 033 private static final int NO_MATCH = 0; 034 private static final int APPROXIMATE_MATCH = 1; 035 private static final int EXACT_MATCH = 2; 036 037 /** 038 * Look up a constructor. 039 * 040 * @param targetClass the class constructed 041 * @param parameters arguments 042 * @return Constructor found if any. 043 */ 044 public static Constructor lookupConstructor(final Class targetClass, final Object[] parameters) { 045 boolean tryExact = true; 046 final int count = parameters == null ? 0 : parameters.length; 047 final Class[] types = new Class[count]; 048 for (int i = 0; i < count; i++) { 049 final Object param = parameters[i]; 050 if (param != null) { 051 types[i] = param.getClass(); 052 } else { 053 types[i] = null; 054 tryExact = false; 055 } 056 } 057 Constructor constructor = null; 058 if (tryExact) { 059 // First - without type conversion 060 try { 061 constructor = targetClass.getConstructor(types); 062 if (constructor != null) { 063 return constructor; 064 } 065 } catch (final NoSuchMethodException ignore) { // NOPMD 066 // Ignore 067 } 068 } 069 int currentMatch = 0; 070 boolean ambiguous = false; 071 // Then - with type conversion 072 final Constructor[] constructors = targetClass.getConstructors(); 073 for (final Constructor constructor2 : constructors) { 074 final int match = matchParameterTypes(constructor2.getParameterTypes(), parameters); 075 if (match != NO_MATCH) { 076 if (match > currentMatch) { 077 constructor = constructor2; 078 currentMatch = match; 079 ambiguous = false; 080 } else if (match == currentMatch) { 081 ambiguous = true; 082 } 083 } 084 } 085 if (ambiguous) { 086 throw new JXPathException("Ambiguous constructor " + Arrays.asList(parameters)); 087 } 088 return constructor; 089 } 090 091 /** 092 * Look up a method. 093 * 094 * @param targetClass owning class 095 * @param name method name 096 * @param parameters method parameters 097 * @return Method found if any 098 */ 099 public static Method lookupMethod(Class targetClass, final String name, final Object[] parameters) { 100 if (parameters == null || parameters.length < 1 || parameters[0] == null) { 101 return null; 102 } 103 if (matchType(targetClass, parameters[0]) == NO_MATCH) { 104 return null; 105 } 106 targetClass = TypeUtils.convert(parameters[0], targetClass).getClass(); 107 boolean tryExact = true; 108 final int count = parameters.length - 1; 109 final Class[] types = new Class[count]; 110 final Object[] arguments = new Object[count]; 111 for (int i = 0; i < count; i++) { 112 final Object param = parameters[i + 1]; 113 arguments[i] = param; 114 if (param != null) { 115 types[i] = param.getClass(); 116 } else { 117 types[i] = null; 118 tryExact = false; 119 } 120 } 121 Method method = null; 122 if (tryExact) { 123 // First - without type conversion 124 try { 125 method = targetClass.getMethod(name, types); 126 if (method != null && !Modifier.isStatic(method.getModifiers())) { 127 return method; 128 } 129 } catch (final NoSuchMethodException ignore) { // NOPMD 130 // Ignore 131 } 132 } 133 int currentMatch = 0; 134 boolean ambiguous = false; 135 // Then - with type conversion 136 final Method[] methods = targetClass.getMethods(); 137 for (final Method method2 : methods) { 138 if (!Modifier.isStatic(method2.getModifiers()) && method2.getName().equals(name)) { 139 final int match = matchParameterTypes(method2.getParameterTypes(), arguments); 140 if (match != NO_MATCH) { 141 if (match > currentMatch) { 142 method = method2; 143 currentMatch = match; 144 ambiguous = false; 145 } else if (match == currentMatch) { 146 ambiguous = true; 147 } 148 } 149 } 150 } 151 if (ambiguous) { 152 throw new JXPathException("Ambiguous method call: " + name); 153 } 154 return method; 155 } 156 157 /** 158 * Look up a static method. 159 * 160 * @param targetClass the owning class 161 * @param name method name 162 * @param parameters method parameters 163 * @return Method found if any 164 */ 165 public static Method lookupStaticMethod(final Class targetClass, final String name, final Object[] parameters) { 166 boolean tryExact = true; 167 final int count = parameters == null ? 0 : parameters.length; 168 final Class[] types = new Class[count]; 169 for (int i = 0; i < count; i++) { 170 final Object param = parameters[i]; 171 if (param != null) { 172 types[i] = param.getClass(); 173 } else { 174 types[i] = null; 175 tryExact = false; 176 } 177 } 178 Method method = null; 179 if (tryExact) { 180 // First - without type conversion 181 try { 182 method = targetClass.getMethod(name, types); 183 if (method != null && Modifier.isStatic(method.getModifiers())) { 184 return method; 185 } 186 } catch (final NoSuchMethodException ignore) { // NOPMD 187 // Ignore 188 } 189 } 190 int currentMatch = 0; 191 boolean ambiguous = false; 192 // Then - with type conversion 193 final Method[] methods = targetClass.getMethods(); 194 for (final Method method2 : methods) { 195 if (Modifier.isStatic(method2.getModifiers()) && method2.getName().equals(name)) { 196 final int match = matchParameterTypes(method2.getParameterTypes(), parameters); 197 if (match != NO_MATCH) { 198 if (match > currentMatch) { 199 method = method2; 200 currentMatch = match; 201 ambiguous = false; 202 } else if (match == currentMatch) { 203 ambiguous = true; 204 } 205 } 206 } 207 } 208 if (ambiguous) { 209 throw new JXPathException("Ambiguous method call: " + name); 210 } 211 return method; 212 } 213 214 /** 215 * Return a match code of objects to types. 216 * 217 * @param types Class[] of expected types 218 * @param parameters Object[] to attempt to match 219 * @return int code 220 */ 221 private static int matchParameterTypes(final Class[] types, final Object[] parameters) { 222 int pi = 0; 223 if (types.length >= 1 && ExpressionContext.class.isAssignableFrom(types[0])) { 224 pi++; 225 } 226 final int length = parameters == null ? 0 : parameters.length; 227 if (types.length != length + pi) { 228 return NO_MATCH; 229 } 230 int totalMatch = EXACT_MATCH; 231 for (int i = 0; i < length; i++) { 232 final int match = matchType(types[i + pi], parameters[i]); 233 if (match == NO_MATCH) { 234 return NO_MATCH; 235 } 236 if (match < totalMatch) { 237 totalMatch = match; 238 } 239 } 240 return totalMatch; 241 } 242 243 /** 244 * Return a match code between an object and type. 245 * 246 * @param expected class to test 247 * @param object object to test 248 * @return int code 249 */ 250 private static int matchType(final Class expected, final Object object) { 251 if (object == null) { 252 return APPROXIMATE_MATCH; 253 } 254 final Class actual = object.getClass(); 255 if (expected.equals(actual)) { 256 return EXACT_MATCH; 257 } 258 if (expected.isAssignableFrom(actual)) { 259 return EXACT_MATCH; 260 } 261 if (TypeUtils.canConvert(object, expected)) { 262 return APPROXIMATE_MATCH; 263 } 264 return NO_MATCH; 265 } 266 267 /** 268 * Constructs a new instance. 269 * 270 * @deprecated Will be private in the next major version. 271 */ 272 @Deprecated 273 public MethodLookupUtils() { 274 // empty 275 } 276}