View Javadoc

1   // Copyright 2006 Daniel Gredler
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   //     http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
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     /* ------ Allow getting and setting the value of this property on a specific bean. ------ */
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 }