You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MethodProperty.java 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.data.util;
  5. import java.io.IOException;
  6. import java.lang.reflect.Constructor;
  7. import java.lang.reflect.InvocationTargetException;
  8. import java.lang.reflect.Method;
  9. import java.util.LinkedList;
  10. import com.vaadin.data.Property;
  11. /**
  12. * <p>
  13. * Proxy class for creating Properties from pairs of getter and setter methods
  14. * of a Bean property. An instance of this class can be thought as having been
  15. * attached to a field of an object. Accessing the object through the Property
  16. * interface directly manipulates the underlying field.
  17. * </p>
  18. *
  19. * <p>
  20. * It's assumed that the return value returned by the getter method is
  21. * assignable to the type of the property, and the setter method parameter is
  22. * assignable to that value.
  23. * </p>
  24. *
  25. * <p>
  26. * A valid getter method must always be available, but instance of this class
  27. * can be constructed with a <code>null</code> setter method in which case the
  28. * resulting MethodProperty is read-only.
  29. * </p>
  30. *
  31. * <p>
  32. * MethodProperty implements Property.ValueChangeNotifier, but does not
  33. * automatically know whether or not the getter method will actually return a
  34. * new value - value change listeners are always notified when setValue is
  35. * called, without verifying what the getter returns.
  36. * </p>
  37. *
  38. * @author IT Mill Ltd.
  39. * @version
  40. * @VERSION@
  41. * @since 3.0
  42. */
  43. @SuppressWarnings("serial")
  44. public class MethodProperty implements Property, Property.ValueChangeNotifier,
  45. Property.ReadOnlyStatusChangeNotifier {
  46. /**
  47. * The object that includes the property the MethodProperty is bound to.
  48. */
  49. private transient Object instance;
  50. /**
  51. * Argument arrays for the getter and setter methods.
  52. */
  53. private transient Object[] setArgs, getArgs;
  54. /**
  55. * Is the MethodProperty read-only?
  56. */
  57. private boolean readOnly;
  58. /**
  59. * The getter and setter methods.
  60. */
  61. private transient Method setMethod, getMethod;
  62. /**
  63. * Index of the new value in the argument list for the setter method. If the
  64. * setter method requires several parameters, this index tells which one is
  65. * the actual value to change.
  66. */
  67. private int setArgumentIndex;
  68. /**
  69. * Type of the property.
  70. */
  71. private Class type;
  72. /**
  73. * List of listeners who are interested in the read-only status changes of
  74. * the MethodProperty
  75. */
  76. private LinkedList readOnlyStatusChangeListeners = null;
  77. /**
  78. * List of listeners who are interested in the value changes of the
  79. * MethodProperty
  80. */
  81. private LinkedList valueChangeListeners = null;
  82. /* Special serialization to handle method references */
  83. private void writeObject(java.io.ObjectOutputStream out) throws IOException {
  84. out.defaultWriteObject();
  85. out.writeObject(instance);
  86. out.writeObject(setArgs);
  87. out.writeObject(getArgs);
  88. if (setMethod != null) {
  89. out.writeObject(setMethod.getName());
  90. out.writeObject(setMethod.getParameterTypes());
  91. } else {
  92. out.writeObject("");
  93. out.writeObject("");
  94. }
  95. if (getMethod != null) {
  96. out.writeObject(getMethod.getName());
  97. out.writeObject(getMethod.getParameterTypes());
  98. } else {
  99. out.writeObject("");
  100. out.writeObject("");
  101. }
  102. };
  103. /* Special serialization to handle method references */
  104. private void readObject(java.io.ObjectInputStream in) throws IOException,
  105. ClassNotFoundException {
  106. in.defaultReadObject();
  107. try {
  108. instance = in.readObject();
  109. setArgs = (Object[]) in.readObject();
  110. getArgs = (Object[]) in.readObject();
  111. String name = (String) in.readObject();
  112. Class<?>[] paramTypes = (Class<?>[]) in.readObject();
  113. if (name != null && !name.equals("")) {
  114. setMethod = instance.getClass().getMethod(name, paramTypes);
  115. } else {
  116. setMethod = null;
  117. }
  118. name = (String) in.readObject();
  119. paramTypes = (Class<?>[]) in.readObject();
  120. if (name != null && !name.equals("")) {
  121. getMethod = instance.getClass().getMethod(name, paramTypes);
  122. } else {
  123. getMethod = null;
  124. }
  125. } catch (SecurityException e) {
  126. System.err.println("Internal deserialization error");
  127. e.printStackTrace();
  128. } catch (NoSuchMethodException e) {
  129. System.err.println("Internal deserialization error");
  130. e.printStackTrace();
  131. }
  132. };
  133. /**
  134. * <p>
  135. * Creates a new instance of <code>MethodProperty</code> from a named bean
  136. * property. This constructor takes an object and the name of a bean
  137. * property and initializes itself with the accessor methods for the
  138. * property.
  139. * </p>
  140. * <p>
  141. * The getter method of a <code>MethodProperty</code> instantiated with this
  142. * constructor will be called with no arguments, and the setter method with
  143. * only the new value as the sole argument.
  144. * </p>
  145. *
  146. * <p>
  147. * If the setter method is unavailable, the resulting
  148. * <code>MethodProperty</code> will be read-only, otherwise it will be
  149. * read-write.
  150. * </p>
  151. *
  152. * <p>
  153. * Method names are constucted from the bean property by adding
  154. * get/is/are/set prefix and capitalising the first character in the name of
  155. * the given bean property.
  156. * </p>
  157. *
  158. * @param instance
  159. * the object that includes the property.
  160. * @param beanPropertyName
  161. * the name of the property to bind to.
  162. */
  163. public MethodProperty(Object instance, String beanPropertyName) {
  164. final Class beanClass = instance.getClass();
  165. // Assure that the first letter is upper cased (it is a common
  166. // mistake to write firstName, not FirstName).
  167. if (Character.isLowerCase(beanPropertyName.charAt(0))) {
  168. final char[] buf = beanPropertyName.toCharArray();
  169. buf[0] = Character.toUpperCase(buf[0]);
  170. beanPropertyName = new String(buf);
  171. }
  172. // Find the get method
  173. getMethod = null;
  174. try {
  175. getMethod = beanClass.getMethod("get" + beanPropertyName,
  176. new Class[] {});
  177. } catch (final java.lang.NoSuchMethodException ignored) {
  178. try {
  179. getMethod = beanClass.getMethod("is" + beanPropertyName,
  180. new Class[] {});
  181. } catch (final java.lang.NoSuchMethodException ignoredAsWell) {
  182. try {
  183. getMethod = beanClass.getMethod("are" + beanPropertyName,
  184. new Class[] {});
  185. } catch (final java.lang.NoSuchMethodException e) {
  186. throw new MethodProperty.MethodException("Bean property "
  187. + beanPropertyName + " can not be found");
  188. }
  189. }
  190. }
  191. // In case the get method is found, resolve the type
  192. type = getMethod.getReturnType();
  193. // Finds the set method
  194. setMethod = null;
  195. try {
  196. setMethod = beanClass.getMethod("set" + beanPropertyName,
  197. new Class[] { type });
  198. } catch (final java.lang.NoSuchMethodException skipped) {
  199. }
  200. // Gets the return type from get method
  201. if (type.isPrimitive()) {
  202. if (type.equals(Boolean.TYPE)) {
  203. type = Boolean.class;
  204. } else if (type.equals(Integer.TYPE)) {
  205. type = Integer.class;
  206. } else if (type.equals(Float.TYPE)) {
  207. type = Float.class;
  208. } else if (type.equals(Double.TYPE)) {
  209. type = Double.class;
  210. } else if (type.equals(Byte.TYPE)) {
  211. type = Byte.class;
  212. } else if (type.equals(Character.TYPE)) {
  213. type = Character.class;
  214. } else if (type.equals(Short.TYPE)) {
  215. type = Short.class;
  216. } else if (type.equals(Long.TYPE)) {
  217. type = Long.class;
  218. }
  219. }
  220. setArguments(new Object[] {}, new Object[] { null }, 0);
  221. readOnly = (setMethod == null);
  222. this.instance = instance;
  223. }
  224. /**
  225. * <p>
  226. * Creates a new instance of <code>MethodProperty</code> from named getter
  227. * and setter methods. The getter method of a <code>MethodProperty</code>
  228. * instantiated with this constructor will be called with no arguments, and
  229. * the setter method with only the new value as the sole argument.
  230. * </p>
  231. *
  232. * <p>
  233. * If the setter method is <code>null</code>, the resulting
  234. * <code>MethodProperty</code> will be read-only, otherwise it will be
  235. * read-write.
  236. * </p>
  237. *
  238. * @param type
  239. * the type of the property.
  240. * @param instance
  241. * the object that includes the property.
  242. * @param getMethodName
  243. * the name of the getter method.
  244. * @param setMethodName
  245. * the name of the setter method.
  246. *
  247. */
  248. public MethodProperty(Class type, Object instance, String getMethodName,
  249. String setMethodName) {
  250. this(type, instance, getMethodName, setMethodName, new Object[] {},
  251. new Object[] { null }, 0);
  252. }
  253. /**
  254. * <p>
  255. * Creates a new instance of <code>MethodProperty</code> with the getter and
  256. * setter methods. The getter method of a <code>MethodProperty</code>
  257. * instantiated with this constructor will be called with no arguments, and
  258. * the setter method with only the new value as the sole argument.
  259. * </p>
  260. *
  261. * <p>
  262. * If the setter method is <code>null</code>, the resulting
  263. * <code>MethodProperty</code> will be read-only, otherwise it will be
  264. * read-write.
  265. * </p>
  266. *
  267. * @param type
  268. * the type of the property.
  269. * @param instance
  270. * the object that includes the property.
  271. * @param getMethod
  272. * the getter method.
  273. * @param setMethod
  274. * the setter method.
  275. */
  276. public MethodProperty(Class type, Object instance, Method getMethod,
  277. Method setMethod) {
  278. this(type, instance, getMethod, setMethod, new Object[] {},
  279. new Object[] { null }, 0);
  280. }
  281. /**
  282. * <p>
  283. * Creates a new instance of <code>MethodProperty</code> from named getter
  284. * and setter methods and argument lists. The getter method of a
  285. * <code>MethodProperty</code> instantiated with this constructor will be
  286. * called with the getArgs as arguments. The setArgs will be used as the
  287. * arguments for the setter method, though the argument indexed by the
  288. * setArgumentIndex will be replaced with the argument passed to the
  289. * {@link #setValue(Object newValue)} method.
  290. * </p>
  291. *
  292. * <p>
  293. * For example, if the <code>setArgs</code> contains <code>A</code>,
  294. * <code>B</code> and <code>C</code>, and <code>setArgumentIndex =
  295. * 1</code>, the call <code>methodProperty.setValue(X)</code> would result
  296. * in the setter method to be called with the parameter set of
  297. * <code>{A, X, C}</code>
  298. * </p>
  299. *
  300. * @param type
  301. * the type of the property.
  302. * @param instance
  303. * the object that includes the property.
  304. * @param getMethodName
  305. * the name of the getter method.
  306. * @param setMethodName
  307. * the name of the setter method.
  308. * @param getArgs
  309. * the fixed argument list to be passed to the getter method.
  310. * @param setArgs
  311. * the fixed argument list to be passed to the setter method.
  312. * @param setArgumentIndex
  313. * the index of the argument in <code>setArgs</code> to be
  314. * replaced with <code>newValue</code> when
  315. * {@link #setValue(Object newValue)} is called.
  316. */
  317. public MethodProperty(Class type, Object instance, String getMethodName,
  318. String setMethodName, Object[] getArgs, Object[] setArgs,
  319. int setArgumentIndex) {
  320. // Check the setargs and setargs index
  321. if (setMethodName != null && setArgs == null) {
  322. throw new IndexOutOfBoundsException("The setArgs can not be null");
  323. }
  324. if (setMethodName != null
  325. && (setArgumentIndex < 0 || setArgumentIndex >= setArgs.length)) {
  326. throw new IndexOutOfBoundsException(
  327. "The setArgumentIndex must be >= 0 and < setArgs.length");
  328. }
  329. // Set type
  330. this.type = type;
  331. // Find set and get -methods
  332. final Method[] m = instance.getClass().getMethods();
  333. // Finds get method
  334. boolean found = false;
  335. for (int i = 0; i < m.length; i++) {
  336. // Tests the name of the get Method
  337. if (!m[i].getName().equals(getMethodName)) {
  338. // name does not match, try next method
  339. continue;
  340. }
  341. // Tests return type
  342. if (!type.equals(m[i].getReturnType())) {
  343. continue;
  344. }
  345. // Tests the parameter types
  346. final Class[] c = m[i].getParameterTypes();
  347. if (c.length != getArgs.length) {
  348. // not the right amount of parameters, try next method
  349. continue;
  350. }
  351. int j = 0;
  352. while (j < c.length) {
  353. if (getArgs[j] != null
  354. && !c[j].isAssignableFrom(getArgs[j].getClass())) {
  355. // parameter type does not match, try next method
  356. break;
  357. }
  358. j++;
  359. }
  360. if (j == c.length) {
  361. // all paramteters matched
  362. if (found == true) {
  363. throw new MethodProperty.MethodException(
  364. "Could not uniquely identify " + getMethodName
  365. + "-method");
  366. } else {
  367. found = true;
  368. getMethod = m[i];
  369. }
  370. }
  371. }
  372. if (found != true) {
  373. throw new MethodProperty.MethodException("Could not find "
  374. + getMethodName + "-method");
  375. }
  376. // Finds set method
  377. if (setMethodName != null) {
  378. // Finds setMethod
  379. found = false;
  380. for (int i = 0; i < m.length; i++) {
  381. // Checks name
  382. if (!m[i].getName().equals(setMethodName)) {
  383. // name does not match, try next method
  384. continue;
  385. }
  386. // Checks parameter compatibility
  387. final Class[] c = m[i].getParameterTypes();
  388. if (c.length != setArgs.length) {
  389. // not the right amount of parameters, try next method
  390. continue;
  391. }
  392. int j = 0;
  393. while (j < c.length) {
  394. if (setArgs[j] != null
  395. && !c[j].isAssignableFrom(setArgs[j].getClass())) {
  396. // parameter type does not match, try next method
  397. break;
  398. } else if (j == setArgumentIndex && !c[j].equals(type)) {
  399. // Property type is not the same as setArg type
  400. break;
  401. }
  402. j++;
  403. }
  404. if (j == c.length) {
  405. // all parameters match
  406. if (found == true) {
  407. throw new MethodProperty.MethodException(
  408. "Could not identify unique " + setMethodName
  409. + "-method");
  410. } else {
  411. found = true;
  412. setMethod = m[i];
  413. }
  414. }
  415. }
  416. if (found != true) {
  417. throw new MethodProperty.MethodException("Could not identify "
  418. + setMethodName + "-method");
  419. }
  420. }
  421. // Gets the return type from get method
  422. if (type.isPrimitive()) {
  423. if (type.equals(Boolean.TYPE)) {
  424. type = Boolean.class;
  425. } else if (type.equals(Integer.TYPE)) {
  426. type = Integer.class;
  427. } else if (type.equals(Float.TYPE)) {
  428. type = Float.class;
  429. } else if (type.equals(Double.TYPE)) {
  430. type = Double.class;
  431. } else if (type.equals(Byte.TYPE)) {
  432. type = Byte.class;
  433. } else if (type.equals(Character.TYPE)) {
  434. type = Character.class;
  435. } else if (type.equals(Short.TYPE)) {
  436. type = Short.class;
  437. } else if (type.equals(Long.TYPE)) {
  438. type = Long.class;
  439. }
  440. }
  441. setArguments(getArgs, setArgs, setArgumentIndex);
  442. readOnly = (setMethod == null);
  443. this.instance = instance;
  444. }
  445. /**
  446. * <p>
  447. * Creates a new instance of <code>MethodProperty</code> from the getter and
  448. * setter methods, and argument lists.
  449. * </p>
  450. * <p>
  451. * This constructor behaves exactly like
  452. * {@link #MethodProperty(Class type, Object instance, String getMethodName, String setMethodName, Object [] getArgs, Object [] setArgs, int setArgumentIndex)}
  453. * except that instead of names of the getter and setter methods this
  454. * constructor is given the actual methods themselves.
  455. * </p>
  456. *
  457. * @param type
  458. * the type of the property.
  459. * @param instance
  460. * the object that includes the property.
  461. * @param getMethod
  462. * the getter method.
  463. * @param setMethod
  464. * the setter method.
  465. * @param getArgs
  466. * the fixed argument list to be passed to the getter method.
  467. * @param setArgs
  468. * the fixed argument list to be passed to the setter method.
  469. * @param setArgumentIndex
  470. * the index of the argument in <code>setArgs</code> to be
  471. * replaced with <code>newValue</code> when
  472. * {@link #setValue(Object newValue)} is called.
  473. */
  474. public MethodProperty(Class type, Object instance, Method getMethod,
  475. Method setMethod, Object[] getArgs, Object[] setArgs,
  476. int setArgumentIndex) {
  477. if (getMethod == null) {
  478. throw new MethodProperty.MethodException(
  479. "Property GET-method cannot not be null: " + type);
  480. }
  481. if (setMethod != null) {
  482. if (setArgs == null) {
  483. throw new IndexOutOfBoundsException(
  484. "The setArgs can not be null");
  485. }
  486. if (setArgumentIndex < 0 || setArgumentIndex >= setArgs.length) {
  487. throw new IndexOutOfBoundsException(
  488. "The setArgumentIndex must be >= 0 and < setArgs.length");
  489. }
  490. }
  491. // Gets the return type from get method
  492. if (type.isPrimitive()) {
  493. if (type.equals(Boolean.TYPE)) {
  494. type = Boolean.class;
  495. } else if (type.equals(Integer.TYPE)) {
  496. type = Integer.class;
  497. } else if (type.equals(Float.TYPE)) {
  498. type = Float.class;
  499. } else if (type.equals(Double.TYPE)) {
  500. type = Double.class;
  501. } else if (type.equals(Byte.TYPE)) {
  502. type = Byte.class;
  503. } else if (type.equals(Character.TYPE)) {
  504. type = Character.class;
  505. } else if (type.equals(Short.TYPE)) {
  506. type = Short.class;
  507. } else if (type.equals(Long.TYPE)) {
  508. type = Long.class;
  509. }
  510. }
  511. this.getMethod = getMethod;
  512. this.setMethod = setMethod;
  513. setArguments(getArgs, setArgs, setArgumentIndex);
  514. readOnly = (setMethod == null);
  515. this.instance = instance;
  516. this.type = type;
  517. }
  518. /**
  519. * Returns the type of the Property. The methods <code>getValue</code> and
  520. * <code>setValue</code> must be compatible with this type: one must be able
  521. * to safely cast the value returned from <code>getValue</code> to the given
  522. * type and pass any variable assignable to this type as an argument to
  523. * <code>setValue</code>.
  524. *
  525. * @return type of the Property
  526. */
  527. public final Class getType() {
  528. return type;
  529. }
  530. /**
  531. * Tests if the object is in read-only mode. In read-only mode calls to
  532. * <code>setValue</code> will throw <code>ReadOnlyException</code> and will
  533. * not modify the value of the Property.
  534. *
  535. * @return <code>true</code> if the object is in read-only mode,
  536. * <code>false</code> if it's not
  537. */
  538. public boolean isReadOnly() {
  539. return readOnly;
  540. }
  541. /**
  542. * Gets the value stored in the Property. The value is resolved by calling
  543. * the specified getter method with the argument specified at instantiation.
  544. *
  545. * @return the value of the Property
  546. */
  547. public Object getValue() {
  548. try {
  549. return getMethod.invoke(instance, getArgs);
  550. } catch (final Throwable e) {
  551. throw new MethodProperty.MethodException(e);
  552. }
  553. }
  554. /**
  555. * Returns the value of the <code>MethodProperty</code> in human readable
  556. * textual format. The return value should be assignable to the
  557. * <code>setValue</code> method if the Property is not in read-only mode.
  558. *
  559. * @return String representation of the value stored in the Property
  560. */
  561. @Override
  562. public String toString() {
  563. final Object value = getValue();
  564. if (value == null) {
  565. return null;
  566. }
  567. return value.toString();
  568. }
  569. /**
  570. * <p>
  571. * Sets the setter method and getter method argument lists.
  572. * </p>
  573. *
  574. * @param getArgs
  575. * the fixed argument list to be passed to the getter method.
  576. * @param setArgs
  577. * the fixed argument list to be passed to the setter method.
  578. * @param setArgumentIndex
  579. * the index of the argument in <code>setArgs</code> to be
  580. * replaced with <code>newValue</code> when
  581. * {@link #setValue(Object newValue)} is called.
  582. */
  583. public void setArguments(Object[] getArgs, Object[] setArgs,
  584. int setArgumentIndex) {
  585. this.getArgs = new Object[getArgs.length];
  586. for (int i = 0; i < getArgs.length; i++) {
  587. this.getArgs[i] = getArgs[i];
  588. }
  589. this.setArgs = new Object[setArgs.length];
  590. for (int i = 0; i < setArgs.length; i++) {
  591. this.setArgs[i] = setArgs[i];
  592. }
  593. this.setArgumentIndex = setArgumentIndex;
  594. }
  595. /**
  596. * Sets the value of the property. This method supports setting from
  597. * <code>String</code>s if either <code>String</code> is directly assignable
  598. * to property type, or the type class contains a string constructor.
  599. *
  600. * @param newValue
  601. * the New value of the property.
  602. * @throws <code>Property.ReadOnlyException</code> if the object is in
  603. * read-only mode.
  604. * @throws <code>Property.ConversionException</code> if
  605. * <code>newValue</code> can't be converted into the Property's
  606. * native type directly or through <code>String</code>.
  607. * @see #invokeSetMethod(Object)
  608. */
  609. public void setValue(Object newValue) throws Property.ReadOnlyException,
  610. Property.ConversionException {
  611. // Checks the mode
  612. if (isReadOnly()) {
  613. throw new Property.ReadOnlyException();
  614. }
  615. // Try to assign the compatible value directly
  616. if (newValue == null || type.isAssignableFrom(newValue.getClass())) {
  617. invokeSetMethod(newValue);
  618. } else {
  619. Object value;
  620. try {
  621. // Gets the string constructor
  622. final Constructor constr = getType().getConstructor(
  623. new Class[] { String.class });
  624. value = constr
  625. .newInstance(new Object[] { newValue.toString() });
  626. } catch (final java.lang.Exception e) {
  627. throw new Property.ConversionException(e);
  628. }
  629. // Creates new object from the string
  630. invokeSetMethod(value);
  631. }
  632. fireValueChange();
  633. }
  634. /**
  635. * Internal method to actually call the setter method of the wrapped
  636. * property.
  637. *
  638. * @param value
  639. */
  640. private void invokeSetMethod(Object value) {
  641. try {
  642. // Construct a temporary argument array only if needed
  643. if (setArgs.length == 1) {
  644. setMethod.invoke(instance, new Object[] { value });
  645. } else {
  646. // Sets the value to argument array
  647. final Object[] args = new Object[setArgs.length];
  648. for (int i = 0; i < setArgs.length; i++) {
  649. args[i] = (i == setArgumentIndex) ? value : setArgs[i];
  650. }
  651. setMethod.invoke(instance, args);
  652. }
  653. } catch (final InvocationTargetException e) {
  654. final Throwable targetException = e.getTargetException();
  655. throw new MethodProperty.MethodException(targetException);
  656. } catch (final Exception e) {
  657. throw new MethodProperty.MethodException(e);
  658. }
  659. }
  660. /**
  661. * Sets the Property's read-only mode to the specified status.
  662. *
  663. * @param newStatus
  664. * the new read-only status of the Property.
  665. */
  666. public void setReadOnly(boolean newStatus) {
  667. final boolean prevStatus = readOnly;
  668. if (newStatus) {
  669. readOnly = true;
  670. } else {
  671. readOnly = (setMethod == null);
  672. }
  673. if (prevStatus != readOnly) {
  674. fireReadOnlyStatusChange();
  675. }
  676. }
  677. /**
  678. * <code>Exception</code> object that signals that there were problems
  679. * calling or finding the specified getter or setter methods of the
  680. * property.
  681. *
  682. * @author IT Mill Ltd.
  683. * @version
  684. * @VERSION@
  685. * @since 3.0
  686. */
  687. public class MethodException extends RuntimeException {
  688. /**
  689. * Cause of the method exception
  690. */
  691. private Throwable cause;
  692. /**
  693. * Constructs a new <code>MethodException</code> with the specified
  694. * detail message.
  695. *
  696. * @param msg
  697. * the detail message.
  698. */
  699. public MethodException(String msg) {
  700. super(msg);
  701. }
  702. /**
  703. * Constructs a new <code>MethodException</code> from another exception.
  704. *
  705. * @param cause
  706. * the cause of the exception.
  707. */
  708. public MethodException(Throwable cause) {
  709. this.cause = cause;
  710. }
  711. /**
  712. * @see java.lang.Throwable#getCause()
  713. */
  714. @Override
  715. public Throwable getCause() {
  716. return cause;
  717. }
  718. /**
  719. * Gets the method property this exception originates from.
  720. */
  721. public MethodProperty getMethodProperty() {
  722. return MethodProperty.this;
  723. }
  724. }
  725. /* Events */
  726. /**
  727. * An <code>Event</code> object specifying the Property whose read-only
  728. * status has been changed.
  729. *
  730. * @author IT Mill Ltd.
  731. * @version
  732. * @VERSION@
  733. * @since 3.0
  734. */
  735. private class ReadOnlyStatusChangeEvent extends java.util.EventObject
  736. implements Property.ReadOnlyStatusChangeEvent {
  737. /**
  738. * Constructs a new read-only status change event for this object.
  739. *
  740. * @param source
  741. * source object of the event.
  742. */
  743. protected ReadOnlyStatusChangeEvent(MethodProperty source) {
  744. super(source);
  745. }
  746. /**
  747. * Gets the Property whose read-only state has changed.
  748. *
  749. * @return source Property of the event.
  750. */
  751. public Property getProperty() {
  752. return (Property) getSource();
  753. }
  754. }
  755. /**
  756. * Registers a new read-only status change listener for this Property.
  757. *
  758. * @param listener
  759. * the new Listener to be registered.
  760. */
  761. public void addListener(Property.ReadOnlyStatusChangeListener listener) {
  762. if (readOnlyStatusChangeListeners == null) {
  763. readOnlyStatusChangeListeners = new LinkedList();
  764. }
  765. readOnlyStatusChangeListeners.add(listener);
  766. }
  767. /**
  768. * Removes a previously registered read-only status change listener.
  769. *
  770. * @param listener
  771. * the listener to be removed.
  772. */
  773. public void removeListener(Property.ReadOnlyStatusChangeListener listener) {
  774. if (readOnlyStatusChangeListeners != null) {
  775. readOnlyStatusChangeListeners.remove(listener);
  776. }
  777. }
  778. /**
  779. * Sends a read only status change event to all registered listeners.
  780. */
  781. private void fireReadOnlyStatusChange() {
  782. if (readOnlyStatusChangeListeners != null) {
  783. final Object[] l = readOnlyStatusChangeListeners.toArray();
  784. final Property.ReadOnlyStatusChangeEvent event = new MethodProperty.ReadOnlyStatusChangeEvent(
  785. this);
  786. for (int i = 0; i < l.length; i++) {
  787. ((Property.ReadOnlyStatusChangeListener) l[i])
  788. .readOnlyStatusChange(event);
  789. }
  790. }
  791. }
  792. /**
  793. * An <code>Event</code> object specifying the Property whose value has been
  794. * changed.
  795. *
  796. * @author IT Mill Ltd.
  797. * @version
  798. * @VERSION@
  799. * @since 5.3
  800. */
  801. private class ValueChangeEvent extends java.util.EventObject implements
  802. Property.ValueChangeEvent {
  803. /**
  804. * Constructs a new value change event for this object.
  805. *
  806. * @param source
  807. * source object of the event.
  808. */
  809. protected ValueChangeEvent(MethodProperty source) {
  810. super(source);
  811. }
  812. /**
  813. * Gets the Property whose value has changed.
  814. *
  815. * @return source Property of the event.
  816. */
  817. public Property getProperty() {
  818. return (Property) getSource();
  819. }
  820. }
  821. public void addListener(ValueChangeListener listener) {
  822. if (valueChangeListeners == null) {
  823. valueChangeListeners = new LinkedList();
  824. }
  825. valueChangeListeners.add(listener);
  826. }
  827. public void removeListener(ValueChangeListener listener) {
  828. if (valueChangeListeners != null) {
  829. valueChangeListeners.remove(listener);
  830. }
  831. }
  832. /**
  833. * Sends a value change event to all registered listeners.
  834. */
  835. public void fireValueChange() {
  836. if (valueChangeListeners != null) {
  837. final Object[] l = valueChangeListeners.toArray();
  838. final Property.ValueChangeEvent event = new MethodProperty.ValueChangeEvent(
  839. this);
  840. for (int i = 0; i < l.length; i++) {
  841. ((Property.ValueChangeListener) l[i]).valueChange(event);
  842. }
  843. }
  844. }
  845. }