1 /*
2  * Copyright (C) 2012 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.reflect;
18 
19 import com.google.common.annotations.Beta;
20 
21 import java.lang.reflect.InvocationHandler;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Proxy;
24 import java.util.Arrays;
25 
26 import javax.annotation.Nullable;
27 
28 /**
29  * Abstract implementation of {@link InvocationHandler} that handles {@link Object#equals},
30  * {@link Object#hashCode} and {@link Object#toString}. For example: <pre>
31  * class Unsupported extends AbstractInvocationHandler {
32  *   protected Object handleInvocation(
33  *       Object proxy, Method method, Object[] args) {
34  *     throw new UnsupportedOperationException();
35  *   }
36  * }
37  *
38  * CharSequence unsupported = Reflection.newProxy(CharSequence.class, new Unsupported());
39  * </pre>
40  *
41  * @author Ben Yu
42  * @since 12.0
43  */
44 @Beta
45 public abstract class AbstractInvocationHandler implements InvocationHandler {
46 
47   private static final Object[] NO_ARGS = {};
48 
49   /**
50    * {@inheritDoc}
51    *
52    * <p><ul>
53    * <li>{@code proxy.hashCode()} delegates to {@link AbstractInvocationHandler#hashCode}
54    * <li>{@code proxy.toString()} delegates to {@link AbstractInvocationHandler#toString}
55    * <li>{@code proxy.equals(argument)} returns true if: <ul>
56    *   <li>{@code proxy} and {@code argument} are of the same type
57    *   <li>and {@link AbstractInvocationHandler#equals} returns true for the {@link
58    *       InvocationHandler} of {@code argument}
59    *   </ul>
60    * <li>other method calls are dispatched to {@link #handleInvocation}.
61    * </ul>
62    */
invoke(Object proxy, Method method, @Nullable Object[] args)63   @Override public final Object invoke(Object proxy, Method method, @Nullable Object[] args)
64       throws Throwable {
65     if (args == null) {
66       args = NO_ARGS;
67     }
68     if (args.length == 0 && method.getName().equals("hashCode")) {
69       return hashCode();
70     }
71     if (args.length == 1
72         && method.getName().equals("equals")
73         && method.getParameterTypes()[0] == Object.class) {
74       Object arg = args[0];
75       if (arg == null) {
76         return false;
77       }
78       if (proxy == arg) {
79         return true;
80       }
81       return isProxyOfSameInterfaces(arg, proxy.getClass())
82           && equals(Proxy.getInvocationHandler(arg));
83     }
84     if (args.length == 0 && method.getName().equals("toString")) {
85       return toString();
86     }
87     return handleInvocation(proxy, method, args);
88   }
89 
90   /**
91    * {@link #invoke} delegates to this method upon any method invocation on the proxy instance,
92    * except {@link Object#equals}, {@link Object#hashCode} and {@link Object#toString}. The result
93    * will be returned as the proxied method's return value.
94    *
95    * <p>Unlike {@link #invoke}, {@code args} will never be null. When the method has no parameter,
96    * an empty array is passed in.
97    */
handleInvocation(Object proxy, Method method, Object[] args)98   protected abstract Object handleInvocation(Object proxy, Method method, Object[] args)
99       throws Throwable;
100 
101   /**
102    * By default delegates to {@link Object#equals} so instances are only equal if they are
103    * identical. {@code proxy.equals(argument)} returns true if: <ul>
104    * <li>{@code proxy} and {@code argument} are of the same type
105    * <li>and this method returns true for the {@link InvocationHandler} of {@code argument}
106    * </ul>
107    * <p>Subclasses can override this method to provide custom equality.
108    */
equals(Object obj)109   @Override public boolean equals(Object obj) {
110     return super.equals(obj);
111   }
112 
113   /**
114    * By default delegates to {@link Object#hashCode}. The dynamic proxies' {@code hashCode()} will
115    * delegate to this method. Subclasses can override this method to provide custom equality.
116    */
hashCode()117   @Override public int hashCode() {
118     return super.hashCode();
119   }
120 
121   /**
122    * By default delegates to {@link Object#toString}. The dynamic proxies' {@code toString()} will
123    * delegate to this method. Subclasses can override this method to provide custom string
124    * representation for the proxies.
125    */
toString()126   @Override public String toString() {
127     return super.toString();
128   }
129 
isProxyOfSameInterfaces(Object arg, Class<?> proxyClass)130   private static boolean isProxyOfSameInterfaces(Object arg, Class<?> proxyClass) {
131     return proxyClass.isInstance(arg)
132         // Equal proxy instances should mostly be instance of proxyClass
133         // Under some edge cases (such as the proxy of JDK types serialized and then deserialized)
134         // the proxy type may not be the same.
135         // We first check isProxyClass() so that the common case of comparing with non-proxy objects
136         // is efficient.
137         || (Proxy.isProxyClass(arg.getClass())
138             && Arrays.equals(arg.getClass().getInterfaces(), proxyClass.getInterfaces()));
139   }
140 }
141