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.reflect;
21  
22  import com.google.common.collect.Sets;
23  import org.grouplens.grapht.annotation.AliasFor;
24  import org.grouplens.grapht.annotation.AllowUnqualifiedMatch;
25  import org.grouplens.grapht.util.ClassProxy;
26  import org.grouplens.grapht.util.Preconditions;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import javax.annotation.Nonnull;
31  import javax.inject.Qualifier;
32  import java.io.InvalidObjectException;
33  import java.io.ObjectInputStream;
34  import java.io.ObjectStreamException;
35  import java.io.Serializable;
36  import java.lang.annotation.Annotation;
37  import java.util.Set;
38  
39  /**
40   * Utilities related to Qualifier implementations.
41   * 
42   * @author <a href="http://grouplens.org">GroupLens Research</a>
43   */
44  public final class Qualifiers {
45      private static final Logger logger = LoggerFactory.getLogger(Qualifiers.class);
46      private Qualifiers() { }
47  
48      /**
49       * Return true or false whether or not the annotation type represents a
50       * {@link Qualifier}
51       * 
52       * @param type The annotation type
53       * @return True if the annotation is a {@link Qualifier} or parameter
54       * @throws NullPointerException if the type is null
55       */
56      public static boolean isQualifier(Class<? extends Annotation> type) {
57          return type.getAnnotation(javax.inject.Qualifier.class) != null;
58      }
59  
60      /**
61       * Resolve qualifier aliases, returning the target qualifier.  Aliases are resolved
62       * recursively.
63       *
64       * @param type The annotation type.
65       * @return The annotation type for which this type is an alias, or {@code type} if it is not an
66       * alias.
67       * @throws java.lang.IllegalArgumentException if there is a problem with the type, such as a
68       *                                            circular alias reference.
69       */
70      @Nonnull
71      public static Class<? extends Annotation> resolveAliases(@Nonnull Class<? extends Annotation> type) {
72          Preconditions.notNull("qualifier type", type);
73          Set<Class<? extends Annotation>> seen = Sets.newHashSet();
74          seen.add(type);
75          Class<? extends Annotation> result = type;
76          AliasFor alias;
77          while ((alias = result.getAnnotation(AliasFor.class)) != null) {
78              if (result.getDeclaredMethods().length > 0) {
79                  throw new IllegalArgumentException("aliased qualifier cannot have parameters");
80              }
81              result = alias.value();
82              if (!result.isAnnotationPresent(Qualifier.class)) {
83                  throw new IllegalArgumentException("alias target " + type + " is not a qualifier");
84              }
85              if (!seen.add(result)) {
86                  throw new IllegalArgumentException("Circular alias reference starting with " + type);
87              }
88          }
89          return result;
90      }
91  
92      /**
93       * The default qualifier matcher. This is currently the {@linkplain #matchAny() any matcher}.
94       * @return A QualifierMatcher that matches using the default policy.
95       */
96      public static QualifierMatcher matchDefault() {
97          return new DefaultMatcher();
98      }
99      
100     /**
101      * @return A QualifierMatcher that matches any qualifier
102      */
103     public static QualifierMatcher matchAny() {
104         return new AnyMatcher();
105     }
106     
107     /**
108      * @return A QualifierMatcher that matches only the null qualifier
109      */
110     public static QualifierMatcher matchNone() {
111         return new NullMatcher();
112     }
113     
114     /**
115      * @param annotType Annotation type class to match; {@code null} to match only the lack of a
116      *                  qualifier.
117      * @return A QualifierMatcher that matches any annotation of the given class
118      *         type.
119      */
120     public static QualifierMatcher match(Class<? extends Annotation> annotType) {
121         if (annotType == null) {
122             return matchNone();
123         } else {
124             return new AnnotationClassMatcher(annotType);
125         }
126     }
127 
128     /**
129      * @param annot Annotation instance to match, or {@code null} to match only the lack of a qualifier.
130      * @return A QualifierMatcher that matches annotations equaling annot
131      */
132     public static QualifierMatcher match(Annotation annot) {
133         if (annot == null) {
134             return matchNone();
135         } else if (annot.annotationType().getDeclaredMethods().length == 0) {
136             logger.debug("using type matcher for nullary annotation {}", annot);
137             // Instances of the same nullary annotation are all equal to each other, so just do
138             // type checking.  This makes aliasing work with annotation value matchers, b/c we
139             // do not allow aliases to have parameters.  The matcher still has value priority.
140             return new AnnotationClassMatcher(annot.annotationType(),
141                                               DefaultMatcherPriority.MATCH_VALUE);
142         } else {
143             return new AnnotationMatcher(annot);
144         }
145     }
146 
147     private enum DefaultMatcherPriority {
148         MATCH_VALUE,
149         MATCH_TYPE,
150         MATCH_ANY,
151         MATCH_DEFAULT
152     }
153 
154     private abstract static class AbstractMatcher implements QualifierMatcher {
155         private static final long serialVersionUID = 1L;
156         private final DefaultMatcherPriority priority;
157 
158         AbstractMatcher(DefaultMatcherPriority prio) {
159             priority = prio;
160         }
161 
162         @Override
163         public final int getPriority() {
164             return priority.ordinal();
165         }
166 
167         @Override
168         @Deprecated
169         public boolean matches(Annotation q) {
170             return apply(q);
171         }
172 
173         @Override
174         public int compareTo(QualifierMatcher o) {
175             if (o == null) {
176                 // other type is unknown, so extend it to the front
177                 return 1;
178             } else {
179                 // lower priorities sort lower (higher precedence)
180                 return getPriority() - o.getPriority();
181             }
182         }
183     }
184 
185     private static class DefaultMatcher extends AbstractMatcher {
186         private static final long serialVersionUID = 1L;
187 
188         DefaultMatcher() {
189             super(DefaultMatcherPriority.MATCH_DEFAULT);
190         }
191 
192         @Override
193         public boolean apply(Annotation q) {
194             if (q == null) {
195                 return true;
196             } else {
197                 Class<? extends Annotation> atype = q.annotationType();
198                 return atype.isAnnotationPresent(AllowUnqualifiedMatch.class);
199             }
200         }
201 
202         @Override
203         public boolean equals(Object o) {
204             return o instanceof DefaultMatcher;
205         }
206 
207         @Override
208         public int hashCode() {
209             return DefaultMatcher.class.hashCode();
210         }
211 
212         @Override
213         public String toString() {
214             return "%";
215         }
216     }
217     
218     private static class AnyMatcher extends AbstractMatcher {
219         private static final long serialVersionUID = 1L;
220 
221         AnyMatcher() {
222             super(DefaultMatcherPriority.MATCH_ANY);
223         }
224 
225         @Override
226         public boolean apply(Annotation q) {
227             return true;
228         }
229         
230         @Override
231         public boolean equals(Object o) {
232             return o instanceof AnyMatcher;
233         }
234         
235         @Override
236         public int hashCode() {
237             return AnyMatcher.class.hashCode();
238         }
239         
240         @Override
241         public String toString() {
242             return "*";
243         }
244     }
245     
246     private static class NullMatcher extends AbstractMatcher {
247         private static final long serialVersionUID = 1L;
248 
249         NullMatcher() {
250             super(DefaultMatcherPriority.MATCH_VALUE);
251         }
252 
253         @Override
254         public boolean apply(Annotation q) {
255             return q == null;
256         }
257         
258         @Override
259         public boolean equals(Object o) {
260             return o instanceof NullMatcher;
261         }
262         
263         @Override
264         public int hashCode() {
265             return NullMatcher.class.hashCode();
266         }
267         
268         @Override
269         public String toString() {
270             return "-";
271         }
272     }
273     
274     static class AnnotationClassMatcher extends AbstractMatcher {
275         private static final long serialVersionUID = -1L;
276         private final Class<? extends Annotation> type;
277         private final Class<? extends Annotation> actual;
278 
279         public AnnotationClassMatcher(Class<? extends Annotation> type) {
280             this(type, DefaultMatcherPriority.MATCH_TYPE);
281         }
282         
283         public AnnotationClassMatcher(Class<? extends Annotation> type,
284                                       DefaultMatcherPriority prio) {
285             super(prio);
286             Preconditions.notNull("type", type);
287             Preconditions.isQualifier(type);
288             this.type = type;
289             // find the actual type to match (resolving aliases)
290             actual = resolveAliases(type);
291         }
292         
293         @Override
294         public boolean apply(Annotation q) {
295             // We test if the alias-resolved types match.
296             Class<? extends Annotation> qtype = (q == null ? null : q.annotationType());
297             if (qtype == null) {
298                 return false;
299             } else {
300                 Class<? extends Annotation> qact = resolveAliases(qtype);
301                 return actual.equals(qact);
302             }
303         }
304         
305         @Override
306         public boolean equals(Object o) {
307             return o instanceof AnnotationClassMatcher
308                    && ((AnnotationClassMatcher) o).actual.equals(actual);
309         }
310         
311         @Override
312         public int hashCode() {
313             return actual.hashCode();
314         }
315         
316         @Override
317         public String toString() {
318             if (type.equals(actual)) {
319                 return type.toString();
320             } else {
321                 return type.toString() + "( alias of " + actual.toString() + ")";
322             }
323         }
324 
325         private Object writeReplace() {
326             // We just serialize the type. If its alias status changes, that is fine.
327             return new SerialProxy(type);
328         }
329 
330         private void readObject(ObjectInputStream stream) throws ObjectStreamException {
331             throw new InvalidObjectException("must use serialization proxy");
332         }
333 
334         static class SerialProxy implements Serializable {
335             private static final long serialVersionUID = 1L;
336 
337             private final ClassProxy type;
338 
339             public SerialProxy(Class<?> cls) {
340                 type = ClassProxy.of(cls);
341             }
342 
343             private Object readResolve() throws ObjectStreamException {
344                 try {
345                     return new AnnotationClassMatcher(type.resolve().asSubclass(Annotation.class));
346                 } catch (ClassNotFoundException e) {
347                     InvalidObjectException ex = new InvalidObjectException("cannot resolve " + type);
348                     ex.initCause(e);
349                     throw ex;
350                 } catch (ClassCastException e) {
351                     InvalidObjectException ex =
352                             new InvalidObjectException("class " + type + " not an annotation");
353                     ex.initCause(e);
354                     throw ex;
355                 }
356             }
357         }
358     }
359     
360     private static class AnnotationMatcher extends AbstractMatcher implements Serializable {
361         private static final long serialVersionUID = 1L;
362 
363         @SuppressWarnings("squid:S1948") // serializable warning; annotations are serializable
364         private final Annotation annotation;
365         
366         public AnnotationMatcher(Annotation annot) {
367             super(DefaultMatcherPriority.MATCH_VALUE);
368             Preconditions.notNull("annotation", annot);
369             Preconditions.isQualifier(annot.annotationType());
370             annotation = annot;
371         }
372         
373         @Override
374         public boolean apply(Annotation q) {
375             return annotation.equals(q);
376         }
377         
378         @Override
379         public boolean equals(Object o) {
380             return (o instanceof AnnotationMatcher)
381                    && ((AnnotationMatcher) o).annotation.equals(annotation);
382         }
383         
384         @Override
385         public int hashCode() {
386             return annotation.hashCode();
387         }
388         
389         @Override
390         public String toString() {
391             return annotation.toString();
392         }
393     }
394 }