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 }