summaryrefslogtreecommitdiff
path: root/src/org/chromium/support_lib_boundary/util/BoundaryInterfaceReflectionUtil.java
blob: 5a772c52309407ee493f2474b67985ccb418bd24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 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 {
    /**
     * Utility method for fetching a method from the current classloader, with the same signature
     * (package + class + method name + parameters) as a given method defined in another
     * classloader.
     */
    public static Method dupeMethod(Method method)
            throws ClassNotFoundException, NoSuchMethodException {
        Class<?> declaringClass = Class.forName(method.getDeclaringClass().getName());
        Class[] otherSideParameterClasses = method.getParameterTypes();
        Class[] parameterClasses = new Class[otherSideParameterClasses.length];
        for (int n = 0; n < parameterClasses.length; n++) {
            Class<?> clazz = otherSideParameterClasses[n];
            // Primitive classes are shared between the classloaders - so we can use the same
            // primitive class declarations on either side. Non-primitive classes must be looked up
            // by name.
            parameterClasses[n] = clazz.isPrimitive() ? clazz : Class.forName(clazz.getName());
        }
        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 InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                try {
                    return dupeMethod(method).invoke(delegate, 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);
                }
            }
        };
    }
}