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 019package org.apache.commons.beanutils; 020 021 022import java.io.IOException; 023import java.io.ObjectInputStream; 024import java.io.ObjectOutputStream; 025import java.io.Serializable; 026import java.io.StreamCorruptedException; 027import java.util.List; 028import 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 * @version $Id$ 042 */ 043 044public class DynaProperty implements Serializable { 045 046 // ----------------------------------------------------------- Constants 047 048 private static final long serialVersionUID = -3084907613499830175L; 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(final 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(final String name, final 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(final String name, final Class<?> type, final 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 @Override 213 public boolean equals(final Object obj) { 214 215 boolean result = false; 216 217 result = (obj == this); 218 219 if ((!result) && obj instanceof DynaProperty) { 220 final DynaProperty that = (DynaProperty) obj; 221 result = 222 ((this.name == null) ? (that.name == null) : (this.name.equals(that.name))) && 223 ((this.type == null) ? (that.type == null) : (this.type.equals(that.type))) && 224 ((this.contentType == null) ? (that.contentType == null) : (this.contentType.equals(that.contentType))); 225 } 226 227 return result; 228 } 229 230 /** 231 * @return the hashcode for this dyna property 232 * @see java.lang.Object#hashCode 233 * @since 1.8.0 234 */ 235 @Override 236 public int hashCode() { 237 238 int result = 1; 239 240 result = result * 31 + ((name == null) ? 0 : name.hashCode()); 241 result = result * 31 + ((type == null) ? 0 : type.hashCode()); 242 result = result * 31 + ((contentType == null) ? 0 : contentType.hashCode()); 243 244 return result; 245 } 246 247 /** 248 * Return a String representation of this Object. 249 * @return a String representation of the dyna property 250 */ 251 @Override 252 public String toString() { 253 254 final StringBuilder sb = new StringBuilder("DynaProperty[name="); 255 sb.append(this.name); 256 sb.append(",type="); 257 sb.append(this.type); 258 if (isMapped() || isIndexed()) { 259 sb.append(" <").append(this.contentType).append(">"); 260 } 261 sb.append("]"); 262 return (sb.toString()); 263 264 } 265 266 // --------------------------------------------------------- Serialization helper methods 267 268 /** 269 * Writes this object safely. 270 * There are issues with serializing primitive class types on certain JVM versions 271 * (including java 1.3). 272 * This method provides a workaround. 273 * 274 * @param out {@link ObjectOutputStream} to write object to 275 * @throws IOException if the object can't be written 276 */ 277 private void writeObject(final ObjectOutputStream out) throws IOException { 278 279 writeAnyClass(this.type,out); 280 281 if (isMapped() || isIndexed()) { 282 writeAnyClass(this.contentType,out); 283 } 284 285 // write out other values 286 out.defaultWriteObject(); 287 } 288 289 /** 290 * Write a class using safe encoding to workaround java 1.3 serialization bug. 291 */ 292 private void writeAnyClass(final Class<?> clazz, final ObjectOutputStream out) throws IOException { 293 // safely write out any class 294 int primitiveType = 0; 295 if (Boolean.TYPE.equals(clazz)) { 296 primitiveType = BOOLEAN_TYPE; 297 } else if (Byte.TYPE.equals(clazz)) { 298 primitiveType = BYTE_TYPE; 299 } else if (Character.TYPE.equals(clazz)) { 300 primitiveType = CHAR_TYPE; 301 } else if (Double.TYPE.equals(clazz)) { 302 primitiveType = DOUBLE_TYPE; 303 } else if (Float.TYPE.equals(clazz)) { 304 primitiveType = FLOAT_TYPE; 305 } else if (Integer.TYPE.equals(clazz)) { 306 primitiveType = INT_TYPE; 307 } else if (Long.TYPE.equals(clazz)) { 308 primitiveType = LONG_TYPE; 309 } else if (Short.TYPE.equals(clazz)) { 310 primitiveType = SHORT_TYPE; 311 } 312 313 if (primitiveType == 0) { 314 // then it's not a primitive type 315 out.writeBoolean(false); 316 out.writeObject(clazz); 317 } else { 318 // we'll write out a constant instead 319 out.writeBoolean(true); 320 out.writeInt(primitiveType); 321 } 322 } 323 324 /** 325 * Reads field values for this object safely. 326 * There are issues with serializing primitive class types on certain JVM versions 327 * (including java 1.3). 328 * This method provides a workaround. 329 * 330 * @param in {@link ObjectInputStream} to read object from 331 * @throws StreamCorruptedException when the stream data values are outside expected range 332 * @throws IOException if the input stream can't be read 333 * @throws ClassNotFoundException When trying to read an object of class that is not on the classpath 334 */ 335 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 336 337 this.type = readAnyClass(in); 338 339 if (isMapped() || isIndexed()) { 340 this.contentType = readAnyClass(in); 341 } 342 343 // read other values 344 in.defaultReadObject(); 345 } 346 347 348 /** 349 * Reads a class using safe encoding to workaround java 1.3 serialization bug. 350 */ 351 private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException { 352 // read back type class safely 353 if (in.readBoolean()) { 354 // it's a type constant 355 switch (in.readInt()) { 356 357 case BOOLEAN_TYPE: return Boolean.TYPE; 358 case BYTE_TYPE: return Byte.TYPE; 359 case CHAR_TYPE: return Character.TYPE; 360 case DOUBLE_TYPE: return Double.TYPE; 361 case FLOAT_TYPE: return Float.TYPE; 362 case INT_TYPE: return Integer.TYPE; 363 case LONG_TYPE: return Long.TYPE; 364 case SHORT_TYPE: return Short.TYPE; 365 default: 366 // something's gone wrong 367 throw new StreamCorruptedException( 368 "Invalid primitive type. " 369 + "Check version of beanutils used to serialize is compatible."); 370 371 } 372 373 } else { 374 // it's another class 375 return ((Class<?>) in.readObject()); 376 } 377 } 378}