1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.beanutils;
  17. import java.io.IOException;
  18. import java.io.Serializable;
  19. import java.io.ObjectOutputStream;
  20. import java.io.ObjectInputStream;
  21. import java.io.StreamCorruptedException;
  22. import java.util.List;
  23. import java.util.Map;
  24. /**
  25. * <p>The metadata describing an individual property of a DynaBean.</p>
  26. *
  27. * <p>The meta contains an <em>optional</em> content type property ({@link #getContentType})
  28. * for use by mapped and iterated properties.
  29. * A mapped or iterated property may choose to indicate the type it expects.
  30. * The DynaBean implementation may choose to enforce this type on its entries.
  31. * Alternatively, an implementatin may choose to ignore this property.
  32. * All keys for maps must be of type String so no meta data is needed for map keys.</p>
  33. *
  34. * @author Craig R. McClanahan
  35. * @version $Revision: 1.13 $ $Date: 2004/02/28 13:18:33 $
  36. */
  37. public class DynaProperty implements Serializable {
  38. // ----------------------------------------------------------- Constants
  39. /*
  40. * There are issues with serializing primitive class types on certain JVM versions
  41. * (including java 1.3).
  42. * This class uses a custom serialization implementation that writes an integer
  43. * for these primitive class.
  44. * This list of constants are the ones used in serialization.
  45. * If these values are changed, then older versions will no longer be read correctly
  46. */
  47. private static final int BOOLEAN_TYPE = 1;
  48. private static final int BYTE_TYPE = 2;
  49. private static final int CHAR_TYPE = 3;
  50. private static final int DOUBLE_TYPE = 4;
  51. private static final int FLOAT_TYPE = 5;
  52. private static final int INT_TYPE = 6;
  53. private static final int LONG_TYPE = 7;
  54. private static final int SHORT_TYPE = 8;
  55. // ----------------------------------------------------------- Constructors
  56. /**
  57. * Construct a property that accepts any data type.
  58. *
  59. * @param name Name of the property being described
  60. */
  61. public DynaProperty(String name) {
  62. this(name, Object.class);
  63. }
  64. /**
  65. * Construct a property of the specified data type.
  66. *
  67. * @param name Name of the property being described
  68. * @param type Java class representing the property data type
  69. */
  70. public DynaProperty(String name, Class type) {
  71. super();
  72. this.name = name;
  73. this.type = type;
  74. }
  75. /**
  76. * Construct an indexed or mapped <code>DynaProperty</code> that supports (pseudo)-introspection
  77. * of the content type.
  78. *
  79. * @param name Name of the property being described
  80. * @param type Java class representing the property data type
  81. * @param contentType Class that all indexed or mapped elements are instances of
  82. */
  83. public DynaProperty(String name, Class type, Class contentType) {
  84. super();
  85. this.name = name;
  86. this.type = type;
  87. this.contentType = contentType;
  88. }
  89. // ------------------------------------------------------------- Properties
  90. /** Property name */
  91. protected String name = null;
  92. /**
  93. * Get the name of this property.
  94. */
  95. public String getName() {
  96. return (this.name);
  97. }
  98. /** Property type */
  99. protected transient Class type = null;
  100. /**
  101. * <p>Gets the Java class representing the data type of the underlying property
  102. * values.</p>
  103. *
  104. * <p>There are issues with serializing primitive class types on certain JVM versions
  105. * (including java 1.3).
  106. * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
  107. *
  108. * <p><strong>Please leave this field as <code>transient</code></strong></p>
  109. */
  110. public Class getType() {
  111. return (this.type);
  112. }
  113. /** The <em>(optional)</em> type of content elements for indexed <code>DynaProperty</code> */
  114. protected transient Class contentType;
  115. /**
  116. * Gets the <em>(optional)</em> type of the indexed content for <code>DynaProperty</code>'s
  117. * that support this feature.
  118. *
  119. * <p>There are issues with serializing primitive class types on certain JVM versions
  120. * (including java 1.3).
  121. * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
  122. *
  123. * @return the Class for the content type if this is an indexed <code>DynaProperty</code>
  124. * and this feature is supported. Otherwise null.
  125. */
  126. public Class getContentType() {
  127. return contentType;
  128. }
  129. // --------------------------------------------------------- Public Methods
  130. /**
  131. * Does this property represent an indexed value (ie an array or List)?
  132. */
  133. public boolean isIndexed() {
  134. if (type == null) {
  135. return (false);
  136. } else if (type.isArray()) {
  137. return (true);
  138. } else if (List.class.isAssignableFrom(type)) {
  139. return (true);
  140. } else {
  141. return (false);
  142. }
  143. }
  144. /**
  145. * Does this property represent a mapped value (ie a Map)?
  146. */
  147. public boolean isMapped() {
  148. if (type == null) {
  149. return (false);
  150. } else {
  151. return (Map.class.isAssignableFrom(type));
  152. }
  153. }
  154. /**
  155. * Return a String representation of this Object.
  156. */
  157. public String toString() {
  158. StringBuffer sb = new StringBuffer("DynaProperty[name=");
  159. sb.append(this.name);
  160. sb.append(",type=");
  161. sb.append(this.type);
  162. if (isMapped() || isIndexed()) {
  163. sb.append(" <").append(this.contentType).append(">");
  164. }
  165. sb.append("]");
  166. return (sb.toString());
  167. }
  168. // --------------------------------------------------------- Serialization helper methods
  169. /**
  170. * Writes this object safely.
  171. * There are issues with serializing primitive class types on certain JVM versions
  172. * (including java 1.3).
  173. * This method provides a workaround.
  174. */
  175. private void writeObject(ObjectOutputStream out) throws IOException {
  176. writeAnyClass(this.type,out);
  177. if (isMapped() || isIndexed()) {
  178. writeAnyClass(this.contentType,out);
  179. }
  180. // write out other values
  181. out.defaultWriteObject();
  182. }
  183. /**
  184. * Write a class using safe encoding to workaround java 1.3 serialization bug.
  185. */
  186. private void writeAnyClass(Class clazz, ObjectOutputStream out) throws IOException {
  187. // safely write out any class
  188. int primitiveType = 0;
  189. if (Boolean.TYPE.equals(clazz)) {
  190. primitiveType = BOOLEAN_TYPE;
  191. } else if (Byte.TYPE.equals(clazz)) {
  192. primitiveType = BYTE_TYPE;
  193. } else if (Character.TYPE.equals(clazz)) {
  194. primitiveType = CHAR_TYPE;
  195. } else if (Double.TYPE.equals(clazz)) {
  196. primitiveType = DOUBLE_TYPE;
  197. } else if (Float.TYPE.equals(clazz)) {
  198. primitiveType = FLOAT_TYPE;
  199. } else if (Integer.TYPE.equals(clazz)) {
  200. primitiveType = INT_TYPE;
  201. } else if (Long.TYPE.equals(clazz)) {
  202. primitiveType = LONG_TYPE;
  203. } else if (Short.TYPE.equals(clazz)) {
  204. primitiveType = SHORT_TYPE;
  205. }
  206. if (primitiveType == 0) {
  207. // then it's not a primitive type
  208. out.writeBoolean(false);
  209. out.writeObject(clazz);
  210. } else {
  211. // we'll write out a constant instead
  212. out.writeBoolean(true);
  213. out.writeInt(primitiveType);
  214. }
  215. }
  216. /**
  217. * Reads field values for this object safely.
  218. * There are issues with serializing primitive class types on certain JVM versions
  219. * (including java 1.3).
  220. * This method provides a workaround.
  221. *
  222. * @throws StreamCorruptedException when the stream data values are outside expected range
  223. */
  224. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  225. this.type = readAnyClass(in);
  226. if (isMapped() || isIndexed()) {
  227. this.contentType = readAnyClass(in);
  228. }
  229. // read other values
  230. in.defaultReadObject();
  231. }
  232. /**
  233. * Reads a class using safe encoding to workaround java 1.3 serialization bug.
  234. */
  235. private Class readAnyClass(ObjectInputStream in) throws IOException, ClassNotFoundException {
  236. // read back type class safely
  237. if (in.readBoolean()) {
  238. // it's a type constant
  239. switch (in.readInt()) {
  240. case BOOLEAN_TYPE: return Boolean.TYPE;
  241. case BYTE_TYPE: return Byte.TYPE;
  242. case CHAR_TYPE: return Character.TYPE;
  243. case DOUBLE_TYPE: return Double.TYPE;
  244. case FLOAT_TYPE: return Float.TYPE;
  245. case INT_TYPE: return Integer.TYPE;
  246. case LONG_TYPE: return Long.TYPE;
  247. case SHORT_TYPE: return Short.TYPE;
  248. default:
  249. // something's gone wrong
  250. throw new StreamCorruptedException(
  251. "Invalid primitive type. "
  252. + "Check version of beanutils used to serialize is compatible.");
  253. }
  254. } else {
  255. // it's another class
  256. return ((Class) in.readObject());
  257. }
  258. }
  259. }