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 /* 049 * There are issues with serializing primitive class types on certain JVM versions 050 * (including java 1.3). 051 * This class uses a custom serialization implementation that writes an integer 052 * for these primitive class. 053 * This list of constants are the ones used in serialization. 054 * If these values are changed, then older versions will no longer be read correctly 055 */ 056 private static final int BOOLEAN_TYPE = 1; 057 private static final int BYTE_TYPE = 2; 058 private static final int CHAR_TYPE = 3; 059 private static final int DOUBLE_TYPE = 4; 060 private static final int FLOAT_TYPE = 5; 061 private static final int INT_TYPE = 6; 062 private static final int LONG_TYPE = 7; 063 private static final int SHORT_TYPE = 8; 064 065 066 // ----------------------------------------------------------- Constructors 067 068 069 /** 070 * Construct a property that accepts any data type. 071 * 072 * @param name Name of the property being described 073 */ 074 public DynaProperty(final String name) { 075 076 this(name, Object.class); 077 078 } 079 080 081 /** 082 * Construct a property of the specified data type. 083 * 084 * @param name Name of the property being described 085 * @param type Java class representing the property data type 086 */ 087 public DynaProperty(final String name, final Class<?> type) { 088 089 super(); 090 this.name = name; 091 this.type = type; 092 if (type != null && type.isArray()) { 093 this.contentType = type.getComponentType(); 094 } 095 096 } 097 098 /** 099 * Construct an indexed or mapped <code>DynaProperty</code> that supports (pseudo)-introspection 100 * of the content type. 101 * 102 * @param name Name of the property being described 103 * @param type Java class representing the property data type 104 * @param contentType Class that all indexed or mapped elements are instances of 105 */ 106 public DynaProperty(final String name, final Class<?> type, final Class<?> contentType) { 107 108 super(); 109 this.name = name; 110 this.type = type; 111 this.contentType = contentType; 112 113 } 114 115 // ------------------------------------------------------------- Properties 116 117 /** Property name */ 118 protected String name = null; 119 /** 120 * Get the name of this property. 121 * @return the name of the property 122 */ 123 public String getName() { 124 return (this.name); 125 } 126 127 /** Property type */ 128 protected transient Class<?> type = null; 129 /** 130 * <p>Gets the Java class representing the data type of the underlying property 131 * values.</p> 132 * 133 * <p>There are issues with serializing primitive class types on certain JVM versions 134 * (including java 1.3). 135 * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p> 136 * 137 * <p><strong>Please leave this field as <code>transient</code></strong></p> 138 * 139 * @return the property type 140 */ 141 public Class<?> getType() { 142 return (this.type); 143 } 144 145 146 /** The <em>(optional)</em> type of content elements for indexed <code>DynaProperty</code> */ 147 protected transient Class<?> contentType; 148 /** 149 * Gets the <em>(optional)</em> type of the indexed content for <code>DynaProperty</code>'s 150 * that support this feature. 151 * 152 * <p>There are issues with serializing primitive class types on certain JVM versions 153 * (including java 1.3). 154 * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p> 155 * 156 * @return the Class for the content type if this is an indexed <code>DynaProperty</code> 157 * and this feature is supported. Otherwise null. 158 */ 159 public Class<?> getContentType() { 160 return contentType; 161 } 162 163 // --------------------------------------------------------- Public Methods 164 165 166 /** 167 * Does this property represent an indexed value (ie an array or List)? 168 * 169 * @return <code>true</code> if the property is indexed (i.e. is a List or 170 * array), otherwise <code>false</code> 171 */ 172 public boolean isIndexed() { 173 174 if (type == null) { 175 return (false); 176 } else if (type.isArray()) { 177 return (true); 178 } else if (List.class.isAssignableFrom(type)) { 179 return (true); 180 } else { 181 return (false); 182 } 183 184 } 185 186 187 /** 188 * Does this property represent a mapped value (ie a Map)? 189 * 190 * @return <code>true</code> if the property is a Map 191 * otherwise <code>false</code> 192 */ 193 public boolean isMapped() { 194 195 if (type == null) { 196 return (false); 197 } else { 198 return (Map.class.isAssignableFrom(type)); 199 } 200 201 } 202 203 /** 204 * Checks this instance against the specified Object for equality. Overrides the 205 * default refererence test for equality provided by {@link java.lang.Object#equals(Object)} 206 * @param obj The object to compare to 207 * @return <code>true</code> if object is a dyna property with the same name 208 * type and content type, otherwise <code>false</code> 209 * @since 1.8.0 210 */ 211 @Override 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 @Override 235 public int hashCode() { 236 237 int result = 1; 238 239 result = result * 31 + ((name == null) ? 0 : name.hashCode()); 240 result = result * 31 + ((type == null) ? 0 : type.hashCode()); 241 result = result * 31 + ((contentType == null) ? 0 : contentType.hashCode()); 242 243 return result; 244 } 245 246 /** 247 * Return a String representation of this Object. 248 * @return a String representation of the dyna property 249 */ 250 @Override 251 public String toString() { 252 253 final StringBuilder sb = new StringBuilder("DynaProperty[name="); 254 sb.append(this.name); 255 sb.append(",type="); 256 sb.append(this.type); 257 if (isMapped() || isIndexed()) { 258 sb.append(" <").append(this.contentType).append(">"); 259 } 260 sb.append("]"); 261 return (sb.toString()); 262 263 } 264 265 // --------------------------------------------------------- Serialization helper methods 266 267 /** 268 * Writes this object safely. 269 * There are issues with serializing primitive class types on certain JVM versions 270 * (including java 1.3). 271 * This method provides a workaround. 272 */ 273 private void writeObject(final ObjectOutputStream out) throws IOException { 274 275 writeAnyClass(this.type,out); 276 277 if (isMapped() || isIndexed()) { 278 writeAnyClass(this.contentType,out); 279 } 280 281 // write out other values 282 out.defaultWriteObject(); 283 } 284 285 /** 286 * Write a class using safe encoding to workaround java 1.3 serialization bug. 287 */ 288 private void writeAnyClass(final Class<?> clazz, final ObjectOutputStream out) throws IOException { 289 // safely write out any class 290 int primitiveType = 0; 291 if (Boolean.TYPE.equals(clazz)) { 292 primitiveType = BOOLEAN_TYPE; 293 } else if (Byte.TYPE.equals(clazz)) { 294 primitiveType = BYTE_TYPE; 295 } else if (Character.TYPE.equals(clazz)) { 296 primitiveType = CHAR_TYPE; 297 } else if (Double.TYPE.equals(clazz)) { 298 primitiveType = DOUBLE_TYPE; 299 } else if (Float.TYPE.equals(clazz)) { 300 primitiveType = FLOAT_TYPE; 301 } else if (Integer.TYPE.equals(clazz)) { 302 primitiveType = INT_TYPE; 303 } else if (Long.TYPE.equals(clazz)) { 304 primitiveType = LONG_TYPE; 305 } else if (Short.TYPE.equals(clazz)) { 306 primitiveType = SHORT_TYPE; 307 } 308 309 if (primitiveType == 0) { 310 // then it's not a primitive type 311 out.writeBoolean(false); 312 out.writeObject(clazz); 313 } else { 314 // we'll write out a constant instead 315 out.writeBoolean(true); 316 out.writeInt(primitiveType); 317 } 318 } 319 320 /** 321 * Reads field values for this object safely. 322 * There are issues with serializing primitive class types on certain JVM versions 323 * (including java 1.3). 324 * This method provides a workaround. 325 * 326 * @throws StreamCorruptedException when the stream data values are outside expected range 327 */ 328 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 329 330 this.type = readAnyClass(in); 331 332 if (isMapped() || isIndexed()) { 333 this.contentType = readAnyClass(in); 334 } 335 336 // read other values 337 in.defaultReadObject(); 338 } 339 340 341 /** 342 * Reads a class using safe encoding to workaround java 1.3 serialization bug. 343 */ 344 private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException { 345 // read back type class safely 346 if (in.readBoolean()) { 347 // it's a type constant 348 switch (in.readInt()) { 349 350 case BOOLEAN_TYPE: return Boolean.TYPE; 351 case BYTE_TYPE: return Byte.TYPE; 352 case CHAR_TYPE: return Character.TYPE; 353 case DOUBLE_TYPE: return Double.TYPE; 354 case FLOAT_TYPE: return Float.TYPE; 355 case INT_TYPE: return Integer.TYPE; 356 case LONG_TYPE: return Long.TYPE; 357 case SHORT_TYPE: return Short.TYPE; 358 default: 359 // something's gone wrong 360 throw new StreamCorruptedException( 361 "Invalid primitive type. " 362 + "Check version of beanutils used to serialize is compatible."); 363 364 } 365 366 } else { 367 // it's another class 368 return ((Class<?>) in.readObject()); 369 } 370 } 371}