1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package de.smartics.properties.spi.core.metadata;
17
18 import static de.smartics.util.lang.StaticAnalysis.RAWTYPES;
19 import static de.smartics.util.lang.StaticAnalysis.UNCHECKED;
20
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26
27 import javax.validation.constraints.Size;
28
29 import org.apache.commons.lang.ArrayUtils;
30
31 import de.smartics.properties.api.core.annotations.AccessType;
32 import de.smartics.properties.api.core.annotations.PropertyDefinitionTime;
33 import de.smartics.properties.api.core.annotations.PropertyElementType;
34 import de.smartics.properties.api.core.annotations.PropertyListElementConstraints;
35 import de.smartics.properties.api.core.annotations.PropertySet;
36 import de.smartics.properties.api.core.annotations.PropertyUse.UseType;
37 import de.smartics.properties.api.core.annotations.PropertyValueSecured;
38 import de.smartics.properties.api.core.domain.PropertiesContext;
39 import de.smartics.properties.api.core.domain.PropertyCategories;
40 import de.smartics.properties.api.core.domain.PropertyConstraint;
41 import de.smartics.properties.api.core.domain.PropertyDescriptor;
42 import de.smartics.properties.api.core.domain.PropertyExpression;
43 import de.smartics.properties.api.core.domain.PropertyKey;
44 import de.smartics.properties.api.core.domain.PropertyType;
45 import de.smartics.properties.api.core.domain.PropertyValueRange;
46 import de.smartics.properties.spi.core.constraint.jsr303.GenericPropertyConstraint;
47 import de.smartics.properties.spi.core.constraints.ListValueConstraint;
48 import de.smartics.properties.spi.core.constraints.PropertyRangeConstraint;
49 import de.smartics.properties.spi.core.context.PropertyContextProxy;
50 import de.smartics.properties.spi.core.metadata.PropertyMetaData.Builder;
51 import de.smartics.properties.spi.core.util.PropertyUtils;
52 import de.smartics.properties.spi.core.value.CollectionPropertyValueRange;
53 import de.smartics.properties.spi.core.value.EnumeratedPropertyValueRange;
54 import de.smartics.util.lang.Arg;
55
56
57
58
59 public final class PropertyMetaDataParser
60 {
61
62
63
64
65
66
67
68
69
70 private final PropertiesContext context;
71
72
73
74
75
76
77
78
79
80
81
82 private PropertyMetaDataParser(final PropertiesContext context)
83 {
84 this.context = context;
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 public static PropertyMetaDataParser createWithoutContextAccess()
101 {
102 return new PropertyMetaDataParser(null);
103 }
104
105
106
107
108
109
110
111
112
113 public static PropertyMetaDataParser create(final PropertiesContext context)
114 throws NullPointerException
115 {
116 Arg.checkNotNull("context", context);
117 return new PropertyMetaDataParser(context);
118 }
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 public List<PropertyDescriptor> readDescriptors(final Class<?> propertySetType)
135 throws NullPointerException, IllegalArgumentException
136 {
137 Arg.checkNotNull("propertySetType", propertySetType);
138 PropertyUtils.checkPropertySetType(propertySetType);
139
140 final Method[] methods = propertySetType.getMethods();
141 final List<PropertyDescriptor> descriptors =
142 new ArrayList<PropertyDescriptor>(methods.length);
143 for (final Method method : methods)
144 {
145 if (PropertyUtils.isPropertyMethod(method))
146 {
147 final PropertyDescriptor descriptor = readDescriptor(method);
148 descriptors.add(descriptor);
149 }
150 }
151
152 return descriptors;
153 }
154
155
156
157
158
159
160
161
162 public PropertyDescriptor readDescriptor(final Method method)
163 throws NullPointerException
164 {
165 Arg.checkNotNull("method", method);
166
167 final PropertyMetaData.Builder builder = new PropertyMetaData.Builder();
168
169 final Class<?> declaringType = method.getDeclaringClass();
170 final PropertyKey key = readKey(method);
171 final PropertyType type = readType(method);
172 builder.with(new PropertyContextProxy(context))
173 .withDeclaringType(declaringType).with(key).with(type);
174
175 addValueRange(builder, method);
176 addExpression(builder, method);
177 addLifecycle(builder, method);
178 addConstraints(builder, method);
179 addDocumentProxy(builder, method, key);
180 addComments(builder);
181 addSecured(builder, method);
182 addCategories(builder, method);
183 addUseType(builder, method);
184
185 return builder.build();
186 }
187
188 private static void addSecured(final Builder builder, final Method method)
189 {
190 final boolean secured = readSecured(method);
191 builder.setSecured(secured);
192 }
193
194 private static boolean readSecured(final Method method)
195 {
196 final PropertyValueSecured secured =
197 method.getAnnotation(PropertyValueSecured.class);
198 final boolean value;
199 if (secured != null)
200 {
201 value = secured.decrypt();
202 }
203 else
204 {
205 value = false;
206 }
207 return value;
208 }
209
210
211
212
213
214
215
216 public PropertyKey readKey(final Method method)
217 {
218 final String set = readPropertySetName(method);
219 final String name = readPropertyKeyName(method);
220 return PropertyKey.create(set, name);
221 }
222
223
224
225
226
227
228
229 public String readPropertySetName(final Class<?> type)
230 {
231 String propertySetName = readPropertySetNameInternal(type);
232
233 if (" ".equals(propertySetName))
234 {
235 propertySetName = type.getName();
236 }
237 else if ("".equals(propertySetName))
238 {
239 propertySetName = null;
240 }
241
242 return propertySetName;
243 }
244
245 private static String readPropertyKeyName(final Method method)
246 {
247 final String keyName;
248
249 final de.smartics.properties.api.core.annotations.PropertyKeyName propertyKey =
250 method
251 .getAnnotation(de.smartics.properties.api.core.annotations.PropertyKeyName.class);
252 if (propertyKey != null)
253 {
254 keyName = propertyKey.value();
255 }
256 else
257 {
258 final String methodName = method.getName();
259 if (isGetterMethod(methodName))
260 {
261 keyName = calcPropertyName(methodName);
262 }
263 else
264 {
265 keyName = methodName;
266 }
267 }
268
269 return keyName;
270 }
271
272 private static String calcPropertyName(final String methodName)
273 {
274 return Character.toLowerCase(methodName.charAt(3))
275 + methodName.substring(4);
276 }
277
278 private static boolean isGetterMethod(final String methodName)
279 {
280 return methodName.length() > 3 && methodName.startsWith("get")
281 && Character.isUpperCase(methodName.charAt(3));
282 }
283
284 private static String readPropertySetName(final Method method)
285 {
286 final String propertySetName;
287
288 final PropertySet primaryPropertySet =
289 method.getAnnotation(PropertySet.class);
290 if (primaryPropertySet != null)
291 {
292 propertySetName = primaryPropertySet.value();
293 }
294 else
295 {
296 final Class<?> type = method.getDeclaringClass();
297 propertySetName = readPropertySetNameInternal(type);
298 }
299
300 if (" ".equals(propertySetName))
301 {
302 final Class<?> type = method.getDeclaringClass();
303 return type.getName();
304 }
305 else if ("".equals(propertySetName))
306 {
307 return null;
308 }
309
310 return propertySetName;
311 }
312
313 private static String readPropertySetNameInternal(final Class<?> type)
314 {
315 final String propertySetName;
316 final PropertySet propertySet = type.getAnnotation(PropertySet.class);
317 if (propertySet != null)
318 {
319 propertySetName = propertySet.value();
320 }
321 else
322 {
323 propertySetName = type.getName();
324 }
325 return propertySetName;
326 }
327
328 private static PropertyType readType(final Method method)
329 {
330 final Class<?> type = method.getReturnType();
331 final Class<?> subType = determineSubType(method);
332
333 return new PropertyType(type, subType);
334 }
335
336 private static Class<?> determineSubType(final Method method)
337 {
338 final PropertyElementType elementType =
339 method.getAnnotation(PropertyElementType.class);
340 final Class<?> subType;
341 if (elementType != null)
342 {
343 subType = elementType.value();
344 }
345 else
346 {
347 subType = null;
348 }
349 return subType;
350 }
351
352 @SuppressWarnings({ RAWTYPES, UNCHECKED })
353 private static void addValueRange(final Builder builder, final Method method)
354 {
355 final PropertyValueRange<?> valueRange = calculateValueRange(method);
356 if (valueRange != null)
357 {
358 builder.with(valueRange);
359 final PropertyRangeConstraint<?> constraint =
360 new PropertyRangeConstraint(valueRange);
361 builder.add(constraint);
362 }
363 }
364
365 @SuppressWarnings({ RAWTYPES, UNCHECKED })
366 private static PropertyValueRange<?> calculateValueRange(final Method method)
367 {
368 final Class<?> returnType = method.getReturnType();
369 if (returnType.isEnum())
370 {
371 final PropertyValueRange<?> valueRange =
372 new EnumeratedPropertyValueRange(returnType);
373 return valueRange;
374 }
375 else
376 {
377 final de.smartics.properties.api.core.annotations.PropertyIntValueRange intAnnotation =
378 method
379 .getAnnotation(de.smartics.properties.api.core.annotations.PropertyIntValueRange.class);
380 if (intAnnotation != null)
381 {
382 final Integer[] values = ArrayUtils.toObject(intAnnotation.value());
383 final CollectionPropertyValueRange<Integer> valueRange =
384 new CollectionPropertyValueRange<Integer>(values);
385 return valueRange;
386 }
387
388 final de.smartics.properties.api.core.annotations.PropertyStringValueRange annotation =
389 method
390 .getAnnotation(de.smartics.properties.api.core.annotations.PropertyStringValueRange.class);
391 if (annotation != null)
392 {
393 final String[] values = annotation.value();
394 final CollectionPropertyValueRange<String> valueRange =
395 new CollectionPropertyValueRange<String>(values);
396 return valueRange;
397 }
398 }
399
400 return null;
401 }
402
403 private static void addExpression(final Builder builder, final Method method)
404 {
405 final de.smartics.properties.api.core.annotations.PropertyExpression annotation =
406 method
407 .getAnnotation(de.smartics.properties.api.core.annotations.PropertyExpression.class);
408 if (annotation != null)
409 {
410 final String expressionString = annotation.value();
411 final PropertyExpression expression =
412 PropertyExpression.create(expressionString);
413 builder.with(expression);
414 }
415 }
416
417 private void addLifecycle(final Builder builder, final Method method)
418 {
419 final de.smartics.properties.api.core.annotations.PropertyLifecycle annotation =
420 method
421 .getAnnotation(de.smartics.properties.api.core.annotations.PropertyLifecycle.class);
422 if (annotation != null)
423 {
424 final PropertyDefinitionTime configurationTime =
425 annotation.definitionTime();
426 if (configurationTime != null)
427 {
428 builder.with(configurationTime);
429 }
430
431 final AccessType accessType = annotation.access();
432 if (accessType != null)
433 {
434 builder.with(accessType);
435 }
436
437 final long updateInterval = annotation.updateInterval();
438 builder.withUpdateIntervalInMs(updateInterval);
439 }
440 }
441
442 @SuppressWarnings({ RAWTYPES, UNCHECKED })
443 private void addConstraints(final Builder builder, final Method method)
444 {
445 final Annotation[] annotations = method.getAnnotations();
446 boolean collectionConstraintAdded = false;
447
448 for (final Annotation annotation : annotations)
449 {
450 final javax.validation.Constraint annotationAnnotation =
451 annotation.annotationType().getAnnotation(
452 javax.validation.Constraint.class);
453 if (annotationAnnotation != null)
454 {
455 if (annotation.annotationType() != Size.class)
456 {
457 final GenericPropertyConstraint propertyConstraint =
458 new GenericPropertyConstraint(annotation, method.getReturnType());
459 builder.add(propertyConstraint);
460 }
461 else
462 {
463 checkCollection(builder, method, (Size) annotation);
464 collectionConstraintAdded = true;
465 }
466 }
467 }
468
469 if (!collectionConstraintAdded)
470 {
471 checkCollection(builder, method, null);
472 }
473 }
474
475 private void checkCollection(final PropertyMetaData.Builder builder,
476 final Method method, final Size size)
477 {
478 final Class<?> type = builder.getType().getType();
479
480 if (Collection.class.isAssignableFrom(type))
481 {
482 final PropertyElementType elementTypeAnnotation =
483 method.getAnnotation(PropertyElementType.class);
484
485 final Class<?> elementType = calcElementType(elementTypeAnnotation);
486 final List<? extends PropertyConstraint<?>> elementConstraints =
487 calcElementConstraints(method);
488 Integer min = null;
489 Integer max = null;
490 if (size != null)
491 {
492 min = size.min();
493 max = size.max();
494 }
495
496 final PropertyConstraint<?> propertyConstraint =
497 new ListValueConstraint(elementType, min, max, elementConstraints);
498 builder.add(propertyConstraint);
499 }
500 }
501
502 private Class<?> calcElementType(
503 final PropertyElementType elementTypeAnnotation)
504 {
505 final Class<?> elementType;
506 if (elementTypeAnnotation != null)
507 {
508 elementType = elementTypeAnnotation.value();
509 }
510 else
511 {
512 elementType = String.class;
513 }
514 return elementType;
515 }
516
517 @SuppressWarnings({ RAWTYPES, UNCHECKED })
518 private List<? extends PropertyConstraint<?>> calcElementConstraints(
519 final Method method)
520 {
521 final Annotation[] annotations = calcElementsConstraintsType(method);
522 if (annotations != null)
523 {
524 final List constraints = new ArrayList(annotations.length);
525
526 for (final Annotation annotation : annotations)
527 {
528 final javax.validation.Constraint annotationAnnotation =
529 annotation.annotationType().getAnnotation(
530 javax.validation.Constraint.class);
531 if (annotationAnnotation != null)
532 {
533 final GenericPropertyConstraint propertyConstraint =
534 new GenericPropertyConstraint(annotation, method.getReturnType());
535
536 constraints.add(propertyConstraint);
537 }
538 }
539
540 return constraints;
541 }
542
543 return new ArrayList(0);
544 }
545
546 private static Annotation[] calcElementsConstraintsType(final Method method)
547 {
548 final Class<?> enclosingType = method.getDeclaringClass();
549 final Class<?>[] nestedClasses = enclosingType.getDeclaredClasses();
550 if (nestedClasses != null)
551 {
552 for (final Class<?> nestedClass : nestedClasses)
553 {
554 final PropertyListElementConstraints constraintsType =
555 nestedClass.getAnnotation(PropertyListElementConstraints.class);
556 if (constraintsType != null)
557 {
558 final String methodName = method.getName();
559 return calcElementConstraintMethod(nestedClass, methodName);
560 }
561 }
562 }
563
564 return null;
565 }
566
567 private static Annotation[] calcElementConstraintMethod(
568 final Class<?> nestedClass, final String methodName)
569 {
570 try
571 {
572 final Method elementConstraintMethod =
573 nestedClass.getMethod(methodName, new Class<?>[0]);
574 return elementConstraintMethod.getAnnotations();
575 }
576 catch (final Exception e)
577 {
578 return null;
579 }
580 }
581
582 private void addDocumentProxy(final Builder builder, final Method method,
583 final PropertyKey key)
584 {
585 final DocumentMetaDataProxy proxy =
586 new MetaInfDocumentMetaDataProxy(key, context, method);
587 builder.with(proxy);
588 }
589
590 private void addComments(final Builder builder)
591 {
592 final PropertyCommentProxy commentProxy = new PropertyCommentProxy(context);
593 builder.with(commentProxy);
594 }
595
596 private void addCategories(final Builder builder, final Method method)
597 {
598 final List<Class<?>> categories = readCategories(method);
599 if (!categories.isEmpty())
600 {
601 final PropertyCategories.Builder catBuilder =
602 new PropertyCategories.Builder();
603 for (final Class<?> category : categories)
604 {
605 catBuilder.with(category);
606 }
607 final PropertyCategories propertyCategories = catBuilder.build();
608 builder.with(propertyCategories);
609 }
610 }
611
612 private static List<Class<?>> readCategories(final Method method)
613 {
614 final List<Class<?>> categories = new ArrayList<Class<?>>();
615
616 final de.smartics.properties.api.core.annotations.PropertyCategories methodAnnotation =
617 method
618 .getAnnotation(de.smartics.properties.api.core.annotations.PropertyCategories.class);
619 addCategoriesFromAnnotation(categories, methodAnnotation);
620
621 final Class<?> type = method.getDeclaringClass();
622 final de.smartics.properties.api.core.annotations.PropertyCategories typeAnnotation =
623 type.getAnnotation(de.smartics.properties.api.core.annotations.PropertyCategories.class);
624 addCategoriesFromAnnotation(categories, typeAnnotation);
625
626 return categories;
627 }
628
629 private static void addCategoriesFromAnnotation(
630 final List<Class<?>> categories,
631 final de.smartics.properties.api.core.annotations.PropertyCategories typeAnnotation)
632 {
633 if (typeAnnotation != null)
634 {
635 final Class<?>[] categoryTypes = typeAnnotation.value();
636 for (final Class<?> categoryType : categoryTypes)
637 {
638 categories.add(categoryType);
639 }
640 }
641 }
642
643 private void addUseType(final Builder builder, final Method method)
644 {
645 final Class<?> type = method.getDeclaringClass();
646 final de.smartics.properties.api.core.annotations.PropertyUse typeAnnotation =
647 type.getAnnotation(de.smartics.properties.api.core.annotations.PropertyUse.class);
648 if (typeAnnotation != null)
649 {
650 final UseType useType = typeAnnotation.value();
651 if (useType != null)
652 {
653 builder.with(useType);
654 }
655 }
656 }
657
658
659
660 }