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