1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
48
49
50
51
52
53
54
55
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
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
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
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
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
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
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
307 out.writeBoolean(false);
308 out.writeObject(clazz);
309 } else {
310
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
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
342 if (in.readBoolean()) {
343
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
356 throw new StreamCorruptedException(
357 "Invalid primitive type. "
358 + "Check version of beanutils used to serialize is compatible.");
359
360 }
361
362 } else {
363
364 return ((Class) in.readObject());
365 }
366 }
367 }