1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package net.sf.beanform.prop;
16
17 import java.beans.PropertyDescriptor;
18 import java.io.Serializable;
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationTargetException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Date;
25 import java.util.List;
26
27 import net.sf.beanform.integration.IntegratorChain;
28 import net.sf.beanform.util.ReflectionUtils;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.hivemind.ApplicationRuntimeException;
33 import org.apache.hivemind.util.Defense;
34
35 /***
36 * <p>A property of a Java bean, identified by the owner class and the name of the property.</p>
37 *
38 * <p>Note that the name of the property may be recursive; for example, if the owner class is <tt>Person</tt>,
39 * and the property name is <tt>address.state.abbreviation</tt>, then this bean property represents a person's
40 * address's state's 2-letter abbreviation, and its type is <tt>java.lang.String</tt> (not <tt>Address</tt>
41 * or <tt>State</tt>).</p>
42 *
43 * @author Daniel Gredler
44 */
45 public class BeanProperty implements Serializable {
46
47 private static final long serialVersionUID = -9064407627959060313L;
48
49 private static final Log LOG = LogFactory.getLog( BeanProperty.class );
50 private static final Annotation[] EMPTY = new Annotation[ 0 ];
51
52 private static final String STRING = String.class.getName();
53 private static final String BOOLEAN = Boolean.class.getName();
54 private static final String BOOL = boolean.class.getName();
55 private static final String SHORT = Short.class.getName();
56 private static final String SRT = short.class.getName();
57 private static final String INTEGER = Integer.class.getName();
58 private static final String INT = int.class.getName();
59 private static final String LONG = Long.class.getName();
60 private static final String LNG = long.class.getName();
61 private static final String FLOAT = Float.class.getName();
62 private static final String FLT = float.class.getName();
63 private static final String DOUBLE = Double.class.getName();
64 private static final String DBL = double.class.getName();
65 private static final String DATE = Date.class.getName();
66 private static final String BYTE_ARRAY = byte[].class.getName();
67
68 private final static String INPUT_TEXTFIELD = "TextField";
69 private final static String INPUT_TEXTAREA = "TextArea";
70 private final static String INPUT_CHECKBOX = "Checkbox";
71 private final static String INPUT_DATEPICKER = "DatePicker";
72 private final static String INPUT_UPLOAD = "Upload";
73 private final static String INPUT_INSERT = "Insert";
74
75 private final static List<String> INPUTS = Arrays.asList(
76 INPUT_TEXTFIELD,
77 INPUT_TEXTAREA,
78 INPUT_CHECKBOX,
79 INPUT_DATEPICKER,
80 INPUT_UPLOAD,
81 INPUT_INSERT
82 );
83
84 private final Class ownerClass;
85 private final String name;
86 private final String[] names;
87 private final String validators;
88 private final String input;
89
90 private transient PropertyDescriptor descriptor;
91 private transient Field field;
92
93 public BeanProperty( Class ownerClass, String name, String validators, String input ) {
94
95 Defense.notNull( ownerClass, "ownerClass" );
96 Defense.notNull( name, "name" );
97
98 this.ownerClass = ownerClass;
99 this.name = name;
100 this.names = name.split( "//." );
101
102 if( validators != null ) this.validators = validators;
103 else this.validators = IntegratorChain.getValidation( this );
104
105 if( input != null ) this.input = input;
106 else this.input = this.inferInputFromType();
107
108 if( (!this.usesTextField()) && (!this.usesTextArea()) && (!this.usesCheckbox()) &&
109 (!this.usesDatePicker()) && (!this.usesUpload()) && (!this.usesInsert()) ) {
110 String msg = BeanPropertyMessages.unrecognizedInputType( this, INPUTS );
111 throw new ApplicationRuntimeException( msg );
112 }
113 }
114
115 private String inferInputFromType() {
116 if( this.isShortString() || this.isNumber() ) return INPUT_TEXTFIELD;
117 else if( this.isLongString() ) return INPUT_TEXTAREA;
118 else if( this.isBoolean() ) return INPUT_CHECKBOX;
119 else if( this.isDate() ) return INPUT_DATEPICKER;
120 else if( this.isByteArray() ) return INPUT_UPLOAD;
121 else return INPUT_INSERT;
122 }
123
124 private PropertyDescriptor getPropertyDescriptor() {
125 if( this.descriptor == null ) {
126 this.descriptor = ReflectionUtils.getPropertyDescriptor( this.ownerClass, this.names );
127 }
128 return this.descriptor;
129 }
130
131 private Field getField() {
132 if( this.field == null ) {
133 try {
134 this.field = ReflectionUtils.getField( this.ownerClass, this.names );
135 }
136 catch( NoSuchFieldException e ) {
137 if( LOG.isDebugEnabled() ) {
138 String msg = "The field '" + this.getName() + "' does not exist. ";
139 msg += "Perhaps the field name does not match the property name?";
140 LOG.debug( msg );
141 }
142 }
143 }
144 return this.field;
145 }
146
147 public Class getOwnerClass() {
148 return this.ownerClass;
149 }
150
151 public String getName() {
152 return this.name;
153 }
154
155 public String getValidators() {
156 return this.validators;
157 }
158
159 public String getInput() {
160 return this.input;
161 }
162
163 public boolean usesTextField() {
164 return INPUT_TEXTFIELD.equalsIgnoreCase( this.input );
165 }
166
167 public boolean usesTextArea() {
168 return INPUT_TEXTAREA.equalsIgnoreCase( this.input );
169 }
170
171 public boolean usesCheckbox() {
172 return INPUT_CHECKBOX.equalsIgnoreCase( this.input );
173 }
174
175 public boolean usesDatePicker() {
176 return INPUT_DATEPICKER.equalsIgnoreCase( this.input );
177 }
178
179 public boolean usesUpload() {
180 return INPUT_UPLOAD.equalsIgnoreCase( this.input );
181 }
182
183 public boolean usesInsert() {
184 return INPUT_INSERT.equalsIgnoreCase( this.input );
185 }
186
187 public List<Annotation> getAnnotations() {
188 PropertyDescriptor pd = this.getPropertyDescriptor();
189 Annotation[] a1 = ( pd.getReadMethod() == null ? EMPTY : pd.getReadMethod().getAnnotations() );
190 Annotation[] a2 = ( pd.getWriteMethod() == null ? EMPTY : pd.getWriteMethod().getAnnotations() );
191 Annotation[] a3 = ( this.getField() == null ? EMPTY : this.getField().getAnnotations() );
192 List<Annotation> list = new ArrayList<Annotation>();
193 list.addAll( Arrays.asList( a1 ) );
194 list.addAll( Arrays.asList( a2 ) );
195 list.addAll( Arrays.asList( a3 ) );
196 return list;
197 }
198
199 @SuppressWarnings( "unchecked" )
200 public <T extends Annotation> T getAnnotation( Class<T> clazz ) {
201 Annotation result = null;
202 for( Annotation a : this.getAnnotations() ) {
203 if( clazz.isAssignableFrom( a.getClass() ) ) {
204 result = a;
205 break;
206 }
207 }
208 return (T) result;
209 }
210
211 public boolean isReadable() {
212 return this.getPropertyDescriptor().getReadMethod() != null;
213 }
214
215 public boolean isWriteable() {
216 return this.getPropertyDescriptor().getWriteMethod() != null;
217 }
218
219 public boolean isNullable() {
220 return IntegratorChain.isNullable( this );
221 }
222
223 public Class getType() {
224 return this.getPropertyDescriptor().getPropertyType();
225 }
226
227 public String getTypeName() {
228 Class type = this.getType();
229 return ( type != null ? type.getName() : null );
230 }
231
232 public boolean isEnum() {
233 Class type = this.getType();
234 return ( type != null && type.isEnum() );
235 }
236
237 public boolean isString() {
238 String typeName = this.getTypeName();
239 return STRING.equals( typeName );
240 }
241
242 public boolean isShortString() {
243 if( this.isString() == false ) return false;
244 Integer maxLength = IntegratorChain.getMaxLength( this );
245 if( maxLength == null ) return true;
246 if( maxLength < 256 ) return true;
247 return false;
248 }
249
250 public boolean isLongString() {
251 return this.isString() && ! this.isShortString();
252 }
253
254 public boolean isBoolean() {
255 String typeName = this.getTypeName();
256 return BOOLEAN.equals( typeName ) || BOOL.equals( typeName );
257 }
258
259 public boolean isShort() {
260 String typeName = this.getTypeName();
261 return SHORT.equals( typeName ) || SRT.equals( typeName );
262 }
263
264 public boolean isInteger() {
265 String typeName = this.getTypeName();
266 return INTEGER.equals( typeName ) || INT.equals( typeName );
267 }
268
269 public boolean isLong() {
270 String typeName = this.getTypeName();
271 return LONG.equals( typeName ) || LNG.equals( typeName );
272 }
273
274 public boolean isFloat() {
275 String typeName = this.getTypeName();
276 return FLOAT.equals( typeName ) || FLT.equals( typeName );
277 }
278
279 public boolean isDouble() {
280 String typeName = this.getTypeName();
281 return DOUBLE.equals( typeName ) || DBL.equals( typeName );
282 }
283
284 public boolean isNumber() {
285 return this.isShort() || this.isInteger() || this.isLong() || this.isFloat() || this.isDouble();
286 }
287
288 public boolean isDate() {
289 String typeName = this.getTypeName();
290 return DATE.equals( typeName );
291 }
292
293 public boolean isByteArray() {
294 String typeName = this.getTypeName();
295 return BYTE_ARRAY.equals( typeName );
296 }
297
298 public boolean isEditableType() {
299 return this.isString() || this.isBoolean() || this.isNumber() || this.isDate() || this.isByteArray();
300 }
301
302 @Override
303 public String toString() {
304 return this.name +
305 ( this.input != null ? "=" + this.input : "" ) +
306 ( this.validators != null ? "{" + this.validators + "}" : "" );
307 }
308
309 /***
310 * Compares two <tt>BeanProperty</tt> instances based on the same fields that {@link #hashCode()}
311 * uses: <tt>ownerClass</tt>, <tt>name</tt>, <tt>validators</tt> and <tt>input</tt>.
312 */
313 @Override
314 public boolean equals( Object o ) {
315 if( o instanceof BeanProperty == false ) return false;
316 BeanProperty prop = (BeanProperty) o;
317 boolean c = ( this.ownerClass == null ? prop.ownerClass == null : this.ownerClass.equals( prop.ownerClass ) );
318 boolean n = ( this.name == null ? prop.name == null : this.name.equals( prop.name ) );
319 boolean v = ( this.validators == null ? prop.validators == null : this.validators.equals( prop.validators ) );
320 boolean i = ( this.input == null ? prop.input == null : this.input.equals( prop.input ) );
321 return c && n && v && i;
322 }
323
324 /***
325 * Builds a hashCode based on the same fields that {@link #equals(Object)} uses: <tt>ownerClass</tt>,
326 * <tt>name</tt>, <tt>validators</tt> and <tt>input</tt>.
327 *
328 * @see http://jakarta.apache.org/commons/lang/xref/org/apache/commons/lang/builder/HashCodeBuilder.html
329 */
330 @Override
331 public int hashCode() {
332 int result = 17;
333 result = ( this.ownerClass != null ? 37 * result + this.ownerClass.hashCode() : result * 37 );
334 result = ( this.name != null ? 37 * result + this.name.hashCode() : result * 37 );
335 result = ( this.validators != null ? 37 * result + this.validators.hashCode() : result * 37 );
336 result = ( this.input != null ? 37 * result + this.input.hashCode() : result * 37 );
337 return result;
338 }
339
340
341
342
343
344 private transient Object bean;
345
346 public void setBean( Object bean ) {
347 if( bean == null ) {
348 LOG.warn( BeanPropertyMessages.setBeanNullBean() );
349 }
350 this.bean = bean;
351 }
352
353 public Object getValue() {
354 if( this.bean == null ) {
355 String msg = BeanPropertyMessages.getValueNullBean();
356 throw new ApplicationRuntimeException( msg );
357 }
358 if( this.isReadable() == false ) {
359 String msg = BeanPropertyMessages.getValueNotReadable( this );
360 throw new ApplicationRuntimeException( msg );
361 }
362 try {
363 return ReflectionUtils.getPropertyValue( this.bean, this.names );
364 }
365 catch( IllegalAccessException iae ) {
366 throw new ApplicationRuntimeException( iae );
367 }
368 catch( InvocationTargetException ite ) {
369 throw new ApplicationRuntimeException( ite );
370 }
371 finally {
372 this.bean = null;
373 }
374 }
375
376 public void setValue( Object value ) {
377 if( this.bean == null ) {
378 String msg = BeanPropertyMessages.setValueNullBean();
379 throw new ApplicationRuntimeException( msg );
380 }
381 if( this.isWriteable() == false ) {
382 String msg = BeanPropertyMessages.setValueNotWriteable( this );
383 throw new ApplicationRuntimeException( msg );
384 }
385 try {
386 ReflectionUtils.setPropertyValue( value, this.bean, this.names );
387 }
388 catch( IllegalAccessException iae ) {
389 throw new ApplicationRuntimeException( iae );
390 }
391 catch( InvocationTargetException ite ) {
392 throw new ApplicationRuntimeException( ite );
393 }
394 catch( InstantiationException ie ) {
395 throw new ApplicationRuntimeException( ie );
396 }
397 catch( IllegalArgumentException iae ) {
398 String msg = BeanPropertyMessages.setValueIllegalArgument( value );
399 throw new ApplicationRuntimeException( msg, iae );
400 }
401 finally {
402 this.bean = null;
403 }
404 }
405
406 }