]> git.somenet.org - pub/jan/dst18.git/blob - ass2-di/src/test/java/dst/ass2/di/InjectionUtils.java
[2.2.2] Transparent DI works.
[pub/jan/dst18.git] / ass2-di / src / test / java / dst / ass2 / di / InjectionUtils.java
1 package dst.ass2.di;
2
3 import static org.springframework.util.ReflectionUtils.FieldCallback;
4 import static org.springframework.util.ReflectionUtils.FieldFilter;
5 import static org.springframework.util.ReflectionUtils.doWithFields;
6 import static org.springframework.util.ReflectionUtils.getField;
7 import static org.springframework.util.ReflectionUtils.makeAccessible;
8
9 import java.lang.reflect.Field;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14
15 import org.springframework.core.annotation.AnnotationUtils;
16 import org.springframework.util.Assert;
17
18 import dst.ass2.di.annotation.Component;
19 import dst.ass2.di.annotation.ComponentId;
20 import dst.ass2.di.annotation.Inject;
21
22 /**
23  * Contains some utilities for testing dependency injection.
24  */
25 public final class InjectionUtils {
26     private InjectionUtils() {
27     }
28
29     /**
30      * Returns the component ID of the given object declared by a certain type.
31      * <p/>
32      * The ID must be declared by the given type.
33      * Inherited fields or fields of super classes are not checked.<br/>
34      * If {@code type} is {@code null}, the actual type of the object is used instead.
35      *
36      * @param component the object
37      * @param type the type of the object to retrieve (may be {@code null})
38      * @return the component ID or {@code null} if none was found
39      * @see Class#getDeclaredFields()
40      */
41     public static Long getId(Object component, Class<?> type) {
42         Assert.notNull(AnnotationUtils.findAnnotation(component.getClass(), Component.class), "Object is not annotated with @Component: " + component);
43         type = type != null ? type : component.getClass();
44         for (Field field : type.getDeclaredFields()) {
45             if (field.isAnnotationPresent(ComponentId.class)) {
46                 makeAccessible(field);
47                 return (Long) getField(field, component);
48             }
49         }
50         return null;
51     }
52
53     /**
54      * Returns all component ID fields (including inherited and hidden) and their values of the given object.
55      *
56      * @param component the component to check
57      * @return the fields and their values
58      */
59     public static Map<Field, Long> getIdsOfHierarchy(Object component) {
60         Assert.notNull(AnnotationUtils.findAnnotation(component.getClass(), Component.class), "Object is not annotated with @Component: " + component);
61         ComponentIdCallback callback = new ComponentIdCallback(component);
62         doWithFields(component.getClass(), callback, new ComponentIdFilter());
63         return callback.ids;
64     }
65
66     /**
67      * Traverses the given object graph and returns all component IDs.
68      *
69      * @param component the component to check
70      * @param map the map to store the IDs
71      * @return the component IDs of the objects
72      * @see #getIdsOfHierarchy(Object)
73      */
74     public static Map<Object, Map<Field, Long>> getIdsOfObjectGraph(final Object component, Map<Object, Map<Field, Long>> map) {
75         map = map != null ? map : new HashMap<Object, Map<Field, Long>>();
76         if (component != null && !map.containsKey(component)) {
77             final Map<Object, Map<Field, Long>> finalMap = map;
78             map.put(component, getIdsOfHierarchy(component));
79             doWithFields(component.getClass(), new FieldCallback() {
80                 @Override
81                 public void doWith(Field field) {
82                     makeAccessible(field);
83                     Object value = getField(field, component);
84                     if (value != null) {
85                         finalMap.putAll(getIdsOfObjectGraph(value, finalMap));
86                     }
87                 }
88             }, new InjectFilter());
89         }
90         return map;
91     }
92
93     /**
94      * Returns a list of all component IDs retrieved by {@link #getIdsOfObjectGraph(Object, Map)}.<br/>
95      *
96      * @param component the component to check
97      * @return the component IDs
98      */
99     public static List<Long> getIds(Object component) {
100         Map<Object, Map<Field, Long>> map = getIdsOfObjectGraph(component, null);
101         List<Long> ids = new ArrayList<Long>();
102         for (Map.Entry<Object, Map<Field, Long>> entry : map.entrySet()) {
103             for (Map.Entry<Field, Long> id : entry.getValue().entrySet()) {
104                 ids.add(id.getValue());
105             }
106         }
107         return ids;
108     }
109
110     /**
111      * Returns the names of all declared fields and their values of the given object declared by a certain type.
112      * <p/>
113      * The ID must be declared by the given type.
114      * Inherited fields or fields of super classes are not checked.<br/>
115      * If {@code type} is {@code null}, the actual type of the object is used instead.
116      *
117      * @param obj the object
118      * @param type the type of the object to retrieve (may be {@code null})
119      * @return all declared field names and the field values
120      */
121     public static Map<String, Object> getFields(Object obj, Class<?> type) {
122         type = type != null ? type : obj.getClass();
123         Map<String, Object> map = new HashMap<String, Object>();
124         for (Field field : type.getDeclaredFields()) {
125             makeAccessible(field);
126             map.put(field.getName(), getField(field, obj));
127         }
128         return map;
129     }
130
131     /**
132      * Callback invoked on each component ID in the hierarchy.
133      */
134     private static class ComponentIdCallback implements FieldCallback {
135         protected final Map<Field, Long> ids = new HashMap<Field, Long>();
136         protected Object component;
137
138         private ComponentIdCallback(Object component) {
139             this.component = component;
140         }
141
142         @Override
143         public void doWith(Field field) throws IllegalAccessException {
144             makeAccessible(field);
145             ids.put(field, (Long) field.get(component));
146         }
147
148     }
149
150     /**
151      * Callback used to filter component IDs.<br/>
152      * A component ID is a field of type {@link Long} annotated with {@link ComponentId @ComponentId}.
153      */
154     private static class ComponentIdFilter implements FieldFilter {
155         @Override
156         public boolean matches(Field field) {
157             return field.getType() == Long.class && field.isAnnotationPresent(ComponentId.class);
158         }
159     }
160
161     /**
162      * Callback used to filter fields marked to be injected i.e., annotated with {@link Inject @Inject}.
163      */
164     private static class InjectFilter implements FieldFilter {
165         @Override
166         public boolean matches(Field field) {
167             return field.isAnnotationPresent(Inject.class);
168         }
169     }
170 }