/* * 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.eigenbase.util; import java.lang.reflect.*; import java.nio.*; import java.util.*; import com.google.common.collect.ImmutableList; /** * Static utilities for Java reflection. */ public abstract class ReflectUtil { //~ Static fields/initializers --------------------------------------------- private static Map<Class, Class> primitiveToBoxingMap; private static Map<Class, Method> primitiveToByteBufferReadMethod; private static Map<Class, Method> primitiveToByteBufferWriteMethod; static { primitiveToBoxingMap = new HashMap<Class, Class>(); primitiveToBoxingMap.put(Boolean.TYPE, Boolean.class); primitiveToBoxingMap.put(Byte.TYPE, Byte.class); primitiveToBoxingMap.put(Character.TYPE, Character.class); primitiveToBoxingMap.put(Double.TYPE, Double.class); primitiveToBoxingMap.put(Float.TYPE, Float.class); primitiveToBoxingMap.put(Integer.TYPE, Integer.class); primitiveToBoxingMap.put(Long.TYPE, Long.class); primitiveToBoxingMap.put(Short.TYPE, Short.class); primitiveToByteBufferReadMethod = new HashMap<Class, Method>(); primitiveToByteBufferWriteMethod = new HashMap<Class, Method>(); Method[] methods = ByteBuffer.class.getDeclaredMethods(); for (int i = 0; i < methods.length; ++i) { Method method = methods[i]; Class[] paramTypes = method.getParameterTypes(); if (method.getName().startsWith("get")) { if (!method.getReturnType().isPrimitive()) { continue; } if (paramTypes.length != 1) { continue; } primitiveToByteBufferReadMethod.put( method.getReturnType(), method); // special case for Boolean: treat as byte if (method.getReturnType().equals(Byte.TYPE)) { primitiveToByteBufferReadMethod.put(Boolean.TYPE, method); } } else if (method.getName().startsWith("put")) { if (paramTypes.length != 2) { continue; } if (!paramTypes[1].isPrimitive()) { continue; } primitiveToByteBufferWriteMethod.put(paramTypes[1], method); // special case for Boolean: treat as byte if (paramTypes[1].equals(Byte.TYPE)) { primitiveToByteBufferWriteMethod.put(Boolean.TYPE, method); } } } } //~ Methods ---------------------------------------------------------------- /** * Uses reflection to find the correct java.nio.ByteBuffer "absolute get" * method for a given primitive type. * * @param clazz the Class object representing the primitive type * @return corresponding method */ public static Method getByteBufferReadMethod(Class clazz) { assert clazz.isPrimitive(); return primitiveToByteBufferReadMethod.get(clazz); } /** * Uses reflection to find the correct java.nio.ByteBuffer "absolute put" * method for a given primitive type. * * @param clazz the Class object representing the primitive type * @return corresponding method */ public static Method getByteBufferWriteMethod(Class clazz) { assert clazz.isPrimitive(); return primitiveToByteBufferWriteMethod.get(clazz); } /** * Gets the Java boxing class for a primitive class. * * @param primitiveClass representative class for primitive (e.g. * java.lang.Integer.TYPE) * @return corresponding boxing Class (e.g. java.lang.Integer) */ public static Class getBoxingClass(Class primitiveClass) { assert primitiveClass.isPrimitive(); return primitiveToBoxingMap.get(primitiveClass); } /** * Gets the name of a class with no package qualifiers; if it's an inner * class, it will still be qualified by the containing class (X$Y). * * @param c the class of interest * @return the unqualified name */ public static String getUnqualifiedClassName(Class c) { String className = c.getName(); int lastDot = className.lastIndexOf('.'); if (lastDot < 0) { return className; } return className.substring(lastDot + 1); } /** * Composes a string representing a human-readable method name (with neither * exception nor return type information). * * @param declaringClass class on which method is defined * @param methodName simple name of method without signature * @param paramTypes method parameter types * @return unmangled method name */ public static String getUnmangledMethodName( Class declaringClass, String methodName, Class[] paramTypes) { StringBuilder sb = new StringBuilder(); sb.append(declaringClass.getName()); sb.append("."); sb.append(methodName); sb.append("("); for (int i = 0; i < paramTypes.length; ++i) { if (i > 0) { sb.append(", "); } sb.append(paramTypes[i].getName()); } sb.append(")"); return sb.toString(); } /** * Composes a string representing a human-readable method name (with neither * exception nor return type information). * * @param method method whose name is to be generated * @return unmangled method name */ public static String getUnmangledMethodName( Method method) { return getUnmangledMethodName( method.getDeclaringClass(), method.getName(), method.getParameterTypes()); } /** * Implements the {@link Glossary#VISITOR_PATTERN} via reflection. The basic * technique is taken from <a * href="http://www.javaworld.com/javaworld/javatips/jw-javatip98.html">a * Javaworld article</a>. For an example of how to use it, see {@code * ReflectVisitorTest}. Visit method lookup follows the same rules as if * compile-time resolution for VisitorClass.visit(VisiteeClass) were * performed. An ambiguous match due to multiple interface inheritance * results in an IllegalArgumentException. A non-match is indicated by * returning false. * * @param visitor object whose visit method is to be invoked * @param visitee object to be passed as a parameter to the visit * method * @param hierarchyRoot if non-null, visitor method will only be invoked if * it takes a parameter whose type is a subtype of * hierarchyRoot * @param visitMethodName name of visit method, e.g. "visit" * @return true if a matching visit method was found and invoked */ public static boolean invokeVisitor( ReflectiveVisitor visitor, Object visitee, Class hierarchyRoot, String visitMethodName) { return invokeVisitorInternal( visitor, visitee, hierarchyRoot, visitMethodName); } /** * Shared implementation of the two forms of invokeVisitor. * * @param visitor object whose visit method is to be invoked * @param visitee object to be passed as a parameter to the visit * method * @param hierarchyRoot if non-null, visitor method will only be invoked if * it takes a parameter whose type is a subtype of * hierarchyRoot * @param visitMethodName name of visit method, e.g. "visit" * @return true if a matching visit method was found and invoked */ private static boolean invokeVisitorInternal( Object visitor, Object visitee, Class hierarchyRoot, String visitMethodName) { Class<?> visitorClass = visitor.getClass(); Class visiteeClass = visitee.getClass(); Method method = lookupVisitMethod( visitorClass, visiteeClass, visitMethodName); if (method == null) { return false; } if (hierarchyRoot != null) { Class paramType = method.getParameterTypes()[0]; if (!hierarchyRoot.isAssignableFrom(paramType)) { return false; } } try { method.invoke( visitor, visitee); } catch (IllegalAccessException ex) { throw Util.newInternal(ex); } catch (InvocationTargetException ex) { // visit methods aren't allowed to have throws clauses, // so the only exceptions which should come // to us are RuntimeExceptions and Errors Throwable t = ex.getTargetException(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else { throw new AssertionError(t.getClass().getName()); } } return true; } /** * Looks up a visit method. * * @param visitorClass class of object whose visit method is to be invoked * @param visiteeClass class of object to be passed as a parameter to the * visit method * @param visitMethodName name of visit method * @return method found, or null if none found */ public static Method lookupVisitMethod( Class<?> visitorClass, Class<?> visiteeClass, String visitMethodName) { return lookupVisitMethod( visitorClass, visiteeClass, visitMethodName, Collections.<Class>emptyList()); } /** * Looks up a visit method taking additional parameters beyond the * overloaded visitee type. * * @param visitorClass class of object whose visit method is to be * invoked * @param visiteeClass class of object to be passed as a parameter * to the visit method * @param visitMethodName name of visit method * @param additionalParameterTypes list of additional parameter types * @return method found, or null if none found * @see #createDispatcher(Class, Class) */ public static Method lookupVisitMethod( Class<?> visitorClass, Class<?> visiteeClass, String visitMethodName, List<Class> additionalParameterTypes) { // Prepare an array to re-use in recursive calls. The first argument // will have the visitee class substituted into it. Class<?>[] paramTypes = new Class[1 + additionalParameterTypes.size()]; int iParam = 0; paramTypes[iParam++] = null; for (Class<?> paramType : additionalParameterTypes) { paramTypes[iParam++] = paramType; } // Cache Class to candidate Methods, to optimize the case where // the original visiteeClass has a diamond-shaped interface inheritance // graph. (This is common, for example, in JMI.) The idea is to avoid // iterating over a single interface's method more than once in a call. Map<Class<?>, Method> cache = new HashMap<Class<?>, Method>(); return lookupVisitMethod( visitorClass, visiteeClass, visitMethodName, paramTypes, cache); } private static Method lookupVisitMethod( final Class<?> visitorClass, final Class<?> visiteeClass, final String visitMethodName, final Class<?>[] paramTypes, final Map<Class<?>, Method> cache) { // Use containsKey since the result for a Class might be null. if (cache.containsKey(visiteeClass)) { return cache.get(visiteeClass); } Method candidateMethod = null; paramTypes[0] = visiteeClass; try { candidateMethod = visitorClass.getMethod( visitMethodName, paramTypes); cache.put(visiteeClass, candidateMethod); return candidateMethod; } catch (NoSuchMethodException ex) { // not found: carry on with lookup } Class<?> superClass = visiteeClass.getSuperclass(); if (superClass != null) { candidateMethod = lookupVisitMethod( visitorClass, superClass, visitMethodName, paramTypes, cache); } Class<?>[] interfaces = visiteeClass.getInterfaces(); for (int i = 0; i < interfaces.length; ++i) { Method method = lookupVisitMethod( visitorClass, interfaces[i], visitMethodName, paramTypes, cache); if (method != null) { if (candidateMethod != null) { if (!method.equals(candidateMethod)) { Class<?> c1 = method.getParameterTypes()[0]; Class<?> c2 = candidateMethod.getParameterTypes()[0]; if (c1.isAssignableFrom(c2)) { // c2 inherits from c1, so keep candidateMethod // (which is more specific than method) continue; } else if (c2.isAssignableFrom(c1)) { // c1 inherits from c2 (method is more specific // than candidate method), so fall through // to set candidateMethod = method } else { // c1 and c2 are not directly related throw new IllegalArgumentException( "dispatch ambiguity between " + candidateMethod + " and " + method); } } } candidateMethod = method; } } cache.put(visiteeClass, candidateMethod); return candidateMethod; } /** * Creates a dispatcher for calls to {@link #lookupVisitMethod}. The * dispatcher caches methods between invocations. * * @param visitorBaseClazz Visitor base class * @param visiteeBaseClazz Visitee base class * @return cache of methods */ public static <R extends ReflectiveVisitor, E> ReflectiveVisitDispatcher<R, E> createDispatcher( final Class<R> visitorBaseClazz, final Class<E> visiteeBaseClazz) { assert ReflectiveVisitor.class.isAssignableFrom(visitorBaseClazz); assert Object.class.isAssignableFrom(visiteeBaseClazz); return new ReflectiveVisitDispatcher<R, E>() { final Map<List<Object>, Method> map = new HashMap<List<Object>, Method>(); public Method lookupVisitMethod( Class<? extends R> visitorClass, Class<? extends E> visiteeClass, String visitMethodName) { return lookupVisitMethod( visitorClass, visiteeClass, visitMethodName, Collections.<Class>emptyList()); } public Method lookupVisitMethod( Class<? extends R> visitorClass, Class<? extends E> visiteeClass, String visitMethodName, List<Class> additionalParameterTypes) { final List<Object> key = ImmutableList.of( visitorClass, visiteeClass, visitMethodName, additionalParameterTypes); Method method = map.get(key); if (method == null) { if (map.containsKey(key)) { // We already looked for the method and found nothing. } else { method = ReflectUtil.lookupVisitMethod( visitorClass, visiteeClass, visitMethodName, additionalParameterTypes); map.put(key, method); } } return method; } public boolean invokeVisitor( R visitor, E visitee, String visitMethodName) { return ReflectUtil.invokeVisitor( visitor, visitee, visiteeBaseClazz, visitMethodName); } }; } /** * Creates a dispatcher for calls to a single multi-method on a particular * object. * * <p>Calls to that multi-method are resolved by looking for a method on * the runtime type of that object, with the required name, and with * the correct type or a subclass for the first argument, and precisely the * same types for other arguments. * * <p>For instance, a dispatcher created for the method * * <blockquote>String foo(Vehicle, int, List)</blockquote> * * could be used to call the methods * * <blockquote>String foo(Car, int, List)<br> * String foo(Bus, int, List)</blockquote> * * (because Car and Bus are subclasses of Vehicle, and they occur in the * polymorphic first argument) but not the method * * <blockquote>String foo(Car, int, ArrayList)</blockquote> * * (only the first argument is polymorphic). * * <p>You must create an implementation of the method for the base class. * Otherwise throws {@link IllegalArgumentException}. * * @param returnClazz Return type of method * @param visitor Object on which to invoke the method * @param methodName Name of method * @param arg0Clazz Base type of argument zero * @param otherArgClasses Types of remaining arguments */ public static <E, T> MethodDispatcher<T> createMethodDispatcher( final Class<T> returnClazz, final ReflectiveVisitor visitor, final String methodName, final Class<E> arg0Clazz, final Class... otherArgClasses) { final List<Class> otherArgClassList = ImmutableList.copyOf(otherArgClasses); @SuppressWarnings({"unchecked" }) final ReflectiveVisitDispatcher<ReflectiveVisitor, E> dispatcher = createDispatcher( (Class<ReflectiveVisitor>) visitor.getClass(), arg0Clazz); return new MethodDispatcher<T>() { public T invoke(Object... args) { Method method = lookupMethod(args[0]); try { final Object o = method.invoke(visitor, args); return returnClazz.cast(o); } catch (IllegalAccessException e) { throw Util.newInternal( e, "While invoking method '" + method + "'"); } catch (InvocationTargetException e) { throw Util.newInternal( e, "While invoking method '" + method + "'"); } } private Method lookupMethod(final Object arg0) { if (!arg0Clazz.isInstance(arg0)) { throw new IllegalArgumentException(); } Method method = dispatcher.lookupVisitMethod( visitor.getClass(), (Class<? extends E>) arg0.getClass(), methodName, otherArgClassList); if (method == null) { List<Class> classList = new ArrayList<Class>(); classList.add(arg0Clazz); classList.addAll(otherArgClassList); throw new IllegalArgumentException( "Method not found: " + methodName + "(" + classList + ")"); } return method; } }; } //~ Inner Classes ---------------------------------------------------------- /** * Can invoke a method on an object of type E with return type T. * * @param <T> Return type of method */ public interface MethodDispatcher<T> { /** * Invokes method on an object with a given set of arguments. * * @param args Arguments to method * @return Return value of method */ T invoke(Object... args); } } // End ReflectUtil.java