001 /*
002 * Copyright 2008-2017 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.Evicts;
021 import griffon.inject.Typed;
022 import org.slf4j.Logger;
023 import org.slf4j.LoggerFactory;
024
025 import javax.annotation.Nonnull;
026 import javax.annotation.Nullable;
027 import javax.inject.Named;
028 import javax.inject.Qualifier;
029 import java.io.Serializable;
030 import java.lang.annotation.Annotation;
031 import java.lang.reflect.Field;
032 import java.lang.reflect.Method;
033 import java.util.ArrayList;
034 import java.util.Arrays;
035 import java.util.Collection;
036 import java.util.Collections;
037 import java.util.Iterator;
038 import java.util.LinkedHashMap;
039 import java.util.LinkedHashSet;
040 import java.util.List;
041 import java.util.Map;
042 import java.util.Set;
043
044 import static griffon.util.GriffonClassUtils.requireState;
045 import static griffon.util.GriffonNameUtils.getLogicalPropertyName;
046 import static griffon.util.GriffonNameUtils.getPropertyName;
047 import static griffon.util.GriffonNameUtils.isBlank;
048 import static griffon.util.GriffonNameUtils.uncapitalize;
049 import static java.util.Objects.requireNonNull;
050
051 /**
052 * @author Andres Almiray
053 * @since 2.0.0
054 */
055 public class AnnotationUtils {
056 private static final Logger LOG = LoggerFactory.getLogger(AnnotationUtils.class);
057 private static final String ERROR_CLASS_NULL = "Argument 'class' must not be null";
058 private static final String ERROR_METHOD_NULL = "Argument 'method' must not be null";
059 private static final String ERROR_SUFFIX_NULL = "Argument 'suffix' must not be null";
060 private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
061 private static final String ERROR_ANNOTATION_NULL = "Argument 'annotation' must not be null";
062 private static final String ERROR_ANNOTATION_TYPE_NULL = "Argument 'annotationType' must not be null";
063 private static final String ERROR_FIELD_NULL = "Argument 'field' must not be null";
064 private static final String ERROR_SETTER_METHOD_NULL = "Argument 'setterMethod' must not be null";
065 private static final String VALUE = "value";
066
067 private AnnotationUtils() {
068 // prevent instantiation
069 }
070
071 @Nonnull
072 public static List<Annotation> harvestQualifiers(@Nonnull Class<?> klass) {
073 requireNonNull(klass, ERROR_CLASS_NULL);
074 List<Annotation> list = new ArrayList<>();
075 Annotation[] annotations = klass.getAnnotations();
076 for (Annotation annotation : annotations) {
077 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
078 // special case @BindTo is only used during tests
079 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
080 continue;
081 }
082 // special case for @Named
083 if (Named.class.isAssignableFrom(annotation.getClass())) {
084 Named named = (Named) annotation;
085 if (isBlank(named.value())) {
086 list.add(named(getPropertyName(klass)));
087 continue;
088 }
089 }
090 list.add(annotation);
091 }
092 }
093 return list;
094 }
095
096 @Nullable
097 public static <A extends Annotation> A findAnnotation(@Nonnull Class<?> klass, @Nonnull Class<A> annotationType) {
098 return findAnnotation(klass, annotationType, false);
099 }
100
101 @Nullable
102 public static <A extends Annotation> A findAnnotation(@Nonnull Class<?> klass, @Nonnull Class<A> annotationType, boolean deep) {
103 requireNonNull(klass, ERROR_CLASS_NULL);
104 requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
105
106 while (klass != null) {
107 Annotation annotation = findAnnotation(klass.getAnnotations(), annotationType, deep);
108 if (annotation != null) { return (A) annotation; }
109 klass = klass.getSuperclass();
110 }
111 return null;
112 }
113
114 @Nullable
115 public static <A extends Annotation> A findAnnotation(@Nonnull Method method, @Nonnull Class<A> annotationType) {
116 return findAnnotation(method, annotationType, false);
117 }
118
119 @Nullable
120 public static <A extends Annotation> A findAnnotation(@Nonnull Method method, @Nonnull Class<A> annotationType, boolean deep) {
121 requireNonNull(method, ERROR_METHOD_NULL);
122 requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
123
124 for (Annotation annotation : method.getAnnotations()) {
125 if (annotationType.equals(annotation.annotationType())) {
126 return (A) annotation;
127 }
128 if (deep) {
129 A a = findAnnotation(annotation.annotationType().getAnnotations(), annotationType, deep);
130 if (a != null) {
131 return a;
132 }
133 }
134 }
135
136 return null;
137 }
138
139 @Nullable
140 public static <A extends Annotation> A findAnnotation(@Nonnull Annotation[] annotations, @Nonnull Class<A> annotationType) {
141 return findAnnotation(annotations, annotationType, false);
142 }
143
144 @Nullable
145 public static <A extends Annotation> A findAnnotation(@Nonnull Annotation[] annotations, @Nonnull Class<A> annotationType, boolean deep) {
146 requireNonNull(annotations, "Argument 'annotations' must not be null");
147 requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
148
149 for (Annotation annotation : annotations) {
150 if (annotationType.isAssignableFrom(annotation.annotationType())) {
151 return (A) annotation;
152 }
153 /*
154 if (deep) {
155 A a = findAnnotation(annotation.annotationType().getAnnotations(), annotationType, deep);
156 if (a != null) {
157 return a;
158 }
159 }*/
160 }
161 return null;
162 }
163
164 public static boolean isAnnotatedWith(@Nonnull Object instance, @Nonnull Class<? extends Annotation> annotationType) {
165 return isAnnotatedWith(requireNonNull(instance, ERROR_INSTANCE_NULL).getClass(), annotationType);
166 }
167
168 public static boolean isAnnotatedWith(@Nonnull Method method, @Nonnull Class<? extends Annotation> annotationType) {
169 return isAnnotatedWith(method, annotationType, false);
170 }
171
172 public static boolean isAnnotatedWith(@Nonnull Method method, @Nonnull Class<? extends Annotation> annotationType, boolean deep) {
173 requireNonNull(method, ERROR_METHOD_NULL);
174 requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
175
176 for (Annotation annotation : method.getAnnotations()) {
177 if (annotationType.equals(annotation.annotationType())) {
178 return true;
179 }
180 if (deep) {
181 if (isAnnotatedWith(annotation, annotationType)) {
182 return true;
183 }
184 }
185 }
186 return false;
187 }
188
189 public static boolean isAnnotatedWith(@Nonnull Annotation annotation, @Nonnull Class<? extends Annotation> annotationType) {
190 return isAnnotatedWith(annotation, annotationType, false);
191 }
192
193 public static boolean isAnnotatedWith(@Nonnull Annotation annotation, @Nonnull Class<? extends Annotation> annotationType, boolean deep) {
194 requireNonNull(annotation, ERROR_ANNOTATION_NULL);
195 requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
196
197 for (Annotation a : annotation.annotationType().getAnnotations()) {
198 if (annotationType.equals(a.annotationType())) {
199 return true;
200 }
201
202 if (deep) {
203 if (isAnnotatedWith(a, annotationType)) {
204 return true;
205 }
206 }
207 }
208 return false;
209 }
210
211 public static boolean isAnnotatedWith(@Nonnull Class<?> clazz, @Nonnull Class<? extends Annotation> annotationType) {
212 requireNonNull(clazz, ERROR_CLASS_NULL);
213 requireNonNull(annotationType, ERROR_ANNOTATION_TYPE_NULL);
214
215 while (clazz != null) {
216 for (Annotation annotation : clazz.getAnnotations()) {
217 if (annotationType.equals(annotation.annotationType())) {
218 return true;
219 }
220 }
221 for (Class<?> iface : clazz.getInterfaces()) {
222 if (isAnnotatedWith(iface, annotationType)) {
223 return true;
224 }
225 }
226
227 clazz = clazz.getSuperclass();
228 }
229 return false;
230 }
231
232 @Nonnull
233 public static <T> T requireAnnotation(@Nonnull T instance, @Nonnull Class<? extends Annotation> annotationType) {
234 if (!isAnnotatedWith(instance, annotationType)) {
235 throw new IllegalArgumentException("Instance of " + instance.getClass() + " is not annotated with " + annotationType.getName());
236 }
237 return instance;
238 }
239
240 @Nonnull
241 public static <T> Class<T> requireAnnotation(@Nonnull Class<T> klass, @Nonnull Class<? extends Annotation> annotationType) {
242 if (!isAnnotatedWith(klass, annotationType)) {
243 throw new IllegalArgumentException("Class " + klass.getName() + " is not annotated with " + annotationType.getName());
244 }
245 return klass;
246 }
247
248 @Nonnull
249 public static String[] getDependsOn(@Nonnull Object instance) {
250 requireNonNull(instance, ERROR_INSTANCE_NULL);
251
252 DependsOn dependsOn = instance.getClass().getAnnotation(DependsOn.class);
253 return dependsOn != null ? dependsOn.value() : new String[0];
254 }
255
256 @Nonnull
257 public static String getEvicts(@Nonnull Object instance) {
258 requireNonNull(instance, ERROR_INSTANCE_NULL);
259
260 Evicts evicts = instance.getClass().getAnnotation(Evicts.class);
261 return evicts != null ? evicts.value() : "";
262 }
263
264 @Nonnull
265 public static String nameFor(@Nonnull Class<?> klass) {
266 return nameFor(klass, false);
267 }
268
269 @Nonnull
270 public static String nameFor(@Nonnull Class<?> klass, boolean simple) {
271 requireNonNull(klass, ERROR_CLASS_NULL);
272
273 Named annotation = klass.getAnnotation(Named.class);
274 if (annotation != null && !isBlank(annotation.value())) {
275 return annotation.value();
276 } else {
277 return simple ? klass.getSimpleName() : klass.getName();
278 }
279 }
280
281 @Nonnull
282 public static String nameFor(@Nonnull Object instance) {
283 return nameFor(instance, false);
284 }
285
286 @Nonnull
287 public static String nameFor(@Nonnull Object instance, boolean simple) {
288 requireNonNull(instance, ERROR_INSTANCE_NULL);
289
290 Named annotation = instance.getClass().getAnnotation(Named.class);
291 if (annotation != null && !isBlank(annotation.value())) {
292 return annotation.value();
293 } else {
294 return simple ? instance.getClass().getSimpleName() : instance.getClass().getName();
295 }
296 }
297
298 @Nonnull
299 public static String nameFor(@Nonnull Field field) {
300 return nameFor(field, false);
301 }
302
303 @Nonnull
304 public static String nameFor(@Nonnull Field field, boolean simple) {
305 requireNonNull(field, ERROR_FIELD_NULL);
306
307 Named annotation = field.getAnnotation(Named.class);
308 if (annotation != null && !isBlank(annotation.value())) {
309 return annotation.value();
310 } else {
311 return simple ? field.getType().getSimpleName() : field.getType().getName();
312 }
313 }
314
315 @Nonnull
316 public static String[] namesFor(@Nonnull Field field) {
317 requireNonNull(field, ERROR_FIELD_NULL);
318
319 List<String> names = new ArrayList<>();
320 Named annotation = field.getAnnotation(Named.class);
321 if (annotation != null && !isBlank(annotation.value())) {
322 names.add(annotation.value());
323 } else {
324 names.add(field.getName());
325 }
326 names.add(field.getType().getName());
327 return names.toArray(new String[names.size()]);
328 }
329
330 @Nonnull
331 public static String nameFor(@Nonnull Method setterMethod) {
332 requireNonNull(setterMethod, ERROR_SETTER_METHOD_NULL);
333
334 Class<?>[] parameterTypes = setterMethod.getParameterTypes();
335 requireState(parameterTypes != null && parameterTypes.length > 0, "Argument 'setterMethod' must have at least one parameter. " + MethodDescriptor.forMethod(setterMethod));
336
337 Named annotation = findAnnotation(annotationsOfMethodParameter(setterMethod, 0), Named.class);
338 if (annotation != null && !isBlank(annotation.value())) {
339 return annotation.value();
340 } else {
341 return parameterTypes[0].getName();
342 }
343 }
344
345 @Nonnull
346 public static String[] namesFor(@Nonnull Method setterMethod) {
347 requireNonNull(setterMethod, ERROR_SETTER_METHOD_NULL);
348
349 Class<?>[] parameterTypes = setterMethod.getParameterTypes();
350 requireState(parameterTypes != null && parameterTypes.length > 0, "Argument 'setterMethod' must have at least one parameter. " + MethodDescriptor.forMethod(setterMethod));
351
352
353 List<String> names = new ArrayList<>();
354 Named annotation = findAnnotation(annotationsOfMethodParameter(setterMethod, 0), Named.class);
355 if (annotation != null && !isBlank(annotation.value())) {
356 names.add(annotation.value());
357 } else {
358 if (GriffonClassUtils.isSetterMethod(setterMethod)) {
359 names.add(uncapitalize(setterMethod.getName().substring(3)));
360 } else {
361 names.add(uncapitalize(setterMethod.getName()));
362 }
363 }
364 names.add(parameterTypes[0].getName());
365 return names.toArray(new String[names.size()]);
366 }
367
368 @Nonnull
369 public static Annotation[] annotationsOfMethodParameter(@Nonnull Method method, int paramIndex) {
370 requireNonNull(method, "Argument 'method' must not be null");
371
372 Class<?>[] parameterTypes = method.getParameterTypes();
373 requireState(parameterTypes != null && parameterTypes.length > paramIndex, "Index " + paramIndex + " is out of bounds");
374
375 return method.getParameterAnnotations()[paramIndex];
376 }
377
378 @Nonnull
379 public static <T> Map<String, T> mapInstancesByName(@Nonnull Collection<T> instances, @Nonnull String suffix) {
380 Map<String, T> map = new LinkedHashMap<>();
381
382 for (T instance : instances) {
383 map.put(getLogicalPropertyName(nameFor(instance), suffix), instance);
384 }
385
386 return map;
387 }
388
389 @Nonnull
390 public static <T> Map<String, T> mapInstancesByName(@Nonnull Collection<T> instances, @Nonnull String suffix, @Nonnull String type) {
391 Map<String, T> map = new LinkedHashMap<>();
392
393 for (T instance : instances) {
394 String currentEvicts = getEvicts(instance);
395 String name = getLogicalPropertyName(nameFor(instance), suffix);
396 Object evictedInstance = isBlank(currentEvicts) ? null : map.get(currentEvicts);
397
398 if (evictedInstance != null) {
399 String evictedEvicts = getEvicts(evictedInstance);
400 if (!isBlank(evictedEvicts)) {
401 throw new IllegalArgumentException(type + " " + name + " has an eviction conflict between " + instance + " and " + evictedInstance);
402 } else {
403 name = currentEvicts;
404 LOG.info("{} {} with instance {} evicted by {}", type, name, evictedInstance, instance);
405 }
406 } else {
407 name = isBlank(currentEvicts) ? name : currentEvicts;
408 Object previousInstance = map.get(name);
409 if (previousInstance != null) {
410 if (isBlank(getEvicts(previousInstance))) {
411 throw new IllegalArgumentException(type + " " + name + " neither " + instance + " nor " + previousInstance + " is marked with @Evict");
412 } else {
413 LOG.info("{} {} with instance {} evicted by {}", type, name, instance, previousInstance);
414 }
415 }
416 }
417
418 map.put(name, instance);
419 }
420
421 return map;
422 }
423
424 @Nonnull
425 public static <T> Map<String, T> sortByDependencies(@Nonnull Collection<T> instances, @Nonnull String suffix, @Nonnull String type) {
426 return sortByDependencies(instances, suffix, type, Collections.<String>emptyList());
427 }
428
429 @Nonnull
430 public static <T> Map<String, T> sortByDependencies(@Nonnull Collection<T> instances, @Nonnull String suffix, @Nonnull String type, @Nonnull List<String> order) {
431 requireNonNull(instances, "Argument 'instances' must not be null");
432 requireNonNull(suffix, ERROR_SUFFIX_NULL);
433 requireNonNull(type, "Argument 'type' must not be null");
434 requireNonNull(order, "Argument 'order' must not be null");
435
436 Map<String, T> instancesByName = mapInstancesByName(instances, suffix, type);
437
438 Map<String, T> map = new LinkedHashMap<>();
439 map.putAll(instancesByName);
440
441 if (!order.isEmpty()) {
442 Map<String, T> tmp1 = new LinkedHashMap<>(instancesByName);
443 Map<String, T> tmp2 = new LinkedHashMap<>();
444 //noinspection ConstantConditions
445 for (String name : order) {
446 if (tmp1.containsKey(name)) {
447 tmp2.put(name, tmp1.remove(name));
448 }
449 }
450 tmp2.putAll(tmp1);
451 map.clear();
452 map.putAll(tmp2);
453 }
454
455 List<T> sorted = new ArrayList<>();
456 Set<String> instanceDeps = new LinkedHashSet<>();
457
458 while (!map.isEmpty()) {
459 int processed = 0;
460
461 LOG.debug("Current {} order is {}", type, instancesByName.keySet());
462
463 for (Iterator<Map.Entry<String, T>> iter = map.entrySet().iterator(); iter.hasNext(); ) {
464 Map.Entry<String, T> entry = iter.next();
465 String instanceName = entry.getKey();
466 String[] dependsOn = getDependsOn(entry.getValue());
467
468 LOG.trace("Processing {} '{}'", type, instanceName);
469 LOG.trace(" depends on '{}'", Arrays.toString(dependsOn));
470
471 if (dependsOn.length != 0) {
472 LOG.trace(" checking {} '{}' dependencies ({})", type, instanceName, dependsOn.length);
473
474 boolean failedDep = false;
475 for (String dep : dependsOn) {
476 LOG.trace(" checking {} '{}' dependencies: ", type, instanceName, dep);
477 if (!instanceDeps.contains(dep)) {
478 // dep not in the list yet, we need to skip adding this to the list for now
479 LOG.trace(" skipped {} '{}', since dependency '{}' not yet added", type, instanceName, dep);
480 failedDep = true;
481 break;
482 } else {
483 LOG.trace(" {} '{}' dependency '{}' already added", type, instanceName, dep);
484 }
485 }
486
487 if (failedDep) {
488 // move on to next dependency
489 continue;
490 }
491 }
492
493 LOG.trace(" adding {} '{}', since all dependencies have been added", type, instanceName);
494 sorted.add(entry.getValue());
495 instanceDeps.add(instanceName);
496 iter.remove();
497 processed++;
498 }
499
500 if (processed == 0) {
501 // we have a cyclical dependency, warn the user and load in the order they appeared originally
502 LOG.warn(" unresolved {} dependencies detected", type);
503 LOG.warn(" continuing with original {} order", type);
504 for (Map.Entry<String, T> entry : map.entrySet()) {
505 String instanceName = entry.getKey();
506 String[] dependsOn = getDependsOn(entry.getValue());
507
508 // display this as a cyclical dep
509 LOG.warn(" {} {} ", type, instanceName);
510 if (dependsOn.length != 0) {
511 for (String dep : dependsOn) {
512 LOG.warn(" depends on {}", dep);
513 }
514 } else {
515 // we should only have items left in the list with deps, so this should never happen
516 // but a wise man once said...check for true, false and otherwise...just in case
517 LOG.warn(" problem while resolving dependencies.");
518 LOG.warn(" unable to resolve dependency hierarchy.");
519 }
520 }
521 break;
522 // if we have processed all the instances, we are done
523 } else if (sorted.size() == instancesByName.size()) {
524 LOG.trace("{} dependency ordering complete", type);
525 break;
526 }
527 }
528
529 instancesByName = mapInstancesByName(sorted, suffix);
530 LOG.debug("computed {} order is {}", type, instancesByName.keySet());
531
532 return instancesByName;
533 }
534
535 @Nonnull
536 public static Named named(@Nonnull String name) {
537 return new NamedImpl(requireNonNull(name, "Argument 'name' must not be null"));
538 }
539
540 @Nonnull
541 public static Typed typed(@Nonnull Class<?> clazz) {
542 return new TypedImpl(requireNonNull(clazz, ERROR_CLASS_NULL));
543 }
544
545 @Nonnull
546 public static BindTo bindto(@Nonnull Class<?> clazz) {
547 return new BindToImpl(requireNonNull(clazz, ERROR_CLASS_NULL));
548 }
549
550 /**
551 * @author Andres Almiray
552 * @since 2.0.0
553 */
554 @SuppressWarnings("ClassExplicitlyAnnotation")
555 private static class NamedImpl implements Named, Serializable {
556 private static final long serialVersionUID = 0;
557 private final String value;
558
559 public NamedImpl(String value) {
560 this.value = requireNonNull(value, VALUE);
561 }
562
563 public String value() {
564 return this.value;
565 }
566
567 public int hashCode() {
568 // This is specified in java.lang.Annotation.
569 return (127 * VALUE.hashCode()) ^ value.hashCode();
570 }
571
572 public boolean equals(Object o) {
573 if (!(o instanceof Named)) {
574 return false;
575 }
576
577 Named other = (Named) o;
578 return value.equals(other.value());
579 }
580
581 public String toString() {
582 return "@" + Named.class.getName() + "(value=" + value + ")";
583 }
584
585 public Class<? extends Annotation> annotationType() {
586 return Named.class;
587 }
588 }
589
590 /**
591 * @author Andres Almiray
592 * @since 2.0.0
593 */
594 @SuppressWarnings("ClassExplicitlyAnnotation")
595 private static class TypedImpl implements Typed, Serializable {
596 private static final long serialVersionUID = 0;
597 private final Class<?> value;
598
599 public TypedImpl(Class<?> value) {
600 this.value = requireNonNull(value, VALUE);
601 }
602
603 public Class<?> value() {
604 return this.value;
605 }
606
607 public int hashCode() {
608 // This is specified in java.lang.Annotation.
609 return (127 * VALUE.hashCode()) ^ value.hashCode();
610 }
611
612 public boolean equals(Object o) {
613 if (!(o instanceof Typed)) {
614 return false;
615 }
616
617 Typed other = (Typed) o;
618 return value.equals(other.value());
619 }
620
621 public String toString() {
622 return "@" + Typed.class.getName() + "(value=" + value + ")";
623 }
624
625 public Class<? extends Annotation> annotationType() {
626 return Typed.class;
627 }
628 }
629
630 /**
631 * @author Andres Almiray
632 * @since 2.0.0
633 */
634 @SuppressWarnings("ClassExplicitlyAnnotation")
635 private static class BindToImpl implements BindTo, Serializable {
636 private static final long serialVersionUID = 0;
637 private final Class<?> value;
638
639 public BindToImpl(Class<?> value) {
640 this.value = requireNonNull(value, VALUE);
641 }
642
643 public Class<?> value() {
644 return this.value;
645 }
646
647 public int hashCode() {
648 // This is specified in java.lang.Annotation.
649 return (127 * VALUE.hashCode()) ^ value.hashCode();
650 }
651
652 public boolean equals(Object o) {
653 if (!(o instanceof BindTo)) {
654 return false;
655 }
656
657 BindTo other = (BindTo) o;
658 return value.equals(other.value());
659 }
660
661 public String toString() {
662 return "@" + BindTo.class.getName() + "(value=" + value + ")";
663 }
664
665 public Class<? extends Annotation> annotationType() {
666 return BindTo.class;
667 }
668 }
669 }
|