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