1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
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  
41  
42  
43  
44  public final class Qualifiers {
45      private static final Logger logger = LoggerFactory.getLogger(Qualifiers.class);
46      private Qualifiers() { }
47  
48      
49  
50  
51  
52  
53  
54  
55  
56      public static boolean isQualifier(Class<? extends Annotation> type) {
57          return type.getAnnotation(javax.inject.Qualifier.class) != null;
58      }
59  
60      
61  
62  
63  
64  
65  
66  
67  
68  
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  
94  
95  
96      public static QualifierMatcher matchDefault() {
97          return new DefaultMatcher();
98      }
99      
100     
101 
102 
103     public static QualifierMatcher matchAny() {
104         return new AnyMatcher();
105     }
106     
107     
108 
109 
110     public static QualifierMatcher matchNone() {
111         return new NullMatcher();
112     }
113     
114     
115 
116 
117 
118 
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 
130 
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             
138             
139             
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                 
177                 return 1;
178             } else {
179                 
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             
290             actual = resolveAliases(type);
291         }
292         
293         @Override
294         public boolean apply(Annotation q) {
295             
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             
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") 
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 }