All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robovm.rt.ClassProxy Maven / Gradle / Ivy

There is a newer version: 1.14.0
Show newest version
/*
 * Copyright (c) 2015, RoboVM AB. All Rights Reserved.
 *
 * Redistribution and use is subject to the RoboVM Software License terms
 * available at (http://robovm.com)
 *
 * This notice and attribution to RoboVM AB may not be removed.
 */
package org.robovm.rt;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/**
 * {@code ClassProxy} defines methods for creating dynamic proxy classes. Unlike
 * {@link Proxy} the proxies created by {@link ClassProxy} extend from a
 * specified superclass. Proxy classes created by {@link ClassProxy} delegates
 * non-final superclass method and interface method invocations to an
 * {@link InvocationHandler}. Unlike {@link Proxy} the {@link Method} passed to
 * {@link InvocationHandler#invoke(Object, Method, Object[])} represents the
 * method on the generated class. Use {@link #getProxiedMethod(Method)} to get
 * the overridden superclass method or interface method. Use
 * {@link #invokeSuper(Method, Object, Object...)} to forward the call to the
 * superclass method implementation.
 */
public class ClassProxy {
    private static final Object[] EMPTY_ARGS = new Object[0];
    private static final Class[] EMPTY_INTERFACES = new Class[0];

    private static class CacheKey {
        private final Class superclass;
        private final Class[] interfaces;
        private final InvocationHandler handler;

        public CacheKey(Class superclass, Class[] interfaces, InvocationHandler handler) {
            this.superclass = superclass;
            this.interfaces = interfaces;
            this.handler = handler;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((handler == null) ? 0 : handler.hashCode());
            result = prime * result + Arrays.hashCode(interfaces);
            result = prime * result + ((superclass == null) ? 0 : superclass.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            CacheKey other = (CacheKey) obj;
            if (handler == null) {
                if (other.handler != null) {
                    return false;
                }
            } else if (handler != other.handler) {
                return false;
            }
            if (!Arrays.equals(interfaces, other.interfaces)) {
                return false;
            }
            if (superclass == null) {
                if (other.superclass != null) {
                    return false;
                }
            } else if (!superclass.equals(other.superclass)) {
                return false;
            }
            return true;
        }
    }

    // maps class loaders to created classes
    private static final HashMap>> loaderCache = new HashMap<>();

    // to find previously created types
    private static final HashSet> proxyCache = new HashSet<>();

    private static int nextClassNameIndex = 0;

    /**
     * Returns a proxy {@code Class} extending from the specified superclass and
     * implementing the specified interfaces. The order of the interfaces is
     * relevant. Invocations of this method with the same interfaces but
     * different order result in different generated classes. The interfaces
     * must be visible from the supplied class loader; no duplicates are
     * permitted. All non-public interfaces must be defined in the same package.
     *
     * @param loader the class loader that will define the proxy {@link Class}.
     * @param handler the {@link InvocationHandler} which will be called for any
     *            invocation on the generated proxy {@link Class}.
     * @param interfaces an array of {@code Class} objects, each one identifying
     *            an interface that will be implemented by the returned proxy
     *            class.
     * @return a proxy {@link Class} that implements all of the interfaces
     *         referred to in the contents of {@code interfaces}.
     * @throws IllegalArgumentException if the superclass cannot be extended or
     *             if any of the interface restrictions are violated.
     * @throws NullPointerException if either {@code superclass},
     *             {@code interfaces} or any of its elements are {@code null}.
     */
    public static  Class getProxyClass(ClassLoader loader, InvocationHandler handler, Class superclass,
            Class... interfaces) throws IllegalArgumentException {

        if (superclass == null) {
            throw new NullPointerException("superclass == null");
        }
        if (superclass.isPrimitive()) {
            throw new IllegalArgumentException("Cannot create proxy with primitive class " + superclass.getName()
                    + " as superclass");
        }
        if (superclass.isArray()) {
            throw new IllegalArgumentException("Cannot create proxy with array class " + superclass.getName()
                    + " as superclass");
        }
        if (superclass.isInterface()) {
            throw new IllegalArgumentException("Cannot create proxy with interface class " + superclass.getName()
                    + " as superclass");
        }
        if (Modifier.isFinal(superclass.getModifiers())) {
            throw new IllegalArgumentException("Superclass " + superclass.getName() + " is final");
        }
        if (loader != superclass.getClassLoader()) {
            try {
                if (superclass != Class.forName(superclass.getName(), false, loader)) {
                    throw new IllegalArgumentException("Superclass " + superclass.getName() +
                            " is not visible from class loader");
                }
            } catch (ClassNotFoundException ex) {
                throw new IllegalArgumentException("Superclass " + superclass.getName() +
                        " is not visible from class loader");
            }
        }

        if (handler == null) {
            throw new NullPointerException("handler == null");
        }

        if (interfaces == null) {
            interfaces = EMPTY_INTERFACES;
        }

        String commonPackageName = null;
        if (!Modifier.isPublic(superclass.getModifiers()) || interfaces.length == 0) {
            commonPackageName = getPackageName(superclass);
        }

        for (int i = 0, length = interfaces.length; i < length; i++) {
            Class next = interfaces[i];
            if (next == null) {
                throw new NullPointerException("interfaces[" + i + "] == null");
            }
            String name = next.getName();
            if (!next.isInterface()) {
                throw new IllegalArgumentException(name + " is not an interface");
            }
            if (loader != next.getClassLoader()) {
                try {
                    if (next != Class.forName(name, false, loader)) {
                        throw new IllegalArgumentException(name +
                                " is not visible from class loader");
                    }
                } catch (ClassNotFoundException ex) {
                    throw new IllegalArgumentException(name + " is not visible from class loader");
                }
            }
            for (int j = i + 1; j < length; j++) {
                if (next == interfaces[j]) {
                    throw new IllegalArgumentException(name + " appears more than once");
                }
            }
            if (!Modifier.isPublic(next.getModifiers())) {
                int last = name.lastIndexOf('.');
                String p = last == -1 ? "" : name.substring(0, last);
                if (commonPackageName == null) {
                    commonPackageName = p;
                } else if (!commonPackageName.equals(p)) {
                    throw new IllegalArgumentException("non-public interfaces must be " +
                            "in the same package");
                }
            }
        }

        // search cache for matching proxy class using the class loader
        synchronized (loaderCache) {
            Map> cache = loaderCache.get(loader);
            if (cache == null) {
                cache = new HashMap>();
                loaderCache.put(loader, cache);
            }

            CacheKey cacheKey = new CacheKey(superclass, interfaces, handler);
            @SuppressWarnings("unchecked") Class newClass = (Class) cache.get(cacheKey);
            if (newClass == null) {
                String className = superclass.getName() + "$$ClassProxy" + nextClassNameIndex++;
                if (loader == null) {
                    loader = ClassLoader.getSystemClassLoader();
                }
                newClass = generateProxy(className.replace('.', '/'), superclass, interfaces, loader, handler);
                cache.put(cacheKey, newClass);
                synchronized (proxyCache) {
                    proxyCache.add(newClass);
                }
            }
            return newClass;
        }
    }

