001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.proxy2.stub;
018
019import java.io.Serializable;
020import java.lang.annotation.Annotation;
021import java.lang.reflect.InvocationHandler;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Proxy;
025import java.util.Map;
026
027import org.apache.commons.lang3.AnnotationUtils;
028import org.apache.commons.lang3.ArrayUtils;
029import org.apache.commons.lang3.ObjectUtils;
030import org.apache.commons.lang3.Validate;
031import org.apache.commons.lang3.reflect.TypeUtils;
032import org.apache.commons.proxy2.Interceptor;
033import org.apache.commons.proxy2.Invocation;
034import org.apache.commons.proxy2.Invoker;
035import org.apache.commons.proxy2.ObjectProvider;
036import org.apache.commons.proxy2.ProxyFactory;
037import org.apache.commons.proxy2.ProxyUtils;
038import org.apache.commons.proxy2.impl.AbstractProxyFactory;
039import org.apache.commons.proxy2.provider.ObjectProviderUtils;
040
041public class AnnotationBuilder<A extends Annotation> extends StubBuilder<A>
042{
043    // underlying proxyfactory implementation based on
044    // org.apache.commons.proxy2.jdk.JdkProxyFactory
045
046    private static class InterceptorInvocationHandler implements InvocationHandler, Serializable
047    {
048        /** Serialization version */
049        private static final long serialVersionUID = 1L;
050
051        private final ObjectProvider<?> provider;
052        private final Interceptor methodInterceptor;
053
054        public InterceptorInvocationHandler(ObjectProvider<?> provider, Interceptor methodInterceptor)
055        {
056            this.provider = provider;
057            this.methodInterceptor = methodInterceptor;
058        }
059
060        /**
061         * {@inheritDoc}
062         */
063        @Override
064        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
065        {
066            if (ProxyUtils.isHashCode(method))
067            {
068                return Integer.valueOf(AnnotationUtils.hashCode((Annotation) proxy));
069            }
070            if (ProxyUtils.isEqualsMethod(method))
071            {
072                return Boolean.valueOf(args[0] instanceof Annotation
073                        && AnnotationUtils.equals((Annotation) proxy, (Annotation) args[0]));
074            }
075            if ("toString".equals(method.getName()) && method.getParameterTypes().length == 0)
076            {
077                return AnnotationUtils.toString((Annotation) proxy);
078            }
079            final ReflectionInvocation invocation = new ReflectionInvocation(provider.getObject(), method, args);
080            return methodInterceptor.intercept(invocation);
081        }
082
083    }
084
085    private static class ReflectionInvocation implements Invocation
086    {
087        private final Method method;
088        private final Object[] arguments;
089        private final Object target;
090
091        public ReflectionInvocation(Object target, Method method, Object[] arguments)
092        {
093            this.method = method;
094            this.arguments = ObjectUtils.defaultIfNull(ArrayUtils.clone(arguments), ProxyUtils.EMPTY_ARGUMENTS);
095            this.target = target;
096        }
097
098        @Override
099        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}