View Javadoc

    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: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
    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          this(name, Object.class);
    78
    79      }
    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          super();
    91          this.name = name;
    92          this.type = type;
    93          if (type != null && type.isArray()) {
    94              this.contentType = type.getComponentType();
    95          }
    96
    97      }
    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         super();
    110         this.name = name;
    111         this.type = type;
    112         this.contentType = contentType;
    113
    114     }
    115
    116     // ------------------------------------------------------------- Properties
    117
    118     /*** Property name */
    119     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         return (this.name);
    126     }
    127
    128     /*** Property type */
    129     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         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         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         if (type == null) {
    176             return (false);
    177         } else if (type.isArray()) {
    178             return (true);
    179         } else if (List.class.isAssignableFrom(type)) {
    180             return (true);
    181         } else {
    182             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         if (type == null) {
    197             return (false);
    198         } else {
    199             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      */
    211     public boolean equals(final Object obj) {
    212
    213         boolean result = false;
    214
    215         result = (obj == this);
    216
    217         if ((!result) && obj instanceof DynaProperty) {
    218             final DynaProperty that = (DynaProperty) obj;
    219             result =
    220                ((this.name == null) ? (that.name == null) : (this.name.equals(that.name))) &&
    221                ((this.type == null) ? (that.type == null) : (this.type.equals(that.type))) &&
    222                ((this.contentType == null) ? (that.contentType == null) : (this.contentType.equals(that.contentType)));
    223         }
    224
    225         return result;
    226     }
    227
    228     /***
    229      * @return the hashcode for this dyna property
    230      * @see java.lang.Object#hashCode
    231      */
    232     public int hashCode() {
    233
    234        int result = 1;
    235
    236        result = result * 31 + ((name == null) ? 0 : name.hashCode());
    237        result = result * 31 + ((type == null) ? 0 : type.hashCode());
    238        result = result * 31 + ((contentType == null) ? 0 : contentType.hashCode());
    239
    240        return result;
    241     }
    242
    243     /***
    244      * Return a String representation of this Object.
    245      * @return a String representation of the dyna property
    246      */
    247     public String toString() {
    248
    249         StringBuffer sb = new StringBuffer("DynaProperty[name=");
    250         sb.append(this.name);
    251         sb.append(",type=");
    252         sb.append(this.type);
    253         if (isMapped() || isIndexed()) {
    254             sb.append(" <").append(this.contentType).append(">");
    255         }
    256         sb.append("]");
    257         return (sb.toString());
    258
    259     }
    260
    261     // --------------------------------------------------------- Serialization helper methods
    262
    263     /***
    264      * Writes this object safely.
    265      * There are issues with serializing primitive class types on certain JVM versions
    266      * (including java 1.3).
    267      * This method provides a workaround.
    268      */
    269     private void writeObject(ObjectOutputStream out) throws IOException {
    270
    271         writeAnyClass(this.type,out);
    272
    273         if (isMapped() || isIndexed()) {
    274             writeAnyClass(this.contentType,out);
    275         }
    276
    277         // write out other values
    278         out.defaultWriteObject();
    279     }
    280
    281     /***
    282      * Write a class using safe encoding to workaround java 1.3 serialization bug.
    283      */
    284     private void writeAnyClass(Class clazz, ObjectOutputStream out) throws IOException {
    285         // safely write out any class
    286         int primitiveType = 0;
    287         if (Boolean.TYPE.equals(clazz)) {
    288             primitiveType = BOOLEAN_TYPE;
    289         } else if (Byte.TYPE.equals(clazz)) {
    290             primitiveType = BYTE_TYPE;
    291         } else if (Character.TYPE.equals(clazz)) {
    292             primitiveType = CHAR_TYPE;
    293         } else if (Double.TYPE.equals(clazz)) {
    294             primitiveType = DOUBLE_TYPE;
    295         } else if (Float.TYPE.equals(clazz)) {
    296             primitiveType = FLOAT_TYPE;
    297         } else if (Integer.TYPE.equals(clazz)) {
    298             primitiveType = INT_TYPE;
    299         } else if (Long.TYPE.equals(clazz)) {
    300             primitiveType = LONG_TYPE;
    301         } else if (Short.TYPE.equals(clazz)) {
    302             primitiveType = SHORT_TYPE;
    303         }
    304
    305         if (primitiveType == 0) {
    306             // then it's not a primitive type
    307             out.writeBoolean(false);
    308             out.writeObject(clazz);
    309         } else {
    310             // we'll write out a constant instead
    311             out.writeBoolean(true);
    312             out.writeInt(primitiveType);
    313         }
    314     }
    315
    316     /***
    317      * Reads field values for this object safely.
    318      * There are issues with serializing primitive class types on certain JVM versions
    319      * (including java 1.3).
    320      * This method provides a workaround.
    321      *
    322      * @throws StreamCorruptedException when the stream data values are outside expected range
    323      */
    324     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    325
    326         this.type = readAnyClass(in);
    327
    328         if (isMapped() || isIndexed()) {
    329             this.contentType = readAnyClass(in);
    330         }
    331
    332         // read other values
    333         in.defaultReadObject();
    334     }
    335
    336
    337     /***
    338      * Reads a class using safe encoding to workaround java 1.3 serialization bug.
    339      */
    340     private Class readAnyClass(ObjectInputStream in) throws IOException, ClassNotFoundException {
    341         // read back type class safely
    342         if (in.readBoolean()) {
    343             // it's a type constant
    344             switch (in.readInt()) {
    345
    346                 case BOOLEAN_TYPE: return   Boolean.TYPE;
    347                 case BYTE_TYPE:    return      Byte.TYPE;
    348                 case CHAR_TYPE:    return Character.TYPE;
    349                 case DOUBLE_TYPE:  return    Double.TYPE;
    350                 case FLOAT_TYPE:   return     Float.TYPE;
    351                 case INT_TYPE:     return   Integer.TYPE;
    352                 case LONG_TYPE:    return      Long.TYPE;
    353                 case SHORT_TYPE:   return     Short.TYPE;
    354                 default:
    355                     // something's gone wrong
    356                     throw new StreamCorruptedException(
    357                         "Invalid primitive type. "
    358                         + "Check version of beanutils used to serialize is compatible.");
    359
    360             }
    361
    362         } else {
    363             // it's another class
    364             return ((Class) in.readObject());
    365         }
    366     }
    367 }