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 }