選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

NestedMethodProperty.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. * Copyright 2000-2016 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.v7.data.util;
  17. import static com.vaadin.util.ReflectTools.convertPrimitiveType;
  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.lang.reflect.InvocationTargetException;
  22. import java.lang.reflect.Method;
  23. import java.util.ArrayList;
  24. import java.util.Collections;
  25. import java.util.List;
  26. import com.vaadin.data.Binder;
  27. import com.vaadin.data.ValueProvider;
  28. import com.vaadin.server.Setter;
  29. import com.vaadin.shared.util.SharedUtil;
  30. import com.vaadin.v7.data.Property;
  31. import com.vaadin.v7.data.util.MethodProperty.MethodException;
  32. /**
  33. * Nested accessor based property for a bean.
  34. *
  35. * The property is specified in the dotted notation, e.g. "address.street", and
  36. * can contain multiple levels of nesting.
  37. *
  38. * When accessing the property value, all intermediate getters must exist and
  39. * should return non-null values when the property value is accessed. If an
  40. * intermediate getter returns null, a null value will be returned.
  41. *
  42. * @see MethodProperty
  43. *
  44. * @since 6.6
  45. *
  46. * @deprecated As of 8.0, replaced by {@link ValueProvider}, {@link Setter}, see
  47. * {@link Binder}
  48. */
  49. @Deprecated
  50. public class NestedMethodProperty<T> extends AbstractProperty<T> {
  51. // needed for de-serialization
  52. private String propertyName;
  53. // chain of getter methods
  54. private transient List<Method> getMethods;
  55. /**
  56. * The setter method.
  57. */
  58. private transient Method setMethod;
  59. /**
  60. * Bean instance used as a starting point for accessing the property value.
  61. */
  62. private Object instance;
  63. private Class<? extends T> type;
  64. /* Special serialization to handle method references */
  65. private void writeObject(ObjectOutputStream out) throws IOException {
  66. out.defaultWriteObject();
  67. // getMethods and setMethod are reconstructed on read based on
  68. // propertyName
  69. }
  70. /* Special serialization to handle method references */
  71. private void readObject(ObjectInputStream in)
  72. throws IOException, ClassNotFoundException {
  73. in.defaultReadObject();
  74. initialize(instance.getClass(), propertyName);
  75. }
  76. /**
  77. * Constructs a nested method property for a given object instance. The
  78. * property name is a dot separated string pointing to a nested property,
  79. * e.g. "manager.address.street".
  80. * <p>
  81. * Calling getValue will return null if any intermediate getter returns null
  82. *
  83. * @param instance
  84. * top-level bean to which the property applies
  85. * @param propertyName
  86. * dot separated nested property name
  87. * @throws IllegalArgumentException
  88. * if the property name is invalid
  89. */
  90. public NestedMethodProperty(Object instance, String propertyName) {
  91. this.instance = instance;
  92. initialize(instance.getClass(), propertyName);
  93. }
  94. /**
  95. * For internal use to deduce property type etc. without a bean instance.
  96. * Calling {@link #setValue(Object)} or {@link #getValue()} on properties
  97. * constructed this way is not supported.
  98. *
  99. * @param instanceClass
  100. * class of the top-level bean
  101. * @param propertyName
  102. */
  103. NestedMethodProperty(Class<?> instanceClass, String propertyName) {
  104. instance = null;
  105. initialize(instanceClass, propertyName);
  106. }
  107. /**
  108. * Initializes most of the internal fields based on the top-level bean
  109. * instance and property name (dot-separated string).
  110. *
  111. * @param beanClass
  112. * class of the top-level bean to which the property applies
  113. * @param propertyName
  114. * dot separated nested property name
  115. * @throws IllegalArgumentException
  116. * if the property name is invalid
  117. */
  118. private void initialize(Class<?> beanClass, String propertyName)
  119. throws IllegalArgumentException {
  120. List<Method> getMethods = new ArrayList<Method>();
  121. String lastSimplePropertyName = propertyName;
  122. Class<?> lastClass = beanClass;
  123. // first top-level property, then go deeper in a loop
  124. Class<?> propertyClass = beanClass;
  125. String[] simplePropertyNames = propertyName.split("\\.");
  126. if (propertyName.endsWith(".") || 0 == simplePropertyNames.length) {
  127. throw new IllegalArgumentException(
  128. "Invalid property name '" + propertyName + "'");
  129. }
  130. for (String simplePropertyName : simplePropertyNames) {
  131. simplePropertyName = simplePropertyName.trim();
  132. if (!simplePropertyName.isEmpty()) {
  133. lastSimplePropertyName = simplePropertyName;
  134. lastClass = propertyClass;
  135. try {
  136. Method getter = MethodProperty.initGetterMethod(
  137. simplePropertyName, propertyClass);
  138. propertyClass = getter.getReturnType();
  139. getMethods.add(getter);
  140. } catch (final NoSuchMethodException e) {
  141. throw new IllegalArgumentException("Bean property '"
  142. + simplePropertyName + "' not found", e);
  143. }
  144. } else {
  145. throw new IllegalArgumentException(
  146. "Empty or invalid bean property identifier in '"
  147. + propertyName + "'");
  148. }
  149. }
  150. // In case the get method is found, resolve the type
  151. Method lastGetMethod = getMethods.get(getMethods.size() - 1);
  152. Class<?> type = lastGetMethod.getReturnType();
  153. // Finds the set method
  154. Method setMethod = null;
  155. try {
  156. // Assure that the first letter is upper cased (it is a common
  157. // mistake to write firstName, not FirstName).
  158. lastSimplePropertyName = SharedUtil
  159. .capitalize(lastSimplePropertyName);
  160. setMethod = lastClass.getMethod("set" + lastSimplePropertyName,
  161. new Class[] { type });
  162. } catch (final NoSuchMethodException skipped) {
  163. }
  164. this.type = (Class<? extends T>) convertPrimitiveType(type);
  165. this.propertyName = propertyName;
  166. this.getMethods = getMethods;
  167. this.setMethod = setMethod;
  168. }
  169. @Override
  170. public Class<? extends T> getType() {
  171. return type;
  172. }
  173. @Override
  174. public boolean isReadOnly() {
  175. return super.isReadOnly() || (null == setMethod);
  176. }
  177. /**
  178. * Gets the value stored in the Property. The value is resolved by calling
  179. * the specified getter methods on the current instance:
  180. *
  181. * @return the value of the Property
  182. * @see #getInstance()
  183. */
  184. @Override
  185. public T getValue() {
  186. try {
  187. Object object = instance;
  188. for (Method m : getMethods) {
  189. object = m.invoke(object);
  190. if (object == null) {
  191. return null;
  192. }
  193. }
  194. return (T) object;
  195. } catch (final Throwable e) {
  196. throw new MethodException(this, e);
  197. }
  198. }
  199. /**
  200. * Sets the value of the property. The new value must be assignable to the
  201. * type of this property.
  202. *
  203. * @param newValue
  204. * the New value of the property.
  205. * @throws <code>Property.ReadOnlyException</code>
  206. * if the object is in read-only mode.
  207. * @see #invokeSetMethod(Object)
  208. */
  209. @Override
  210. public void setValue(T newValue) throws ReadOnlyException {
  211. // Checks the mode
  212. if (isReadOnly()) {
  213. throw new Property.ReadOnlyException();
  214. }
  215. invokeSetMethod(newValue);
  216. fireValueChange();
  217. }
  218. /**
  219. * Internal method to actually call the setter method of the wrapped
  220. * property.
  221. *
  222. * @param value
  223. */
  224. protected void invokeSetMethod(T value) {
  225. try {
  226. Object object = instance;
  227. for (int i = 0; i < getMethods.size() - 1; i++) {
  228. object = getMethods.get(i).invoke(object);
  229. if (object == null) {
  230. return;
  231. }
  232. }
  233. setMethod.invoke(object, new Object[] { value });
  234. } catch (final InvocationTargetException e) {
  235. throw new MethodException(this, e.getTargetException());
  236. } catch (final Exception e) {
  237. throw new MethodException(this, e);
  238. }
  239. }
  240. /**
  241. * Returns an unmodifiable list of getter methods to call in sequence to get
  242. * the property value.
  243. *
  244. * This API may change in future versions.
  245. *
  246. * @return unmodifiable list of getter methods corresponding to each segment
  247. * of the property name
  248. */
  249. protected List<Method> getGetMethods() {
  250. return Collections.unmodifiableList(getMethods);
  251. }
  252. /**
  253. * The instance used by this property.
  254. *
  255. * @return the instance used for fetching the property value
  256. * @since 7.7.7
  257. */
  258. public Object getInstance() {
  259. return instance;
  260. }
  261. /**
  262. * Sets the instance used by this property.
  263. * <p>
  264. * The new instance must be of the same type as the old instance
  265. * <p>
  266. * To be consistent with {@link #setValue(Object)}, this method will fire a
  267. * value change event even if the value stays the same
  268. *
  269. * @param instance
  270. * the instance to use
  271. * @since 7.7.7
  272. */
  273. public void setInstance(Object instance) {
  274. if (this.instance.getClass() != instance.getClass()) {
  275. throw new IllegalArgumentException("The new instance is of type "
  276. + instance.getClass().getName()
  277. + " which does not match the old instance type "
  278. + this.instance.getClass().getName());
  279. }
  280. this.instance = instance;
  281. fireValueChange();
  282. }
  283. }