1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package tools.gsf.config;
17
18 import org.apache.commons.lang3.StringUtils;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.util.HashMap;
25 import java.util.Map;
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public abstract class AbstractDelegatingFactory<SCOPE> implements Factory {
50
51 private static final Logger LOG = LoggerFactory.getLogger(AbstractDelegatingFactory.class);
52
53 private final Map<String, Object> objectCache = new HashMap<>();
54
55 private final SCOPE scope;
56 private final Factory delegate;
57
58 protected AbstractDelegatingFactory(SCOPE scope, Factory delegate) {
59 this.scope = scope;
60 this.delegate = delegate;
61 }
62
63 @Override
64 public final <T> T getObject(final String name, final Class<T> fieldType) {
65
66 T o;
67 try {
68
69 o = locate(name, fieldType);
70 if (o != null) {
71 LOG.debug("Located object {} of type {} in scope {}", name, fieldType.getName(), this.getClass().getName());
72 } else {
73
74 LOG.debug("Did NOT locate object {} of type {} in scope {}", name, fieldType.getName(), this.getClass().getName());
75 if (delegate != null) {
76 LOG.debug("Will attempt locating object {} of type {} in delegate {}", name, fieldType.getName(), delegate.getClass().getName());
77 o = delegate.getObject(name, fieldType);
78 if (o != null) {
79 LOG.debug("Located object {} of type {} in delegate {}", name, fieldType.getName(), delegate.getClass().getName());
80 }
81 } else {
82
83 LOG.debug("Cannot delegate lookup onto any other scope.");
84 }
85 }
86 } catch (InvocationTargetException e) {
87 throw new RuntimeException(e.getTargetException());
88 }
89 return o;
90 }
91
92
93
94
95
96
97
98
99
100
101 @SuppressWarnings("unchecked")
102 private <T> T locate(final String askedName, final Class<T> fieldType) throws InvocationTargetException {
103 if (scope.getClass().isAssignableFrom(fieldType)) {
104 return (T) scope;
105 }
106 if (fieldType.isArray()) {
107 throw new IllegalArgumentException("Arrays are not supported");
108 }
109 final String name = StringUtils.isNotBlank(askedName) ? askedName : fieldType.getSimpleName();
110 if (StringUtils.isBlank(name)) {
111 return null;
112 }
113
114 Object o = locateInCache(fieldType, name);
115 if (o == null) {
116 o = namedAnnotationStrategy(name, fieldType);
117 }
118 if (o == null) {
119 o = unnamedAnnotationStrategy(name, fieldType);
120 }
121 return (T) o;
122 }
123
124 private <T> Object locateInCache(Class<T> c, String name) {
125 Object o = objectCache.get(name);
126 if (o != null) {
127 if (!c.isAssignableFrom(o.getClass())) {
128 throw new IllegalStateException("Name conflict: '" + name + "' is in cache and is of type '"
129 + o.getClass() + "' but a '" + c.getName()
130 + "' was asked for. Please check your factories for naming conflicts.");
131 } else {
132 LOG.debug("Object named {} was found in cache in factory {}. An object of type {} was requested, which is assignable from the returned object, whose type is {}.", name, this.getClass().getName(), c.getName(), o.getClass().getName());
133 }
134 }
135 return o;
136 }
137
138 private static boolean shouldCache(Method m) {
139 boolean r = false;
140 if (m.isAnnotationPresent(ServiceProducer.class)) {
141 ServiceProducer ann = m.getAnnotation(ServiceProducer.class);
142 r = ann.cache();
143 }
144 return r;
145 }
146
147
148
149
150
151
152
153
154
155
156
157 private <T> T namedAnnotationStrategy(String name, Class<T> c) throws InvocationTargetException {
158
159 for (Method m : this.getClass().getMethods()) {
160 if (m.isAnnotationPresent(ServiceProducer.class)) {
161 if (c.isAssignableFrom(m.getReturnType())) {
162 String n = m.getAnnotation(ServiceProducer.class).name();
163 if (name.equals(n)) {
164 return constructAndCacheObject(name, c, m);
165 }
166 }
167 }
168 }
169 return null;
170 }
171
172
173
174
175
176
177
178
179
180
181
182 private <T> T unnamedAnnotationStrategy(String name, Class<T> c) throws InvocationTargetException {
183 for (Method m : this.getClass().getMethods()) {
184 if (m.isAnnotationPresent(ServiceProducer.class)) {
185 if (c.isAssignableFrom(m.getReturnType())) {
186 String n = m.getAnnotation(ServiceProducer.class).name();
187 if (StringUtils.isBlank(n)) {
188 return constructAndCacheObject(name, c, m);
189 }
190 }
191 }
192 }
193 return null;
194 }
195
196 private <T> T constructAndCacheObject(String name, Class<T> c, Method m) throws InvocationTargetException {
197 switch (m.getParameterCount()) {
198 case 0: {
199 T result = ReflectionUtils.createFromMethod(name, c, this, m);
200 if (shouldCache(m)) {
201 objectCache.put(name, result);
202 }
203 return result;
204 }
205 case 1: {
206 Class type = m.getParameterTypes()[0];
207 if (type.isAssignableFrom(scope.getClass())) {
208 T result = ReflectionUtils.createFromMethod(name, c, this, m, scope);
209 if (shouldCache(m)) {
210 objectCache.put(name, result);
211 }
212 return result;
213 } else {
214 throw new UnsupportedOperationException("Cannot create object with parameter type " + type.getName() + " using method " + m.getName() + " in class " + m.getDeclaringClass().getName());
215 }
216 }
217 default: {
218 throw new UnsupportedOperationException("Cannot create object using method " + m.getName() + " in class " + m.getDeclaringClass().getName() + " - invalid number of parameters");
219 }
220 }
221 }
222
223 @Override
224 public String toString() {
225 String s = this.getClass().getSimpleName();
226 return "{" + s + (delegate == null ? "}" : "::delegate:" + delegate.getClass().getName() + "}");
227 }
228 }