1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package com.lhkbob.entreri.impl;
28
29 import com.lhkbob.entreri.*;
30 import com.lhkbob.entreri.property.*;
31
32 import java.lang.annotation.Annotation;
33 import java.lang.reflect.Constructor;
34 import java.lang.reflect.Method;
35 import java.util.*;
36
37
38
39
40
41
42
43
44
45 class ReflectionComponentSpecification implements ComponentSpecification {
46 private final Class<? extends Component> type;
47 private final List<ReflectionPropertyDeclaration> properties;
48
49 public ReflectionComponentSpecification(Class<? extends Component> type) {
50 if (!Component.class.isAssignableFrom(type)) {
51 throw fail(type, "Class must extend Component");
52 }
53 if (!type.isInterface()) {
54 throw fail(type, "Component definition must be an interface");
55 }
56
57 List<ReflectionPropertyDeclaration> properties = new ArrayList<>();
58
59
60
61
62 Map<String, Method> getters = new HashMap<>();
63 Map<String, Method> setters = new HashMap<>();
64 Map<String, Integer> setterParameters = new HashMap<>();
65
66 for (Method method : type.getMethods()) {
67
68 Class<?> md = method.getDeclaringClass();
69 if (md.equals(Component.class) || md.equals(Owner.class) ||
70 md.equals(Ownable.class) || md.equals(Object.class)) {
71 continue;
72 }
73
74 if (!Component.class.isAssignableFrom(method.getDeclaringClass())) {
75 throw fail(md, method + ", method is not declared by a component");
76 }
77
78 if (method.getName().startsWith("is")) {
79 processGetter(method, "is", getters);
80 } else if (method.getName().startsWith("has")) {
81 processGetter(method, "has", getters);
82 } else if (method.getName().startsWith("get")) {
83 processGetter(method, "get", getters);
84 } else if (method.getName().startsWith("set")) {
85 processSetter(method, setters, setterParameters);
86 } else {
87 throw fail(md, method + " is an illegal property method");
88 }
89 }
90
91 for (String property : getters.keySet()) {
92 Method getter = getters.get(property);
93 Method setter = setters.remove(property);
94 Integer param = setterParameters.remove(property);
95
96 if (setter == null) {
97 throw fail(type, property + " has no matching setter");
98 } else if (!setter.getParameterTypes()[param]
99 .equals(getter.getReturnType())) {
100 throw fail(type, property + " has inconsistent type");
101 }
102
103 properties.add(new ReflectionPropertyDeclaration(property,
104 createFactory(getter),
105 getter, setter, param));
106 }
107
108 if (!setters.isEmpty()) {
109 throw fail(type, setters.keySet() + " have no matching getters");
110 }
111
112
113 Collections.sort(properties);
114 this.type = type;
115 this.properties = Collections.unmodifiableList(properties);
116 }
117
118 @Override
119 public String getType() {
120 String canonicalName = type.getCanonicalName();
121 String packageName = type.getPackage().getName();
122 if (packageName.isEmpty()) {
123 return canonicalName;
124 } else {
125
126 return canonicalName.substring(getPackage().length() + 1);
127 }
128 }
129
130 @Override
131 public String getPackage() {
132 return type.getPackage().getName();
133 }
134
135 @Override
136 public List<? extends PropertyDeclaration> getProperties() {
137 return properties;
138 }
139
140 private static IllegalComponentDefinitionException fail(Class<?> cls, String msg) {
141 return new IllegalComponentDefinitionException(cls.getCanonicalName(), msg);
142 }
143
144
145
146
147
148 private static class ReflectionPropertyDeclaration implements PropertyDeclaration {
149 private final String name;
150 private final PropertyFactory<?> factory;
151
152 private final Method setter;
153 private final int setterParameter;
154
155 private final Method getter;
156 private final boolean isSharedInstance;
157
158 private final Class<? extends Property> propertyType;
159
160 @SuppressWarnings("unchecked")
161 private ReflectionPropertyDeclaration(String name, PropertyFactory<?> factory,
162 Method getter, Method setter,
163 int setterParameter) {
164 this.name = name;
165 this.factory = factory;
166 this.getter = getter;
167 this.setter = setter;
168 this.setterParameter = setterParameter;
169 isSharedInstance = getter.getAnnotation(SharedInstance.class) != null;
170
171 propertyType = getCreatedType(
172 (Class<? extends PropertyFactory<?>>) factory.getClass());
173 }
174
175 @Override
176 public String getName() {
177 return name;
178 }
179
180 @Override
181 public String getType() {
182 return getter.getReturnType().getCanonicalName();
183 }
184
185 @Override
186 public String getPropertyImplementation() {
187 return propertyType.getCanonicalName();
188 }
189
190 @Override
191 public String getSetterMethod() {
192 return setter.getName();
193 }
194
195 @Override
196 public String getGetterMethod() {
197 return getter.getName();
198 }
199
200 @Override
201 public int getSetterParameter() {
202 return setterParameter;
203 }
204
205 @Override
206 public boolean getSetterReturnsComponent() {
207 return !setter.getReturnType().equals(void.class);
208 }
209
210 @Override
211 public boolean isShared() {
212 return isSharedInstance;
213 }
214
215 @Override
216 public PropertyFactory<?> getPropertyFactory() {
217 return factory;
218 }
219
220 @Override
221 public int compareTo(PropertyDeclaration o) {
222 return name.compareTo(o.getName());
223 }
224 }
225
226 private static void processSetter(Method m, Map<String, Method> setters,
227 Map<String, Integer> parameters) {
228 if (!m.getReturnType().equals(m.getDeclaringClass()) &&
229 !m.getReturnType().equals(void.class)) {
230 throw fail(m.getDeclaringClass(), m + " has invalid return type for setter");
231 }
232 if (m.getParameterTypes().length == 0) {
233 throw fail(m.getDeclaringClass(), m + " must have at least one parameter");
234 }
235
236 if (m.getParameterTypes().length == 1) {
237 String name = getNameFromParameter(m, 0);
238 if (name != null) {
239
240 if (m.getAnnotation(Named.class) != null) {
241 throw fail(m.getDeclaringClass(),
242 m + ", @Named cannot be on both parameter and method");
243 }
244 } else {
245 name = getName(m, "set");
246 }
247
248 if (setters.containsKey(name)) {
249 throw fail(m.getDeclaringClass(), name + " already declared on a setter");
250 }
251 setters.put(name, m);
252 parameters.put(name, 0);
253 } else {
254
255 if (m.getAnnotation(Named.class) != null) {
256 throw fail(m.getDeclaringClass(), m +
257 ", @Named cannot be applied to setter method with multiple parameters");
258 }
259
260 int numP = m.getParameterTypes().length;
261 for (int i = 0; i < numP; i++) {
262 String name = getNameFromParameter(m, i);
263 if (name == null) {
264 throw fail(m.getDeclaringClass(), m +
265 ", @Named must be applied to each parameter for multi-parameter setter methods");
266 }
267
268 if (setters.containsKey(name)) {
269 throw fail(m.getDeclaringClass(),
270 name + " already declared on a setter");
271 }
272
273 setters.put(name, m);
274 parameters.put(name, i);
275 }
276 }
277 }
278
279 private static void processGetter(Method m, String prefix,
280 Map<String, Method> getters) {
281 String name = getName(m, prefix);
282 if (getters.containsKey(name)) {
283 throw fail(m.getDeclaringClass(), name + " already declared on a getter");
284 }
285 if (m.getParameterTypes().length != 0) {
286 throw fail(m.getDeclaringClass(), m + ", getter must not take arguments");
287 }
288 if (m.getReturnType().equals(void.class)) {
289 throw fail(m.getDeclaringClass(),
290 m + ", getter must have non-void return type");
291 }
292
293 getters.put(name, m);
294 }
295
296 private static String getNameFromParameter(Method m, int p) {
297 for (Annotation annot : m.getParameterAnnotations()[p]) {
298 if (annot instanceof Named) {
299 return ((Named) annot).value();
300 }
301 }
302 return null;
303 }
304
305 private static String getName(Method m, String prefix) {
306 Named n = m.getAnnotation(Named.class);
307 if (n != null) {
308 return n.value();
309 } else {
310 return Character.toLowerCase(m.getName().charAt(prefix.length())) +
311 m.getName().substring(prefix.length() + 1);
312 }
313 }
314
315 @SuppressWarnings("unchecked")
316 private static Class<? extends Property> getCreatedType(
317 Class<? extends PropertyFactory<?>> factory) {
318 try {
319 return (Class<? extends Property>) factory.getMethod("create")
320 .getReturnType();
321 } catch (NoSuchMethodException e) {
322 throw new RuntimeException("Cannot inspect property factory " + factory, e);
323 }
324 }
325
326 private static PropertyFactory<?> createFactory(Method getter) {
327 Class<?> baseType = getter.getReturnType();
328
329 Class<? extends PropertyFactory<?>> factoryType;
330 if (getter.getAnnotation(com.lhkbob.entreri.property.Factory.class) != null) {
331
332 factoryType = getter.getAnnotation(com.lhkbob.entreri.property.Factory.class)
333 .value();
334 validateFactory(getter, factoryType, null);
335 } else {
336
337 Class<? extends Property> mappedType = TypePropertyMapping
338 .getPropertyForType(baseType);
339 if (mappedType.getAnnotation(com.lhkbob.entreri.property.Factory.class) ==
340 null) {
341 throw fail(getter.getDeclaringClass(),
342 mappedType + " has no @Factory annotation");
343 } else {
344 factoryType = mappedType
345 .getAnnotation(com.lhkbob.entreri.property.Factory.class).value();
346 validateFactory(getter, factoryType, mappedType);
347 }
348 }
349
350 PropertyFactory<?> factory = invokeConstructor(factoryType, new Attributes(
351 getter.getAnnotations()));
352 if (factory == null) {
353 factory = invokeConstructor(factoryType);
354 }
355
356 if (factory == null) {
357
358 throw fail(getter.getDeclaringClass(),
359 "Cannot create PropertyFactory for " + getter);
360 } else {
361 return factory;
362 }
363 }
364
365 private static void validateFactory(Method getter,
366 Class<? extends PropertyFactory<?>> factory,
367 Class<? extends Property> propertyType) {
368 boolean isShared = getter.getAnnotation(SharedInstance.class) != null;
369 Class<?> baseType = getter.getReturnType();
370 Class<? extends Property> createdType = getCreatedType(factory);
371
372 if (propertyType == null) {
373
374 propertyType = createdType;
375 } else {
376
377 if (!propertyType.isAssignableFrom(createdType)) {
378 throw fail(getter.getDeclaringClass(), "Factory creates " + createdType +
379 ", which is incompatible with expected type " +
380 propertyType);
381 }
382 }
383
384
385 if (propertyType.equals(ObjectProperty.class)) {
386
387
388 if (isShared) {
389 throw fail(getter.getDeclaringClass(),
390 propertyType + " can't be used with @SharedInstance");
391 } else if (baseType.isPrimitive()) {
392 throw fail(getter.getDeclaringClass(),
393 "ObjectProperty cannot be used with primitive types");
394 }
395
396 } else {
397 try {
398 Method g = propertyType.getMethod("get", int.class);
399 if (!g.getReturnType().equals(baseType)) {
400 throw fail(getter.getDeclaringClass(),
401 propertyType + " does not implement " + baseType +
402 " get()");
403 }
404 Method s = propertyType.getMethod("set", int.class, baseType);
405 if (!s.getReturnType().equals(void.class)) {
406 throw fail(getter.getDeclaringClass(),
407 propertyType + " does not implement void set(int, " +
408 baseType + ")");
409 }
410 } catch (NoSuchMethodException e) {
411 throw fail(getter.getDeclaringClass(),
412 propertyType + " does not implement " + baseType +
413 " get() or void set(" + baseType + ", int)");
414 }
415
416 if (isShared) {
417 if (!ShareableProperty.class.isAssignableFrom(propertyType)) {
418 throw fail(getter.getDeclaringClass(),
419 propertyType + " can't be used with @SharedInstance");
420 }
421
422
423 try {
424 Method sg = propertyType.getMethod("get", int.class, baseType);
425 if (!sg.getReturnType().equals(void.class)) {
426 throw fail(getter.getDeclaringClass(),
427 propertyType + " does not implement void get(int, " +
428 baseType + ")");
429 }
430 Method creator = propertyType.getMethod("createShareableInstance");
431 if (!creator.getReturnType().equals(baseType)) {
432 throw fail(getter.getDeclaringClass(),
433 propertyType + " does not implement " + baseType +
434 " createShareableInstance()");
435 }
436 } catch (NoSuchMethodException e) {
437 throw fail(getter.getDeclaringClass(),
438 propertyType + " does not implement void get(int, " +
439 baseType + ") or " + baseType +
440 " createShareableInstance()");
441 }
442 }
443 }
444 }
445
446 private static PropertyFactory<?> invokeConstructor(
447 Class<? extends PropertyFactory<?>> type, Object... args) {
448 Class<?>[] paramTypes = new Class<?>[args.length];
449 for (int i = 0; i < args.length; i++) {
450 paramTypes[i] = args[i].getClass();
451 }
452
453 try {
454
455
456 Constructor<?> ctor = type.getDeclaredConstructor(paramTypes);
457 ctor.setAccessible(true);
458 return (PropertyFactory<?>) ctor.newInstance(args);
459 } catch (SecurityException e) {
460 throw new RuntimeException("Unable to inspect factory's constructor", e);
461 } catch (NoSuchMethodException e) {
462
463 return null;
464 } catch (Exception e) {
465
466 throw new RuntimeException("Unexpected exception during factory creation", e);
467 }
468 }
469 }