1 package net.sf.stitch.crud;
2
3 import java.lang.annotation.Annotation;
4 import java.lang.reflect.Field;
5 import java.lang.reflect.Method;
6 import java.util.Map;
7 import java.util.TreeMap;
8
9 import javax.persistence.Basic;
10 import javax.persistence.Column;
11 import javax.persistence.EnumType;
12 import javax.persistence.Enumerated;
13 import javax.persistence.FetchType;
14 import javax.persistence.Id;
15 import javax.persistence.Lob;
16 import javax.persistence.ManyToOne;
17 import javax.persistence.OneToMany;
18 import javax.persistence.OneToOne;
19 import javax.persistence.Temporal;
20 import javax.persistence.TemporalType;
21 import javax.persistence.Version;
22
23 import org.hibernate.validator.NotNull;
24
25
26
27
28
29
30
31
32
33 public class CrudEntityProperty
34 {
35
36 private final Field field;
37
38 private Method getter;
39
40 private Method setter;
41
42 public CrudEntityProperty (final Field field)
43 {
44 assert null != field;
45 this.field = field;
46 }
47
48 public Method getGetter()
49 {
50 return getter;
51 }
52
53 public void setGetter(final Method getter)
54 {
55 assert getter.getReturnType().equals(this.field.getType());
56 this.getter = getter;
57 }
58
59 public Method getSetter()
60 {
61 return setter;
62 }
63
64 public void setSetter(final Method setter)
65 {
66 this.setter = setter;
67 }
68
69 public Field getField()
70 {
71 return field;
72 }
73
74
75
76
77
78 public String getFieldName()
79 {
80 return this.field.getName();
81 }
82
83
84
85
86
87 public boolean isReadOnly ()
88 {
89 return null == this.setter;
90 }
91
92
93
94
95
96
97
98
99 private <T extends Annotation> T getAnnotation (final Class<T> annotationClass)
100 {
101 final T annotationField = this.field.getAnnotation(annotationClass);
102 final T annotationGetter = this.getter.getAnnotation(annotationClass);
103
104 if (null != annotationField && null != annotationGetter
105 && !annotationField.equals(annotationGetter))
106 {
107 throw new RuntimeException("Inconsistent annotation ("
108 + annotationClass.getCanonicalName() + ") for entity property "
109 + this.getFieldName());
110 }
111 return (null != annotationField) ? annotationField : annotationGetter;
112 }
113
114
115
116
117 private boolean isAnnotationPresent (final Class<? extends Annotation> annotationClass)
118 {
119 return this.field.isAnnotationPresent(annotationClass)
120 || (null != this.getter && this.getter.isAnnotationPresent(annotationClass));
121 }
122
123
124
125
126
127
128 public boolean isRequired ()
129 {
130
131 final Class<?> fieldType = this.field.getType();
132 boolean isRequired = fieldType.isPrimitive() || fieldType.isEnum();
133 if (!isRequired)
134 {
135
136
137
138 final Basic basicAnnotation = getAnnotation(Basic.class);
139 final boolean isOptional = (null != basicAnnotation) ? basicAnnotation.optional() : true;
140
141
142 final Column columnAnnotation = getAnnotation(Column.class);
143 final boolean isNullable = (null != columnAnnotation) ? columnAnnotation.nullable() : true;
144
145
146 final ManyToOne manyToOneAnnotation = getAnnotation(ManyToOne.class);
147 final boolean isNto1Optional = (null != manyToOneAnnotation) ? manyToOneAnnotation.optional() : true;
148
149
150 final OneToOne oneToOneAnnotation = getAnnotation(OneToOne.class);
151 final boolean is1to1Optional = (null != oneToOneAnnotation) ? oneToOneAnnotation.optional() : true;
152
153
154 final boolean hibernateNotNull = isAnnotationPresent(NotNull.class);
155
156 isRequired = !isOptional || !isNullable || !isNto1Optional || !is1to1Optional
157 || hibernateNotNull;
158 }
159
160 return isRequired;
161 }
162
163
164
165
166
167 public boolean isId ()
168 {
169 return isAnnotationPresent(Id.class);
170 }
171
172
173
174
175
176 public boolean isVersion ()
177 {
178 return isAnnotationPresent(Version.class);
179 }
180
181
182
183
184
185 public boolean isLob ()
186 {
187 return isAnnotationPresent(Lob.class);
188 }
189
190
191
192
193
194 public boolean isFetchLazy ()
195 {
196
197 final Basic basicAnnotation = getAnnotation(Basic.class);
198 final boolean isBasicLazy = FetchType.LAZY == ((null != basicAnnotation) ? basicAnnotation.fetch() : FetchType.EAGER);
199
200
201 final ManyToOne manyToOneAnnotation = getAnnotation(ManyToOne.class);
202 final boolean isNto1Lazy = FetchType.LAZY == ((null != manyToOneAnnotation) ? manyToOneAnnotation.fetch() : FetchType.LAZY);
203
204
205 final OneToOne oneToOneAnnotation = getAnnotation(OneToOne.class);
206 final boolean is1to1Lazy = FetchType.LAZY == ((null != oneToOneAnnotation) ? oneToOneAnnotation.fetch() : FetchType.EAGER);
207
208
209 final OneToMany oneToManyAnnotation = getAnnotation(OneToMany.class);
210 final boolean is1toNLazy = FetchType.LAZY == ((null != oneToManyAnnotation) ? oneToManyAnnotation.fetch() : FetchType.LAZY);
211
212 return isBasicLazy || isNto1Lazy || is1to1Lazy || is1toNLazy;
213 }
214
215
216
217
218
219 public boolean isBoolean ()
220 {
221 final Class<?> fieldType = this.field.getType();
222 return fieldType.equals(Boolean.class) || fieldType.equals(boolean.class);
223 }
224
225
226
227
228
229 public boolean isEnum ()
230 {
231 return this.field.getType().isEnum();
232 }
233
234
235
236
237
238
239
240 public boolean isSearchable ()
241 {
242 return this.field.getType().equals(String.class);
243 }
244
245
246
247
248
249
250 public boolean isListable ()
251 {
252 final Class<?> fieldType = this.field.getType();
253
254 return (fieldType.isPrimitive() || fieldType.getCanonicalName().startsWith("java.lang."))
255 && !isId() && !isVersion() && !isFetchLazy() && !isLob();
256 }
257
258
259
260
261 public Integer getMaxLength ()
262 {
263 Integer maxLength = null;
264 final Column columnAnnotation = getAnnotation(Column.class);
265 if (this.field.getType().equals(String.class))
266 {
267 maxLength = (null != columnAnnotation && 0 != columnAnnotation.length())
268 ? columnAnnotation.length() : 255;
269 }
270 else if (null != columnAnnotation && (0 != columnAnnotation.precision() || 0 != columnAnnotation.scale()))
271 {
272 maxLength = columnAnnotation.precision() + 1 + columnAnnotation.scale();
273 }
274 return maxLength;
275 }
276
277
278
279
280 private static final Map<String,Integer> SIZE_MAP;
281 static
282 {
283 final Integer int4 = Integer.valueOf(4);
284 final Integer int6 = Integer.valueOf(6);
285 final Integer int12 = Integer.valueOf(12);
286 final Integer int20 = Integer.valueOf(20);
287
288 SIZE_MAP = new TreeMap<String,Integer>();
289 SIZE_MAP.put(Byte.class.getName(), int4);
290 SIZE_MAP.put(Double.class.getName(), int20);
291 SIZE_MAP.put(double.class.getName(), int20);
292 SIZE_MAP.put(double.class.getName(), int20);
293 SIZE_MAP.put(Float.class.getName(), int12);
294 SIZE_MAP.put(float.class.getName(), int12);
295 SIZE_MAP.put(Integer.class.getName(), int12);
296 SIZE_MAP.put(int.class.getName(), int12);
297 SIZE_MAP.put(Long.class.getName(), int20);
298 SIZE_MAP.put(long.class.getName(), int20);
299 SIZE_MAP.put(Short.class.getName(), int6);
300 SIZE_MAP.put(short.class.getName(), int6);
301 }
302
303 static final int DEFAULT_SIZE = 20;
304
305
306
307
308 public int getSize ()
309 {
310 int size;
311 if (isAnnotationPresent(Temporal.class))
312 {
313 switch (getTemporalType())
314 {
315 case DATE:
316 size = 10;
317 break;
318 case TIME:
319 size = 5;
320 break;
321 case TIMESTAMP:
322 size = 16;
323 break;
324 default:
325 assert false;
326 size = 99;
327 break;
328 }
329 }
330 else
331 {
332 final Integer sizeForClass = SIZE_MAP.get(this.field.getType().getName());
333 if (null != sizeForClass)
334 {
335 size = sizeForClass.intValue();
336 }
337 else
338 {
339 final Integer maxLength = getMaxLength();
340 size = (null != maxLength) ? maxLength.intValue() : DEFAULT_SIZE;
341 }
342 }
343
344 return size;
345 }
346
347
348
349
350
351 public String getMessageKey ()
352 {
353 return this.field.getDeclaringClass().getName() + '.' + this.field.getName();
354 }
355
356
357
358
359
360 public EnumType getEnumType ()
361 {
362 assert isEnum();
363 final Enumerated enumeration = getAnnotation(Enumerated.class);
364 return (null != enumeration) ? enumeration.value() : EnumType.ORDINAL;
365 }
366
367
368
369
370
371 public TemporalType getTemporalType ()
372 {
373 final Temporal temporal = getAnnotation(Temporal.class);
374 return (null != temporal) ? temporal.value() : null;
375 }
376
377
378
379
380
381
382 public String getConverter ()
383 {
384 String converter = null;
385
386 final Class<?> fieldType = this.field.getType();
387 if (fieldType.isEnum())
388 {
389 converter = "<s:convertEnum/>";
390 }
391 else if (isAnnotationPresent(Temporal.class))
392 {
393 switch (getTemporalType())
394 {
395 case DATE:
396 converter = "<s:convertDateTime type=\"date\" dateStyle=\"short\" pattern=\"MM/dd/yyyy\"/>";
397 break;
398 case TIME:
399 converter = "<s:convertDateTime type=\"time\"/>";
400 break;
401 case TIMESTAMP:
402 converter = "<s:convertDateTime type=\"both\" dateStyle=\"short\"/>";
403 break;
404 default:
405 assert false;
406 break;
407 }
408 }
409 else if (fieldType.isPrimitive() || fieldType.getCanonicalName().startsWith("java.lang."))
410 {
411 assert null == converter;
412 }
413 else
414 {
415 converter = "<s:convertEntity/>";
416 }
417
418 return converter;
419 }
420 }