    private static String getPackageName(Class cls) {
        String name = cls.getName();
        int last = name.lastIndexOf('.');
        String p = last == -1 ? "" : name.substring(0, last);
        return p;
    }

    /**
     * Returns whether or not the specified class is a proxy {@link Class}
     * created by
     * {@link #getProxyClass(ClassLoader, InvocationHandler, Class, Class...)}.
     *
     * @param cl the class.
     * @return {@code true} if the class is a proxy class, {@code false}
     *         otherwise.
     * @throws NullPointerException if the class is {@code null}.
     */
    public static boolean isProxyClass(Class cl) {
        if (cl == null) {
            throw new NullPointerException("cl == null");
        }
        synchronized (proxyCache) {
            return proxyCache.contains(cl);
        }
    }

    /**
     * Returns the {@link InvocationHandler} associated with the specified proxy
     * {@link Class}.
     */
    public static InvocationHandler getInvocationHandler(Class cls)
            throws IllegalArgumentException {

        if (isProxyClass(cls)) {
            return getInvocationHandler0(cls);
        }

        throw new IllegalArgumentException("not a proxy class");
    }

    /**
     * Given a proxy {@link Method} returns the proxied {@link Method}.
     */
    public static Method getProxiedMethod(Method method) {
        if (!isProxyClass(method.getDeclaringClass())) {
            throw new IllegalArgumentException("Not a proxy class method: " + method);
        }

        return getProxiedMethod0(method);
    }

    /**
     * Invokes the super method of the specified proxy {@link Method} on the
     * specified receiver object.
     * 
     * @param method the proxy method.
     * @param receiver the receiver. Must not be {@code null}.
     * @param args method arguments.
     * @return the result of the invocation. Primitives are boxed. Returns
     *         {@code null} if method has no return type ({@code void}).
     */
    public static Object invokeSuper(Method method, Object receiver, Object... args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        if (receiver == null) {
            throw new NullPointerException("receiver == null");
        }
        if (!isProxyClass(method.getDeclaringClass())) {
            throw new IllegalArgumentException("Not a proxy class method: " + method);
        }
        if (method.getDeclaringClass() != receiver.getClass()) {
            StringBuilder sb = new StringBuilder();
            sb.append("expected receiver of type ")
                    .append(method.getDeclaringClass().getName())
                    .append(", but got ")
                    .append(receiver.getClass().getName());
            throw new IllegalArgumentException(sb.toString());
        }

        if (args == null) {
            args = EMPTY_ARGS;
        }

        Class[] pTypes = method.getParameterTypes();
        if (args.length != pTypes.length) {
            throw new IllegalArgumentException("wrong number of arguments; "
                    + "expected " + pTypes.length + ", got " + args.length);
        }

        return invokeSuper0(method, pTypes, receiver, args);
    }

    native private static InvocationHandler getInvocationHandler0(Class cls);

    native private static Method getProxiedMethod0(Method method);

    native private static Object invokeSuper0(Method method, Class[] parameterTypes, Object receiver, Object... args);

    native private static  Class generateProxy(String name, Class superclass, Class[] interfaces,
            ClassLoader loader, InvocationHandler handler);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy