Coverage Report - net.sf.beanform.prop.BeanProperty
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanProperty
92% 
100% 
0
 
 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  1
     private static final Log LOG = LogFactory.getLog( BeanProperty.class );
 50  1
     private static final Annotation[] EMPTY = new Annotation[ 0 ];
 51  
 
 52  1
     private static final String STRING = String.class.getName();
 53  1
     private static final String BOOLEAN = Boolean.class.getName();
 54  1
     private static final String BOOL = boolean.class.getName();
 55  1
     private static final String SHORT = Short.class.getName();
 56  1
     private static final String SRT = short.class.getName();
 57  1
     private static final String INTEGER = Integer.class.getName();
 58  1
     private static final String INT = int.class.getName();
 59  1
     private static final String LONG = Long.class.getName();
 60  1
     private static final String LNG = long.class.getName();
 61  1
     private static final String FLOAT = Float.class.getName();
 62  1
     private static final String FLT = float.class.getName();
 63  1
     private static final String DOUBLE = Double.class.getName();
 64  1
     private static final String DBL = double.class.getName();
 65  1
     private static final String DATE = Date.class.getName();
 66  1
     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  1
     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  214
     public BeanProperty( Class ownerClass, String name, String validators, String input ) {
 94  
 
 95  214
         Defense.notNull( ownerClass, "ownerClass" );
 96  213
         Defense.notNull( name, "name" );
 97  
 
 98  212
         this.ownerClass = ownerClass;
 99  212
         this.name = name;
 100  212
         this.names = name.split( "\\." );
 101  
 
 102  212
         if( validators != null ) this.validators = validators;
 103  174
         else this.validators = IntegratorChain.getValidation( this );
 104  
 
 105  212
         if( input != null ) this.input = input;
 106  164
         else this.input = this.inferInputFromType();
 107  
 
 108  212
         if( (!this.usesTextField()) && (!this.usesTextArea()) && (!this.usesCheckbox()) &&
 109  
             (!this.usesDatePicker()) && (!this.usesUpload()) && (!this.usesInsert()) ) {
 110  1
             String msg = BeanPropertyMessages.unrecognizedInputType( this, INPUTS );
 111  1
             throw new ApplicationRuntimeException( msg );
 112  
         }
 113  211
     }
 114  
 
 115  
     private String inferInputFromType() {
 116  164
         if( this.isShortString() || this.isNumber() ) return INPUT_TEXTFIELD;
 117  74
         else if( this.isLongString() ) return INPUT_TEXTAREA;
 118  67
         else if( this.isBoolean() ) return INPUT_CHECKBOX;
 119  49
         else if( this.isDate() ) return INPUT_DATEPICKER;
 120  41
         else if( this.isByteArray() ) return INPUT_UPLOAD;
 121  36
         else return INPUT_INSERT;
 122  
     }
 123  
 
 124  
     private PropertyDescriptor getPropertyDescriptor() {
 125  3731
         if( this.descriptor == null ) {
 126  190
             this.descriptor = ReflectionUtils.getPropertyDescriptor( this.ownerClass, this.names );
 127  
         }
 128  3731
         return this.descriptor;
 129  
     }
 130  
 
 131  
     private Field getField() {
 132  4092
         if( this.field == null ) {
 133  
             try {
 134  220
                 this.field = ReflectionUtils.getField( this.ownerClass, this.names );
 135  
             }
 136  44
             catch( NoSuchFieldException e ) {
 137  44
                 if( LOG.isDebugEnabled() ) {
 138  44
                     String msg = "The field '" + this.getName() + "' does not exist. ";
 139  44
                     msg += "Perhaps the field name does not match the property name?";
 140  44
                     LOG.debug( msg );
 141  
                 }
 142  176
             }
 143  
         }
 144  4092
         return this.field;
 145  
     }
 146  
 
 147  
     public Class getOwnerClass() {
 148  13
         return this.ownerClass;
 149  
     }
 150  
 
 151  
     public String getName() {
 152  317
         return this.name;
 153  
     }
 154  
 
 155  
     public String getValidators() {
 156  68
         return this.validators;
 157  
     }
 158  
 
 159  
     public String getInput() {
 160  55
         return this.input;
 161  
     }
 162  
 
 163  
     public boolean usesTextField() {
 164  226
         return INPUT_TEXTFIELD.equalsIgnoreCase( this.input );
 165  
     }
 166  
 
 167  
     public boolean usesTextArea() {
 168  120
         return INPUT_TEXTAREA.equalsIgnoreCase( this.input );
 169  
     }
 170  
 
 171  
     public boolean usesCheckbox() {
 172  100
         return INPUT_CHECKBOX.equalsIgnoreCase( this.input );
 173  
     }
 174  
 
 175  
     public boolean usesDatePicker() {
 176  75
         return INPUT_DATEPICKER.equalsIgnoreCase( this.input );
 177  
     }
 178  
 
 179  
     public boolean usesUpload() {
 180  58
         return INPUT_UPLOAD.equalsIgnoreCase( this.input );
 181  
     }
 182  
 
 183  
     public boolean usesInsert() {
 184  54
         return INPUT_INSERT.equalsIgnoreCase( this.input );
 185  
     }
 186  
 
 187  
     public List<Annotation> getAnnotations() {
 188  2068
         PropertyDescriptor pd = this.getPropertyDescriptor();
 189  2068
         Annotation[] a1 = ( pd.getReadMethod() == null ? EMPTY : pd.getReadMethod().getAnnotations() );
 190  2068
         Annotation[] a2 = ( pd.getWriteMethod() == null ? EMPTY : pd.getWriteMethod().getAnnotations() );
 191  2068
         Annotation[] a3 = ( this.getField() == null ? EMPTY : this.getField().getAnnotations() );
 192  2068
         List<Annotation> list = new ArrayList<Annotation>();
 193  2068
         list.addAll( Arrays.asList( a1 ) );
 194  2068
         list.addAll( Arrays.asList( a2 ) );
 195  2068
         list.addAll( Arrays.asList( a3 ) );
 196  2068
         return list;
 197  
     }
 198  
 
 199  
     @SuppressWarnings( "unchecked" )
 200  
     public <T extends Annotation> T getAnnotation( Class<T> clazz ) {
 201  2098
         Annotation result = null;
 202  2098
         for( Annotation a : this.getAnnotations() ) {
 203  366
             if( clazz.isAssignableFrom( a.getClass() ) ) {
 204  73
                 result = a;
 205  73
                 break;
 206  
             }
 207  293
         }
 208  2098
         return (T) result;
 209  
     }
 210  
 
 211  
     public boolean isReadable() {
 212  12
         return this.getPropertyDescriptor().getReadMethod() != null;
 213  
     }
 214  
 
 215  
     public boolean isWriteable() {
 216  13
         return this.getPropertyDescriptor().getWriteMethod() != null;
 217  
     }
 218  
 
 219  
     public boolean isNullable() {
 220  9
         return IntegratorChain.isNullable( this );
 221  
     }
 222  
 
 223  
     public Class getType() {
 224  1638
         return this.getPropertyDescriptor().getPropertyType();
 225  
     }
 226  
 
 227  
     public String getTypeName() {
 228  1571
         Class type = this.getType();
 229  1571
         return ( type != null ? type.getName() : null );
 230  
     }
 231  
 
 232  
     public boolean isEnum() {
 233  96
         Class type = this.getType();
 234  96
         return ( type != null && type.isEnum() );
 235  
     }
 236  
 
 237  
     public boolean isString() {
 238  381
         String typeName = this.getTypeName();
 239  381
         return STRING.equals( typeName );
 240  
     }
 241  
 
 242  
     public boolean isShortString() {
 243  192
         if( this.isString() == false ) return false;
 244  72
         Integer maxLength = IntegratorChain.getMaxLength( this );
 245  72
         if( maxLength == null ) return true;
 246  47
         if( maxLength < 256 ) return true;
 247  20
         return false;
 248  
     }
 249  
 
 250  
     public boolean isLongString() {
 251  87
         return this.isString() && ! this.isShortString();
 252  
     }
 253  
 
 254  
     public boolean isBoolean() {
 255  152
         String typeName = this.getTypeName();
 256  152
         return BOOLEAN.equals( typeName ) || BOOL.equals( typeName );
 257  
     }
 258  
 
 259  
     public boolean isShort() {
 260  220
         String typeName = this.getTypeName();
 261  220
         return SHORT.equals( typeName ) || SRT.equals( typeName );
 262  
     }
 263  
 
 264  
     public boolean isInteger() {
 265  191
         String typeName = this.getTypeName();
 266  191
         return INTEGER.equals( typeName ) || INT.equals( typeName );
 267  
     }
 268  
 
 269  
     public boolean isLong() {
 270  170
         String typeName = this.getTypeName();
 271  170
         return LONG.equals( typeName ) || LNG.equals( typeName );
 272  
     }
 273  
 
 274  
     public boolean isFloat() {
 275  160
         String typeName = this.getTypeName();
 276  160
         return FLOAT.equals( typeName ) || FLT.equals( typeName );
 277  
     }
 278  
 
 279  
     public boolean isDouble() {
 280  139
         String typeName = this.getTypeName();
 281  139
         return DOUBLE.equals( typeName ) || DBL.equals( typeName );
 282  
     }
 283  
 
 284  
     public boolean isNumber() {
 285  202
         return this.isShort() || this.isInteger() || this.isLong() || this.isFloat() || this.isDouble();
 286  
     }
 287  
 
 288  
     public boolean isDate() {
 289  84
         String typeName = this.getTypeName();
 290  84
         return DATE.equals( typeName );
 291  
     }
 292  
 
 293  
     public boolean isByteArray() {
 294  67
         String typeName = this.getTypeName();
 295  67
         return BYTE_ARRAY.equals( typeName );
 296  
     }
 297  
 
 298  
     public boolean isEditableType() {
 299  91
         return this.isString() || this.isBoolean() || this.isNumber() || this.isDate() || this.isByteArray();
 300  
     }
 301  
 
 302  
     @Override
 303  
     public String toString() {
 304  204
         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  29
         if( o instanceof BeanProperty == false ) return false;
 316  29
         BeanProperty prop = (BeanProperty) o;
 317  29
         boolean c = ( this.ownerClass == null ? prop.ownerClass == null : this.ownerClass.equals( prop.ownerClass ) );
 318  29
         boolean n = ( this.name == null       ? prop.name == null       : this.name.equals( prop.name ) );
 319  29
         boolean v = ( this.validators == null ? prop.validators == null : this.validators.equals( prop.validators ) );
 320  29
         boolean i = ( this.input == null      ? prop.input == null      : this.input.equals( prop.input ) );
 321  29
         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  130
         int result = 17;
 333  130
         result = ( this.ownerClass != null ? 37 * result + this.ownerClass.hashCode() : result * 37 );
 334  130
         result = ( this.name       != null ? 37 * result + this.name.hashCode()       : result * 37 );
 335  130
         result = ( this.validators != null ? 37 * result + this.validators.hashCode() : result * 37 );
 336  130
         result = ( this.input      != null ? 37 * result + this.input.hashCode()      : result * 37 );
 337  130
         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  8
         if( bean == null ) {
 348  1
             LOG.warn( BeanPropertyMessages.setBeanNullBean() );
 349  
         }
 350  8
         this.bean = bean;
 351  8
     }
 352  
 
 353  
     public Object getValue() {
 354  4
         if( this.bean == null ) {
 355  1
             String msg = BeanPropertyMessages.getValueNullBean();
 356  1
             throw new ApplicationRuntimeException( msg );
 357  
         }
 358  3
         if( this.isReadable() == false ) {
 359  1
             String msg = BeanPropertyMessages.getValueNotReadable( this );
 360  1
             throw new ApplicationRuntimeException( msg );
 361  
         }
 362  
         try {
 363  2
             return ReflectionUtils.getPropertyValue( this.bean, this.names );
 364  
         }
 365  0
         catch( IllegalAccessException iae ) {
 366  0
             throw new ApplicationRuntimeException( iae );
 367  
         }
 368  0
         catch( InvocationTargetException ite ) {
 369  0
             throw new ApplicationRuntimeException( ite );
 370  
         }
 371  
         finally {
 372  2
             this.bean = null;
 373  2
         }
 374  
     }
 375  
 
 376  
     public void setValue( Object value ) {
 377  4
         if( this.bean == null ) {
 378  1
             String msg = BeanPropertyMessages.setValueNullBean();
 379  1
             throw new ApplicationRuntimeException( msg );
 380  
         }
 381  3
         if( this.isWriteable() == false ) {
 382  1
             String msg = BeanPropertyMessages.setValueNotWriteable( this );
 383  1
             throw new ApplicationRuntimeException( msg );
 384  
         }
 385  
         try {
 386  2
             ReflectionUtils.setPropertyValue( value, this.bean, this.names );
 387  
         }
 388  0
         catch( IllegalAccessException iae ) {
 389  0
             throw new ApplicationRuntimeException( iae );
 390  
         }
 391  0
         catch( InvocationTargetException ite ) {
 392  0
             throw new ApplicationRuntimeException( ite );
 393  
         }
 394  0
         catch( InstantiationException ie ) {
 395  0
             throw new ApplicationRuntimeException( ie );
 396  
         }
 397  0
         catch( IllegalArgumentException iae ) {
 398  0
             String msg = BeanPropertyMessages.setValueIllegalArgument( value );
 399  0
             throw new ApplicationRuntimeException( msg, iae );
 400  
         }
 401  
         finally {
 402  2
             this.bean = null;
 403  2
         }
 404  2
     }
 405  
 
 406  
 }