001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018
019 package org.apache.commons.beanutils;
020
021
022 import java.io.IOException;
023 import java.io.Serializable;
024 import java.io.ObjectOutputStream;
025 import java.io.ObjectInputStream;
026 import java.io.StreamCorruptedException;
027 import java.util.List;
028 import java.util.Map;
029
030
031 /**
032 * <p>The metadata describing an individual property of a DynaBean.</p>
033 *
034 * <p>The meta contains an <em>optional</em> content type property ({@link #getContentType})
035 * for use by mapped and iterated properties.
036 * A mapped or iterated property may choose to indicate the type it expects.
037 * The DynaBean implementation may choose to enforce this type on its entries.
038 * Alternatively, an implementatin may choose to ignore this property.
039 * All keys for maps must be of type String so no meta data is needed for map keys.</p>
040 *
041 * @author Craig R. McClanahan
042 * @version $Revision: 690380 $ $Date: 2008-08-29 21:04:38 +0100 (Fri, 29 Aug 2008) $
043 */
044
045 public class DynaProperty implements Serializable {
046
047 // ----------------------------------------------------------- Constants
048
049 /*
050 * There are issues with serializing primitive class types on certain JVM versions
051 * (including java 1.3).
052 * This class uses a custom serialization implementation that writes an integer
053 * for these primitive class.
054 * This list of constants are the ones used in serialization.
055 * If these values are changed, then older versions will no longer be read correctly
056 */
057 private static final int BOOLEAN_TYPE = 1;
058 private static final int BYTE_TYPE = 2;
059 private static final int CHAR_TYPE = 3;
060 private static final int DOUBLE_TYPE = 4;
061 private static final int FLOAT_TYPE = 5;
062 private static final int INT_TYPE = 6;
063 private static final int LONG_TYPE = 7;
064 private static final int SHORT_TYPE = 8;
065
066
067 // ----------------------------------------------------------- Constructors
068
069
070 /**
071 * Construct a property that accepts any data type.
072 *
073 * @param name Name of the property being described
074 */
075 public DynaProperty(String name) {
076
077 this(name, Object.class);
078
079 }
080
081
082 /**
083 * Construct a property of the specified data type.
084 *
085 * @param name Name of the property being described
086 * @param type Java class representing the property data type
087 */
088 public DynaProperty(String name, Class type) {
089
090 super();
091 this.name = name;
092 this.type = type;
093 if (type != null && type.isArray()) {
094 this.contentType = type.getComponentType();
095 }
096
097 }
098
099 /**
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 * @since 1.8.0
211 */
212 public boolean equals(final Object obj) {
213
214 boolean result = false;
215
216 result = (obj == this);
217
218 if ((!result) && obj instanceof DynaProperty) {
219 final DynaProperty that = (DynaProperty) obj;
220 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 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 int result = 1;
237
238 result = result * 31 + ((name == null) ? 0 : name.hashCode());
239 result = result * 31 + ((type == null) ? 0 : type.hashCode());
240 result = result * 31 + ((contentType == null) ? 0 : contentType.hashCode());
241
242 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 StringBuffer sb = new StringBuffer("DynaProperty[name=");
252 sb.append(this.name);
253 sb.append(",type=");
254 sb.append(this.type);
255 if (isMapped() || isIndexed()) {
256 sb.append(" <").append(this.contentType).append(">");
257 }
258 sb.append("]");
259 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 writeAnyClass(this.type,out);
274
275 if (isMapped() || isIndexed()) {
276 writeAnyClass(this.contentType,out);
277 }
278
279 // write out other values
280 out.defaultWriteObject();
281 }
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 int primitiveType = 0;
289 if (Boolean.TYPE.equals(clazz)) {
290 primitiveType = BOOLEAN_TYPE;
291 } else if (Byte.TYPE.equals(clazz)) {
292 primitiveType = BYTE_TYPE;
293 } else if (Character.TYPE.equals(clazz)) {
294 primitiveType = CHAR_TYPE;
295 } else if (Double.TYPE.equals(clazz)) {
296 primitiveType = DOUBLE_TYPE;
297 } else if (Float.TYPE.equals(clazz)) {
298 primitiveType = FLOAT_TYPE;
299 } else if (Integer.TYPE.equals(clazz)) {
300 primitiveType = INT_TYPE;
301 } else if (Long.TYPE.equals(clazz)) {
302 primitiveType = LONG_TYPE;
303 } else if (Short.TYPE.equals(clazz)) {
304 primitiveType = SHORT_TYPE;
305 }
306
307 if (primitiveType == 0) {
308 // then it's not a primitive type
309 out.writeBoolean(false);
310 out.writeObject(clazz);
311 } else {
312 // we'll write out a constant instead
313 out.writeBoolean(true);
314 out.writeInt(primitiveType);
315 }
316 }
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 this.type = readAnyClass(in);
329
330 if (isMapped() || isIndexed()) {
331 this.contentType = readAnyClass(in);
332 }
333
334 // read other values
335 in.defaultReadObject();
336 }
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 if (in.readBoolean()) {
345 // it's a type constant
346 switch (in.readInt()) {
347
348 case BOOLEAN_TYPE: return Boolean.TYPE;
349 case BYTE_TYPE: return Byte.TYPE;
350 case CHAR_TYPE: return Character.TYPE;
351 case DOUBLE_TYPE: return Double.TYPE;
352 case FLOAT_TYPE: return Float.TYPE;
353 case INT_TYPE: return Integer.TYPE;
354 case LONG_TYPE: return Long.TYPE;
355 case SHORT_TYPE: return Short.TYPE;
356 default:
357 // something's gone wrong
358 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 return ((Class) in.readObject());
367 }
368 }
369 }