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.

NestedMethodProperty.java 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.data.util;
  5. import java.io.IOException;
  6. import java.lang.reflect.InvocationTargetException;
  7. import java.lang.reflect.Method;
  8. import java.util.ArrayList;
  9. import java.util.Collections;
  10. import java.util.List;
  11. import com.vaadin.data.Property;
  12. import com.vaadin.data.util.MethodProperty.MethodException;
  13. /**
  14. * Nested accessor based property for a bean.
  15. *
  16. * The property is specified in the dotted notation, e.g. "address.street", and
  17. * can contain multiple levels of nesting.
  18. *
  19. * When accessing the property value, all intermediate getters must return
  20. * non-null values.
  21. *
  22. * @see MethodProperty
  23. *
  24. * @since 6.6
  25. */
  26. public class NestedMethodProperty<T> extends AbstractProperty<T> {
  27. // needed for de-serialization
  28. private String propertyName;
  29. // chain of getter methods
  30. private transient List<Method> getMethods;
  31. /**
  32. * The setter method.
  33. */
  34. private transient Method setMethod;
  35. /**
  36. * Bean instance used as a starting point for accessing the property value.
  37. */
  38. private Object instance;
  39. private Class<? extends T> type;
  40. /* Special serialization to handle method references */
  41. private void writeObject(java.io.ObjectOutputStream out) throws IOException {
  42. out.defaultWriteObject();
  43. // getMethods and setMethod are reconstructed on read based on
  44. // propertyName
  45. }
  46. /* Special serialization to handle method references */
  47. private void readObject(java.io.ObjectInputStream in) throws IOException,
  48. ClassNotFoundException {
  49. in.defaultReadObject();
  50. initialize(instance.getClass(), propertyName);
  51. }
  52. /**
  53. * Constructs a nested method property for a given object instance. The
  54. * property name is a dot separated string pointing to a nested property,
  55. * e.g. "manager.address.street".
  56. *
  57. * @param instance
  58. * top-level bean to which the property applies
  59. * @param propertyName
  60. * dot separated nested property name
  61. * @throws IllegalArgumentException
  62. * if the property name is invalid
  63. */
  64. public NestedMethodProperty(Object instance, String propertyName) {
  65. this.instance = instance;
  66. initialize(instance.getClass(), propertyName);
  67. }
  68. /**
  69. * For internal use to deduce property type etc. without a bean instance.
  70. * Calling {@link #setValue(Object)} or {@link #getValue()} on properties
  71. * constructed this way is not supported.
  72. *
  73. * @param instanceClass
  74. * class of the top-level bean
  75. * @param propertyName
  76. */
  77. NestedMethodProperty(Class<?> instanceClass, String propertyName) {
  78. instance = null;
  79. initialize(instanceClass, propertyName);
  80. }
  81. /**
  82. * Initializes most of the internal fields based on the top-level bean
  83. * instance and property name (dot-separated string).
  84. *
  85. * @param beanClass
  86. * class of the top-level bean to which the property applies
  87. * @param propertyName
  88. * dot separated nested property name
  89. * @throws IllegalArgumentException
  90. * if the property name is invalid
  91. */
  92. private void initialize(Class<?> beanClass, String propertyName)
  93. throws IllegalArgumentException {
  94. List<Method> getMethods = new ArrayList<Method>();
  95. String lastSimplePropertyName = propertyName;
  96. Class<?> lastClass = beanClass;
  97. // first top-level property, then go deeper in a loop
  98. Class<?> propertyClass = beanClass;
  99. String[] simplePropertyNames = propertyName.split("\\.");
  100. if (propertyName.endsWith(".") || 0 == simplePropertyNames.length) {
  101. throw new IllegalArgumentException("Invalid property name '"
  102. + propertyName + "'");
  103. }
  104. for (int i = 0; i < simplePropertyNames.length; i++) {
  105. String simplePropertyName = simplePropertyNames[i].trim();
  106. if (simplePropertyName.length() > 0) {
  107. lastSimplePropertyName = simplePropertyName;
  108. lastClass = propertyClass;
  109. try {
  110. Method getter = MethodProperty.initGetterMethod(
  111. simplePropertyName, propertyClass);
  112. propertyClass = getter.getReturnType();
  113. getMethods.add(getter);
  114. } catch (final java.lang.NoSuchMethodException e) {
  115. throw new IllegalArgumentException("Bean property '"
  116. + simplePropertyName + "' not found", e);
  117. }
  118. } else {
  119. throw new IllegalArgumentException(
  120. "Empty or invalid bean property identifier in '"
  121. + propertyName + "'");
  122. }
  123. }
  124. // In case the get method is found, resolve the type
  125. Method lastGetMethod = getMethods.get(getMethods.size() - 1);
  126. Class<?> type = lastGetMethod.getReturnType();
  127. // Finds the set method
  128. Method setMethod = null;
  129. try {
  130. // Assure that the first letter is upper cased (it is a common
  131. // mistake to write firstName, not FirstName).
  132. if (Character.isLowerCase(lastSimplePropertyName.charAt(0))) {
  133. final char[] buf = lastSimplePropertyName.toCharArray();
  134. buf[0] = Character.toUpperCase(buf[0]);
  135. lastSimplePropertyName = new String(buf);
  136. }
  137. setMethod = lastClass.getMethod("set" + lastSimplePropertyName,
  138. new Class[] { type });
  139. } catch (final NoSuchMethodException skipped) {
  140. }
  141. this.type = (Class<? extends T>) MethodProperty
  142. .convertPrimitiveType(type);
  143. this.propertyName = propertyName;
  144. this.getMethods = getMethods;
  145. this.setMethod = setMethod;
  146. }
  147. @Override
  148. public Class<? extends T> getType() {
  149. return type;
  150. }
  151. @Override
  152. public boolean isReadOnly() {
  153. return super.isReadOnly() || (null == setMethod);
  154. }
  155. /**
  156. * Gets the value stored in the Property. The value is resolved by calling
  157. * the specified getter method with the argument specified at instantiation.
  158. *
  159. * @return the value of the Property
  160. */
  161. @Override
  162. public T getValue() {
  163. try {
  164. Object object = instance;
  165. for (Method m : getMethods) {
  166. object = m.invoke(object);
  167. }
  168. return (T) object;
  169. } catch (final Throwable e) {
  170. throw new MethodException(this, e);
  171. }
  172. }
  173. /**
  174. * Sets the value of the property. The new value must be assignable to the
  175. * type of this property.
  176. *
  177. * @param newValue
  178. * the New value of the property.
  179. * @throws <code>Property.ReadOnlyException</code> if the object is in
  180. * read-only mode.
  181. * @see #invokeSetMethod(Object)
  182. */
  183. @Override
  184. public void setValue(Object newValue) throws ReadOnlyException {
  185. // Checks the mode
  186. if (isReadOnly()) {
  187. throw new Property.ReadOnlyException();
  188. }
  189. // Checks the type of the value
  190. if (newValue != null && !type.isAssignableFrom(newValue.getClass())) {
  191. throw new IllegalArgumentException(
  192. "Invalid value type for NestedMethodProperty.");
  193. }
  194. invokeSetMethod((T) newValue);
  195. fireValueChange();
  196. }
  197. /**
  198. * Internal method to actually call the setter method of the wrapped
  199. * property.
  200. *
  201. * @param value
  202. */
  203. protected void invokeSetMethod(T value) {
  204. try {
  205. Object object = instance;
  206. for (int i = 0; i < getMethods.size() - 1; i++) {
  207. object = getMethods.get(i).invoke(object);
  208. }
  209. setMethod.invoke(object, new Object[] { value });
  210. } catch (final InvocationTargetException e) {
  211. throw new MethodException(this, e.getTargetException());
  212. } catch (final Exception e) {
  213. throw new MethodException(this, e);
  214. }
  215. }
  216. /**
  217. * Returns an unmodifiable list of getter methods to call in sequence to get
  218. * the property value.
  219. *
  220. * This API may change in future versions.
  221. *
  222. * @return unmodifiable list of getter methods corresponding to each segment
  223. * of the property name
  224. */
  225. protected List<Method> getGetMethods() {
  226. return Collections.unmodifiableList(getMethods);
  227. }
  228. }