com.gemstone.gemfire.cache.query.internal.MethodDispatch Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gemfire-core Show documentation
Show all versions of gemfire-core Show documentation
SnappyData store based off Pivotal GemFireXD
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed 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. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.cache.query.internal;
import java.util.*;
import java.lang.reflect.*;
import com.gemstone.gemfire.cache.query.*;
import com.gemstone.gemfire.cache.query.internal.types.TypeUtils;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
/**
* Utility class for mapping operations in the query language to Java methods
*
* @author Eric Zoerner
* @version $Revision: 1.1 $
*/
public class MethodDispatch
{
private Class _targetClass;
private String _methodName;
private Class[] _argTypes;
private Method _method; // remember the right method
public MethodDispatch(Class targetClass, String methodName, List argTypes)
throws NameResolutionException
{
_targetClass = targetClass;
_methodName = methodName;
_argTypes = (Class[])argTypes.toArray(new Class[argTypes.size()]);
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
{
return _method.invoke(target, argsArray);
}
catch (IllegalAccessException e)
{
throw new NameNotFoundException(LocalizedStrings.MethodDispatch_METHOD_0_IN_CLASS_1_IS_NOT_ACCESSIBLE_TO_THE_QUERY_PROCESSOR.toLocalizedString(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 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(LocalizedStrings.MethodDispatch_NO_APPLICABLE_AND_ACCESSIBLE_METHOD_NAMED_0_WAS_FOUND_IN_CLASS_1_FOR_THE_ARGUMENT_TYPES_2.toLocalizedString(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 = (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 = (Method)candidates.get(0);
Method meth2 = (Method)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(LocalizedStrings.MethodDispatch_TWO_OR_MORE_MAXIMALLY_SPECIFIC_METHODS_WERE_FOUND_FOR_THE_METHOD_NAMED_0_IN_CLASS_1_FOR_THE_ARGUMENT_TYPES_2.toLocalizedString(new Object[] {meth1.getName(), _targetClass.getName(), Arrays.asList(_argTypes)}));
_method = meth1;
}
private void sortByDecreasingSpecificity(List methods)
{
Collections.sort(methods, new Comparator()
{
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;
}
}