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 * https://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 018package org.apache.commons.beanutils; 019 020import java.io.IOException; 021import java.io.ObjectInputStream; 022import java.io.ObjectOutputStream; 023import java.io.Serializable; 024import java.io.StreamCorruptedException; 025import java.util.List; 026import java.util.Map; 027 028/** 029 * <p>The metadata describing an individual property of a DynaBean.</p> 030 * 031 * <p>The meta contains an <em>optional</em> content type property ({@link #getContentType}) 032 * for use by mapped and iterated properties. 033 * A mapped or iterated property may choose to indicate the type it expects. 034 * The DynaBean implementation may choose to enforce this type on its entries. 035 * Alternatively, an implementatin may choose to ignore this property. 036 * All keys for maps must be of type String so no meta data is needed for map keys.</p> 037 * 038 */ 039 040public class DynaProperty implements Serializable { 041 042 private static final long serialVersionUID = 1L; 043 /* 044 * There are issues with serializing primitive class types on certain JVM versions 045 * (including java 1.3). 046 * This class uses a custom serialization implementation that writes an integer 047 * for these primitive class. 048 * This list of constants are the ones used in serialization. 049 * If these values are changed, then older versions will no longer be read correctly 050 */ 051 private static final int BOOLEAN_TYPE = 1; 052 private static final int BYTE_TYPE = 2; 053 private static final int CHAR_TYPE = 3; 054 private static final int DOUBLE_TYPE = 4; 055 private static final int FLOAT_TYPE = 5; 056 private static final int INT_TYPE = 6; 057 private static final int LONG_TYPE = 7; 058 private static final int SHORT_TYPE = 8; 059 060 /** Property name */ 061 protected String name; 062 063 /** Property type */ 064 protected transient Class<?> type; 065 066 /** The <em>(optional)</em> type of content elements for indexed <code>DynaProperty</code> */ 067 protected transient Class<?> contentType; 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 * Construct a property of the specified data type. 081 * 082 * @param name Name of the property being described 083 * @param type Java class representing the property data type 084 */ 085 public DynaProperty(final String name, final Class<?> type) { 086 087 this.name = name; 088 this.type = type; 089 if (type != null && type.isArray()) { 090 this.contentType = type.getComponentType(); 091 } 092 093 } 094 095 /** 096 * Construct an indexed or mapped <code>DynaProperty</code> that supports (pseudo)-introspection 097 * of the content type. 098 * 099 * @param name Name of the property being described 100 * @param type Java class representing the property data type 101 * @param contentType Class that all indexed or mapped elements are instances of 102 */ 103 public DynaProperty(final String name, final Class<?> type, final Class<?> contentType) { 104 105 this.name = name; 106 this.type = type; 107 this.contentType = contentType; 108 109 } 110 /** 111 * Checks this instance against the specified Object for equality. Overrides the 112 * default refererence test for equality provided by {@link java.lang.Object#equals(Object)} 113 * @param obj The object to compare to 114 * @return <code>true</code> if object is a dyna property with the same name 115 * type and content type, otherwise <code>false</code> 116 * @since 1.8.0 117 */ 118 @Override 119 public boolean equals(final Object obj) { 120 121 boolean result = false; 122 123 result = obj == this; 124 125 if (!result && obj instanceof DynaProperty) { 126 final DynaProperty that = (DynaProperty) obj; 127 result = 128 (this.name == null ? that.name == null : this.name.equals(that.name)) && 129 (this.type == null ? that.type == null : this.type.equals(that.type)) && 130 (this.contentType == null ? that.contentType == null : this.contentType.equals(that.contentType)); 131 } 132 133 return result; 134 } 135 136 /** 137 * Gets the <em>(optional)</em> type of the indexed content for <code>DynaProperty</code>'s 138 * that support this feature. 139 * 140 * <p>There are issues with serializing primitive class types on certain JVM versions 141 * (including java 1.3). 142 * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p> 143 * 144 * @return the Class for the content type if this is an indexed <code>DynaProperty</code> 145 * and this feature is supported. Otherwise null. 146 */ 147 public Class<?> getContentType() { 148 return contentType; 149 } 150 /** 151 * Get the name of this property. 152 * @return the name of the property 153 */ 154 public String getName() { 155 return this.name; 156 } 157 158 /** 159 * <p>Gets the Java class representing the data type of the underlying property 160 * values.</p> 161 * 162 * <p>There are issues with serializing primitive class types on certain JVM versions 163 * (including java 1.3). 164 * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p> 165 * 166 * <p><strong>Please leave this field as <code>transient</code></strong></p> 167 * 168 * @return the property type 169 */ 170 public Class<?> getType() { 171 return this.type; 172 } 173 174 /** 175 * @return the hashcode for this dyna property 176 * @see java.lang.Object#hashCode 177 * @since 1.8.0 178 */ 179 @Override 180 public int hashCode() { 181 182 int result = 1; 183 184 result = result * 31 + (name == null ? 0 : name.hashCode()); 185 result = result * 31 + (type == null ? 0 : type.hashCode()); 186 return result * 31 + (contentType == null ? 0 : contentType.hashCode()); 187 } 188 189 /** 190 * Does this property represent an indexed value (ie an array or List)? 191 * 192 * @return <code>true</code> if the property is indexed (i.e. is a List or 193 * array), otherwise <code>false</code> 194 */ 195 public boolean isIndexed() { 196 197 if (type == null) { 198 return false; 199 } 200 if (type.isArray() || List.class.isAssignableFrom(type)) { 201 return true; 202 } 203 return false; 204 205 } 206 207 /** 208 * Does this property represent a mapped value (ie a Map)? 209 * 210 * @return <code>true</code> if the property is a Map 211 * otherwise <code>false</code> 212 */ 213 public boolean isMapped() { 214 215 if (type == null) { 216 return false; 217 } 218 return Map.class.isAssignableFrom(type); 219 220 } 221 222 /** 223 * Reads a class using safe encoding to workaround java 1.3 serialization bug. 224 */ 225 private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException { 226 // read back type class safely 227 if (!in.readBoolean()) { 228 // it's another class 229 return (Class<?>) in.readObject(); 230 } 231 // it's a type constant 232 switch (in.readInt()) { 233 234 case BOOLEAN_TYPE: return Boolean.TYPE; 235 case BYTE_TYPE: return Byte.TYPE; 236 case CHAR_TYPE: return Character.TYPE; 237 case DOUBLE_TYPE: return Double.TYPE; 238 case FLOAT_TYPE: return Float.TYPE; 239 case INT_TYPE: return Integer.TYPE; 240 case LONG_TYPE: return Long.TYPE; 241 case SHORT_TYPE: return Short.TYPE; 242 default: 243 // something's gone wrong 244 throw new StreamCorruptedException( 245 "Invalid primitive type. " 246 + "Check version of beanutils used to serialize is compatible."); 247 248 } 249 } 250 251 /** 252 * Reads field values for this object safely. There are issues with serializing primitive class types on certain JVM versions (including java 1.3). This 253 * method provides a workaround. 254 * 255 * @param in the content source. 256 * @throws IOException when the stream data values are outside expected range 257 * @throws ClassNotFoundException Class of a serialized object cannot be found. 258 */ 259 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 260 this.type = readAnyClass(in); 261 if (isMapped() || isIndexed()) { 262 this.contentType = readAnyClass(in); 263 } 264 // read other values 265 in.defaultReadObject(); 266 } 267 268 /** 269 * Return a String representation of this Object. 270 * 271 * @return a String representation of the dyna property 272 */ 273 @Override 274 public String toString() { 275 final StringBuilder sb = new StringBuilder("DynaProperty[name="); 276 sb.append(this.name); 277 sb.append(",type="); 278 sb.append(this.type); 279 if (isMapped() || isIndexed()) { 280 sb.append(" <").append(this.contentType).append(">"); 281 } 282 sb.append("]"); 283 return sb.toString(); 284 285 } 286 287 /** 288 * Writes a class using safe encoding to workaround java 1.3 serialization bug. 289 * 290 * @throws IOException if I/O errors occur while writing to the underlying stream. 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 if (primitiveType == 0) { 313 // then it's not a primitive type 314 out.writeBoolean(false); 315 out.writeObject(clazz); 316 } else { 317 // we'll write out a constant instead 318 out.writeBoolean(true); 319 out.writeInt(primitiveType); 320 } 321 } 322 323 /** 324 * Writes this object safely. There are issues with serializing primitive class types on certain JVM versions (including java 1.3). This method provides a 325 * workaround. 326 * 327 * @param out Where to write. 328 * @throws IOException if I/O errors occur while writing to the underlying stream. 329 */ 330 private void writeObject(final ObjectOutputStream out) throws IOException { 331 writeAnyClass(this.type,out); 332 if (isMapped() || isIndexed()) { 333 writeAnyClass(this.contentType,out); 334 } 335 // write out other values 336 out.defaultWriteObject(); 337 } 338}