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 }
|