View Javadoc
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  package org.apache.commons.proxy2.stub;
18  
19  import java.io.Serializable;
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.InvocationHandler;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Proxy;
25  import java.util.Map;
26  
27  import org.apache.commons.lang3.AnnotationUtils;
28  import org.apache.commons.lang3.ArrayUtils;
29  import org.apache.commons.lang3.ObjectUtils;
30  import org.apache.commons.lang3.Validate;
31  import org.apache.commons.lang3.reflect.TypeUtils;
32  import org.apache.commons.proxy2.Interceptor;
33  import org.apache.commons.proxy2.Invocation;
34  import org.apache.commons.proxy2.Invoker;
35  import org.apache.commons.proxy2.ObjectProvider;
36  import org.apache.commons.proxy2.ProxyFactory;
37  import org.apache.commons.proxy2.ProxyUtils;
38  import org.apache.commons.proxy2.impl.AbstractProxyFactory;
39  import org.apache.commons.proxy2.provider.ObjectProviderUtils;
40  
41  public class AnnotationBuilder<A extends Annotation> extends StubBuilder<A>
42  {
43      // underlying proxyfactory implementation based on
44      // org.apache.commons.proxy2.jdk.JdkProxyFactory
45  
46      private static class InterceptorInvocationHandler implements InvocationHandler, Serializable
47      {
48          /** Serialization version */
49          private static final long serialVersionUID = 1L;
50  
51          private final ObjectProvider<?> provider;
52          private final Interceptor methodInterceptor;
53  
54          public InterceptorInvocationHandler(ObjectProvider<?> provider, Interceptor methodInterceptor)
55          {
56              this.provider = provider;
57              this.methodInterceptor = methodInterceptor;
58          }
59  
60          /**
61           * {@inheritDoc}
62           */
63          @Override
64          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
65          {
66              if (ProxyUtils.isHashCode(method))
67              {
68                  return Integer.valueOf(AnnotationUtils.hashCode((Annotation) proxy));
69              }
70              if (ProxyUtils.isEqualsMethod(method))
71              {
72                  return Boolean.valueOf(args[0] instanceof Annotation
73                          && AnnotationUtils.equals((Annotation) proxy, (Annotation) args[0]));
74              }
75              if ("toString".equals(method.getName()) && method.getParameterTypes().length == 0)
76              {
77                  return AnnotationUtils.toString((Annotation) proxy);
78              }
79              final ReflectionInvocation invocation = new ReflectionInvocation(provider.getObject(), method, args);
80              return methodInterceptor.intercept(invocation);
81          }
82  
83      }
84  
85      private static class ReflectionInvocation implements Invocation
86      {
87          private final Method method;
88          private final Object[] arguments;
89          private final Object target;
90  
91          public ReflectionInvocation(Object target, Method method, Object[] arguments)
92          {
93              this.method = method;
94              this.arguments = ObjectUtils.defaultIfNull(ArrayUtils.clone(arguments), ProxyUtils.EMPTY_ARGUMENTS);
95              this.target = target;
96          }
97  
98          @Override
99          public Object[] getArguments()
100         {
101             return arguments;
102         }
103 
104         @Override
105         public Method getMethod()
106         {
107             return method;
108         }
109 
110         @Override
111         public Object getProxy()
112         {
113             return target;
114         }
115 
116         @Override
117         public Object proceed() throws Throwable
118         {
119             try
120             {
121                 return method.invoke(target, arguments);
122             }
123             catch (InvocationTargetException e)
124             {
125                 throw e.getTargetException();
126             }
127         }
128     }
129 
130     private static final ProxyFactory PROXY_FACTORY = new AbstractProxyFactory()
131     {
132         @Override
133         public <T> T createInvokerProxy(ClassLoader classLoader, final Invoker invoker, Class<?>... proxyClasses)
134         {
135             @SuppressWarnings("unchecked") // type inference
136             final T result = (T) Proxy.newProxyInstance(classLoader, proxyClasses, new InvocationHandler()
137             {
138                 @Override
139                 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
140                 {
141                     return invoker.invoke(proxy, method, args);
142                 }
143             });
144             return result;
145         }
146 
147         @Override
148         public <T> T createInterceptorProxy(ClassLoader classLoader, Object target, Interceptor interceptor,
149                 Class<?>... proxyClasses)
150         {
151             @SuppressWarnings("unchecked") // type inference
152             final T result = (T) Proxy.newProxyInstance(classLoader, proxyClasses, new InterceptorInvocationHandler(
153                     ObjectProviderUtils.constant(target), interceptor));
154             return result;
155         }
156 
157         @Override
158         public <T> T createDelegatorProxy(ClassLoader classLoader, final ObjectProvider<?> delegateProvider,
159                 Class<?>... proxyClasses)
160         {
161             @SuppressWarnings("unchecked") // type inference
162             final T result = (T) Proxy.newProxyInstance(classLoader, proxyClasses, new InterceptorInvocationHandler(
163                     delegateProvider, new Interceptor()
164                     {
165                         private static final long serialVersionUID = 1L;
166 
167                         @Override
168                         public Object intercept(Invocation invocation) throws Throwable
169                         {
170                             return invocation.proceed();
171                         }
172                     }));
173             return result;
174         }
175     };
176 
177     private class MapAnnotationTrainer extends AnnotationTrainer<A>
178     {
179         private final Map<String, ?> members;
180 
181         MapAnnotationTrainer(Map<String, ?> members)
182         {
183             super(annotationType);
184             this.members = members;
185         }
186 
187         @Override
188         protected void train(A trainee)
189         {
190             WhenObject<Object> bud;
191             AnnotationTrainer<A> dy = this;
192             for (Map.Entry<String, ?> attr : members.entrySet())
193             {
194                 final Method m;
195                 try
196                 {
197                     m = traineeType.getDeclaredMethod(attr.getKey());
198                 }
199                 catch (Exception e1)
200                 {
201                     throw new IllegalArgumentException(String.format("Could not detect annotation member %1$s",
202                             attr.getKey()));
203                 }
204                 try
205                 {
206                     bud = dy.when(m.invoke(trainee));
207                 }
208                 catch (Exception e)
209                 {
210                     // it must have happened on the invoke, so we didn't call
211                     // when... it shouldn't happen, but we'll simply skip:
212                     continue;
213                 }
214                 final Object value = attr.getValue();
215                 Validate.isTrue(TypeUtils.isInstance(value, m.getReturnType()), "Value %s can not be assigned to %s",
216                         value, m.getReturnType());
217                 dy = bud.thenReturn(value);
218             }
219         }
220     }
221 
222     public static <A extends Annotation> A buildDefault(Class<A> type)
223     {
224         return of(type).build();
225     }
226 
227     public static <A extends Annotation> AnnotationBuilder<A> of(Class<A> type)
228     {
229         return new AnnotationBuilder<A>(type, AnnotationInvoker.INSTANCE);
230     }
231 
232     public static <A extends Annotation> AnnotationBuilder<A> of(Class<A> type, ObjectProvider<? extends A> provider)
233     {
234         return new AnnotationBuilder<A>(type, provider);
235     }
236 
237     public static <A extends Annotation> AnnotationBuilder<A> of(Class<A> type, A target)
238     {
239         return new AnnotationBuilder<A>(type, target);
240     }
241 
242     private final Class<A> annotationType;
243 
244     private AnnotationBuilder(Class<A> type, Invoker invoker)
245     {
246         super(PROXY_FACTORY, type, invoker);
247         this.annotationType = type;
248         train(new AnnotationTypeTrainer<A>(type));
249     }
250 
251     private AnnotationBuilder(Class<A> type, ObjectProvider<? extends A> provider)
252     {
253         super(PROXY_FACTORY, type, provider);
254         this.annotationType = type;
255         train(new AnnotationTypeTrainer<A>(type));
256     }
257 
258     private AnnotationBuilder(Class<A> type, A target)
259     {
260         super(PROXY_FACTORY, type, target);
261         this.annotationType = type;
262         train(new AnnotationTypeTrainer<A>(type));
263     }
264 
265     public AnnotationBuilder<A> withMembers(Map<String, ?> members)
266     {
267         return train(new MapAnnotationTrainer(members));
268     }
269 
270     @Override
271     public <O> AnnotationBuilder<A> train(BaseTrainer<?, O> trainer)
272     {
273         return (AnnotationBuilder<A>) super.train(trainer);
274     }
275 }