blob: 566535bdbc421dac7aba689d86bd46490cdcbc44 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.support_lib_boundary.util;
import android.annotation.TargetApi;
import android.os.Build;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* A set of utility methods used for calling across the support library boundary.
*/
public class BoundaryInterfaceReflectionUtil {
/**
* Check if an object is an instance of {@code className}, resolving {@code className} in
* the object's own ClassLoader. This is useful when {@code obj} may have been created in a
* ClassLoader other than the current one (in which case {@code obj instanceof Foo} would fail
* but {@code instanceOfInOwnClassLoader(obj, "Foo")} may succeed).
*/
public static boolean instanceOfInOwnClassLoader(Object obj, String className) {
try {
ClassLoader loader = obj.getClass().getClassLoader();
// We intentionally set initialize = false because instanceof shouldn't trigger
// static initialization.
Class<?> clazz = Class.forName(className, false, loader);
return clazz.isInstance(obj);
} catch (ClassNotFoundException e) {
// If className is not in the ClassLoader, then this cannot be an instance.
return false;
}
}
/**
* Utility method for fetching a method from {@param delegateLoader}, with the same signature
* (package + class + method name + parameters) as a given method defined in another
* classloader.
*/
public static Method dupeMethod(Method method, ClassLoader delegateLoader)
throws ClassNotFoundException, NoSuchMethodException {
// We're converting one type to another. This is analogous to instantiating the type on the
// other side of the Boundary, so it makes sense to perform static initialization if it
// hasn't already happened (initialize = true).
Class<?> declaringClass =
Class.forName(method.getDeclaringClass().getName(), true, delegateLoader);
// We do not need to convert parameter types across ClassLoaders because we never pass
// BoundaryInterfaces in methods, but pass InvocationHandlers instead.
Class[] parameterClasses = method.getParameterTypes();
return declaringClass.getDeclaredMethod(method.getName(), parameterClasses);
}
/**
* Returns an implementation of the boundary interface named clazz, by delegating method calls
* to the {@link InvocationHandler} invocationHandler.
*/
public static <T> T castToSuppLibClass(Class<T> clazz, InvocationHandler invocationHandler) {
return clazz.cast(
Proxy.newProxyInstance(BoundaryInterfaceReflectionUtil.class.getClassLoader(),
new Class[] {clazz}, invocationHandler));
}
/**
* Create an {@link java.lang.reflect.InvocationHandler} that delegates method calls to
* {@param delegate}, making sure that the {@link java.lang.reflect.Method} and parameters being
* passed to {@param delegate} exist in the same {@link java.lang.ClassLoader} as {@param
* delegate}.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static InvocationHandler createInvocationHandlerFor(final Object delegate) {
return new InvocationHandlerWithDelegateGetter(delegate);
}
/**
* Plural version of {@link #createInvocationHandlerFor(Object)}.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static InvocationHandler[] createInvocationHandlersForArray(final Object[] delegates) {
if (delegates == null) return null;
InvocationHandler[] handlers = new InvocationHandler[delegates.length];
for (int i = 0; i < handlers.length; i++) {
handlers[i] = createInvocationHandlerFor(delegates[i]);
}
return handlers;
}
/**
* Assuming that the given InvocationHandler was created in the current classloader and is an
* InvocationHandlerWithDelegateGetter, return the object the InvocationHandler delegates its
* method calls to.
*/
public static Object getDelegateFromInvocationHandler(InvocationHandler invocationHandler) {
InvocationHandlerWithDelegateGetter objectHolder =
(InvocationHandlerWithDelegateGetter) invocationHandler;
return objectHolder.getDelegate();
}
/**
* An InvocationHandler storing the original object that method calls are delegated to.
* This allows us to pass InvocationHandlers across the support library boundary and later
* unwrap the objects used as delegates within those InvocationHandlers.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static class InvocationHandlerWithDelegateGetter implements InvocationHandler {
private final Object mDelegate;
public InvocationHandlerWithDelegateGetter(final Object delegate) {
mDelegate = delegate;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
final ClassLoader delegateLoader = mDelegate.getClass().getClassLoader();
try {
return dupeMethod(method, delegateLoader).invoke(mDelegate, objects);
} catch (InvocationTargetException e) {
// If something went wrong, ensure we throw the original exception.
throw e.getTargetException();
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Reflection failed for method " + method, e);
}
}
public Object getDelegate() {
return mDelegate;
}
}
/**
* Check whether a set of features {@param features} contains a certain feature {@param
* soughtFeature}.
*/
public static boolean containsFeature(String[] features, String soughtFeature) {
for (String feature : features) {
if (feature.equals(soughtFeature)) return true;
}
return false;
}
}