1. /*
  2. * @(#)PropertyDescriptor.java 1.57 03/01/23
  3. *
  4. * Copyright 2003 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. private Class propertyType;
  15. private Method readMethod;
  16. private Method writeMethod;
  17. private boolean bound;
  18. private boolean constrained;
  19. private Class propertyEditorClass;
  20. /**
  21. * Constructs a PropertyDescriptor for a property that follows
  22. * the standard Java convention by having getFoo and setFoo
  23. * accessor methods. Thus if the argument name is "fred", it will
  24. * assume that the writer method is "setFred" and the reader method
  25. * is "getFred" (or "isFred" for a boolean property). Note that the
  26. * property name should start with a lower case character, which will
  27. * be capitalized in the method names.
  28. *
  29. * @param propertyName The programmatic name of the property.
  30. * @param beanClass The Class object for the target bean. For
  31. * example sun.beans.OurButton.class.
  32. * @exception IntrospectionException if an exception occurs during
  33. * introspection.
  34. */
  35. public PropertyDescriptor(String propertyName, Class beanClass)
  36. throws IntrospectionException {
  37. if (propertyName == null || propertyName.length() == 0) {
  38. throw new IntrospectionException("bad property name");
  39. }
  40. setName(propertyName);
  41. String base = capitalize(propertyName);
  42. // Since there can be multiple setter methods but only one getter
  43. // method, find the getter method first so that you know what the
  44. // property type is. For booleans, there can be "is" and "get"
  45. // methods. If an "is" method exists, this is the official
  46. // reader method so look for this one first.
  47. try {
  48. readMethod = Introspector.findMethod(beanClass, "is" + base, 0);
  49. } catch (Exception getterExc) {
  50. // no "is" method, so look for a "get" method.
  51. readMethod = Introspector.findMethod(beanClass, "get" + base, 0);
  52. }
  53. Class params[] = { readMethod.getReturnType() };
  54. writeMethod = Introspector.findMethod(beanClass, "set" + base, 1,
  55. params);
  56. propertyType = findPropertyType(readMethod, writeMethod);
  57. }
  58. /**
  59. * This constructor takes the name of a simple property, and method
  60. * names for reading and writing the property.
  61. *
  62. * @param propertyName The programmatic name of the property.
  63. * @param beanClass The Class object for the target bean. For
  64. * example sun.beans.OurButton.class.
  65. * @param getterName The name of the method used for reading the property
  66. * value. May be null if the property is write-only.
  67. * @param setterName The name of the method used for writing the property
  68. * value. May be null if the property is read-only.
  69. * @exception IntrospectionException if an exception occurs during
  70. * introspection.
  71. */
  72. public PropertyDescriptor(String propertyName, Class beanClass,
  73. String getterName, String setterName)
  74. throws IntrospectionException {
  75. if (propertyName == null || propertyName.length() == 0) {
  76. throw new IntrospectionException("bad property name");
  77. }
  78. setName(propertyName);
  79. readMethod = Introspector.findMethod(beanClass, getterName, 0);
  80. if (readMethod != null) {
  81. Class params[] = { readMethod.getReturnType() };
  82. writeMethod = Introspector.findMethod(beanClass, setterName, 1,
  83. params);
  84. } else {
  85. writeMethod = Introspector.findMethod(beanClass, setterName, 1);
  86. }
  87. propertyType = findPropertyType(readMethod, writeMethod);
  88. }
  89. /**
  90. * This constructor takes the name of a simple property, and Method
  91. * objects for reading and writing the property.
  92. *
  93. * @param propertyName The programmatic name of the property.
  94. * @param getter The method used for reading the property value.
  95. * May be null if the property is write-only.
  96. * @param setter The method used for writing the property value.
  97. * May be null if the property is read-only.
  98. * @exception IntrospectionException if an exception occurs during
  99. * introspection.
  100. */
  101. public PropertyDescriptor(String propertyName, Method getter, Method setter)
  102. throws IntrospectionException {
  103. if (propertyName == null || propertyName.length() == 0) {
  104. throw new IntrospectionException("bad property name");
  105. }
  106. setName(propertyName);
  107. readMethod = getter;
  108. writeMethod = setter;
  109. propertyType = findPropertyType(readMethod, writeMethod);
  110. }
  111. /**
  112. * Gets the Class object for the property.
  113. *
  114. * @return The Java type info for the property. Note that
  115. * the "Class" object may describe a built-in Java type such as "int".
  116. * The result may be "null" if this is an indexed property that
  117. * does not support non-indexed access.
  118. * <p>
  119. * This is the type that will be returned by the ReadMethod.
  120. */
  121. public Class getPropertyType() {
  122. return propertyType;
  123. }
  124. /**
  125. * Gets the method that should be used to read the property value.
  126. *
  127. * @return The method that should be used to read the property value.
  128. * May return null if the property can't be read.
  129. */
  130. public Method getReadMethod() {
  131. return readMethod;
  132. }
  133. /**
  134. * Sets the method that should be used to read the property value.
  135. *
  136. * @param getter The new getter method.
  137. */
  138. public void setReadMethod(Method getter)
  139. throws IntrospectionException {
  140. readMethod = getter;
  141. propertyType = findPropertyType(readMethod, writeMethod);
  142. }
  143. /**
  144. * Gets the method that should be used to write the property value.
  145. *
  146. * @return The method that should be used to write the property value.
  147. * May return null if the property can't be written.
  148. */
  149. public Method getWriteMethod() {
  150. return writeMethod;
  151. }
  152. /**
  153. * Sets the method that should be used to write the property value.
  154. *
  155. * @param setter The new setter method.
  156. */
  157. public void setWriteMethod(Method setter)
  158. throws IntrospectionException {
  159. writeMethod = setter;
  160. propertyType = findPropertyType(readMethod, writeMethod);
  161. }
  162. /**
  163. * Updates to "bound" properties will cause a "PropertyChange" event to
  164. * get fired when the property is changed.
  165. *
  166. * @return True if this is a bound property.
  167. */
  168. public boolean isBound() {
  169. return bound;
  170. }
  171. /**
  172. * Updates to "bound" properties will cause a "PropertyChange" event to
  173. * get fired when the property is changed.
  174. *
  175. * @param bound True if this is a bound property.
  176. */
  177. public void setBound(boolean bound) {
  178. this.bound = bound;
  179. }
  180. /**
  181. * Attempted updates to "Constrained" properties will cause a "VetoableChange"
  182. * event to get fired when the property is changed.
  183. *
  184. * @return True if this is a constrained property.
  185. */
  186. public boolean isConstrained() {
  187. return constrained;
  188. }
  189. /**
  190. * Attempted updates to "Constrained" properties will cause a "VetoableChange"
  191. * event to get fired when the property is changed.
  192. *
  193. * @param constrained True if this is a constrained property.
  194. */
  195. public void setConstrained(boolean constrained) {
  196. this.constrained = constrained;
  197. }
  198. /**
  199. * Normally PropertyEditors will be found using the PropertyEditorManager.
  200. * However if for some reason you want to associate a particular
  201. * PropertyEditor with a given property, then you can do it with
  202. * this method.
  203. *
  204. * @param propertyEditorClass The Class for the desired PropertyEditor.
  205. */
  206. public void setPropertyEditorClass(Class propertyEditorClass) {
  207. this.propertyEditorClass = propertyEditorClass;
  208. }
  209. /**
  210. * Gets any explicit PropertyEditor Class that has been registered
  211. * for this property.
  212. *
  213. * @return Any explicit PropertyEditor Class that has been registered
  214. * for this property. Normally this will return "null",
  215. * indicating that no special editor has been registered,
  216. * so the PropertyEditorManager should be used to locate
  217. * a suitable PropertyEditor.
  218. */
  219. public Class getPropertyEditorClass() {
  220. return propertyEditorClass;
  221. }
  222. /**
  223. * Compares this <code>PropertyDescriptor</code> against the specified object.
  224. * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s
  225. * are the same if the read, write, property types, property editor and
  226. * flags are equivalent.
  227. *
  228. * @since 1.4
  229. */
  230. public boolean equals(Object obj) {
  231. if (obj != null && obj instanceof PropertyDescriptor) {
  232. PropertyDescriptor other = (PropertyDescriptor)obj;
  233. Method otherReadMethod = other.getReadMethod();
  234. Method otherWriteMethod = other.getWriteMethod();
  235. if (!compareMethods(readMethod, otherReadMethod)) {
  236. return false;
  237. }
  238. if (!compareMethods(writeMethod, otherWriteMethod)) {
  239. return false;
  240. }
  241. if (propertyType == other.getPropertyType() &&
  242. propertyEditorClass == other.getPropertyEditorClass() &&
  243. bound == other.isBound() && constrained == other.isConstrained()) {
  244. return true;
  245. }
  246. }
  247. return false;
  248. }
  249. /**
  250. * Package private helper method for Descriptor .equals methods.
  251. *
  252. * @param a first method to compare
  253. * @param b second method to compare
  254. * @return boolean to indicate that the methods are equivalent
  255. */
  256. boolean compareMethods(Method a, Method b) {
  257. // Note: perhaps this should be a protected method in FeatureDescriptor
  258. if ((a == null) != (b == null)) {
  259. return false;
  260. }
  261. if (a != null && b != null) {
  262. if (!a.equals(b)) {
  263. return false;
  264. }
  265. }
  266. return true;
  267. }
  268. /**
  269. * Package-private constructor.
  270. * Merge two property descriptors. Where they conflict, give the
  271. * second argument (y) priority over the first argument (x).
  272. *
  273. * @param x The first (lower priority) PropertyDescriptor
  274. * @param y The second (higher priority) PropertyDescriptor
  275. */
  276. PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
  277. super(x,y);
  278. // Figure out the merged read method.
  279. Method xr = x.readMethod;
  280. Method yr = y.readMethod;
  281. readMethod = xr;
  282. // Normally give priority to y's readMethod.
  283. if (yr != null) {
  284. readMethod = yr;
  285. }
  286. // However, if both x and y reference read methods in the same class,
  287. // give priority to a boolean "is" method over a boolean "get" method.
  288. if (xr != null && yr != null &&
  289. xr.getDeclaringClass() == yr.getDeclaringClass() &&
  290. xr.getReturnType() == boolean.class &&
  291. yr.getReturnType() == boolean.class &&
  292. xr.getName().indexOf("is") == 0 &&
  293. yr.getName().indexOf("get") == 0) {
  294. readMethod = xr;
  295. }
  296. writeMethod = x.writeMethod;
  297. if (y.writeMethod != null) {
  298. writeMethod = y.writeMethod;
  299. }
  300. propertyEditorClass = x.propertyEditorClass;
  301. if (y.propertyEditorClass != null) {
  302. propertyEditorClass = y.propertyEditorClass;
  303. }
  304. bound = x.bound | y.bound;
  305. constrained = x.constrained | y.constrained;
  306. try {
  307. propertyType = findPropertyType(readMethod, writeMethod);
  308. } catch (IntrospectionException ex) {
  309. // Given we're merging two valid PDs, this "should never happen".
  310. throw new Error("PropertyDescriptor: internal error while merging PDs: " + ex.getMessage());
  311. }
  312. }
  313. /*
  314. * Package-private dup constructor.
  315. * This must isolate the new object from any changes to the old object.
  316. */
  317. PropertyDescriptor(PropertyDescriptor old) {
  318. super(old);
  319. readMethod = old.readMethod;;
  320. writeMethod = old.writeMethod;
  321. propertyEditorClass = old.propertyEditorClass;
  322. bound = old.bound;
  323. constrained = old.constrained;
  324. propertyType = old.propertyType;
  325. }
  326. /**
  327. * Returns the property type that corresponds to the read and write method.
  328. */
  329. private Class findPropertyType(Method readMethod, Method writeMethod)
  330. throws IntrospectionException {
  331. Class propertyType = null;
  332. try {
  333. if (readMethod != null) {
  334. if (readMethod.getParameterTypes().length != 0) {
  335. throw new IntrospectionException("bad read method arg count");
  336. }
  337. propertyType = readMethod.getReturnType();
  338. if (propertyType == Void.TYPE) {
  339. throw new IntrospectionException("read method " +
  340. readMethod.getName() + " returns void");
  341. }
  342. }
  343. if (writeMethod != null) {
  344. Class params[] = writeMethod.getParameterTypes();
  345. if (params.length != 1) {
  346. throw new IntrospectionException("bad write method arg count");
  347. }
  348. if (propertyType != null && propertyType != params[0]) {
  349. throw new IntrospectionException("type mismatch between read and write methods");
  350. }
  351. propertyType = params[0];
  352. }
  353. } catch (IntrospectionException ex) {
  354. throw ex;
  355. }
  356. return propertyType;
  357. }
  358. static String capitalize(String s) {
  359. if (s.length() == 0) {
  360. return s;
  361. }
  362. char chars[] = s.toCharArray();
  363. chars[0] = Character.toUpperCase(chars[0]);
  364. return new String(chars);
  365. }
  366. }