1. /*
  2. * @(#)PropertyDescriptor.java 1.72 04/05/05
  3. *
  4. * Copyright 2004 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.ref.Reference;
  9. import java.lang.reflect.Method;
  10. import java.lang.reflect.Constructor;
  11. /**
  12. * A PropertyDescriptor describes one property that a Java Bean
  13. * exports via a pair of accessor methods.
  14. */
  15. public class PropertyDescriptor extends FeatureDescriptor {
  16. private Reference propertyTypeRef;
  17. private Reference readMethodRef;
  18. private Reference writeMethodRef;
  19. private Reference propertyEditorClassRef;
  20. private boolean bound;
  21. private boolean constrained;
  22. // The base name of the method name which will be prefixed with the
  23. // read and write method. If name == "foo" then the baseName is "Foo"
  24. private String baseName;
  25. private String writeMethodName;
  26. private String readMethodName;
  27. /**
  28. * Constructs a PropertyDescriptor for a property that follows
  29. * the standard Java convention by having getFoo and setFoo
  30. * accessor methods. Thus if the argument name is "fred", it will
  31. * assume that the writer method is "setFred" and the reader method
  32. * is "getFred" (or "isFred" for a boolean property). Note that the
  33. * property name should start with a lower case character, which will
  34. * be capitalized in the method names.
  35. *
  36. * @param propertyName The programmatic name of the property.
  37. * @param beanClass The Class object for the target bean. For
  38. * example sun.beans.OurButton.class.
  39. * @exception IntrospectionException if an exception occurs during
  40. * introspection.
  41. */
  42. public PropertyDescriptor(String propertyName, Class<?> beanClass)
  43. throws IntrospectionException {
  44. this(propertyName, beanClass,
  45. "is" + capitalize(propertyName),
  46. "set" + capitalize(propertyName));
  47. }
  48. /**
  49. * This constructor takes the name of a simple property, and method
  50. * names for reading and writing the property.
  51. *
  52. * @param propertyName The programmatic name of the property.
  53. * @param beanClass The Class object for the target bean. For
  54. * example sun.beans.OurButton.class.
  55. * @param readMethodName The name of the method used for reading the property
  56. * value. May be null if the property is write-only.
  57. * @param writeMethodName The name of the method used for writing the property
  58. * value. May be null if the property is read-only.
  59. * @exception IntrospectionException if an exception occurs during
  60. * introspection.
  61. */
  62. public PropertyDescriptor(String propertyName, Class<?> beanClass,
  63. String readMethodName, String writeMethodName)
  64. throws IntrospectionException {
  65. if (beanClass == null) {
  66. throw new IntrospectionException("Target Bean class is null");
  67. }
  68. if (propertyName == null || propertyName.length() == 0) {
  69. throw new IntrospectionException("bad property name");
  70. }
  71. if ("".equals(readMethodName) || "".equals(writeMethodName)) {
  72. throw new IntrospectionException("read or write method name should not be the empty string");
  73. }
  74. setName(propertyName);
  75. setClass0(beanClass);
  76. this.readMethodName = readMethodName;
  77. if (readMethodName != null && getReadMethod() == null) {
  78. throw new IntrospectionException("Method not found: " + readMethodName);
  79. }
  80. this.writeMethodName = writeMethodName;
  81. if (writeMethodName != null && getWriteMethod() == null) {
  82. throw new IntrospectionException("Method not found: " + writeMethodName);
  83. }
  84. }
  85. /**
  86. * This constructor takes the name of a simple property, and Method
  87. * objects for reading and writing the property.
  88. *
  89. * @param propertyName The programmatic name of the property.
  90. * @param readMethod The method used for reading the property value.
  91. * May be null if the property is write-only.
  92. * @param writeMethod The method used for writing the property value.
  93. * May be null if the property is read-only.
  94. * @exception IntrospectionException if an exception occurs during
  95. * introspection.
  96. */
  97. public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)
  98. throws IntrospectionException {
  99. if (propertyName == null || propertyName.length() == 0) {
  100. throw new IntrospectionException("bad property name");
  101. }
  102. setName(propertyName);
  103. setReadMethod(readMethod);
  104. setWriteMethod(writeMethod);
  105. }
  106. /**
  107. * Gets the Class object for the property.
  108. *
  109. * @return The Java type info for the property. Note that
  110. * the "Class" object may describe a built-in Java type such as "int".
  111. * The result may be "null" if this is an indexed property that
  112. * does not support non-indexed access.
  113. * <p>
  114. * This is the type that will be returned by the ReadMethod.
  115. */
  116. public synchronized Class<?> getPropertyType() {
  117. Class type = getPropertyType0();
  118. if (type == null) {
  119. try {
  120. type = findPropertyType(getReadMethod(), getWriteMethod());
  121. setPropertyType(type);
  122. } catch (IntrospectionException ex) {
  123. // Fall
  124. }
  125. }
  126. return type;
  127. }
  128. private void setPropertyType(Class type) {
  129. propertyTypeRef = createReference(type);
  130. }
  131. private Class getPropertyType0() {
  132. return (Class)getObject(propertyTypeRef);
  133. }
  134. /**
  135. * Gets the method that should be used to read the property value.
  136. *
  137. * @return The method that should be used to read the property value.
  138. * May return null if the property can't be read.
  139. */
  140. public synchronized Method getReadMethod() {
  141. Method readMethod = getReadMethod0();
  142. if (readMethod == null) {
  143. Class cls = getClass0();
  144. if (cls == null || (readMethodName == null && readMethodRef == null)) {
  145. // The read method was explicitly set to null.
  146. return null;
  147. }
  148. if (readMethodName == null) {
  149. Class type = getPropertyType0();
  150. if (type == boolean.class || type == null) {
  151. readMethodName = "is" + getBaseName();
  152. } else {
  153. readMethodName = "get" + getBaseName();
  154. }
  155. }
  156. // Since there can be multiple write methods but only one getter
  157. // method, find the getter method first so that you know what the
  158. // property type is. For booleans, there can be "is" and "get"
  159. // methods. If an "is" method exists, this is the official
  160. // reader method so look for this one first.
  161. readMethod = Introspector.findMethod(cls, readMethodName, 0);
  162. if (readMethod == null) {
  163. readMethodName = "get" + getBaseName();
  164. readMethod = Introspector.findMethod(cls, readMethodName, 0);
  165. }
  166. try {
  167. setReadMethod(readMethod);
  168. } catch (IntrospectionException ex) {
  169. // fall
  170. }
  171. }
  172. return readMethod;
  173. }
  174. /**
  175. * Sets the method that should be used to read the property value.
  176. *
  177. * @param readMethod The new read method.
  178. */
  179. public synchronized void setReadMethod(Method readMethod)
  180. throws IntrospectionException {
  181. if (readMethod == null) {
  182. readMethodName = null;
  183. readMethodRef = null;
  184. return;
  185. }
  186. // The property type is determined by the read method.
  187. setPropertyType(findPropertyType(readMethod, getWriteMethod0()));
  188. setClass0(readMethod.getDeclaringClass());
  189. readMethodName = readMethod.getName();
  190. readMethodRef = createReference(readMethod, true);
  191. }
  192. /**
  193. * Gets the method that should be used to write the property value.
  194. *
  195. * @return The method that should be used to write the property value.
  196. * May return null if the property can't be written.
  197. */
  198. public synchronized Method getWriteMethod() {
  199. Method writeMethod = getWriteMethod0();
  200. if (writeMethod == null) {
  201. Class cls = getClass0();
  202. if (cls == null || (writeMethodName == null && writeMethodRef == null)) {
  203. // The write method was explicitly set to null.
  204. return null;
  205. }
  206. // We need the type to fetch the correct method.
  207. Class type = getPropertyType0();
  208. if (type == null) {
  209. try {
  210. // Can't use getPropertyType since it will lead to recursive loop.
  211. type = findPropertyType(getReadMethod(), null);
  212. setPropertyType(type);
  213. } catch (IntrospectionException ex) {
  214. // Without the correct property type we can't be guaranteed
  215. // to find the correct method.
  216. return null;
  217. }
  218. }
  219. if (writeMethodName == null) {
  220. writeMethodName = "set" + getBaseName();
  221. }
  222. writeMethod = Introspector.findMethod(cls, writeMethodName, 1,
  223. (type == null) ? null : new Class[] { type });
  224. try {
  225. setWriteMethod(writeMethod);
  226. } catch (IntrospectionException ex) {
  227. // fall through
  228. }
  229. }
  230. return writeMethod;
  231. }
  232. /**
  233. * Sets the method that should be used to write the property value.
  234. *
  235. * @param writeMethod The new write method.
  236. */
  237. public synchronized void setWriteMethod(Method writeMethod)
  238. throws IntrospectionException {
  239. if (writeMethod == null) {
  240. writeMethodName = null;
  241. writeMethodRef = null;
  242. return;
  243. }
  244. // Set the property type - which validates the method
  245. setPropertyType(findPropertyType(getReadMethod(), writeMethod));
  246. setClass0(writeMethod.getDeclaringClass());
  247. writeMethodName = writeMethod.getName();
  248. writeMethodRef = createReference(writeMethod, true);
  249. }
  250. private Method getReadMethod0() {
  251. return (Method)getObject(readMethodRef);
  252. }
  253. private Method getWriteMethod0() {
  254. return (Method)getObject(writeMethodRef);
  255. }
  256. /**
  257. * Overridden to ensure that a super class doesn't take precedent
  258. */
  259. void setClass0(Class clz) {
  260. if (getClass0() != null && clz.isAssignableFrom(getClass0())) {
  261. // dont replace a subclass with a superclass
  262. return;
  263. }
  264. super.setClass0(clz);
  265. }
  266. /**
  267. * Updates to "bound" properties will cause a "PropertyChange" event to
  268. * get fired when the property is changed.
  269. *
  270. * @return True if this is a bound property.
  271. */
  272. public boolean isBound() {
  273. return bound;
  274. }
  275. /**
  276. * Updates to "bound" properties will cause a "PropertyChange" event to
  277. * get fired when the property is changed.
  278. *
  279. * @param bound True if this is a bound property.
  280. */
  281. public void setBound(boolean bound) {
  282. this.bound = bound;
  283. }
  284. /**
  285. * Attempted updates to "Constrained" properties will cause a "VetoableChange"
  286. * event to get fired when the property is changed.
  287. *
  288. * @return True if this is a constrained property.
  289. */
  290. public boolean isConstrained() {
  291. return constrained;
  292. }
  293. /**
  294. * Attempted updates to "Constrained" properties will cause a "VetoableChange"
  295. * event to get fired when the property is changed.
  296. *
  297. * @param constrained True if this is a constrained property.
  298. */
  299. public void setConstrained(boolean constrained) {
  300. this.constrained = constrained;
  301. }
  302. /**
  303. * Normally PropertyEditors will be found using the PropertyEditorManager.
  304. * However if for some reason you want to associate a particular
  305. * PropertyEditor with a given property, then you can do it with
  306. * this method.
  307. *
  308. * @param propertyEditorClass The Class for the desired PropertyEditor.
  309. */
  310. public void setPropertyEditorClass(Class<?> propertyEditorClass) {
  311. propertyEditorClassRef = createReference(propertyEditorClass);
  312. }
  313. /**
  314. * Gets any explicit PropertyEditor Class that has been registered
  315. * for this property.
  316. *
  317. * @return Any explicit PropertyEditor Class that has been registered
  318. * for this property. Normally this will return "null",
  319. * indicating that no special editor has been registered,
  320. * so the PropertyEditorManager should be used to locate
  321. * a suitable PropertyEditor.
  322. */
  323. public Class<?> getPropertyEditorClass() {
  324. return (Class)getObject(propertyEditorClassRef);
  325. }
  326. /**
  327. * Constructs an instance of a property editor using the current
  328. * property editor class.
  329. * <p>
  330. * If the property editor class has a public constructor that takes an
  331. * Object argument then it will be invoked using the bean parameter
  332. * as the argument. Otherwise, the default constructor will be invoked.
  333. *
  334. * @param bean the source object
  335. * @return a property editor instance or null if a property editor has
  336. * not been defined or cannot be created
  337. * @since 1.5
  338. */
  339. public PropertyEditor createPropertyEditor(Object bean) {
  340. Object editor = null;
  341. Class cls = getPropertyEditorClass();
  342. if (cls != null) {
  343. Constructor ctor = null;
  344. if (bean != null) {
  345. try {
  346. ctor = cls.getConstructor(new Class[] { Object.class });
  347. } catch (Exception ex) {
  348. // Fall through
  349. }
  350. }
  351. try {
  352. if (ctor == null) {
  353. editor = cls.newInstance();
  354. } else {
  355. editor = ctor.newInstance(new Object[] { bean });
  356. }
  357. } catch (Exception ex) {
  358. // A serious error has occured.
  359. // Proably due to an invalid property editor.
  360. throw new RuntimeException("PropertyEditor not instantiated",
  361. ex);
  362. }
  363. }
  364. return (PropertyEditor)editor;
  365. }
  366. /**
  367. * Compares this <code>PropertyDescriptor</code> against the specified object.
  368. * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s
  369. * are the same if the read, write, property types, property editor and
  370. * flags are equivalent.
  371. *
  372. * @since 1.4
  373. */
  374. public boolean equals(Object obj) {
  375. if (this == obj) {
  376. return true;
  377. }
  378. if (obj != null && obj instanceof PropertyDescriptor) {
  379. PropertyDescriptor other = (PropertyDescriptor)obj;
  380. Method otherReadMethod = other.getReadMethod();
  381. Method otherWriteMethod = other.getWriteMethod();
  382. if (!compareMethods(getReadMethod(), otherReadMethod)) {
  383. return false;
  384. }
  385. if (!compareMethods(getWriteMethod(), otherWriteMethod)) {
  386. return false;
  387. }
  388. if (getPropertyType() == other.getPropertyType() &&
  389. getPropertyEditorClass() == other.getPropertyEditorClass() &&
  390. bound == other.isBound() && constrained == other.isConstrained() &&
  391. writeMethodName == other.writeMethodName &&
  392. readMethodName == other.readMethodName) {
  393. return true;
  394. }
  395. }
  396. return false;
  397. }
  398. /**
  399. * Package private helper method for Descriptor .equals methods.
  400. *
  401. * @param a first method to compare
  402. * @param b second method to compare
  403. * @return boolean to indicate that the methods are equivalent
  404. */
  405. boolean compareMethods(Method a, Method b) {
  406. // Note: perhaps this should be a protected method in FeatureDescriptor
  407. if ((a == null) != (b == null)) {
  408. return false;
  409. }
  410. if (a != null && b != null) {
  411. if (!a.equals(b)) {
  412. return false;
  413. }
  414. }
  415. return true;
  416. }
  417. /**
  418. * Package-private constructor.
  419. * Merge two property descriptors. Where they conflict, give the
  420. * second argument (y) priority over the first argument (x).
  421. *
  422. * @param x The first (lower priority) PropertyDescriptor
  423. * @param y The second (higher priority) PropertyDescriptor
  424. */
  425. PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
  426. super(x,y);
  427. if (y.baseName != null) {
  428. baseName = y.baseName;
  429. } else {
  430. baseName = x.baseName;
  431. }
  432. if (y.readMethodName != null) {
  433. readMethodName = y.readMethodName;
  434. } else {
  435. readMethodName = x.readMethodName;
  436. }
  437. if (y.writeMethodName != null) {
  438. writeMethodName = y.writeMethodName;
  439. } else {
  440. writeMethodName = x.writeMethodName;
  441. }
  442. if (y.propertyTypeRef != null) {
  443. propertyTypeRef = y.propertyTypeRef;
  444. } else {
  445. propertyTypeRef = x.propertyTypeRef;
  446. }
  447. // Figure out the merged read method.
  448. Method xr = x.getReadMethod();
  449. Method yr = y.getReadMethod();
  450. // Normally give priority to y's readMethod.
  451. try {
  452. if (yr != null && yr.getDeclaringClass() == getClass0()) {
  453. setReadMethod(yr);
  454. } else {
  455. setReadMethod(xr);
  456. }
  457. } catch (IntrospectionException ex) {
  458. // fall through
  459. }
  460. // However, if both x and y reference read methods in the same class,
  461. // give priority to a boolean "is" method over a boolean "get" method.
  462. if (xr != null && yr != null &&
  463. xr.getDeclaringClass() == yr.getDeclaringClass() &&
  464. xr.getReturnType() == boolean.class &&
  465. yr.getReturnType() == boolean.class &&
  466. xr.getName().indexOf("is") == 0 &&
  467. yr.getName().indexOf("get") == 0) {
  468. try {
  469. setReadMethod(xr);
  470. } catch (IntrospectionException ex) {
  471. // fall through
  472. }
  473. }
  474. Method xw = x.getWriteMethod();
  475. Method yw = y.getWriteMethod();
  476. try {
  477. if (yw != null && yw.getDeclaringClass() == getClass0()) {
  478. setWriteMethod(yw);
  479. } else {
  480. setWriteMethod(xw);
  481. }
  482. } catch (IntrospectionException ex) {
  483. // Fall through
  484. }
  485. if (y.getPropertyEditorClass() != null) {
  486. setPropertyEditorClass(y.getPropertyEditorClass());
  487. } else {
  488. setPropertyEditorClass(x.getPropertyEditorClass());
  489. }
  490. bound = x.bound | y.bound;
  491. constrained = x.constrained | y.constrained;
  492. }
  493. /*
  494. * Package-private dup constructor.
  495. * This must isolate the new object from any changes to the old object.
  496. */
  497. PropertyDescriptor(PropertyDescriptor old) {
  498. super(old);
  499. propertyTypeRef = old.propertyTypeRef;
  500. readMethodRef = old.readMethodRef;
  501. writeMethodRef = old.writeMethodRef;
  502. propertyEditorClassRef = old.propertyEditorClassRef;
  503. writeMethodName = old.writeMethodName;
  504. readMethodName = old.readMethodName;
  505. baseName = old.baseName;
  506. bound = old.bound;
  507. constrained = old.constrained;
  508. }
  509. /**
  510. * Returns the property type that corresponds to the read and write method.
  511. * The type precedence is given to the readMethod.
  512. *
  513. * @return the type of the property descriptor or null if both
  514. * read and write methods are null.
  515. * @throws IntrospectionException if the read or write method is invalid
  516. */
  517. private Class findPropertyType(Method readMethod, Method writeMethod)
  518. throws IntrospectionException {
  519. Class propertyType = null;
  520. try {
  521. if (readMethod != null) {
  522. Class[] params = readMethod.getParameterTypes();
  523. if (params.length != 0) {
  524. throw new IntrospectionException("bad read method arg count: "
  525. + readMethod);
  526. }
  527. propertyType = readMethod.getReturnType();
  528. if (propertyType == Void.TYPE) {
  529. throw new IntrospectionException("read method " +
  530. readMethod.getName() + " returns void");
  531. }
  532. }
  533. if (writeMethod != null) {
  534. Class params[] = writeMethod.getParameterTypes();
  535. if (params.length != 1) {
  536. throw new IntrospectionException("bad write method arg count: "
  537. + writeMethod);
  538. }
  539. if (propertyType != null && propertyType != params[0]) {
  540. throw new IntrospectionException("type mismatch between read and write methods");
  541. }
  542. propertyType = params[0];
  543. }
  544. } catch (IntrospectionException ex) {
  545. throw ex;
  546. }
  547. return propertyType;
  548. }
  549. /**
  550. * Returns a hash code value for the object.
  551. * See {@link java.lang.Object#hashCode} for a complete description.
  552. *
  553. * @return a hash code value for this object.
  554. * @since 1.5
  555. */
  556. public int hashCode() {
  557. int result = 7;
  558. result = 37 * result + ((getPropertyType() == null) ? 0 :
  559. getPropertyType().hashCode());
  560. result = 37 * result + ((getReadMethod() == null) ? 0 :
  561. getReadMethod().hashCode());
  562. result = 37 * result + ((getWriteMethod() == null) ? 0 :
  563. getWriteMethod().hashCode());
  564. result = 37 * result + ((getPropertyEditorClass() == null) ? 0 :
  565. getPropertyEditorClass().hashCode());
  566. result = 37 * result + ((writeMethodName == null) ? 0 :
  567. writeMethodName.hashCode());
  568. result = 37 * result + ((readMethodName == null) ? 0 :
  569. readMethodName.hashCode());
  570. result = 37 * result + getName().hashCode();
  571. result = 37 * result + ((bound == false) ? 0 : 1);
  572. result = 37 * result + ((constrained == false) ? 0 : 1);
  573. return result;
  574. }
  575. // Calculate once since capitalize() is expensive.
  576. String getBaseName() {
  577. if (baseName == null) {
  578. baseName = capitalize(getName());
  579. }
  580. return baseName;
  581. }
  582. /*
  583. public String toString() {
  584. String message = "name=" + getName();
  585. message += ", class=" + getClass0();
  586. message += ", type=" + getPropertyType();
  587. message += ", writeMethod=";
  588. message += writeMethodName;
  589. message += ", readMethod=";
  590. message += readMethodName;
  591. message += ", bound=" + bound;
  592. message += ", constrained=" + constrained;
  593. return message;
  594. }
  595. */
  596. }