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: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $ 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 */ 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 }