/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * 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. ********************************************************************************/ package com.analog.lyric.collect; import static java.util.Objects.*; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import org.eclipse.jdt.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.primitives.Primitives; /** * Static utility methods related to super classes. */ public abstract class Supers { // NOTE: Originally I tried to use the CacheBuilder class from Google's guava library, but that created // problems when running in MATLAB because MATLAB uses an old and incompatible google-collections library // and puts it in the static java class path, with the result that the guava code ended up loading an // older version of the Objects utility class. The only way I could figure out to work around this was // to actually edit MATLAB's internal classpath.txt file to put the desired guava jar before google-collections. // In this case, it is easy enough to implement our own cache, but this may come up again - cbarber private static class SuperClassCache extends ClassValue<ImmutableList<Class<?>>> { @Override protected ImmutableList<Class<?>> computeValue(@Nullable Class<?> c) { ArrayList<Class<?>> supers = new ArrayList<Class<?>>(); while (c != null) { Class<?> s = c.getSuperclass(); if (s != null) { supers.add(s); } c = s; } Collections.reverse(supers); // HACK: the google-collections version of guava contained in MATLAB only has // the Iterable version of copyOf, so we need to make sure that is what we are using // here. See comment above. return ImmutableList.copyOf((Iterable<Class<?>>)supers); } } private static final SuperClassCache _superClassCache = new SuperClassCache(); /** * Returns an array of super classes above {@code c}. The first class will always be {@link Object} * or the array will be empty. Returns a cached value. */ public static ImmutableList<Class<?>> superClasses(Class<?> c) { return _superClassCache.get(c); } /** * Looks up and invokes method with given name and applicable to given object and arguments * using reflection. * * WARNING: this is primarily intended for use in writing unit tests. Probably should * avoid using in production code. * * @param object either the {@link Class} of static method to be called, or object on which * method should be invoked. * @param methodName is the name of the method to invoke. * @param args are the arguments to pass to the method. * @return return value from method. * @throws NoSuchMethodException if no method can be found with given name and matching the types * of the arguments. * @throws InvocationTargetException wraps exception thrown by reflectively invoked method. * @throws IllegalAccessException * @see #lookupMethod(Object, String, Object...) * * @since 0.05 */ public static @Nullable Object invokeMethod(Object object, String methodName, @Nullable Object ... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { return invokeMethod(object, object instanceof Class<?> ? (Class<?>)object : object.getClass(), methodName, args); } /** * Looks up and invokes method with given name and applicable to given object and arguments * using reflection. * * WARNING: this is primarily intended for use in writing unit tests. Probably should * avoid using in production code. * * @param object either the {@link Class} of static method to be called, or object on which * method should be invoked. * @param declaredClass is the class in which to look up the method. * @param methodName is the name of the method to invoke. * @param args are the arguments to pass to the method. * @return return value from method. * @throws NoSuchMethodException if no method can be found with given name and matching the types * of the arguments. * @throws InvocationTargetException wraps exception thrown by reflectively invoked method. * @throws IllegalAccessException * @see #lookupMethod(Object, String, Object...) * * @since 0.06 */ public static @Nullable Object invokeMethod(Object object, Class<?> declaredClass, String methodName, @Nullable Object ... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method method = lookupMethod(declaredClass, methodName, args); if (method.isVarArgs()) { Class<?>[] declaredTypes = method.getParameterTypes(); int declaredSize = declaredTypes.length; int nArgs = args == null ? 0 : args.length; if (nArgs != declaredSize || !declaredTypes[declaredSize - 1].isInstance(requireNonNull(args)[declaredSize - 1])) { Class<?> varArgType = declaredTypes[declaredSize - 1].getComponentType(); Object varargs = args == null ? null : Array.newInstance(varArgType, nArgs + 1 - declaredSize); if (args != null) { for (int i = 0, j = declaredSize - 1; j < nArgs; ++i, ++j) { Array.set(varargs, i, args[j]); } } if (nArgs > 0) { args = Arrays.copyOf(args, declaredSize, Object[].class); } else { args = new Object[declaredSize]; } requireNonNull(args)[declaredSize - 1] = varargs; } } if (!Modifier.isPublic(declaredClass.getModifiers())) { // Java for some reason does not let you reflectively invoke a method on a non-public // class, even if the method binding is public and is declared in a public superclass // or interface. As a workaround, try to find method binding from public super class. Class<?>[] parameterTypes = method.getParameterTypes(); Iterable<Class<?>> superclassesAndInterfaces = Iterables.concat(superClasses(declaredClass).reverse(), Arrays.asList(declaredClass.getInterfaces())); for (Class<?> c : superclassesAndInterfaces) { if (Modifier.isPublic(c.getModifiers())) { try { method = c.getMethod(methodName, parameterTypes); break; } catch (NoSuchMethodException ex) { } } } } return method.invoke(object, args); } /** * Determine if {@code subClass} is a subtype of {@code superClass}. * <p> * This is simply syntactic sugar for: * <pre> * superClass.{@linkplain Class#isAssignableFrom(Class) isAssignableFrom}.(subClass) * <pre> * * @since 0.07 */ public static boolean isSubclassOf(Class<?> subClass, Class<?> superClass) { return superClass.isAssignableFrom(subClass); } /** * Determine if {@code subClass} is a strict subtype of {@code superClass}. * <p> * This is the same as {@link #isSubclassOf} but is false if * {@code subClass} and {@code superClass} are the same. * * @since 0.07 */ public static boolean isStrictSubclassOf(Class<?> subClass, Class<?> superClass) { return subClass != superClass && superClass.isAssignableFrom(subClass); } /** * Looks up method with given name and that can be called with the given arguments. * * WARNING: this is primarily intended for use in writing unit tests. Probably should * avoid using in production code. * * @param object if this is a {@link Class}, this will look for match in that class, otherwise * it will look in its runtime class ({@link Object#getClass()}. Must not be null. * @param methodName is the name of the method to find. * @param args are the runtime arguments to the method. * @return {@link Method} which can be called with given arguments. * @throws NoSuchMethodException if no matching method is found. * * @see #invokeMethod(Object, String, Object...) * * @since 0.05 */ public static Method lookupMethod(Object object, String methodName, @Nullable Object ... args) throws NoSuchMethodException { Class<?> objClass = object instanceof Class ? (Class<?>)object : object.getClass(); int nArgs = args == null ? 0 : args.length; Class<?>[] argTypes = new Class<?>[nArgs]; if (args != null) { for (int i = 0; i < nArgs; ++i) { argTypes[i] = args[i] == null ? null : args[i].getClass(); } } // First try direct match: try { return objClass.getMethod(methodName, argTypes); } catch (NoSuchMethodException ex) { } // Next try unwrapping arg types Class<?>[] unwrappedArgTypes = new Class<?>[nArgs]; for (int i = 0; i < nArgs; ++i) { Class<?> argType = argTypes[i]; unwrappedArgTypes[i] = argType == null ? null : Primitives.unwrap(argType); } try { return objClass.getMethod(methodName, unwrappedArgTypes); } catch (NoSuchMethodException ex) { } // Finally, walk through all the methods and return the first one that matches. outer: for (Method method : objClass.getMethods()) { if (!methodName.equals(method.getName())) { continue outer; } Class<?>[] declaredTypes = method.getParameterTypes(); int end = declaredTypes.length; Class<?> varArgType = null; if (method.isVarArgs()) { --end; varArgType = Primitives.wrap(declaredTypes[end].getComponentType()); if (end > argTypes.length) { continue outer; } } else if (end != argTypes.length) { continue outer; } for (int i = 0; i < end; ++i) { Class<?> declaredType = declaredTypes[i]; if (argTypes[i] == null) { // null matches all non-primitive declared types (at least until // java comes up with an actual non-null type declaration) if (declaredType.isPrimitive()) { continue outer; } } else { declaredType = Primitives.wrap(declaredType); if (!declaredType.isAssignableFrom(argTypes[i])) { continue outer; } } } if (varArgType != null) { for (int i = end; i < argTypes.length; ++i) { Class<?> argType = argTypes[i]; if (argType != null && !varArgType.isAssignableFrom(argType)) { continue outer; } } } return method; } StringBuilder sb = new StringBuilder(methodName); sb.append("("); for (int i = 0; i < nArgs; ++i) { if (i > 0) { sb.append(","); } Class<?> argType = unwrappedArgTypes[i]; sb.append(argType == null ? "null" : argType.getSimpleName()); } sb.append(")"); String msg = String.format("No method in %s with signature compatible with %s", objClass.getSimpleName(), sb.toString()); throw new NoSuchMethodException(msg); } /** * Returns array containing {@code elements} with {@linkplain Class#getComponentType() component type} * set to specified type. * <p> * This will return the {@code elements} array itself if its {@linkplain Class#getComponentType() component type} * is already a subclass of {@code type}. It will return null if any of the elements is not a * subclass of {@code type}. * <p> * @since 0.08 */ @SuppressWarnings("unchecked") public static @Nullable <T> T[] narrowArrayOf(Class<T> type, Object[] elements) { if (type.isAssignableFrom(elements.getClass().getComponentType())) { return (T[])elements; } for (Object obj : elements) { if (!type.isInstance(obj)) return null; } final int n = elements.length; T[] array = (T[])Array.newInstance(type, n); for (int i = 0; i < n; ++i) { array[i] = type.cast(elements[i]); } return array; } /** * Returns a new array containing {@code elements} with component type set to the nearest common superclass * {@link #nearestCommonSuperClass(Object...)} of the objects it contains. */ public static <T> T[] narrowArrayOf(T[] elements) { return narrowArrayOf(Object.class, Integer.MAX_VALUE, elements); } /** * Returns a new array containing {@code elements}, which must be a subclass of {@code rootClass}, * with component type set to nearest common superclass ({@link #nearestCommonSuperClass(Object...)}) * of the objects it contains that is no deeper than {@code maxClassDepthBelowRoot} below {@code rootClass}. * * @see #narrowArrayOf(Object...) * @see #copyOf(Class, Object...) */ public static <T> T[] narrowArrayOf( Class<? extends Object> rootClass, int maxRelativeClassDepth, T[] elements) { if (elements.length == 0) { if (rootClass.equals(elements.getClass().getComponentType())) { return elements; } else { @SuppressWarnings("unchecked") final T[] array = (T[])Array.newInstance(rootClass, 0); return array; } } Class<?> c = nearestCommonSuperClass(elements); if (maxRelativeClassDepth < 500) { ImmutableList<Class<?>> supers = superClasses(c); final int maxClassDepth = numberOfSuperClasses(rootClass) + maxRelativeClassDepth; if (maxClassDepth < supers.size()) { c = supers.get(maxClassDepth); } } return copyOf(c, elements); } /** * Returns a new array containing {@code elements} with specified {@code componentType}, which must * be a super class or interface of all the elements. * * @see #narrowArrayOf(Object[]) * @see #copyOf(Class, Object[]) */ public static <T> T[] copyOf(Class<?> componentType, T[] elements) { final int n = elements.length; @SuppressWarnings("unchecked") T[] array = (T[])Array.newInstance(componentType, n); for (int i = 0; i <n; ++i) { array[i] = elements[i]; } return array; } /** * Returns nearest common super class between {@code c1} and {@code c2}. * <ul> * <li>If {@code c1} and {@code c2} are equal, then {@code c1} will be returned, * even if it is an interface or primitive. * <li>Likewise if {@code c1} is assignable from {@code c2} ({@link Class#isAssignableFrom}) * then {@code c1} will be returned even if it is an interface, and vice versa if the * {@code c2} is assignable from {@code c1}. * <li>If the above is not true, and either argument is a primitive, then null will be returned. * <li>If the above is not true, and either argument is an interface, then {@link Object} will * be returned. * <li>Otherwise, the nearest superclass of both {@code c1} and {@code c2} will be returned. * </ul> */ public static @Nullable Class<?> nearestCommonSuperClass(Class<?> c1, Class<?> c2) { if (c1.isAssignableFrom(c2)) { return c1; } else if (c2.isAssignableFrom(c1)) { return c2; } // At this point we know that the common superclass cannot be c1 or c2. if (c1.isPrimitive() || c2.isPrimitive()) { return null; } if (c1.isInterface() || c2.isInterface()) { return Object.class; } ImmutableList<Class<?>> supers1 = superClasses(c1); ImmutableList<Class<?>> supers2 = superClasses(c2); Class<?> common = Object.class; for (int i = 0, end = Math.min(supers1.size(), supers2.size()); i < end; ++i) { Class<?> super1 = supers1.get(i); if (super1.equals(supers2.get(i))) { common = super1; } else { break; } } return common; } /** * Returns the nearest common superclass (not interface) for all of the objects. */ public static <T> Class<?> nearestCommonSuperClass(@Nullable T obj, Object ... moreObjects) { Class<?> superclass = nearestCommonSuperClass(moreObjects); if (obj != null) { superclass = nearestCommonSuperClass(superclass, obj.getClass()); } assert(superclass != null); return superclass; } /** * Returns the nearest common superclass (not interface) for all of the objects. * Returns {@link Object} if array contains no objects. */ public static <T> Class<?> nearestCommonSuperClass(T[] objects) { int n = objects.length; if (n == 0) { return Object.class; } // Skip over empty slots int i = 0; while (objects[i] == null) { if (++i == n) { return Object.class; } } Class<?> declaredType = objects.getClass().getComponentType(); if (declaredType.isInterface()) { declaredType = Object.class; } else if (declaredType.isEnum() || Modifier.isFinal(declaredType.getModifiers())) { // There cannot be any subclasses return declaredType; } Class<?> c = requireNonNull(objects[i].getClass()); // Compute common superclass for all of the classes of elements in the array. while (++i < n) { if (objects[i] == null) { continue; } if (c.isAssignableFrom(declaredType)) { break; } c = requireNonNull(nearestCommonSuperClass(c, objects[i].getClass())); } assert(c != null); return c; } /** * Computes the number of super classes above (and not including) this one. Returns 0 if * {@code c} is {@link Object}, a primitive type or is an interface. */ public static int numberOfSuperClasses(Class<?> c) { return superClasses(c).size(); } // TODO: methods for computing nearest common super interface (or class). Much harder because it // requires searching two DAGs instead of just a list. Because some interfaces such as Clonable, // Comparable, and Serializable are very common, methods dealing with interface probably should // specify root interfaces of interest. }