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