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 9.0KB

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