/*******************************************************************************
* 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.
}