/* * 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.util.*; import java.lang.reflect.*; import org.apache.geode.cache.query.*; import org.apache.geode.cache.query.internal.types.TypeUtils; import org.apache.geode.internal.i18n.LocalizedStrings; /** * 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 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; } }