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