View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht.annotation;
21  
22  import com.google.common.collect.ImmutableMap;
23  import org.apache.commons.lang3.AnnotationUtils;
24  import org.apache.commons.lang3.ClassUtils;
25  import org.grouplens.grapht.util.ClassProxy;
26  
27  import java.io.*;
28  import java.lang.annotation.Annotation;
29  import java.lang.reflect.InvocationHandler;
30  import java.lang.reflect.Method;
31  import java.util.Arrays;
32  import java.util.Map;
33  
34  /**
35   * Proxy used to implement annotation interfaces.  It implements the {@link Annotation}
36   * contract by delegating to a map of named attribute values.  A new AnnotationProxy instance
37   * should be created for each proxy annotation.
38   *
39   * @see AnnotationBuilder
40   */
41  class AnnotationProxy<T extends Annotation> implements InvocationHandler, Serializable {
42      private static final long serialVersionUID = 1L;
43      private final ClassProxy annotationType;
44      private final ImmutableMap<String, Object> attributes;
45      private transient Class<T> cachedType;
46  
47      public AnnotationProxy(Class<T> type, Map<String, Object> attrs) {
48          annotationType = ClassProxy.of(type);
49          cachedType = type;
50          attributes = ImmutableMap.copyOf(attrs);
51      }
52  
53      /**
54       * Customized {@code readObject} implementation to ensure the cached type is resolved.
55       *
56       * @param in The stream.
57       * @throws java.io.ObjectStreamException If there is an error reading the object from the stream.
58       */
59      @SuppressWarnings("unchecked")
60      private void readObject(ObjectInputStream in) throws ObjectStreamException {
61          try {
62              in.defaultReadObject();
63              cachedType = (Class<T>) annotationType.resolve();
64          } catch (IOException e) {
65              ObjectStreamException ex = new StreamCorruptedException("IO exception");
66              ex.initCause(e);
67              throw ex;
68          } catch (ClassNotFoundException e) {
69              ObjectStreamException ex = new InvalidObjectException("IO exception");
70              ex.initCause(e);
71              throw ex;
72          }
73      }
74  
75      @Override
76      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
77          if (isHashCode(method)) {
78              return proxyHashCode(proxy);
79          } else if (isEquals(method)) {
80              return proxyEquals(proxy, args[0]);
81          } else if (isAnnotationType(method)) {
82              return proxyAnnotationType();
83          } else if (isToString(method)) {
84              return proxyToString(proxy);
85          } else if (attributes.containsKey(method.getName()) && method.getParameterTypes().length == 0) {
86              return copyAnnotationValue(attributes.get(method.getName()));
87          } else {
88              // fall back to the default
89              return copyAnnotationValue(method.getDefaultValue());
90          }
91          // wait() and other Object methods do not get sent to the InvocationHandler
92          // so we don't have any other cases
93      }
94  
95      private boolean isEquals(Method m) {
96          return m.getName().equals("equals") && m.getReturnType().equals(boolean.class)
97              && m.getParameterTypes().length == 1 && m.getParameterTypes()[0].equals(Object.class);
98      }
99  
100     private boolean isHashCode(Method m) {
101         return m.getName().equals("hashCode") && m.getReturnType().equals(int.class)
102             && m.getParameterTypes().length == 0;
103     }
104 
105     private boolean isAnnotationType(Method m) {
106         return m.getName().equals("annotationType") && m.getReturnType().equals(Class.class)
107             && m.getParameterTypes().length == 0;
108     }
109 
110     private boolean isToString(Method m) {
111         return m.getName().equals("toString") && m.getReturnType().equals(String.class)
112             && m.getParameterTypes().length == 0;
113     }
114 
115     private Class<? extends Annotation> proxyAnnotationType() {
116         return cachedType;
117     }
118 
119     private String proxyToString(Object o) {
120         return AnnotationUtils.toString((Annotation) o);
121     }
122 
123     private int proxyHashCode(Object proxy) {
124         return AnnotationUtils.hashCode((Annotation) proxy);
125     }
126 
127     private boolean proxyEquals(Object o1, Object o2) {
128         return AnnotationUtils.equals((Annotation) o1, (Annotation) o2);
129     }
130 
131     /**
132      * Safe clone of an object.  If the object is an array, it is copied; otherwise, it is
133      * returned as-is.  This object is only applicable to valid annotation value types, which
134      * are all either arrays or immutable.
135      * @param o The annotation value.
136      * @return A copy of the value.
137      */
138     @SuppressWarnings("unchecked")
139     static Object copyAnnotationValue(Object o) {
140         if (o.getClass().isArray()) {
141             // make a shallow copy of the array
142             if (o instanceof boolean[]) {
143                 boolean[] a = (boolean[]) o;
144                 return Arrays.copyOf(a, a.length);
145             } else if (o instanceof byte[]) {
146                 byte[] a = (byte[]) o;
147                 return Arrays.copyOf(a, a.length);
148             } else if (o instanceof short[]) {
149                 short[] a = (short[]) o;
150                 return Arrays.copyOf(a, a.length);
151             } else if (o instanceof int[]) {
152                 int[] a = (int[]) o;
153                 return Arrays.copyOf(a, a.length);
154             } else if (o instanceof long[]) {
155                 long[] a = (long[]) o;
156                 return Arrays.copyOf(a, a.length);
157             } else if (o instanceof char[]) {
158                 char[] a = (char[]) o;
159                 return Arrays.copyOf(a, a.length);
160             } else if (o instanceof float[]) {
161                 float[] a = (float[]) o;
162                 return Arrays.copyOf(a, a.length);
163             } else if (o instanceof double[]) {
164                 double[] a = (double[]) o;
165                 return Arrays.copyOf(a, a.length);
166             } else {
167                 Object[] a = (Object[]) o;
168                 return Arrays.copyOf(a, a.length, (Class<? extends Object[]>) o.getClass());
169             }
170         } else if (o instanceof String
171                    || o instanceof Annotation
172                    || ClassUtils.isPrimitiveOrWrapper(o.getClass())
173                    || o instanceof Enum
174                    || o instanceof Class) {
175             // the value is immutable and a copy is not necessary
176             return o;
177         } else {
178             throw new IllegalArgumentException("not an annotation value");
179         }
180     }
181 }