Coverage Report - org.apache.commons.beanutils.DynaProperty
 
Classes in this File Line Coverage Branch Coverage Complexity
DynaProperty
83%
77/92
71%
59/83
4.533
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 
 19  
 package org.apache.commons.beanutils;
 20  
 
 21  
 
 22  
 import java.io.IOException;
 23  
 import java.io.Serializable;
 24  
 import java.io.ObjectOutputStream;
 25  
 import java.io.ObjectInputStream;
 26  
 import java.io.StreamCorruptedException;
 27  
 import java.util.List;
 28  
 import java.util.Map;
 29  
 
 30  
 
 31  
 /**
 32  
  * <p>The metadata describing an individual property of a DynaBean.</p>
 33  
  *
 34  
  * <p>The meta contains an <em>optional</em> content type property ({@link #getContentType})
 35  
  * for use by mapped and iterated properties. 
 36  
  * A mapped or iterated property may choose to indicate the type it expects.
 37  
  * The DynaBean implementation may choose to enforce this type on its entries.
 38  
  * Alternatively, an implementatin may choose to ignore this property.
 39  
  * All keys for maps must be of type String so no meta data is needed for map keys.</p>
 40  
  *
 41  
  * @author Craig R. McClanahan
 42  
  * @version $Revision: 926529 $ $Date: 2010-03-23 07:44:24 -0400 (Tue, 23 Mar 2010) $
 43  
  */
 44  
 
 45  
 public class DynaProperty implements Serializable {
 46  
 
 47  
     // ----------------------------------------------------------- Constants
 48  
     
 49  
     /*
 50  
      * There are issues with serializing primitive class types on certain JVM versions
 51  
      * (including java 1.3).
 52  
      * This class uses a custom serialization implementation that writes an integer
 53  
      * for these primitive class.
 54  
      * This list of constants are the ones used in serialization.
 55  
      * If these values are changed, then older versions will no longer be read correctly
 56  
      */
 57  
     private static final int BOOLEAN_TYPE = 1;
 58  
     private static final int BYTE_TYPE = 2;
 59  
     private static final int CHAR_TYPE = 3;
 60  
     private static final int DOUBLE_TYPE = 4;
 61  
     private static final int FLOAT_TYPE = 5;
 62  
     private static final int INT_TYPE = 6;
 63  
     private static final int LONG_TYPE = 7;
 64  
     private static final int SHORT_TYPE = 8;
 65  
     
 66  
 
 67  
     // ----------------------------------------------------------- Constructors
 68  
 
 69  
 
 70  
     /**
 71  
      * Construct a property that accepts any data type.
 72  
      *
 73  
      * @param name Name of the property being described
 74  
      */
 75  
     public DynaProperty(String name) {
 76  
 
 77  11
         this(name, Object.class);
 78  
 
 79  11
     }
 80  
 
 81  
 
 82  
     /**
 83  
      * Construct a property of the specified data type.
 84  
      *
 85  
      * @param name Name of the property being described
 86  
      * @param type Java class representing the property data type
 87  
      */
 88  
     public DynaProperty(String name, Class type) {
 89  
 
 90  3137
         super();
 91  3137
         this.name = name;
 92  3137
         this.type = type;
 93  3137
         if (type != null && type.isArray()) {
 94  607
             this.contentType = type.getComponentType();
 95  
         }
 96  
 
 97  3137
     }
 98  
     
 99  
     /**
 100  
      * Construct an indexed or mapped <code>DynaProperty</code> that supports (pseudo)-introspection
 101  
      * of the content type.
 102  
      *
 103  
      * @param name Name of the property being described
 104  
      * @param type Java class representing the property data type
 105  
      * @param contentType Class that all indexed or mapped elements are instances of
 106  
      */
 107  
     public DynaProperty(String name, Class type, Class contentType) {
 108  
 
 109  4
         super();
 110  4
         this.name = name;
 111  4
         this.type = type;
 112  4
         this.contentType = contentType;
 113  
         
 114  4
     }
 115  
 
 116  
     // ------------------------------------------------------------- Properties
 117  
 
 118  
     /** Property name */
 119  3141
     protected String name = null;
 120  
     /**
 121  
      * Get the name of this property.
 122  
      * @return the name of the property
 123  
      */
 124  
     public String getName() {
 125  4210
         return (this.name);
 126  
     }
 127  
     
 128  
     /** Property type */
 129  3141
     protected transient Class type = null;
 130  
     /**
 131  
      * <p>Gets the Java class representing the data type of the underlying property
 132  
      * values.</p>
 133  
      * 
 134  
      * <p>There are issues with serializing primitive class types on certain JVM versions
 135  
      * (including java 1.3).
 136  
      * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
 137  
      * 
 138  
      * <p><strong>Please leave this field as <code>transient</code></strong></p>
 139  
      *
 140  
      * @return the property type
 141  
      */
 142  
     public Class getType() {
 143  4196
         return (this.type);
 144  
     }
 145  
     
 146  
     
 147  
     /** The <em>(optional)</em> type of content elements for indexed <code>DynaProperty</code> */
 148  
     protected transient Class contentType;
 149  
     /**
 150  
      * Gets the <em>(optional)</em> type of the indexed content for <code>DynaProperty</code>'s
 151  
      * that support this feature.
 152  
      *
 153  
      * <p>There are issues with serializing primitive class types on certain JVM versions
 154  
      * (including java 1.3).
 155  
      * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
 156  
      *
 157  
      * @return the Class for the content type if this is an indexed <code>DynaProperty</code> 
 158  
      * and this feature is supported. Otherwise null.
 159  
      */
 160  
     public Class getContentType() {
 161  42
         return contentType;
 162  
     }
 163  
     
 164  
     // --------------------------------------------------------- Public Methods
 165  
 
 166  
 
 167  
     /**
 168  
      * Does this property represent an indexed value (ie an array or List)?
 169  
      *
 170  
      * @return <code>true</code> if the property is indexed (i.e. is a List or
 171  
      * array), otherwise <code>false</code>
 172  
      */
 173  
     public boolean isIndexed() {
 174  
 
 175  92
         if (type == null) {
 176  0
             return (false);
 177  92
         } else if (type.isArray()) {
 178  32
             return (true);
 179  60
         } else if (List.class.isAssignableFrom(type)) {
 180  34
             return (true);
 181  
         } else {
 182  26
             return (false);
 183  
         }
 184  
 
 185  
     }
 186  
 
 187  
 
 188  
     /**
 189  
      * Does this property represent a mapped value (ie a Map)?
 190  
      *
 191  
      * @return <code>true</code> if the property is a Map
 192  
      * otherwise <code>false</code>
 193  
      */
 194  
     public boolean isMapped() {
 195  
 
 196  68
         if (type == null) {
 197  0
             return (false);
 198  
         } else {
 199  68
             return (Map.class.isAssignableFrom(type));
 200  
         }
 201  
 
 202  
     }
 203  
 
 204  
     /**
 205  
      * Checks this instance against the specified Object for equality. Overrides the
 206  
      * default refererence test for equality provided by {@link java.lang.Object#equals(Object)}  
 207  
      * @param obj The object to compare to
 208  
      * @return <code>true</code> if object is a dyna property with the same name
 209  
      * type and content type, otherwise <code>false</code>
 210  
      * @since 1.8.0
 211  
      */
 212  
     public boolean equals(final Object obj) {
 213  
 
 214  6
         boolean result = false;
 215  
 
 216  6
         result = (obj == this);
 217  
 
 218  6
         if ((!result) && obj instanceof DynaProperty) {
 219  5
             final DynaProperty that = (DynaProperty) obj;
 220  5
             result = 
 221  
                ((this.name == null) ? (that.name == null) : (this.name.equals(that.name))) &&
 222  
                ((this.type == null) ? (that.type == null) : (this.type.equals(that.type))) &&
 223  
                ((this.contentType == null) ? (that.contentType == null) : (this.contentType.equals(that.contentType)));
 224  
         }
 225  
 
 226  6
         return result;
 227  
     }
 228  
 
 229  
     /**
 230  
      * @return the hashcode for this dyna property
 231  
      * @see java.lang.Object#hashCode
 232  
      * @since 1.8.0
 233  
      */
 234  
     public int hashCode() {
 235  
 
 236  8
        int result = 1;
 237  
        
 238  8
        result = result * 31 + ((name == null) ? 0 : name.hashCode());
 239  8
        result = result * 31 + ((type == null) ? 0 : type.hashCode());
 240  8
        result = result * 31 + ((contentType == null) ? 0 : contentType.hashCode());
 241  
 
 242  8
        return result;
 243  
     }
 244  
 
 245  
     /**
 246  
      * Return a String representation of this Object.
 247  
      * @return a String representation of the dyna property
 248  
      */
 249  
     public String toString() {
 250  
 
 251  0
         StringBuffer sb = new StringBuffer("DynaProperty[name=");
 252  0
         sb.append(this.name);
 253  0
         sb.append(",type=");
 254  0
         sb.append(this.type);
 255  0
         if (isMapped() || isIndexed()) {
 256  0
             sb.append(" <").append(this.contentType).append(">");
 257  
         }
 258  0
         sb.append("]");
 259  0
         return (sb.toString());
 260  
 
 261  
     }
 262  
 
 263  
     // --------------------------------------------------------- Serialization helper methods
 264  
     
 265  
     /**
 266  
      * Writes this object safely.
 267  
      * There are issues with serializing primitive class types on certain JVM versions
 268  
      * (including java 1.3).
 269  
      * This method provides a workaround.
 270  
      */
 271  
     private void writeObject(ObjectOutputStream out) throws IOException {
 272  
         
 273  19
         writeAnyClass(this.type,out);
 274  
         
 275  19
         if (isMapped() || isIndexed()) {
 276  8
             writeAnyClass(this.contentType,out);
 277  
         }
 278  
         
 279  
         // write out other values
 280  19
         out.defaultWriteObject();
 281  19
     }
 282  
 
 283  
     /**
 284  
      * Write a class using safe encoding to workaround java 1.3 serialization bug.
 285  
      */
 286  
     private void writeAnyClass(Class clazz, ObjectOutputStream out) throws IOException {
 287  
         // safely write out any class
 288  27
         int primitiveType = 0;
 289  27
         if (Boolean.TYPE.equals(clazz)) {
 290  2
             primitiveType = BOOLEAN_TYPE;
 291  25
         } else if (Byte.TYPE.equals(clazz)) {
 292  0
             primitiveType = BYTE_TYPE;
 293  25
         } else if (Character.TYPE.equals(clazz)) {
 294  0
             primitiveType = CHAR_TYPE;
 295  25
         } else if (Double.TYPE.equals(clazz)) {
 296  1
             primitiveType = DOUBLE_TYPE;
 297  24
         } else if (Float.TYPE.equals(clazz)) {
 298  1
             primitiveType = FLOAT_TYPE;
 299  23
         } else if (Integer.TYPE.equals(clazz)) {
 300  3
             primitiveType = INT_TYPE;
 301  20
         } else if (Long.TYPE.equals(clazz)) {
 302  1
             primitiveType = LONG_TYPE;
 303  19
         } else if (Short.TYPE.equals(clazz)) {
 304  1
             primitiveType = SHORT_TYPE;
 305  
         }
 306  
         
 307  27
         if (primitiveType == 0) {
 308  
             // then it's not a primitive type
 309  18
             out.writeBoolean(false);
 310  18
             out.writeObject(clazz);
 311  
         } else {
 312  
             // we'll write out a constant instead
 313  9
             out.writeBoolean(true);
 314  9
             out.writeInt(primitiveType);
 315  
         }
 316  27
     }
 317  
     
 318  
     /**
 319  
      * Reads field values for this object safely.
 320  
      * There are issues with serializing primitive class types on certain JVM versions
 321  
      * (including java 1.3).
 322  
      * This method provides a workaround.
 323  
      *
 324  
      * @throws StreamCorruptedException when the stream data values are outside expected range 
 325  
      */
 326  
     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
 327  
         
 328  19
         this.type = readAnyClass(in);
 329  
         
 330  19
         if (isMapped() || isIndexed()) {
 331  8
             this.contentType = readAnyClass(in);
 332  
         }
 333  
         
 334  
         // read other values
 335  19
         in.defaultReadObject();
 336  19
     }
 337  
     
 338  
 
 339  
     /**
 340  
      * Reads a class using safe encoding to workaround java 1.3 serialization bug.
 341  
      */
 342  
     private Class readAnyClass(ObjectInputStream in) throws IOException, ClassNotFoundException {
 343  
         // read back type class safely 
 344  27
         if (in.readBoolean()) {
 345  
             // it's a type constant
 346  9
             switch (in.readInt()) {
 347  
             
 348  2
                 case BOOLEAN_TYPE: return   Boolean.TYPE;
 349  0
                 case BYTE_TYPE:    return      Byte.TYPE;
 350  0
                 case CHAR_TYPE:    return Character.TYPE;
 351  1
                 case DOUBLE_TYPE:  return    Double.TYPE;
 352  1
                 case FLOAT_TYPE:   return     Float.TYPE;
 353  3
                 case INT_TYPE:     return   Integer.TYPE;
 354  1
                 case LONG_TYPE:    return      Long.TYPE;
 355  1
                 case SHORT_TYPE:   return     Short.TYPE;
 356  
                 default:
 357  
                     // something's gone wrong
 358  0
                     throw new StreamCorruptedException(
 359  
                         "Invalid primitive type. "
 360  
                         + "Check version of beanutils used to serialize is compatible.");
 361  
 
 362  
             }
 363  
               
 364  
         } else {
 365  
             // it's another class
 366  18
             return ((Class) in.readObject());
 367  
         }
 368  
     }
 369  
 }