View Javadoc

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