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.

FieldBinder.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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.ui.declarative;
  17. import java.beans.IntrospectionException;
  18. import java.io.Serializable;
  19. import java.lang.reflect.Field;
  20. import java.lang.reflect.InvocationTargetException;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Locale;
  26. import java.util.Map;
  27. import java.util.logging.Logger;
  28. import com.vaadin.ui.Component;
  29. import com.vaadin.util.ReflectTools;
  30. /**
  31. * Binder utility that binds member fields of a design class instance to given
  32. * component instances. Only fields of type {@link Component} are bound
  33. *
  34. * @since 7.4
  35. * @author Vaadin Ltd
  36. */
  37. public class FieldBinder implements Serializable {
  38. // the instance containing the bound fields
  39. private Object bindTarget;
  40. // mapping between field names and Fields
  41. private Map<String, Field> fieldMap = new HashMap<String, Field>();
  42. /**
  43. * Creates a new instance of LayoutFieldBinder
  44. *
  45. * @param design
  46. * the design class instance containing the fields to bind
  47. * @throws IntrospectionException
  48. * if the given design class can not be introspected
  49. */
  50. public FieldBinder(Object design) throws IntrospectionException {
  51. this(design, design.getClass());
  52. }
  53. /**
  54. * Creates a new instance of LayoutFieldBinder
  55. *
  56. * @param design
  57. * the instance containing the fields
  58. * @param classWithFields
  59. * the class which defines the fields to bind
  60. * @throws IntrospectionException
  61. * if the given design class can not be introspected
  62. */
  63. public FieldBinder(Object design, Class<?> classWithFields)
  64. throws IntrospectionException {
  65. if (design == null) {
  66. throw new IllegalArgumentException("The design must not be null");
  67. }
  68. bindTarget = design;
  69. resolveFields(classWithFields);
  70. }
  71. /**
  72. * Returns a collection of field names that are not bound
  73. *
  74. * @return a collection of fields assignable to Component that are not bound
  75. */
  76. public Collection<String> getUnboundFields() throws FieldBindingException {
  77. List<String> unboundFields = new ArrayList<String>();
  78. for (Field f : fieldMap.values()) {
  79. try {
  80. Object value = ReflectTools.getJavaFieldValue(bindTarget, f);
  81. if (value == null) {
  82. unboundFields.add(f.getName());
  83. }
  84. } catch (IllegalArgumentException e) {
  85. throw new FieldBindingException("Could not get field value", e);
  86. } catch (IllegalAccessException e) {
  87. throw new FieldBindingException("Could not get field value", e);
  88. } catch (InvocationTargetException e) {
  89. throw new FieldBindingException("Could not get field value", e);
  90. }
  91. }
  92. if (unboundFields.size() > 0) {
  93. getLogger().severe(
  94. "Found unbound fields in component root :" + unboundFields);
  95. }
  96. return unboundFields;
  97. }
  98. /**
  99. * Resolves the fields of the design class instance
  100. */
  101. private void resolveFields(Class<?> classWithFields) {
  102. for (Field memberField : getFieldsInDeclareOrder(classWithFields)) {
  103. if (Component.class.isAssignableFrom(memberField.getType())) {
  104. fieldMap.put(memberField.getName().toLowerCase(Locale.ENGLISH),
  105. memberField);
  106. }
  107. }
  108. }
  109. /**
  110. * Tries to bind the given {@link Component} instance to a member field of
  111. * the bind target. The name of the bound field is constructed based on the
  112. * id or caption of the instance, depending on which one is defined. If a
  113. * field is already bound (not null), {@link FieldBindingException} is
  114. * thrown.
  115. *
  116. * @param instance
  117. * the instance to be bound to a field
  118. * @return true on success, otherwise false
  119. * @throws FieldBindingException
  120. * if error occurs when trying to bind the instance to a field
  121. */
  122. public boolean bindField(Component instance) {
  123. return bindField(instance, null);
  124. }
  125. /**
  126. * Tries to bind the given {@link Component} instance to a member field of
  127. * the bind target. The fields are matched based on localId, id and caption.
  128. * If a field is already bound (not null), {@link FieldBindingException} is
  129. * thrown.
  130. *
  131. * @param instance
  132. * the instance to be bound to a field
  133. * @param localId
  134. * the localId used for mapping the field to an instance field
  135. * @return true on success
  136. * @throws FieldBindingException
  137. * if error occurs when trying to bind the instance to a field
  138. */
  139. public boolean bindField(Component instance, String localId) {
  140. // check that the field exists, is correct type and is null
  141. boolean success = bindFieldByIdentifier(localId, instance);
  142. if (!success) {
  143. success = bindFieldByIdentifier(instance.getId(), instance);
  144. }
  145. if (!success) {
  146. success = bindFieldByIdentifier(instance.getCaption(), instance);
  147. }
  148. if (!success) {
  149. String idInfo = "localId: " + localId + " id: " + instance.getId()
  150. + " caption: " + instance.getCaption();
  151. getLogger().finest(
  152. "Could not bind component to a field "
  153. + instance.getClass().getName() + " " + idInfo);
  154. }
  155. return success;
  156. }
  157. /**
  158. * Tries to bind the given {@link Component} instance to a member field of
  159. * the bind target. The field is matched based on the given identifier. If a
  160. * field is already bound (not null), {@link FieldBindingException} is
  161. * thrown.
  162. *
  163. * @param identifier
  164. * the identifier for the field.
  165. * @param instance
  166. * the instance to be bound to a field
  167. * @return true on success
  168. * @throws FieldBindingException
  169. * if error occurs when trying to bind the instance to a field
  170. */
  171. private boolean bindFieldByIdentifier(String identifier, Component instance) {
  172. try {
  173. // create and validate field name
  174. String fieldName = asFieldName(identifier);
  175. if (fieldName.length() == 0) {
  176. return false;
  177. }
  178. // validate that the field can be found
  179. Field field = fieldMap.get(fieldName.toLowerCase(Locale.ENGLISH));
  180. if (field == null) {
  181. getLogger().fine(
  182. "No field was found by identifier " + identifier);
  183. return false;
  184. }
  185. // validate that the field is not set
  186. Object fieldValue = ReflectTools.getJavaFieldValue(bindTarget,
  187. field);
  188. if (fieldValue != null) {
  189. getLogger().severe(
  190. "The field with identifier \"" + identifier
  191. + "\" already mapped");
  192. throw new FieldBindingException(
  193. "Duplicate identifier found for a field: " + fieldName);
  194. }
  195. // set the field value
  196. ReflectTools.setJavaFieldValue(bindTarget, field, instance);
  197. return true;
  198. } catch (IllegalAccessException e) {
  199. throw new FieldBindingException("Field binding failed", e);
  200. } catch (IllegalArgumentException e) {
  201. throw new FieldBindingException("Field binding failed", e);
  202. } catch (InvocationTargetException e) {
  203. throw new FieldBindingException("Field binding failed", e);
  204. }
  205. }
  206. /**
  207. * Converts the given identifier to a valid field name by stripping away
  208. * illegal character and setting the first letter of the name to lowercase
  209. *
  210. * @param identifier
  211. * the identifier to be converted to field name
  212. * @return the field name corresponding the identifier
  213. */
  214. private static String asFieldName(String identifier) {
  215. if (identifier == null) {
  216. return "";
  217. }
  218. StringBuilder result = new StringBuilder();
  219. for (int i = 0; i < identifier.length(); i++) {
  220. char character = identifier.charAt(i);
  221. if (Character.isJavaIdentifierPart(character)) {
  222. result.append(character);
  223. }
  224. }
  225. // lowercase first letter
  226. if (result.length() > 0 && Character.isLetter(result.charAt(0))) {
  227. result.setCharAt(0, Character.toLowerCase(result.charAt(0)));
  228. }
  229. return result.toString();
  230. }
  231. /**
  232. * Returns an array containing Field objects reflecting all the fields of
  233. * the class or interface represented by this Class object. The elements in
  234. * the array returned are sorted in declare order. The fields in
  235. * superclasses are excluded.
  236. *
  237. * @param searchClass
  238. * the class to be scanned for fields
  239. * @return the list of fields in this class
  240. */
  241. protected static List<java.lang.reflect.Field> getFieldsInDeclareOrder(
  242. Class<?> searchClass) {
  243. ArrayList<java.lang.reflect.Field> memberFieldsInOrder = new ArrayList<java.lang.reflect.Field>();
  244. for (java.lang.reflect.Field memberField : searchClass
  245. .getDeclaredFields()) {
  246. memberFieldsInOrder.add(memberField);
  247. }
  248. return memberFieldsInOrder;
  249. }
  250. private static Logger getLogger() {
  251. return Logger.getLogger(FieldBinder.class.getName());
  252. }
  253. }