AnnotationUtils.java
001 /*
002  * Copyright 2008-2015 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package griffon.util;
017 
018 import griffon.inject.BindTo;
019 import griffon.inject.DependsOn;
020 import griffon.inject.Typed;
021 import org.slf4j.Logger;
022 import org.slf4j.LoggerFactory;
023 
024 import javax.annotation.Nonnull;
025 import javax.annotation.Nullable;
026 import javax.inject.Named;
027 import javax.inject.Qualifier;
028 import java.io.Serializable;
029 import java.lang.annotation.Annotation;
030 import java.lang.reflect.Field;
031 import java.lang.reflect.Method;
032 import java.util.ArrayList;
033 import java.util.Arrays;
034 import java.util.Collection;
035 import java.util.Collections;
036 import java.util.Iterator;
037 import java.util.LinkedHashMap;
038 import java.util.LinkedHashSet;
039 import java.util.List;
040 import java.util.Map;
041 import java.util.Set;
042 
043 import static griffon.util.GriffonClassUtils.requireState;
044 import static griffon.util.GriffonNameUtils.getLogicalPropertyName;
045 import static griffon.util.GriffonNameUtils.getPropertyName;
046 import static griffon.util.GriffonNameUtils.isBlank;
047 import static java.util.Objects.requireNonNull;
048 
049 /**
050  @author Andres Almiray
051  @since 2.0.0
052  */
053 public class AnnotationUtils {
054     private static final Logger LOG = LoggerFactory.getLogger(AnnotationUtils.class);
055     private static final String ERROR_CLASS_NULL = "Argument 'class' must not be null";
056     private static final String ERROR_SUFFIX_NULL = "Argument 'suffix' must not be null";
057     private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
058     private static final String ERROR_ANNOTATION_TYPE_NULL = "Argument 'annotationType' must not be null";
059 
060     private AnnotationUtils() {
061 
062     }
063 
064     @Nonnull
065     public static List<Annotation> harvestQualifiers(@Nonnull Class<?> klass) {
066         requireNonNull(klass, "Argument 'class' must not be null");
067         List<Annotation> list = new ArrayList<>();
068         Annotation[] annotations = klass.getAnnotations();
069         for (Annotation annotation : annotations) {
070             if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
071                 // special case @BindTo is only used during tests
072                 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
073                     continue;
074                 }
075                 // special case for @Named
076                 if (Named.class.isAssignableFrom(annotation.getClass())) {
077                     Named named = (Namedannotation;
078                     if (isBlank(named.value())) {
079                         list.add(named(getPropertyName(klass)));
080                         continue;
081                     }
082                 }
083                 list.add(annotation);
084             }
085         }
086         return list;
087     }
088 
089     @Nullable
090     public static <A extends Annotation> A findAnnotation(@Nonnull Class<?> klass, @Nonnull Class<A> annotationType) {
091         requireNonNull(klass, ERROR_CLASS_NULL);
092         requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
093 
094         while (klass != null) {
095             Annotation annotation = findAnnotation(klass.getAnnotations(), annotationType);
096             if (annotation != nullreturn (Aannotation;
097             klass = klass.getSuperclass();
098         }
099         return null;
100     }
101 
102     @Nullable
103     public static <A extends Annotation> A findAnnotation(@Nonnull Annotation[] annotations, @Nonnull Class<A> annotationType) {
104         requireNonNull(annotations, "Argument 'annotations' must not be null");
105         requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
106         for (Annotation annotation : annotations) {
107             if (annotationType.isAssignableFrom(annotation.getClass())) {
108                 return (Aannotation;
109             }
110         }
111         return null;
112     }
113 
114     public static boolean isAnnotatedWith(@Nonnull Object instance, @Nonnull Class<? extends Annotation> annotationType) {
115         return isAnnotatedWith(requireNonNull(instance, ERROR_INSTANCE_NULL).getClass(), annotationType);
116     }
117 
118     public static boolean isAnnotatedWith(@Nonnull Class<?> clazz, @Nonnull Class<? extends Annotation> annotationType) {
119         requireNonNull(clazz, ERROR_CLASS_NULL);
120         requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
121 
122         //noinspection ConstantConditions
123         while (clazz != null) {
124             for (Annotation annotation : clazz.getAnnotations()) {
125                 if (annotationType.equals(annotation.annotationType())) {
126                     return true;
127                 }
128             }
129             for (Class<?> iface : clazz.getInterfaces()) {
130                 if (isAnnotatedWith(iface, annotationType)) {
131                     return true;
132                 }
133             }
134 
135             clazz = clazz.getSuperclass();
136         }
137         return false;
138     }
139 
140     @Nonnull
141     public static <T> T requireAnnotation(@Nonnull T instance, @Nonnull Class<? extends Annotation> annotationType) {
142         if (!isAnnotatedWith(instance, annotationType)) {
143             throw new IllegalArgumentException("Instance of " + instance.getClass() " is not annotated with " + annotationType.getName());
144         }
145         return instance;
146     }
147 
148     @Nonnull
149     public static <T> Class<T> requireAnnotation(@Nonnull Class<T> klass, @Nonnull Class<? extends Annotation> annotationType) {
150         if (!isAnnotatedWith(klass, annotationType)) {
151             throw new IllegalArgumentException("Class " + klass.getName() " is not annotated with " + annotationType.getName());
152         }
153         return klass;
154     }
155 
156     @Nonnull
157     public static String[] getDependsOn(@Nonnull Object instance) {
158         requireNonNull(instance, ERROR_INSTANCE_NULL);
159 
160         DependsOn dependsOn = instance.getClass().getAnnotation(DependsOn.class);
161         return dependsOn != null ? dependsOn.value() new String[0];
162     }
163 
164     @Nonnull
165     public static String nameFor(@Nonnull Object instance) {
166         requireNonNull(instance, ERROR_INSTANCE_NULL);
167 
168         Named annotation = instance.getClass().getAnnotation(Named.class);
169         if (annotation != null && !isBlank(annotation.value())) {
170             return annotation.value();
171         else {
172             return instance.getClass().getName();
173         }
174     }
175 
176     @Nonnull
177     public static String nameFor(@Nonnull Field field) {
178         requireNonNull(field, "Argument 'field' must not be null");
179 
180         Named annotation = field.getAnnotation(Named.class);
181         if (annotation != null && !isBlank(annotation.value())) {
182             return annotation.value();
183         else {
184             return field.getType().getName();
185         }
186     }
187 
188     @Nonnull
189     public static String nameFor(@Nonnull Method setterMethod) {
190         requireNonNull(setterMethod, "Argument 'setterMethod' must not be null");
191 
192         Class<?>[] parameterTypes = setterMethod.getParameterTypes();
193         requireState(parameterTypes != null && parameterTypes.length > 0"Argument 'setterMethod' must have at least one parameter. " + MethodDescriptor.forMethod(setterMethod));
194 
195         Named annotation = findAnnotation(annotationsOfMethodParameter(setterMethod, 0), Named.class);
196         if (annotation != null && !isBlank(annotation.value())) {
197             return annotation.value();
198         else {
199             return parameterTypes[0].getName();
200         }
201     }
202 
203     @Nonnull
204     public static Annotation[] annotationsOfMethodParameter(@Nonnull Method method, int paramIndex) {
205         requireNonNull(method, "Argument 'method' must not be null");
206 
207         Class<?>[] parameterTypes = method.getParameterTypes();
208         requireState(parameterTypes != null && parameterTypes.length > paramIndex, "Index " + paramIndex + " is out of bounds");
209 
210         return method.getParameterAnnotations()[paramIndex];
211     }
212 
213     @Nonnull
214     public static <T> Map<String, T> mapInstancesByName(@Nonnull Collection<T> instances, @Nonnull String suffix) {
215         Map<String, T> map = new LinkedHashMap<>();
216 
217         for (T instance : instances) {
218             map.put(getLogicalPropertyName(nameFor(instance), suffix), instance);
219         }
220 
221         return map;
222     }
223 
224     @Nonnull
225     public static <T> Map<String, T> sortByDependencies(@Nonnull Collection<T> instances, @Nonnull String suffix, @Nonnull String type) {
226         return sortByDependencies(instances, suffix, type, Collections.<String>emptyList());
227     }
228 
229     @Nonnull
230     public static <T> Map<String, T> sortByDependencies(@Nonnull Collection<T> instances, @Nonnull String suffix, @Nonnull String type, @Nonnull List<String> order) {
231         requireNonNull(instances, "Argument 'instances' must not be null");
232         requireNonNull(suffix, ERROR_SUFFIX_NULL);
233         requireNonNull(type, "Argument 'type' must not be null");
234         requireNonNull(order, "Argument 'order' must not be null");
235 
236         Map<String, T> instancesByName = mapInstancesByName(instances, suffix);
237 
238         Map<String, T> map = new LinkedHashMap<>();
239         map.putAll(instancesByName);
240 
241         if (!order.isEmpty()) {
242             Map<String, T> tmp1 = new LinkedHashMap<>(instancesByName);
243             Map<String, T> tmp2 = new LinkedHashMap<>();
244             //noinspection ConstantConditions
245             for (String name : order) {
246                 if (tmp1.containsKey(name)) {
247                     tmp2.put(name, tmp1.remove(name));
248                 }
249             }
250             tmp2.putAll(tmp1);
251             map.clear();
252             map.putAll(tmp2);
253         }
254 
255         List<T> sorted = new ArrayList<>();
256         Set<String> instanceDeps = new LinkedHashSet<>();
257 
258         while (!map.isEmpty()) {
259             int processed = 0;
260 
261             LOG.debug("Current {} order is {}", type, instancesByName.keySet());
262 
263             for (Iterator<Map.Entry<String, T>> iter = map.entrySet().iterator(); iter.hasNext()) {
264                 Map.Entry<String, T> entry = iter.next();
265                 String instanceName = entry.getKey();
266                 String[] dependsOn = getDependsOn(entry.getValue());
267 
268                 LOG.trace("Processing {} '{}'", type, instanceName);
269                 LOG.trace("  depends on '{}'", Arrays.toString(dependsOn));
270 
271                 if (dependsOn.length != 0) {
272                     LOG.trace("  checking {} '{}' dependencies ({})", type, instanceName, dependsOn.length);
273 
274                     boolean failedDep = false;
275                     for (String dep : dependsOn) {
276                         LOG.trace("  checking {} '{}' dependencies: ", type, instanceName, dep);
277                         if (!instanceDeps.contains(dep)) {
278                             // dep not in the list yet, we need to skip adding this to the list for now
279                             LOG.trace("  skipped {} '{}', since dependency '{}' not yet added", type, instanceName, dep);
280                             failedDep = true;
281                             break;
282                         else {
283                             LOG.trace("  {} '{}' dependency '{}' already added", type, instanceName, dep);
284                         }
285                     }
286 
287                     if (failedDep) {
288                         // move on to next dependency
289                         continue;
290                     }
291                 }
292 
293                 LOG.trace("  adding {} '{}', since all dependencies have been added", type, instanceName);
294                 sorted.add(entry.getValue());
295                 instanceDeps.add(instanceName);
296                 iter.remove();
297                 processed++;
298             }
299 
300             if (processed == 0) {
301                 // we have a cyclical dependency, warn the user and load in the order they appeared originally
302                 LOG.warn("  unresolved {} dependencies detected", type);
303                 LOG.warn("  continuing with original {} order", type);
304                 for (Map.Entry<String, T> entry : map.entrySet()) {
305                     String instanceName = entry.getKey();
306                     String[] dependsOn = getDependsOn(entry.getValue());
307 
308                     // display this as a cyclical dep
309                     LOG.warn("  {} {} ", type, instanceName);
310                     if (dependsOn.length != 0) {
311                         for (String dep : dependsOn) {
312                             LOG.warn("    depends on {}", dep);
313                         }
314                     else {
315                         // we should only have items left in the list with deps, so this should never happen
316                         // but a wise man once said...check for true, false and otherwise...just in case
317                         LOG.warn("  problem while resolving dependencies.");
318                         LOG.warn("  unable to resolve dependency hierarchy.");
319                     }
320                 }
321                 break;
322                 // if we have processed all the instances, we are done
323             else if (sorted.size() == instancesByName.size()) {
324                 LOG.trace("{} dependency ordering complete", type);
325                 break;
326             }
327         }
328 
329         instancesByName = mapInstancesByName(sorted, suffix);
330         LOG.debug("computed {} order is {}", type, instancesByName.keySet());
331 
332         return instancesByName;
333     }
334 
335     @Nonnull
336     public static Named named(@Nonnull String name) {
337         return new NamedImpl(requireNonNull(name, "Argument 'name' must not be null"));
338     }
339 
340     @Nonnull
341     public static Typed typed(@Nonnull Class<?> clazz) {
342         return new TypedImpl(requireNonNull(clazz, ERROR_CLASS_NULL));
343     }
344 
345     @Nonnull
346     public static BindTo bindto(@Nonnull Class<?> clazz) {
347         return new BindToImpl(requireNonNull(clazz, ERROR_CLASS_NULL));
348     }
349 
350     /**
351      @author Andres Almiray
352      @since 2.0.0
353      */
354     @SuppressWarnings("ClassExplicitlyAnnotation")
355     private static class NamedImpl implements Named, Serializable {
356         private static final long serialVersionUID = 0;
357         private final String value;
358 
359         public NamedImpl(String value) {
360             this.value = requireNonNull(value, "value");
361         }
362 
363         public String value() {
364             return this.value;
365         }
366 
367         public int hashCode() {
368             // This is specified in java.lang.Annotation.
369             return (127 "value".hashCode()) ^ value.hashCode();
370         }
371 
372         public boolean equals(Object o) {
373             if (!(instanceof Named)) {
374                 return false;
375             }
376 
377             Named other = (Namedo;
378             return value.equals(other.value());
379         }
380 
381         public String toString() {
382             return "@" + Named.class.getName() "(value=" + value + ")";
383         }
384 
385         public Class<? extends Annotation> annotationType() {
386             return Named.class;
387         }
388     }
389 
390     /**
391      @author Andres Almiray
392      @since 2.0.0
393      */
394     @SuppressWarnings("ClassExplicitlyAnnotation")
395     private static class TypedImpl implements Typed, Serializable {
396         private static final long serialVersionUID = 0;
397         private final Class<?> value;
398 
399         public TypedImpl(Class<?> value) {
400             this.value = requireNonNull(value, "value");
401         }
402 
403         public Class<?> value() {
404             return this.value;
405         }
406 
407         public int hashCode() {
408             // This is specified in java.lang.Annotation.
409             return (127 "value".hashCode()) ^ value.hashCode();
410         }
411 
412         public boolean equals(Object o) {
413             if (!(instanceof Typed)) {
414                 return false;
415             }
416 
417             Typed other = (Typedo;
418             return value.equals(other.value());
419         }
420 
421         public String toString() {
422             return "@" + Typed.class.getName() "(value=" + value + ")";
423         }
424 
425         public Class<? extends Annotation> annotationType() {
426             return Typed.class;
427         }
428     }
429 
430     /**
431      @author Andres Almiray
432      @since 2.0.0
433      */
434     @SuppressWarnings("ClassExplicitlyAnnotation")
435     private static class BindToImpl implements BindTo, Serializable {
436         private static final long serialVersionUID = 0;
437         private final Class<?> value;
438 
439         public BindToImpl(Class<?> value) {
440             this.value = requireNonNull(value, "value");
441         }
442 
443         public Class<?> value() {
444             return this.value;
445         }
446 
447         public int hashCode() {
448             // This is specified in java.lang.Annotation.
449             return (127 "value".hashCode()) ^ value.hashCode();
450         }
451 
452         public boolean equals(Object o) {
453             if (!(instanceof BindTo)) {
454                 return false;
455             }
456 
457             BindTo other = (BindToo;
458             return value.equals(other.value());
459         }
460 
461         public String toString() {
462             return "@" + BindTo.class.getName() "(value=" + value + ")";
463         }
464 
465         public Class<? extends Annotation> annotationType() {
466             return BindTo.class;
467         }
468     }
469 }