AnnotationUtils.java
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 = (Namedannotation;
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 (Aannotation; }
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 (Aannotation;
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 (Aannotation;
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(currentEvictsnull : 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 (!(instanceof Named)) {
584                 return false;
585             }
586 
587             Named other = (Namedo;
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 (!(instanceof Typed)) {
624                 return false;
625             }
626 
627             Typed other = (Typedo;
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 (!(instanceof BindTo)) {
664                 return false;
665             }
666 
667             BindTo other = (BindToo;
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 }