1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.qualitytest.blueprint;
17
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.Field;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Modifier;
22 import java.lang.reflect.Proxy;
23 import java.text.MessageFormat;
24
25 import javax.annotation.Nonnull;
26 import javax.annotation.Nullable;
27
28 import net.sf.qualitycheck.Check;
29 import net.sf.qualitycheck.Throws;
30 import net.sf.qualitycheck.exception.IllegalNullArgumentException;
31 import net.sf.qualitytest.ModifierBits;
32 import net.sf.qualitytest.blueprint.configuration.DefaultBlueprintConfiguration;
33 import net.sf.qualitytest.blueprint.configuration.RandomBlueprintConfiguration;
34 import net.sf.qualitytest.exception.BlueprintException;
35 import net.sf.qualitytest.exception.NoPublicConstructorException;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public final class Blueprint {
74
75 private static final BlueprintConfiguration DEFAULT_CONFIG = new DefaultBlueprintConfiguration();
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 @Throws(IllegalNullArgumentException.class)
92 private static <T> T bean(@Nonnull final Class<T> clazz, @Nonnull final BlueprintConfiguration config,
93 @Nonnull final BlueprintSession session) {
94 Check.notNull(clazz, "clazz");
95 Check.notNull(config, "config");
96 Check.notNull(session, "sesion");
97
98 final T obj = safeNewInstance(session, clazz);
99 blueprintPublicMethods(obj, clazz, config, session);
100 blueprintPublicAttributes(obj, clazz, config, session);
101 return obj;
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 private static <T> void blueprintAllAttributes(final T obj, final Class<T> clazz, final BlueprintConfiguration config,
121 final BlueprintSession session) {
122 for (final Field f : clazz.getDeclaredFields()) {
123 final boolean isStatic = ModifierBits.isModifierBitSet(f.getModifiers(), Modifier.STATIC);
124 if (!isStatic) {
125 blueprintField(obj, f, config, session);
126 }
127 }
128 }
129
130
131
132
133
134
135
136
137
138
139
140
141
142 private static void blueprintField(final Object that, final Field field, final BlueprintConfiguration config,
143 final BlueprintSession session) {
144 CreationStrategy<?> creator = config.findCreationStrategyForField(field);
145 if (creator == null) {
146 creator = config.findCreationStrategyForType(field.getType());
147 }
148 final Object value = blueprintObject(field.getType(), config, creator, session);
149
150 final String action = MessageFormat.format("Setting field {0} to {1}.", field.getName(), value);
151 SafeInvoke.invoke(new BlueprintExceptionRunnable<Object>(session, action) {
152 @Override
153 public Object runInternal() throws Exception {
154 field.setAccessible(true);
155 field.set(that, value);
156 return null;
157 }
158 }, BlueprintException.class);
159
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174 private static void blueprintMethod(final Object that, final Method m, final BlueprintConfiguration config,
175 final BlueprintSession session) {
176 final CreationStrategy<?> creator = config.findCreationStrategyForMethod(m);
177 if (creator != null) {
178 final Class<?>[] parameterTypes = m.getParameterTypes();
179 final Object[] values = new Object[parameterTypes.length];
180 for (int i = 0; i < parameterTypes.length; i++) {
181 values[i] = creator.createValue(parameterTypes[i], config, session);
182 }
183
184 final String action = MessageFormat.format("Invoking method {0} with arguments {1}.", m.getName(), values);
185 SafeInvoke.invoke(new BlueprintExceptionRunnable<Object>(session, action) {
186 @Override
187 public Object runInternal() throws Exception {
188 m.setAccessible(true);
189 m.invoke(that, values);
190 return null;
191 }
192
193 }, BlueprintException.class);
194 }
195 }
196
197 @Nullable
198 @SuppressWarnings({ "unchecked" })
199 private static <T> T blueprintObject(@Nonnull final Class<T> clazz, @Nonnull final BlueprintConfiguration config,
200 @Nullable final CreationStrategy<?> creator, @Nonnull final BlueprintSession session) {
201 final boolean cycle = session.push(clazz);
202 final T ret;
203 if (cycle) {
204 ret = (T) config.handleCycle(session, clazz);
205 } else {
206 if (creator != null) {
207 ret = (T) creator.createValue(clazz, config, session);
208 } else if (clazz.isInterface()) {
209 ret = (T) proxy(clazz, config, session);
210 } else if (hasPublicDefaultConstructor(clazz)) {
211 ret = bean(clazz, config, session);
212 } else {
213 ret = immutable(clazz, config, session);
214 }
215 }
216 session.pop();
217 return ret;
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238 private static <T> void blueprintPublicAttributes(final T obj, final Class<T> clazz, final BlueprintConfiguration config,
239 final BlueprintSession session) {
240 if (!config.isWithPublicAttributes()) {
241 return;
242 }
243
244 for (final Field f : clazz.getFields()) {
245 final boolean isStatic = ModifierBits.isModifierBitSet(f.getModifiers(), Modifier.STATIC);
246 final boolean isFinal = ModifierBits.isModifierBitSet(f.getModifiers(), Modifier.FINAL);
247 if (!isStatic && !isFinal) {
248 blueprintField(obj, f, config, session);
249 }
250 }
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 private static <T> void blueprintPublicMethods(final T obj, final Class<T> clazz, final BlueprintConfiguration config,
268 final BlueprintSession session) {
269 for (final Method m : clazz.getMethods()) {
270 if (isRelevant(m)) {
271 blueprintMethod(obj, m, config, session);
272 }
273 }
274 }
275
276
277
278
279
280
281
282
283
284
285
286
287
288 @Nullable
289 @Throws(IllegalNullArgumentException.class)
290 public static <T> T construct(@Nonnull final Class<T> clazz) {
291 Check.notNull(clazz, "clazz");
292
293 return Blueprint.construct(clazz, DEFAULT_CONFIG, new BlueprintSession());
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 @Nullable
311 @Throws(IllegalNullArgumentException.class)
312 public static <T> T construct(@Nonnull final Class<T> clazz, @Nonnull final BlueprintConfiguration config) {
313 Check.notNull(clazz, "clazz");
314 Check.notNull(config, "config");
315
316 return Blueprint.construct(clazz, config, new BlueprintSession());
317 }
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335 @Nullable
336 @Throws(IllegalNullArgumentException.class)
337 public static <T> T construct(@Nonnull final Class<T> clazz, @Nonnull final BlueprintConfiguration config,
338 @Nonnull final BlueprintSession session) {
339 Check.notNull(clazz, "clazz");
340 Check.notNull(config, "config");
341 Check.notNull(session, "session");
342
343 final CreationStrategy<?> creator = config.findCreationStrategyForType(clazz);
344 return blueprintObject(clazz, config, creator, session);
345 }
346
347
348
349
350
351
352 public static BlueprintConfiguration def() {
353 return new DefaultBlueprintConfiguration();
354 }
355
356
357
358
359
360
361
362
363
364 private static <T> Constructor<?> findFirstPublicConstructor(final Class<T> clazz) {
365 final Constructor<?>[] constructors = clazz.getConstructors();
366 for (final Constructor<?> c : constructors) {
367 return c;
368 }
369 return null;
370 }
371
372
373
374
375
376
377
378
379 private static boolean hasPublicDefaultConstructor(final Class<?> clazz) {
380 final Constructor<?>[] constructors = clazz.getConstructors();
381 for (final Constructor<?> c : constructors) {
382 if (c.getParameterTypes().length == 0) {
383 return true;
384 }
385 }
386 return false;
387 }
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402 private static <T> T immutable(final Class<T> clazz, final BlueprintConfiguration config, final BlueprintSession session) {
403 final Constructor<?> constructor = findFirstPublicConstructor(clazz);
404 if (constructor == null) {
405 final BlueprintException b = new NoPublicConstructorException(clazz.getSimpleName());
406 final String action = MessageFormat.format("Finding public constructor in {0}", clazz.getName());
407 session.setLastAction(action);
408 b.setSession(session);
409 throw b;
410 }
411
412 final Class<?>[] parameterTypes = constructor.getParameterTypes();
413 final Object[] parameters = new Object[parameterTypes.length];
414 for (int i = 0; i < parameterTypes.length; i++) {
415 parameters[i] = construct(parameterTypes[i], config, session);
416 }
417
418 @SuppressWarnings("unchecked")
419 final T obj = (T) safeNewInstance(session, constructor, parameters);
420 blueprintAllAttributes(obj, clazz, config, session);
421
422 return obj;
423 }
424
425
426
427
428
429
430
431
432 protected static boolean isRelevant(final Method m) {
433 final boolean isNotStatic = !ModifierBits.isModifierBitSet(m.getModifiers(), Modifier.STATIC);
434 final boolean isPublic = ModifierBits.isModifierBitSet(m.getModifiers(), Modifier.PUBLIC);
435 return isNotStatic && isPublic;
436 }
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451 @SuppressWarnings("unchecked")
452 private static <T> T proxy(final Class<T> iface, final BlueprintConfiguration config, final BlueprintSession session) {
453 return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new BlueprintInvocationHandler(config, session));
454 }
455
456
457
458
459
460
461 public static BlueprintConfiguration random() {
462 return new RandomBlueprintConfiguration();
463 }
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478 private static <T> T safeNewInstance(final BlueprintSession session, final Class<T> clazz) {
479 final String action = MessageFormat.format("Creating clazz {0} with default constructor.", clazz.getName());
480 return SafeInvoke.invoke(new BlueprintExceptionRunnable<T>(session, action) {
481
482 @Override
483 public T runInternal() throws Exception {
484 return (T) clazz.newInstance();
485 }
486 }, BlueprintException.class);
487 }
488
489
490
491
492
493
494
495
496
497
498
499 @SuppressWarnings("unchecked")
500 private static <T> T safeNewInstance(final BlueprintSession session, final Constructor<?> constructor, final Object[] parameters) {
501 final String action = MessageFormat.format("Creating clazz {0} with constructor {1}.", constructor.getClass().getName(),
502 constructor.toGenericString());
503 return SafeInvoke.invoke(new BlueprintExceptionRunnable<T>(session, action) {
504
505 @Override
506 public T runInternal() throws Exception {
507 return (T) constructor.newInstance(parameters);
508 }
509 }, BlueprintException.class);
510 }
511
512
513
514
515 private Blueprint() {
516
517 }
518 }