AnnotationUtils.java
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 = (Namedannotation;
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 (Aannotation; }
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 (Aannotation;
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 (Aannotation;
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(currentEvictsnull : 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 (!(instanceof Named)) {
574                 return false;
575             }
576 
577             Named other = (Namedo;
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 (!(instanceof Typed)) {
614                 return false;
615             }
616 
617             Typed other = (Typedo;
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 (!(instanceof BindTo)) {
654                 return false;
655             }
656 
657             BindTo other = (BindToo;
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 }