AnnotationUtils.java
001 /*
002  * Copyright 2008-2014 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.util.*;
031 
032 import static griffon.util.GriffonNameUtils.getLogicalPropertyName;
033 import static griffon.util.GriffonNameUtils.getPropertyName;
034 import static griffon.util.GriffonNameUtils.isBlank;
035 import static java.util.Objects.requireNonNull;
036 
037 /**
038  @author Andres Almiray
039  @since 2.0.0
040  */
041 public class AnnotationUtils {
042     private static final Logger LOG = LoggerFactory.getLogger(AnnotationUtils.class);
043 
044     private AnnotationUtils() {
045 
046     }
047 
048     @Nonnull
049     public static List<Annotation> harvestQualifiers(@Nonnull Class<?> klass) {
050         requireNonNull(klass, "Argument 'class' must not be null");
051         List<Annotation> list = new ArrayList<>();
052         Annotation[] annotations = klass.getAnnotations();
053         for (Annotation annotation : annotations) {
054             if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
055                 // special case @BindTo is only used during tests
056                 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
057                     continue;
058                 }
059                 // special case for @Named
060                 if (Named.class.isAssignableFrom(annotation.getClass())) {
061                     Named named = (Namedannotation;
062                     if (isBlank(named.value())) {
063                         list.add(named(getPropertyName(klass)));
064                         continue;
065                     }
066                 }
067                 list.add(annotation);
068             }
069         }
070         return list;
071     }
072 
073     @Nullable
074     public static <A extends Annotation> A findAnnotation(@Nonnull Class<?> klass, @Nonnull Class<A> annotationType) {
075         requireNonNull(klass, "Argument 'class' must not be null");
076         requireNonNull(annotationType, "Argument 'annotationType' must not be null");
077         while (klass != null) {
078             for (Annotation annotation : klass.getAnnotations()) {
079                 if (annotationType.isAssignableFrom(annotation.getClass())) {
080                     return (Aannotation;
081                 }
082             }
083             klass = klass.getSuperclass();
084         }
085         return null;
086     }
087 
088     public static boolean isAnnotatedWith(@Nonnull Object instance, @Nonnull Class<? extends Annotation> annotationType) {
089         return isAnnotatedWith(requireNonNull(instance, "Argument 'instance' must not be null").getClass(), annotationType);
090     }
091 
092     public static boolean isAnnotatedWith(@Nonnull Class<?> clazz, @Nonnull Class<? extends Annotation> annotationType) {
093         requireNonNull(clazz, "Argument 'class' must not be null");
094         requireNonNull(annotationType, "Argument 'annotationType' must not be null");
095 
096         //noinspection ConstantConditions
097         while (clazz != null) {
098             for (Annotation annotation : clazz.getAnnotations()) {
099                 if (annotationType.equals(annotation.annotationType())) {
100                     return true;
101                 }
102             }
103             for (Class<?> iface : clazz.getInterfaces()) {
104                 if (isAnnotatedWith(iface, annotationType)) {
105                     return true;
106                 }
107             }
108 
109             clazz = clazz.getSuperclass();
110         }
111         return false;
112     }
113 
114     @Nonnull
115     public static <T> T requireAnnotation(@Nonnull T instance, @Nonnull Class<? extends Annotation> annotationType) {
116         if (!isAnnotatedWith(instance, annotationType)) {
117             throw new IllegalArgumentException("Instance of " + instance.getClass() " is not annotated with " + annotationType.getName());
118         }
119         return instance;
120     }
121 
122     @Nonnull
123     public static <T> Class<T> requireAnnotation(@Nonnull Class<T> klass, @Nonnull Class<? extends Annotation> annotationType) {
124         if (!isAnnotatedWith(klass, annotationType)) {
125             throw new IllegalArgumentException("Class " + klass.getName() " is not annotated with " + annotationType.getName());
126         }
127         return klass;
128     }
129 
130     @Nonnull
131     public static String[] getDependsOn(@Nonnull Object instance) {
132         DependsOn dependsOn = instance.getClass().getAnnotation(DependsOn.class);
133         return dependsOn != null ? dependsOn.value() new String[0];
134     }
135 
136     @Nonnull
137     public static String nameFor(@Nonnull Object instance, @Nonnull String suffix) {
138         Named annotation = instance.getClass().getAnnotation(Named.class);
139         if (annotation != null && !isBlank(annotation.value())) {
140             return annotation.value();
141         else {
142             return getLogicalPropertyName(instance.getClass().getName(), suffix);
143         }
144     }
145 
146     @Nonnull
147     public static <T> Map<String, T> mapInstancesByName(@Nonnull Collection<T> instances, @Nonnull String suffix) {
148         Map<String, T> map = new LinkedHashMap<>();
149 
150         for (T instance : instances) {
151             map.put(nameFor(instance, suffix), instance);
152         }
153 
154         return map;
155     }
156 
157     @Nonnull
158     public static <T> Map<String, T> sortByDependencies(@Nonnull Collection<T> instances, @Nonnull String suffix, @Nonnull String type) {
159         return sortByDependencies(instances, suffix, type, Collections.<String>emptyList());
160     }
161 
162     @Nonnull
163     public static <T> Map<String, T> sortByDependencies(@Nonnull Collection<T> instances, @Nonnull String suffix, @Nonnull String type, @Nonnull List<String> order) {
164         requireNonNull(instances, "Argument 'instances' must not be null");
165         requireNonNull(suffix, "Argument 'suffix' must not be null");
166         requireNonNull(type, "Argument 'type' must not be null");
167         requireNonNull(order, "Argument 'order' must not be null");
168 
169         Map<String, T> instancesByName = mapInstancesByName(instances, suffix);
170 
171         Map<String, T> map = new LinkedHashMap<>();
172         map.putAll(instancesByName);
173 
174         if (!order.isEmpty()) {
175             Map<String, T> tmp1 = new LinkedHashMap<>(instancesByName);
176             Map<String, T> tmp2 = new LinkedHashMap<>();
177             //noinspection ConstantConditions
178             for (String name : order) {
179                 if (tmp1.containsKey(name)) {
180                     tmp2.put(name, tmp1.remove(name));
181                 }
182             }
183             tmp2.putAll(tmp1);
184             map.clear();
185             map.putAll(tmp2);
186         }
187 
188         List<T> sorted = new ArrayList<>();
189         Set<String> instanceDeps = new LinkedHashSet<>();
190 
191         while (!map.isEmpty()) {
192             int processed = 0;
193 
194             LOG.debug("Current {} order is {}", type, instancesByName.keySet());
195 
196             for (Iterator<Map.Entry<String, T>> iter = map.entrySet().iterator(); iter.hasNext()) {
197                 Map.Entry<String, T> entry = iter.next();
198                 String instanceName = entry.getKey();
199                 String[] dependsOn = getDependsOn(entry.getValue());
200 
201                 LOG.trace("Processing {} '{}'", type, instanceName);
202                 LOG.trace("  depends on '{}'", Arrays.toString(dependsOn));
203 
204                 if (dependsOn.length != 0) {
205                     LOG.trace("  checking {} '{}' dependencies ({})", type, instanceName, dependsOn.length);
206 
207                     boolean failedDep = false;
208                     for (String dep : dependsOn) {
209                         LOG.trace("  checking {} '{}' dependencies: ", type, instanceName, dep);
210                         if (!instanceDeps.contains(dep)) {
211                             // dep not in the list yet, we need to skip adding this to the list for now
212                             LOG.trace("  skipped {} '{}', since dependency '{}' not yet added", type, instanceName, dep);
213                             failedDep = true;
214                             break;
215                         else {
216                             LOG.trace("  {} '{}' dependency '{}' already added", type, instanceName, dep);
217                         }
218                     }
219 
220                     if (failedDep) {
221                         // move on to next dependency
222                         continue;
223                     }
224                 }
225 
226                 LOG.trace("  adding {} '{}', since all dependencies have been added", type, instanceName);
227                 sorted.add(entry.getValue());
228                 instanceDeps.add(instanceName);
229                 iter.remove();
230                 processed++;
231             }
232 
233             if (processed == 0) {
234                 // we have a cyclical dependency, warn the user and load in the order they appeared originally
235                 LOG.warn("  unresolved {} dependencies detected", type);
236                 LOG.warn("  continuing with original {} order", type);
237                 for (Map.Entry<String, T> entry : map.entrySet()) {
238                     String instanceName = entry.getKey();
239                     String[] dependsOn = getDependsOn(entry.getValue());
240 
241                     // display this as a cyclical dep
242                     LOG.warn("  {} {} ", type, instanceName);
243                     if (dependsOn.length != 0) {
244                         for (String dep : dependsOn) {
245                             LOG.warn("    depends on {}", dep);
246                         }
247                     else {
248                         // we should only have items left in the list with deps, so this should never happen
249                         // but a wise man once said...check for true, false and otherwise...just in case
250                         LOG.warn("  problem while resolving dependencies.");
251                         LOG.warn("  unable to resolve dependency hierarchy.");
252                     }
253                 }
254                 break;
255                 // if we have processed all the instances, we are done
256             else if (sorted.size() == instancesByName.size()) {
257                 LOG.trace("{} dependency ordering complete", type);
258                 break;
259             }
260         }
261 
262         instancesByName = mapInstancesByName(sorted, suffix);
263         LOG.debug("computed {} order is {}", type, instancesByName.keySet());
264 
265         return instancesByName;
266     }
267 
268     @Nonnull
269     public static Named named(@Nonnull String name) {
270         return new NamedImpl(requireNonNull(name, "Argument 'name' must not be null"));
271     }
272 
273     @Nonnull
274     public static Typed typed(@Nonnull Class<?> clazz) {
275         return new TypedImpl(requireNonNull(clazz, "Argument 'clazz' must not be null"));
276     }
277 
278     @Nonnull
279     public static BindTo bindto(@Nonnull Class<?> clazz) {
280         return new BindToImpl(requireNonNull(clazz, "Argument 'clazz' must not be null"));
281     }
282 
283     /**
284      @author Andres Almiray
285      @since 2.0.0
286      */
287     @SuppressWarnings("ClassExplicitlyAnnotation")
288     private static class NamedImpl implements Named, Serializable {
289         private final String value;
290 
291         public NamedImpl(String value) {
292             this.value = requireNonNull(value, "value");
293         }
294 
295         public String value() {
296             return this.value;
297         }
298 
299         public int hashCode() {
300             // This is specified in java.lang.Annotation.
301             return (127 "value".hashCode()) ^ value.hashCode();
302         }
303 
304         public boolean equals(Object o) {
305             if (!(instanceof Named)) {
306                 return false;
307             }
308 
309             Named other = (Namedo;
310             return value.equals(other.value());
311         }
312 
313         public String toString() {
314             return "@" + Named.class.getName() "(value=" + value + ")";
315         }
316 
317         public Class<? extends Annotation> annotationType() {
318             return Named.class;
319         }
320 
321         private static final long serialVersionUID = 0;
322     }
323 
324     /**
325      @author Andres Almiray
326      @since 2.0.0
327      */
328     @SuppressWarnings("ClassExplicitlyAnnotation")
329     private static class TypedImpl implements Typed, Serializable {
330         private final Class<?> value;
331 
332         public TypedImpl(Class<?> value) {
333             this.value = requireNonNull(value, "value");
334         }
335 
336         public Class<?> value() {
337             return this.value;
338         }
339 
340         public int hashCode() {
341             // This is specified in java.lang.Annotation.
342             return (127 "value".hashCode()) ^ value.hashCode();
343         }
344 
345         public boolean equals(Object o) {
346             if (!(instanceof Typed)) {
347                 return false;
348             }
349 
350             Typed other = (Typedo;
351             return value.equals(other.value());
352         }
353 
354         public String toString() {
355             return "@" + Typed.class.getName() "(value=" + value + ")";
356         }
357 
358         public Class<? extends Annotation> annotationType() {
359             return Typed.class;
360         }
361 
362         private static final long serialVersionUID = 0;
363     }
364 
365     /**
366      @author Andres Almiray
367      @since 2.0.0
368      */
369     @SuppressWarnings("ClassExplicitlyAnnotation")
370     private static class BindToImpl implements BindTo, Serializable {
371         private final Class<?> value;
372 
373         public BindToImpl(Class<?> value) {
374             this.value = requireNonNull(value, "value");
375         }
376 
377         public Class<?> value() {
378             return this.value;
379         }
380 
381         public int hashCode() {
382             // This is specified in java.lang.Annotation.
383             return (127 "value".hashCode()) ^ value.hashCode();
384         }
385 
386         public boolean equals(Object o) {
387             if (!(instanceof BindTo)) {
388                 return false;
389             }
390 
391             BindTo other = (BindToo;
392             return value.equals(other.value());
393         }
394 
395         public String toString() {
396             return "@" + BindTo.class.getName() "(value=" + value + ")";
397         }
398 
399         public Class<? extends Annotation> annotationType() {
400             return BindTo.class;
401         }
402 
403         private static final long serialVersionUID = 0;
404     }
405 }