1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.lang.reflect;
19
20import java.io.Serializable;
21import java.lang.ref.WeakReference;
22import java.util.HashMap;
23import java.util.Map;
24import java.util.WeakHashMap;
25
26/**
27 * {@code Proxy} defines methods for creating dynamic proxy classes and instances.
28 * A proxy class implements a declared set of interfaces and delegates method
29 * invocations to an {@code InvocationHandler}.
30 *
31 * @see InvocationHandler
32 * @since 1.3
33 */
34public class Proxy implements Serializable {
35
36    private static final long serialVersionUID = -2222568056686623797L;
37
38    // maps class loaders to created classes by interface names
39    private static final Map<ClassLoader, Map<String, WeakReference<Class<?>>>> loaderCache = new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>();
40
41    // to find previously created types
42    private static final Map<Class<?>, String> proxyCache = new WeakHashMap<Class<?>, String>();
43
44    private static int NextClassNameIndex = 0;
45
46    /**
47     * The invocation handler on which the method calls are dispatched.
48     */
49    protected InvocationHandler h;
50
51    @SuppressWarnings("unused")
52    private Proxy() {
53    }
54
55    /**
56     * Constructs a new {@code Proxy} instance with the specified invocation
57     * handler.
58     *
59     * @param h
60     *            the invocation handler for the newly created proxy
61     */
62    protected Proxy(InvocationHandler h) {
63        this.h = h;
64    }
65
66    /**
67     * Returns the dynamically built {@code Class} for the specified interfaces.
68     * Creates a new {@code Class} when necessary. The order of the interfaces
69     * is relevant. Invocations of this method with the same interfaces but
70     * different order result in different generated classes. The interfaces
71     * must be visible from the supplied class loader; no duplicates are
72     * permitted. All non-public interfaces must be defined in the same package.
73     *
74     * @param loader
75     *            the class loader that will define the proxy class
76     * @param interfaces
77     *            an array of {@code Class} objects, each one identifying an
78     *            interface that will be implemented by the returned proxy
79     *            class
80     * @return a proxy class that implements all of the interfaces referred to
81     *         in the contents of {@code interfaces}
82     * @throws IllegalArgumentException
83     *                if any of the interface restrictions are violated
84     * @throws NullPointerException
85     *                if either {@code interfaces} or any of its elements are
86     *                {@code null}
87     */
88    public static Class<?> getProxyClass(ClassLoader loader,
89            Class<?>... interfaces) throws IllegalArgumentException {
90        // check that interfaces are a valid array of visible interfaces
91        if (interfaces == null) {
92            throw new NullPointerException("interfaces == null");
93        }
94        String commonPackageName = null;
95        for (int i = 0, length = interfaces.length; i < length; i++) {
96            Class<?> next = interfaces[i];
97            if (next == null) {
98                throw new NullPointerException("interfaces[" + i + "] == null");
99            }
100            String name = next.getName();
101            if (!next.isInterface()) {
102                throw new IllegalArgumentException(name + " is not an interface");
103            }
104            if (loader != next.getClassLoader()) {
105                try {
106                    if (next != Class.forName(name, false, loader)) {
107                        throw new IllegalArgumentException(name +
108                                " is not visible from class loader");
109                    }
110                } catch (ClassNotFoundException ex) {
111                    throw new IllegalArgumentException(name + " is not visible from class loader");
112                }
113            }
114            for (int j = i + 1; j < length; j++) {
115                if (next == interfaces[j]) {
116                    throw new IllegalArgumentException(name + " appears more than once");
117                }
118            }
119            if (!Modifier.isPublic(next.getModifiers())) {
120                int last = name.lastIndexOf('.');
121                String p = last == -1 ? "" : name.substring(0, last);
122                if (commonPackageName == null) {
123                    commonPackageName = p;
124                } else if (!commonPackageName.equals(p)) {
125                    throw new IllegalArgumentException("non-public interfaces must be " +
126                            "in the same package");
127                }
128            }
129        }
130
131        // search cache for matching proxy class using the class loader
132        synchronized (loaderCache) {
133            Map<String, WeakReference<Class<?>>> interfaceCache = loaderCache
134                    .get(loader);
135            if (interfaceCache == null) {
136                loaderCache
137                        .put(
138                                loader,
139                                (interfaceCache = new HashMap<String, WeakReference<Class<?>>>()));
140            }
141
142            String interfaceKey = "";
143            if (interfaces.length == 1) {
144                interfaceKey = interfaces[0].getName();
145            } else {
146                StringBuilder names = new StringBuilder();
147                for (int i = 0, length = interfaces.length; i < length; i++) {
148                    names.append(interfaces[i].getName());
149                    names.append(' ');
150                }
151                interfaceKey = names.toString();
152            }
153
154            Class<?> newClass;
155            WeakReference<Class<?>> ref = interfaceCache.get(interfaceKey);
156            if (ref == null) {
157                String nextClassName = "$Proxy" + NextClassNameIndex++;
158                if (commonPackageName != null && commonPackageName.length() > 0) {
159                    nextClassName = commonPackageName + "." + nextClassName;
160                }
161                if (loader == null) {
162                    loader = ClassLoader.getSystemClassLoader();
163                }
164                newClass = generateProxy(nextClassName.replace('.', '/'), interfaces, loader);
165                // Need a weak reference to the class so it can
166                // be unloaded if the class loader is discarded
167                interfaceCache.put(interfaceKey, new WeakReference<Class<?>>(newClass));
168                synchronized (proxyCache) {
169                    // the value is unused
170                    proxyCache.put(newClass, "");
171                }
172            } else {
173                newClass = ref.get();
174                assert newClass != null : "\ninterfaceKey=\"" + interfaceKey + "\""
175                                        + "\nloaderCache=\"" + loaderCache + "\""
176                                        + "\nintfCache=\"" + interfaceCache + "\""
177                                        + "\nproxyCache=\"" + proxyCache + "\"";
178            }
179            return newClass;
180        }
181    }
182
183    /**
184     * Returns an instance of the dynamically built class for the specified
185     * interfaces. Method invocations on the returned instance are forwarded to
186     * the specified invocation handler. The interfaces must be visible from the
187     * supplied class loader; no duplicates are permitted. All non-public
188     * interfaces must be defined in the same package.
189     *
190     * @param loader
191     *            the class loader that will define the proxy class
192     * @param interfaces
193     *            an array of {@code Class} objects, each one identifying an
194     *            interface that will be implemented by the returned proxy
195     *            object
196     * @param h
197     *            the invocation handler that handles the dispatched method
198     *            invocations
199     * @return a new proxy object that delegates to the handler {@code h}
200     * @throws IllegalArgumentException
201     *                if any of the interface restrictions are violated
202     * @throws NullPointerException
203     *                if the interfaces or any of its elements are null
204     */
205    public static Object newProxyInstance(ClassLoader loader,
206            Class<?>[] interfaces, InvocationHandler h)
207            throws IllegalArgumentException {
208        if (h == null) {
209            throw new NullPointerException("h == null");
210        }
211        try {
212            return getProxyClass(loader, interfaces).getConstructor(
213                    new Class<?>[] { InvocationHandler.class }).newInstance(
214                    new Object[] { h });
215        } catch (NoSuchMethodException ex) {
216            throw (InternalError) (new InternalError(ex.toString())
217                    .initCause(ex));
218        } catch (IllegalAccessException ex) {
219            throw (InternalError) (new InternalError(ex.toString())
220                    .initCause(ex));
221        } catch (InstantiationException ex) {
222            throw (InternalError) (new InternalError(ex.toString())
223                    .initCause(ex));
224        } catch (InvocationTargetException ex) {
225            Throwable target = ex.getTargetException();
226            throw (InternalError) (new InternalError(target.toString())
227                    .initCause(target));
228        }
229    }
230
231    /**
232     * Indicates whether or not the specified class is a dynamically generated
233     * proxy class.
234     *
235     * @param cl
236     *            the class
237     * @return {@code true} if the class is a proxy class, {@code false}
238     *         otherwise
239     * @throws NullPointerException
240     *                if the class is {@code null}
241     */
242    public static boolean isProxyClass(Class<?> cl) {
243        if (cl == null) {
244            throw new NullPointerException("cl == null");
245        }
246        synchronized (proxyCache) {
247            return proxyCache.containsKey(cl);
248        }
249    }
250
251    /**
252     * Returns the invocation handler of the specified proxy instance.
253     *
254     * @param proxy
255     *            the proxy instance
256     * @return the invocation handler of the specified proxy instance
257     * @throws IllegalArgumentException
258     *                if the supplied {@code proxy} is not a proxy object
259     */
260    public static InvocationHandler getInvocationHandler(Object proxy)
261            throws IllegalArgumentException {
262
263        if (isProxyClass(proxy.getClass())) {
264            return ((Proxy) proxy).h;
265        }
266
267        throw new IllegalArgumentException("not a proxy instance");
268    }
269
270    native private static Class generateProxy(String name, Class[] interfaces,
271        ClassLoader loader);
272
273    /*
274     * The VM clones this method's descriptor when generating a proxy class.
275     * There is no implementation.
276     */
277    native private static void constructorPrototype(InvocationHandler h);
278}
279