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