001    // Copyright 2007 Regents of the University of California.  May be used 
002    // under the terms of the revised BSD license.  See LICENSING for details.
003    package org.joe_e.reflect;
004    
005    import java.lang.reflect.InvocationHandler;
006    import java.lang.reflect.Modifier;
007    import java.lang.reflect.Proxy;
008    
009    import org.joe_e.Equatable;
010    import org.joe_e.Immutable;
011    import org.joe_e.JoeE;
012    import org.joe_e.Powerless;
013    import org.joe_e.Selfless;
014    
015    /**
016     * The dynamic proxy interface.  This is a wrapper around Java's dynamic proxy
017     * features provided by <code>java.lang.reflect.Proxy</code>.
018     */
019    public final class Proxies {
020    
021        private Proxies() {}
022    
023        /**
024         * Extract the <code>InvocationHandler</code> from a Proxy
025         * @param proxy {@link #proxy proxy} instance
026         * @return the invocation handler for the proxy
027         */
028        static public InvocationHandler getHandler(final Proxy proxy) { 
029            return Proxy.getInvocationHandler(proxy); 
030        }
031    
032        /**
033         * The boot class loader.
034         */
035        static private final ClassLoader boot = Runnable.class.getClassLoader();
036    
037        /**
038         * Constructs a dynamic proxy.
039         * @param handler invocation handler
040         * @param interfaces each implemented interface
041         * @return dynamic proxy
042         * @throws ClassCastException   a restriction on the
043         *                              <code>types</code> is violated
044         * @throws NullPointerException an argument is <code>null</code>
045         */
046        static public Object proxy(final InvocationHandler handler,
047                                   final Class<?>... interfaces) 
048                                            throws ClassCastException { 
049            if (handler == null || interfaces == null) {
050                throw new NullPointerException();
051            }
052            
053            // Determine the classloader.
054            ClassLoader proxyLoader = boot;
055            
056            boolean equatable = false;  // Was the Equatable type claimed?
057            boolean selfless = false;   // Was the Selfless type claimed?
058            boolean immutable = false;  // Was the Immutable type claimed?
059            boolean powerless = false;  // Was the Powerless type claimed?
060            
061            for (final Class<?> i : interfaces) {
062                
063                // Can only implement public interfaces.
064                if (!Modifier.isPublic(i.getModifiers())) {
065                    throw new ClassCastException();
066                }
067                
068                // Perform Joe-E auditor checks.
069                if (!powerless && JoeE.isSubtypeOf(i, Powerless.class)) {
070                    if (JoeE.instanceOf(handler, Powerless.class)) {
071                        powerless = true;
072                        immutable = true;
073                    } else {
074                        throw new ClassCastException();
075                    }
076                }
077                if (!immutable && JoeE.isSubtypeOf(i, Immutable.class)) {
078                    if (JoeE.instanceOf(handler, Immutable.class)) {
079                        immutable = true;
080                    } else {
081                        throw new ClassCastException();
082                    }
083                }
084                if (!equatable && JoeE.isSubtypeOf(i, Equatable.class)) {
085                    // No additional checks are needed here, as we know that Proxy
086                    // is not Selfless.  An Equatable Proxy can have an identity-
087                    // based equals() method if it so chooses by casting the Proxy
088                    // object to Equatable.
089                    if (selfless) { 
090                        throw new ClassCastException();
091                    }
092                    equatable = true;
093                }
094                if (!selfless && JoeE.isSubtypeOf(i, Selfless.class)) {
095                    // No additional checks are needed here, because Object's
096                    // equals() method is not available from the invocation handler
097                    // and Proxy does not expose this method.  Dynamic proxies are
098                    // thus the only Selfless user class whose direct supertype is
099                    // neither a Selfless class nor Object.
100                    if (equatable) { 
101                        throw new ClassCastException();
102                    }
103                    selfless = true;
104                }
105                
106                final ClassLoader interfaceLoader = i.getClassLoader();
107    
108                // TODO: This will change if there is runtime info on banned
109                // interfaces or if a Library interface is added.
110                // if (interfaceLoader == boot && i != Runnable.class) { 
111                //    throw new ClassCastException();
112                //}
113        
114                // Prefer any classloader over the bootstrap classloader.
115                if (proxyLoader == boot) {
116                    proxyLoader = interfaceLoader;
117                }
118            }
119            try {
120                return Proxy.newProxyInstance(proxyLoader, interfaces, handler);
121            } catch (final IllegalArgumentException e) {
122                throw new ClassCastException(e.getMessage());
123            }
124        }
125    
126        /**
127         * Returns <code>true</code> if the argument is an interface that can be
128         * implemented by a Proxy using <code>proxy()</code>.
129         * @param type  candidate interface
130         * @return <code>true</code> if a Joe-E type, else <code>false</code>
131         */
132        static public boolean isImplementable(final Class<?> type) {
133            return type.isInterface() && Modifier.isPublic(type.getModifiers());
134            
135            // return Runnable.class == type ||
136            //       (type.isInterface() && type.getClassLoader() != boot &&
137            //        Modifier.isPublic(type.getModifiers())); 
138            // Can't consult taming database as it doesn't have proper info
139            // TODO: This will change if there are interfaces the implementation of
140            // which must be banned (e.g. Library)
141        }
142    }