blob: 8da0a99dc5a7523ed718871de314d5b5d8aa71fc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.cache.query.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.geode.cache.query.AmbiguousNameException;
import org.apache.geode.cache.query.NameNotFoundException;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.QueryInvocationTargetException;
import org.apache.geode.cache.query.internal.types.TypeUtils;
/**
* Utility class for mapping operations in the query language to Java methods
*
* @version $Revision: 1.1 $
*/
public class MethodDispatch {
private Class _targetClass;
private String _methodName;
private Class[] _argTypes;
private Method _method; // remember the right method
private MethodInvocationAuthorizer _methodInvocationAuthorizer;
public MethodDispatch(MethodInvocationAuthorizer methodInvocationAuthorizer, Class targetClass,
String methodName, List argTypes) throws NameResolutionException {
_targetClass = targetClass;
_methodName = methodName;
_argTypes = (Class[]) argTypes.toArray(new Class[argTypes.size()]);
_methodInvocationAuthorizer = methodInvocationAuthorizer;
resolve();
// override security in case this is a method on a nonpublic class
// with a public method
_method.setAccessible(true);
}
public Object invoke(Object target, List args)
throws NameNotFoundException, QueryInvocationTargetException {
Object[] argsArray = args.toArray();
try {
_methodInvocationAuthorizer.authorizeMethodInvocation(_method, target);
return _method.invoke(target, argsArray);
} catch (IllegalAccessException e) {
throw new NameNotFoundException(
String.format(
"Method ' %s ' in class ' %s ' is not accessible to the query processor",
new Object[] {_method.getName(), target.getClass().getName()}),
e);
} catch (InvocationTargetException e) {
// if targetException is Exception, wrap it, otherwise wrap the InvocationTargetException
// itself
Throwable t = e.getTargetException();
if (t instanceof Exception)
throw new QueryInvocationTargetException(t);
throw new QueryInvocationTargetException(e);
}
}
private void resolve() throws NameResolutionException {
// if argTypes contains a null, then go directly to resolveGeneral(),
// otherwise try to resolve on the specific types first
// (a null type gets passed in if the runtime value of the arg is null)
for (int i = 0; i < _argTypes.length; i++)
if (_argTypes[i] == null) {
resolveGeneral();
return;
}
// first try to get the method based on the exact parameter types
try {
_method = _targetClass.getMethod(_methodName, _argTypes);
} catch (NoSuchMethodException e) {
resolveGeneral();
}
}
private void resolveGeneral() throws NameResolutionException {
Method[] allMethods = _targetClass.getMethods();
// keep only ones whose method names match and have the same number of args
List<Method> candidates = new ArrayList<>();
for (int i = 0; i < allMethods.length; i++) {
Method meth = allMethods[i];
/*
* if (Modifier.isStatic(meth.getModifiers())) continue;
*/
if (!meth.getName().equals(_methodName))
continue;
if (meth.getParameterTypes().length != _argTypes.length)
continue;
// are the args all convertible to the parameter types?
if (!TypeUtils.areTypesConvertible(_argTypes, meth.getParameterTypes()))
continue;
candidates.add(meth);
}
if (candidates.isEmpty()) {
throw new NameNotFoundException(
String.format(
"No applicable and accessible method named ' %s ' was found in class ' %s ' for the argument types %s",
new Object[] {_methodName, _targetClass.getName(), Arrays.asList(_argTypes)}));
}
// now we have a list of accessible and applicable method,
// choose the most specific
if (candidates.size() == 1) {
_method = candidates.get(0);
return;
}
sortByDecreasingSpecificity(candidates);
// get the first two methods in the sorted list,
// if they are equally specific, then throw AmbiguousMethodException
Method meth1 = candidates.get(0);
Method meth2 = candidates.get(1);
// if meth1 cannot be type-converted to meth2, then meth1 is not more
// specific than meth2 and the invocation is ambiguous.
// special case a null argument type in this case, since there should
// be not differentiation for those parameter types regarding specificity
if (equalSpecificity(meth1, meth2, _argTypes))
throw new AmbiguousNameException(
String.format(
"Two or more maximally specific methods were found for the method named ' %s ' in class ' %s ' for the argument types: %s",
new Object[] {meth1.getName(), _targetClass.getName(),
Arrays.asList(_argTypes)}));
_method = meth1;
}
private void sortByDecreasingSpecificity(List methods) {
Collections.sort(methods, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Method m1 = (Method) o1;
Method m2 = (Method) o2;
if (m1.equals(m2))
return 0;
boolean convertible1 = methodConvertible(m1, m2);
boolean convertible2 = methodConvertible(m2, m1);
// check to see if they are convertible both ways or neither way
if (convertible1 == convertible2)
return 0;
return convertible1 ? -1 : 1;
}
});
}
protected boolean methodConvertible(Method m1, Method m2) {
boolean declaringClassesConvertible =
TypeUtils.isTypeConvertible(m1.getDeclaringClass(), m2.getDeclaringClass());
boolean paramsConvertible = true;
Class[] p1 = m1.getParameterTypes();
Class[] p2 = m2.getParameterTypes();
for (int i = 0; i < p1.length; i++) {
if (!TypeUtils.isTypeConvertible(p1[i], p2[i])) {
paramsConvertible = false;
break;
}
}
return declaringClassesConvertible && paramsConvertible;
}
private boolean equalSpecificity(Method m1, Method m2, Class[] argTypes) {
// if the m1 is not convertible to m2, then definitely equal specificity,
// since this would be ambiguous even in Java
if (!methodConvertible(m1, m2))
return true;
// if there is at least one param type that is more specific
// ignoring parameters with a null argument, then
// answer false, otherwise true.
Class[] p1 = m1.getParameterTypes();
Class[] p2 = m2.getParameterTypes();
for (int i = 0; i < p1.length; i++) {
if (argTypes[i] != null && p1[i] != p2[i] && TypeUtils.isTypeConvertible(p1[i], p2[i])) // assumes
// m1
// is
// <=
// m2
// in
// specificity
return false;
}
return true;
}
}