aboutsummaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/ui/declarative/FieldBinder.java
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/com/vaadin/ui/declarative/FieldBinder.java')
-rw-r--r--server/src/com/vaadin/ui/declarative/FieldBinder.java247
1 files changed, 247 insertions, 0 deletions
diff --git a/server/src/com/vaadin/ui/declarative/FieldBinder.java b/server/src/com/vaadin/ui/declarative/FieldBinder.java
new file mode 100644
index 0000000000..c533af15bd
--- /dev/null
+++ b/server/src/com/vaadin/ui/declarative/FieldBinder.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.declarative;
+
+import java.beans.IntrospectionException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import com.vaadin.ui.Component;
+import com.vaadin.util.ReflectTools;
+
+/**
+ * Binder utility that binds member fields of a design class instance to given
+ * component instances. Only fields of type {@link Component} are bound
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class FieldBinder {
+
+ // the design class instance (the instance containing the bound fields)
+ private Component bindTarget;
+ // mapping between field names and Fields
+ private Map<String, Field> fieldMap = new HashMap<String, Field>();
+
+ /**
+ * Creates a new instance of LayoutFieldBinder
+ *
+ * @param design
+ * the design class instance containing the bound fields
+ * @throws IntrospectionException
+ * if the given design class can not be introspected
+ */
+ public FieldBinder(Component design) throws IntrospectionException {
+ if (design == null) {
+ throw new IllegalArgumentException("The design must not be null");
+ }
+ bindTarget = design;
+ resolveFields();
+ }
+
+ /**
+ * Returns true if all the fields assignable to Component are bound
+ *
+ * @return true if all the fields assignable to Component are bound
+ */
+ public boolean isAllFieldsBound() throws FieldBindingException {
+ boolean success = true;
+ for (Field f : fieldMap.values()) {
+ try {
+ Object value = ReflectTools.getJavaFieldValue(bindTarget, f);
+ if (value == null) {
+ success = false;
+ getLogger().severe("Found unbound field :" + f.getName());
+ }
+ } catch (IllegalArgumentException e) {
+ throw new FieldBindingException("Could not get field value", e);
+ } catch (IllegalAccessException e) {
+ throw new FieldBindingException("Could not get field value", e);
+ } catch (InvocationTargetException e) {
+ throw new FieldBindingException("Could not get field value", e);
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Resolves the fields of the design class instance
+ */
+ private void resolveFields() {
+ for (Field memberField : getFieldsInDeclareOrder(bindTarget.getClass())) {
+ if (Component.class.isAssignableFrom(memberField.getType())) {
+ fieldMap.put(memberField.getName(), memberField);
+ }
+ }
+ }
+
+ /**
+ * Tries to bind the given {@link Component} instance to a member field of
+ * the bind target. The name of the bound field is constructed based on the
+ * id or caption of the instance, depending on which one is defined. If a
+ * field is already bound (not null), {@link FieldBindingException} is
+ * thrown.
+ *
+ * @param instance
+ * the instance to be bound to a field
+ * @return true on success, otherwise false
+ * @throws FieldBindingException
+ * if error occurs when trying to bind the instance to a field
+ */
+ public boolean bindField(Component instance) {
+ return bindField(instance, null);
+ }
+
+ /**
+ * Tries to bind the given {@link Component} instance to a member field of
+ * the bind target. The fields are matched based on localId, id and caption.
+ * If a field is already bound (not null), {@link FieldBindingException} is
+ * thrown.
+ *
+ * @param instance
+ * the instance to be bound to a field
+ * @param localId
+ * the localId used for mapping the field to an instance field
+ * @return true on success
+ * @throws FieldBindingException
+ * if error occurs when trying to bind the instance to a field
+ */
+ public boolean bindField(Component instance, String localId) {
+ // check that the field exists, is correct type and is null
+ boolean success = bindFieldByIdentifier(localId, instance);
+ if (!success) {
+ success = bindFieldByIdentifier(instance.getId(), instance);
+ }
+ if (!success) {
+ success = bindFieldByIdentifier(instance.getCaption(), instance);
+ }
+ if (!success) {
+ String idInfo = "localId: " + localId + " id: " + instance.getId()
+ + " caption: " + instance.getCaption();
+ getLogger().info(
+ "Could not bind component to a field "
+ + instance.getClass().getName() + " " + idInfo);
+ }
+ return success;
+ }
+
+ /**
+ * Tries to bind the given {@link Component} instance to a member field of
+ * the bind target. The field is matched based on the given identifier. If a
+ * field is already bound (not null), {@link FieldBindingException} is
+ * thrown.
+ *
+ * @param identifier
+ * the identifier for the field.
+ * @param instance
+ * the instance to be bound to a field
+ * @return true on success
+ * @throws FieldBindingException
+ * if error occurs when trying to bind the instance to a field
+ */
+ private boolean bindFieldByIdentifier(String identifier, Component instance) {
+ try {
+ // create and validate field name
+ String fieldName = asFieldName(identifier);
+ if (fieldName.length() == 0) {
+ return false;
+ }
+ // validate that the field can be found
+ Field field = fieldMap.get(fieldName);
+ if (field == null) {
+ getLogger().fine(
+ "No field was found by identifier " + identifier);
+ return false;
+ }
+ // validate that the field is not set
+ Object fieldValue = ReflectTools.getJavaFieldValue(bindTarget,
+ field);
+ if (fieldValue != null) {
+ getLogger().severe(
+ "The field with identifier \"" + identifier
+ + "\" already mapped");
+ throw new FieldBindingException(
+ "Duplicate identifier found for a field");
+ }
+ // set the field value
+ ReflectTools.setJavaFieldValue(bindTarget, field, instance);
+ return true;
+ } catch (IllegalAccessException e) {
+ throw new FieldBindingException("Field binding failed", e);
+ } catch (IllegalArgumentException e) {
+ throw new FieldBindingException("Field binding failed", e);
+ } catch (InvocationTargetException e) {
+ throw new FieldBindingException("Field binding failed", e);
+ }
+ }
+
+ /**
+ * Converts the given identifier to a valid field name by stripping away
+ * illegal character and setting the first letter of the name to lowercase
+ *
+ * @param identifier
+ * the identifier to be converted to field name
+ * @return the field name corresponding the identifier
+ */
+ private static String asFieldName(String identifier) {
+ if (identifier == null) {
+ return "";
+ }
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < identifier.length(); i++) {
+ char character = identifier.charAt(i);
+ if (Character.isJavaIdentifierPart(character)) {
+ result.append(character);
+ }
+ }
+ // lowercase first letter
+ if (result.length() > 0 && Character.isLetter(result.charAt(0))) {
+ result.setCharAt(0, Character.toLowerCase(result.charAt(0)));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns an array containing Field objects reflecting all the fields of
+ * the class or interface represented by this Class object. The elements in
+ * the array returned are sorted in declare order. The fields in
+ * superclasses are excluded.
+ *
+ * @param searchClass
+ * the class to be scanned for fields
+ * @return the list of fields in this class
+ */
+ protected static List<java.lang.reflect.Field> getFieldsInDeclareOrder(
+ Class<?> searchClass) {
+ ArrayList<java.lang.reflect.Field> memberFieldsInOrder = new ArrayList<java.lang.reflect.Field>();
+
+ for (java.lang.reflect.Field memberField : searchClass
+ .getDeclaredFields()) {
+ memberFieldsInOrder.add(memberField);
+ }
+ return memberFieldsInOrder;
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(FieldBinder.class.getName());
+ }
+
+}