1. /*
  2. * @(#)PropertyDescriptor.java 1.47 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.beans;
  8. import java.lang.reflect.*;
  9. /**
  10. * A PropertyDescriptor describes one property that a Java Bean
  11. * exports via a pair of accessor methods.
  12. */
  13. public class PropertyDescriptor extends FeatureDescriptor {
  14. /**
  15. * Constructs a PropertyDescriptor for a property that follows
  16. * the standard Java convention by having getFoo and setFoo
  17. * accessor methods. Thus if the argument name is "fred", it will
  18. * assume that the writer method is "setFred" and the writer method
  19. * is "getFred" (or "isFred" for a boolean property). Note that the
  20. * property name should start with a lower case character, which will
  21. * be capitalized in the method names.
  22. *
  23. * @param propertyName The programmatic name of the property.
  24. * @param beanClass The Class object for the target bean. For
  25. * example sun.beans.OurButton.class.
  26. * @exception IntrospectionException if an exception occurs during
  27. * introspection.
  28. */
  29. public PropertyDescriptor(String propertyName, Class beanClass)
  30. throws IntrospectionException {
  31. if (propertyName == null || propertyName.length() == 0) {
  32. throw new IntrospectionException("bad property name");
  33. }
  34. setName(propertyName);
  35. String base = capitalize(propertyName);
  36. writeMethod = Introspector.findMethod(beanClass, "set" + base, 1);
  37. // If it's a boolean property check for an "isFoo" first.
  38. if (writeMethod.getParameterTypes()[0] == Boolean.TYPE) {
  39. try {
  40. readMethod = Introspector.findMethod(beanClass, "is" + base, 0);
  41. } catch (Exception ex) {
  42. }
  43. }
  44. if (readMethod == null) {
  45. readMethod = Introspector.findMethod(beanClass, "get" + base, 0);
  46. }
  47. findPropertyType();
  48. }
  49. /**
  50. * This constructor takes the name of a simple property, and method
  51. * names for reading and writing the property.
  52. *
  53. * @param propertyName The programmatic name of the property.
  54. * @param beanClass The Class object for the target bean. For
  55. * example sun.beans.OurButton.class.
  56. * @param getterName The name of the method used for reading the property
  57. * value. May be null if the property is write-only.
  58. * @param setterName The name of the method used for writing the property
  59. * value. May be null if the property is read-only.
  60. * @exception IntrospectionException if an exception occurs during
  61. * introspection.
  62. */
  63. public PropertyDescriptor(String propertyName, Class beanClass,
  64. String getterName, String setterName)
  65. throws IntrospectionException {
  66. if (propertyName == null || propertyName.length() == 0) {
  67. throw new IntrospectionException("bad property name");
  68. }
  69. setName(propertyName);
  70. readMethod = Introspector.findMethod(beanClass, getterName, 0);
  71. writeMethod = Introspector.findMethod(beanClass, setterName, 1);
  72. findPropertyType();
  73. }
  74. /**
  75. * This constructor takes the name of a simple property, and Method
  76. * objects for reading and writing the property.
  77. *
  78. * @param propertyName The programmatic name of the property.
  79. * @param getter The method used for reading the property value.
  80. * May be null if the property is write-only.
  81. * @param setter The method used for writing the property value.
  82. * May be null if the property is read-only.
  83. * @exception IntrospectionException if an exception occurs during
  84. * introspection.
  85. */
  86. public PropertyDescriptor(String propertyName, Method getter, Method setter)
  87. throws IntrospectionException {
  88. if (propertyName == null || propertyName.length() == 0) {
  89. throw new IntrospectionException("bad property name");
  90. }
  91. setName(propertyName);
  92. readMethod = getter;
  93. writeMethod = setter;
  94. findPropertyType();
  95. }
  96. /**
  97. * Gets the Class object for the property.
  98. *
  99. * @return The Java type info for the property. Note that
  100. * the "Class" object may describe a built-in Java type such as "int".
  101. * The result may be "null" if this is an indexed property that
  102. * does not support non-indexed access.
  103. * <p>
  104. * This is the type that will be returned by the ReadMethod.
  105. */
  106. public Class getPropertyType() {
  107. return propertyType;
  108. }
  109. /**
  110. * Gets the method that should be used to read the property value.
  111. *
  112. * @return The method that should be used to read the property value.
  113. * May return null if the property can't be read.
  114. */
  115. public Method getReadMethod() {
  116. return readMethod;
  117. }
  118. /**
  119. * Sets the method that should be used to read the property value.
  120. *
  121. * @param getter The new getter method.
  122. */
  123. public void setReadMethod(Method getter)
  124. throws IntrospectionException {
  125. readMethod = getter;
  126. findPropertyType();
  127. }
  128. /**
  129. * Gets the method that should be used to write the property value.
  130. *
  131. * @return The method that should be used to write the property value.
  132. * May return null if the property can't be written.
  133. */
  134. public Method getWriteMethod() {
  135. return writeMethod;
  136. }
  137. /**
  138. * Sets the method that should be used to write the property value.
  139. *
  140. * @param setter The new setter method.
  141. */
  142. public void setWriteMethod(Method setter)
  143. throws IntrospectionException {
  144. writeMethod = setter;
  145. findPropertyType();
  146. }
  147. /**
  148. * Updates to "bound" properties will cause a "PropertyChange" event to
  149. * get fired when the property is changed.
  150. *
  151. * @return True if this is a bound property.
  152. */
  153. public boolean isBound() {
  154. return bound;
  155. }
  156. /**
  157. * Updates to "bound" properties will cause a "PropertyChange" event to
  158. * get fired when the property is changed.
  159. *
  160. * @param bound True if this is a bound property.
  161. */
  162. public void setBound(boolean bound) {
  163. this.bound = bound;
  164. }
  165. /**
  166. * Attempted updates to "Constrained" properties will cause a "VetoableChange"
  167. * event to get fired when the property is changed.
  168. *
  169. * @return True if this is a constrained property.
  170. */
  171. public boolean isConstrained() {
  172. return constrained;
  173. }
  174. /**
  175. * Attempted updates to "Constrained" properties will cause a "VetoableChange"
  176. * event to get fired when the property is changed.
  177. *
  178. * @param constrained True if this is a constrained property.
  179. */
  180. public void setConstrained(boolean constrained) {
  181. this.constrained = constrained;
  182. }
  183. /**
  184. * Normally PropertyEditors will be found using the PropertyEditorManager.
  185. * However if for some reason you want to associate a particular
  186. * PropertyEditor with a given property, then you can do it with
  187. * this method.
  188. *
  189. * @param propertyEditorClass The Class for the desired PropertyEditor.
  190. */
  191. public void setPropertyEditorClass(Class propertyEditorClass) {
  192. this.propertyEditorClass = propertyEditorClass;
  193. }
  194. /**
  195. * Gets any explicit PropertyEditor Class that has been registered
  196. * for this property.
  197. *
  198. * @return Any explicit PropertyEditor Class that has been registered
  199. * for this property. Normally this will return "null",
  200. * indicating that no special editor has been registered,
  201. * so the PropertyEditorManager should be used to locate
  202. * a suitable PropertyEditor.
  203. */
  204. public Class getPropertyEditorClass() {
  205. return propertyEditorClass;
  206. }
  207. /*
  208. * Package-private constructor.
  209. * Merge two property descriptors. Where they conflict, give the
  210. * second argument (y) priority over the first argument (x).
  211. *
  212. * @param x The first (lower priority) PropertyDescriptor
  213. * @param y The second (higher priority) PropertyDescriptor
  214. */
  215. PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
  216. super(x,y);
  217. // Figure out the merged read method.
  218. Method xr = x.readMethod;
  219. Method yr = y.readMethod;
  220. readMethod = xr;
  221. // Normally give priority to y's readMethod.
  222. if (yr != null) {
  223. readMethod = yr;
  224. }
  225. // However, if both x and y reference read methods in the same class,
  226. // give priority to a boolean "is" method over a boolean "get" method.
  227. if (xr != null && yr != null &&
  228. xr.getDeclaringClass() == yr.getDeclaringClass() &&
  229. xr.getReturnType() == boolean.class &&
  230. yr.getReturnType() == boolean.class &&
  231. xr.getName().indexOf("is") == 0 &&
  232. yr.getName().indexOf("get") == 0) {
  233. readMethod = xr;
  234. }
  235. writeMethod = x.writeMethod;
  236. if (y.writeMethod != null) {
  237. writeMethod = y.writeMethod;
  238. }
  239. propertyEditorClass = x.propertyEditorClass;
  240. if (y.propertyEditorClass != null) {
  241. propertyEditorClass = y.propertyEditorClass;
  242. }
  243. bound = x.bound | y.bound;
  244. constrained = x.constrained | y.constrained;
  245. try {
  246. findPropertyType();
  247. } catch (IntrospectionException ex) {
  248. // Given we're merging two valid PDs, this "should never happen".
  249. throw new Error("PropertyDescriptor: internal error while merging PDs");
  250. }
  251. }
  252. /*
  253. * Package-private dup constructor.
  254. * This must isolate the new object from any changes to the old object.
  255. */
  256. PropertyDescriptor(PropertyDescriptor old) {
  257. super(old);
  258. readMethod = old.readMethod;;
  259. writeMethod = old.writeMethod;
  260. propertyEditorClass = old.propertyEditorClass;
  261. bound = old.bound;
  262. constrained = old.constrained;
  263. propertyType = old.propertyType;
  264. }
  265. private void findPropertyType() throws IntrospectionException {
  266. try {
  267. propertyType = null;
  268. if (readMethod != null) {
  269. if (readMethod.getParameterTypes().length != 0) {
  270. throw new IntrospectionException("bad read method arg count");
  271. }
  272. propertyType = readMethod.getReturnType();
  273. if (propertyType == Void.TYPE) {
  274. throw new IntrospectionException("read method " +
  275. readMethod.getName() + " returns void");
  276. }
  277. }
  278. if (writeMethod != null) {
  279. Class params[] = writeMethod.getParameterTypes();
  280. if (params.length != 1) {
  281. throw new IntrospectionException("bad write method arg count");
  282. }
  283. if (propertyType != null && propertyType != params[0]) {
  284. throw new IntrospectionException("type mismatch between read and write methods");
  285. }
  286. propertyType = params[0];
  287. }
  288. } catch (IntrospectionException ex) {
  289. throw ex;
  290. }
  291. }
  292. static String capitalize(String s) {
  293. if (s.length() == 0) {
  294. return s;
  295. }
  296. char chars[] = s.toCharArray();
  297. chars[0] = Character.toUpperCase(chars[0]);
  298. return new String(chars);
  299. }
  300. private Class propertyType;
  301. private Method readMethod;
  302. private Method writeMethod;
  303. private boolean bound;
  304. private boolean constrained;
  305. private Class propertyEditorClass;
  306. }