summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin
diff options
context:
space:
mode:
authorMatti Hosio <mhosio@vaadin.com>2014-12-02 10:28:25 +0200
committerMatti Hosio <mhosio@vaadin.com>2014-12-02 10:28:25 +0200
commit070b7863cdbf121c74f7486e3e4e89cf57f0c427 (patch)
treed9b0b965d3ee18522812b2be86619fdb6e1798d1 /server/src/com/vaadin
parentd54337ec3259fce95142227935e4471f3712ac68 (diff)
downloadvaadin-framework-070b7863cdbf121c74f7486e3e4e89cf57f0c427.tar.gz
vaadin-framework-070b7863cdbf121c74f7486e3e4e89cf57f0c427.zip
Support for synchronizing to design in AbstractComponent
Diffstat (limited to 'server/src/com/vaadin')
-rw-r--r--server/src/com/vaadin/ui/AbstractComponent.java39
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java504
2 files changed, 408 insertions, 135 deletions
diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java
index 78004d0eca..27d9017374 100644
--- a/server/src/com/vaadin/ui/AbstractComponent.java
+++ b/server/src/com/vaadin/ui/AbstractComponent.java
@@ -33,9 +33,12 @@ import com.vaadin.event.ActionManager;
import com.vaadin.event.ConnectorActionManager;
import com.vaadin.event.ShortcutListener;
import com.vaadin.server.AbstractClientConnector;
+import com.vaadin.server.AbstractErrorMessage.ContentMode;
import com.vaadin.server.ComponentSizeValidator;
import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.ErrorMessage.ErrorLevel;
import com.vaadin.server.Resource;
+import com.vaadin.server.UserError;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ComponentConstants;
@@ -911,19 +914,23 @@ public abstract class AbstractComponent extends AbstractClientConnector
@Override
public void synchronizeFromDesign(Node design, DesignContext designContext) {
Attributes attr = design.attributes();
- DesignSynchronizable def = designContext.getDefaultInstance(this
+ AbstractComponent def = designContext.getDefaultInstance(this
.getClass());
// handle default attributes
- for (String property : getDefaultAttributes()) {
- String value = null;
- if (attr.hasKey(property)) {
- value = attr.get(property);
- }
- DesignAttributeHandler.assignAttribute(this, property, value, def);
+ for (String attribute : getDefaultAttributes()) {
+ DesignAttributeHandler.readAttribute(this, attribute, attr, def);
}
// handle width and height
- DesignAttributeHandler.assignWidth(this, attr, def);
- DesignAttributeHandler.assignHeight(this, attr, def);
+ DesignAttributeHandler.readWidth(this, attr, def);
+ DesignAttributeHandler.readHeight(this, attr, def);
+ // handle component error
+ if (attr.hasKey("error")) {
+ UserError error = new UserError(attr.get("error"),
+ ContentMode.HTML, ErrorLevel.ERROR);
+ setComponentError(error);
+ } else {
+ setComponentError(def.getComponentError());
+ }
}
/**
@@ -955,9 +962,21 @@ public abstract class AbstractComponent extends AbstractClientConnector
*/
@Override
public void synchronizeToDesign(Node design, DesignContext designContext) {
+ // clear node contents
+ DesignAttributeHandler.clearNode(design);
AbstractComponent def = designContext.getDefaultInstance(this
.getClass());
-
+ Attributes attr = design.attributes();
+ // handle default attributes
+ for (String attribute : getDefaultAttributes()) {
+ DesignAttributeHandler.writeAttribute(this, attribute, attr, def);
+ }
+ // handle size
+ DesignAttributeHandler.writeSize(this, attr, def);
+ // handle component error
+ if (getComponentError() != null) {
+ attr.put("error", getComponentError().getFormattedHtmlMessage());
+ }
}
/*
diff --git a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java
index 3b58e0cbc9..9faf3fb0a5 100644
--- a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java
+++ b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java
@@ -17,6 +17,7 @@ package com.vaadin.ui.declarative;
import java.io.File;
import java.lang.reflect.Method;
+import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -24,19 +25,24 @@ import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.apache.commons.lang3.LocaleUtils;
+import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Node;
import com.vaadin.server.ExternalResource;
import com.vaadin.server.FileResource;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.Resource;
+import com.vaadin.server.Sizeable;
import com.vaadin.server.ThemeResource;
+import com.vaadin.ui.Component;
import com.vaadin.ui.DesignSynchronizable;
/**
* Default attribute handler implementation used when parsing designs to
- * component trees. Handles all the component attributes that do require custom
- * handling.
+ * component trees. Handles all the component attributes that do not require
+ * custom handling.
*
* @since 7.4
* @author Vaadin Ltd
@@ -48,6 +54,270 @@ public class DesignAttributeHandler {
}
/**
+ * Clears the children and attributes of the given node
+ *
+ * @since 7.4
+ * @param design
+ * the node to be cleared
+ */
+ public static void clearNode(Node design) {
+ Attributes attr = design.attributes();
+ for (Attribute a : attr.asList()) {
+ attr.remove(a.getKey());
+ }
+ List<Node> children = new ArrayList<Node>();
+ children.addAll(design.childNodes());
+ for (Node node : children) {
+ node.remove();
+ }
+ }
+
+ /**
+ * Assigns the specified design attribute to the given component. If the
+ * attribute is not present, (value is null) the corresponding property is
+ * got from the <code>defaultInstance</code>
+ *
+ * @since 7.4
+ * @param component
+ * the component to which the attribute should be set
+ * @param attribute
+ * the attribute to be set
+ * @param attributes
+ * the attribute map. If the attributes does not contain the
+ * requested attribute, the value is retrieved from the
+ * <code> defaultInstance</code>
+ * @param defaultInstance
+ * the default instance of the class for fetching the default
+ * values
+ */
+ public static boolean readAttribute(DesignSynchronizable component,
+ String attribute, Attributes attributes,
+ DesignSynchronizable defaultInstance) {
+
+ String value = null;
+ if (attributes.hasKey(attribute)) {
+ value = attributes.get(attribute);
+ }
+
+ // find setter for the property
+ boolean success = false;
+ try {
+ Method setter = findSetterForAttribute(component.getClass(),
+ attribute);
+ if (setter == null) {
+ // if we don't have the setter, there is no point in continuing
+ success = false;
+ } else if (value != null) {
+ // we have a value from design attributes, let's use that
+ Object param = fromAttributeValue(
+ setter.getParameterTypes()[0], value);
+ setter.invoke(component, param);
+ success = true;
+ } else {
+ // otherwise find the getter for the property
+ Method getter = findGetterForSetter(component.getClass(),
+ setter);
+ if (getter != null) {
+ // read the default value from defaults
+ Object defaultValue = getter.invoke(defaultInstance);
+ setter.invoke(component, defaultValue);
+ success = true;
+ }
+ }
+ } catch (Exception e) {
+ getLogger().log(Level.WARNING,
+ "Failed to set attribute " + attribute, e);
+ }
+ if (!success) {
+ getLogger().info(
+ "property " + attribute
+ + " ignored by default attribute handler");
+ }
+ return success;
+ }
+
+ /**
+ * Searches for supported setter and getter types from the specified class
+ * and returns the list of corresponding design attributes
+ *
+ * @since 7.4
+ * @param clazz
+ * the class scanned for setters
+ * @return the list of supported design attributes
+ */
+ public static List<String> findSupportedAttributes(Class<?> clazz) {
+ List<String> attributes = new ArrayList<String>();
+ for (Method method : clazz.getMethods()) {
+ // check that the method is setter, has single argument of supported
+ // type and has a corresponding getter
+ if (method.getName().startsWith("set")
+ && method.getParameterTypes().length == 1
+ && isSupported(method.getParameterTypes()[0])
+ && findGetterForSetter(clazz, method) != null) {
+ attributes.add(toAttributeName(method.getName()));
+ // TODO: we might want to cache the getters and setters?
+ }
+ }
+ return attributes;
+ }
+
+ /**
+ * Assigns the width for the component based on the design attributes
+ *
+ * @since 7.4
+ * @param component
+ * the component to assign the width
+ * @param attributes
+ * the attributes to be used for determining the width
+ * @param defaultInstance
+ * the default instance of the class for fetching the default
+ * value
+ */
+ public static void readWidth(DesignSynchronizable component,
+ Attributes attributes, DesignSynchronizable defaultInstance) {
+ if (attributes.hasKey("width-auto") || attributes.hasKey("size-auto")) {
+ component.setWidth(null);
+ } else if (attributes.hasKey("width-full")
+ || attributes.hasKey("size-full")) {
+ component.setWidth("100%");
+ } else if (attributes.hasKey("width")) {
+ component.setWidth(attributes.get("width"));
+ } else {
+ component.setWidth(defaultInstance.getWidth(),
+ defaultInstance.getWidthUnits());
+ }
+ }
+
+ /**
+ * Assigns the height for the component based on the design attributes
+ *
+ * @since 7.4
+ * @param component
+ * the component to assign the height
+ * @param attributes
+ * the attributes to be used for determining the height
+ * @param defaultInstance
+ * the default instance of the class for fetching the default
+ * value
+ */
+ public static void readHeight(DesignSynchronizable component,
+ Attributes attributes, DesignSynchronizable defaultInstance) {
+ if (attributes.hasKey("height-auto") || attributes.hasKey("size-auto")) {
+ component.setHeight(null);
+ } else if (attributes.hasKey("height-full")
+ || attributes.hasKey("size-full")) {
+ component.setHeight("100%");
+ } else if (attributes.hasKey("height")) {
+ component.setHeight(attributes.get("height"));
+ } else {
+ component.setHeight(defaultInstance.getHeight(),
+ defaultInstance.getHeightUnits());
+ }
+ }
+
+ /**
+ * Writes the specified attribute to the design if it differs from the
+ * default value got from the <code> defaultInstance <code>
+ *
+ * @since 7.4
+ * @param component
+ * the component used to get the attribute value
+ * @param attribute
+ * the key for the attribute
+ * @param attr
+ * the attribute list where the attribute will be written
+ * @param defaultInstance
+ * the default instance for comparing default values
+ */
+ public static void writeAttribute(DesignSynchronizable component,
+ String attribute, Attributes attr,
+ DesignSynchronizable defaultInstance) {
+ Method getter = findGetterForAttribute(component.getClass(), attribute);
+ if (getter == null) {
+ getLogger().warning(
+ "Could not find getter for attribute " + attribute);
+ } else {
+ try {
+ // compare the value with default value
+ Object value = getter.invoke(component);
+ Object defaultValue = getter.invoke(defaultInstance);
+ // if the values are not equal, write the data
+ if (value == defaultValue
+ || (value != null && value.equals(defaultValue))) {
+ } else {
+ String attributeValue = toAttributeValue(
+ getter.getReturnType(), value);
+ attr.put(attribute, attributeValue);
+ }
+ } catch (Exception e) {
+ getLogger()
+ .log(Level.SEVERE,
+ "Failed to invoke getter for attribute "
+ + attribute, e);
+ }
+ }
+ }
+
+ /**
+ * Writes the size related attributes for the component if they differ from
+ * the defaults
+ *
+ * @since 7.4
+ * @param component
+ * the component
+ * @param attributes
+ * the attribute map where the attribute are written
+ * @param defaultInstance
+ * the default instance of the class for fetching the default
+ * values
+ */
+ public static void writeSize(DesignSynchronizable component,
+ Attributes attributes, DesignSynchronizable defaultInstance) {
+ if (areEqualSize(component, defaultInstance)) {
+ // we have default values -> ignore
+ return;
+ }
+ boolean widthFull = component.getWidth() == 100f
+ && component.getWidthUnits().equals(Sizeable.Unit.PERCENTAGE);
+ boolean heightFull = component.getHeight() == 100f
+ && component.getHeightUnits().equals(Sizeable.Unit.PERCENTAGE);
+ boolean widthAuto = component.getWidth() == -1;
+ boolean heightAuto = component.getHeight() == -1;
+
+ // first try the full shorthands
+ if (widthFull && heightFull) {
+ attributes.put("size-full", "true");
+ } else if (widthAuto && heightAuto) {
+ attributes.put("size-auto", "true");
+ } else {
+ // handle width
+ if (!areEqualWidth(component, defaultInstance)) {
+ if (widthFull) {
+ attributes.put("width-full", "true");
+ } else if (widthAuto) {
+ attributes.put("width-auto", "true");
+ } else {
+ DecimalFormat fmt = new DecimalFormat();
+ attributes.put("width", fmt.format(component.getWidth())
+ + component.getWidthUnits().getSymbol());
+ }
+ }
+ if (!areEqualHeight(component, defaultInstance)) {
+ // handle height
+ if (heightFull) {
+ attributes.put("height-full", "true");
+ } else if (heightAuto) {
+ attributes.put("height-auto", "true");
+ } else {
+ DecimalFormat fmt = new DecimalFormat();
+ attributes.put("height", fmt.format(component.getHeight())
+ + component.getHeightUnits().getSymbol());
+ }
+ }
+ }
+ }
+
+ /**
* Returns the design attribute name corresponding the given method name.
* For example given a method name <code>setPrimaryStyleName</code> the
* return value would be <code>primary-style-name</code>
@@ -60,7 +330,7 @@ public class DesignAttributeHandler {
private static String toAttributeName(String methodName) {
String[] words = methodName.split("(?<!^)(?=[A-Z])");
StringBuilder builder = new StringBuilder();
- // ignore first toget ("set")
+ // ignore first token ("set")
for (int i = 1; i < words.length; i++) {
if (builder.length() > 0) {
builder.append("-");
@@ -93,6 +363,31 @@ public class DesignAttributeHandler {
}
/**
+ * Returns a list of possible getter method names for the corresponding
+ * design attribute name.
+ *
+ * @since 7.4
+ * @param designAttributeName
+ * the design attribute name
+ * @return the list of getter method names corresponding the given design
+ * attribute name
+ */
+ private static List<String> toGetterNames(String designAttributeName) {
+ String[] parts = designAttributeName.split("-");
+ StringBuilder builder = new StringBuilder();
+ for (String part : parts) {
+ builder.append(part.substring(0, 1).toUpperCase());
+ builder.append(part.substring(1));
+ }
+ String propertyName = builder.toString();
+ List<String> result = new ArrayList<String>();
+ result.add("get" + propertyName);
+ result.add("is" + propertyName);
+ result.add("has" + propertyName);
+ return result;
+ }
+
+ /**
* Parses the given attribute value to specified target type
*
* @since 7.4
@@ -102,7 +397,7 @@ public class DesignAttributeHandler {
* the parsed value
* @return the object of specified target type
*/
- private static Object parseAttributeValue(Class<?> targetType, String value) {
+ private static Object fromAttributeValue(Class<?> targetType, String value) {
if (targetType == String.class) {
return value;
}
@@ -134,7 +429,7 @@ public class DesignAttributeHandler {
return Double.valueOf(value);
}
if (targetType == Locale.class) {
- return new Locale(value);
+ return LocaleUtils.toLocale(value);
}
if (targetType == Resource.class) {
return parseResource(value);
@@ -142,6 +437,37 @@ public class DesignAttributeHandler {
return null;
}
+ /**
+ * Serializes the given value to valid design attribute representation
+ * (string)
+ *
+ * @since 7.4
+ * @param sourceType
+ * the type of the value
+ * @param value
+ * the value to be serialized
+ * @return the given value as design attribute representation
+ */
+ private static String toAttributeValue(Class<?> sourceType, Object value) {
+ if (sourceType == Locale.class) {
+ return value != null ? ((Locale) value).toString() : null;
+ } else if (sourceType == Resource.class) {
+ if (value instanceof ExternalResource) {
+ return ((ExternalResource) value).getURL();
+ } else if (value instanceof ThemeResource) {
+ return "theme://" + ((ThemeResource) value).getResourceId();
+ } else if (value instanceof FontAwesome) {
+ return "font://" + ((FontAwesome) value).name();
+ } else if (value instanceof FileResource) {
+ return ((FileResource) value).getSourceFile().getPath();
+ } else {
+ return null;
+ }
+ } else {
+ return value.toString();
+ }
+ }
+
private static Resource parseResource(String value) {
if (value.startsWith("http://")) {
return new ExternalResource("value");
@@ -164,7 +490,7 @@ public class DesignAttributeHandler {
* the setter that is used to find the matching getter
* @return the matching getter or null if not found
*/
- private static Method findGetter(Class<?> clazz, Method setter) {
+ private static Method findGetterForSetter(Class<?> clazz, Method setter) {
String propertyName = setter.getName().substring(3);
Class<?> returnType = setter.getParameterTypes()[0];
for (Method method : clazz.getMethods()) {
@@ -174,7 +500,6 @@ public class DesignAttributeHandler {
return method;
}
}
- getLogger().warning("Could not find getter for " + setter.getName());
return null;
}
@@ -212,6 +537,33 @@ public class DesignAttributeHandler {
return null;
}
+ /**
+ * Returns a getter that can be used for reading the value of the given
+ * design attribute from the class
+ *
+ * @since 7.4
+ * @param clazz
+ * the class that is scanned for getters
+ * @param attribute
+ * the design attribute to find getter for
+ * @return the getter method or null if not found
+ */
+ private static Method findGetterForAttribute(Class<?> clazz,
+ String attribute) {
+ List<String> methodNames = toGetterNames(attribute);
+ for (Method method : clazz.getMethods()) {
+ if (methodNames.contains(method.getName())
+ && method.getParameterTypes().length == 0
+ && isSupported(method.getReturnType())) {
+ return method;
+ }
+ }
+ getLogger().warning(
+ "Could not find getter with supported return type for attribute "
+ + attribute);
+ return null;
+ }
+
private static final List<Class<?>> supportedClasses = Arrays
.asList(new Class<?>[] { String.class, Boolean.class,
Integer.class, Byte.class, Short.class, Long.class,
@@ -235,139 +587,41 @@ public class DesignAttributeHandler {
}
/**
- * Searches for supported setter types from the specified class and returns
- * the list of corresponding design attributes
+ * Test if the given components have equal width
*
* @since 7.4
- * @param clazz
- * the class scanned for setters
- * @return the list of supported design attributes
+ * @param comp1
+ * @param comp2
+ * @return true if the widths of the components are equal
*/
- public static List<String> findSupportedAttributes(Class<?> clazz) {
- List<String> attributes = new ArrayList<String>();
- // TODO: should we check that we have the corresponding getter too?
- // Otherwise we can not revert to default value. On the other hand, do
- // we want that leaving the getter out will prevent reading the
- // attribute from the design?
- for (Method method : clazz.getMethods()) {
- if (method.getName().startsWith("set")
- && method.getParameterTypes().length == 1
- && isSupported(method.getParameterTypes()[0])) {
- attributes.add(toAttributeName(method.getName()));
- }
- }
- return attributes;
+ private static boolean areEqualWidth(Component comp1, Component comp2) {
+ return comp1.getWidth() == comp2.getWidth()
+ && comp1.getWidthUnits().equals(comp2.getWidthUnits());
}
/**
- * Assigns the specified design attribute to the given component. If the
- * attribute is not present, (value is null) the corresponding property is
- * got from the <code>defaultInstance</code>
+ * Tests if the given components have equal height
*
* @since 7.4
- * @param component
- * the component to which the attribute should be set
- * @param attribute
- * the attribute to be set
- * @param value
- * the value for the attribute. If null, the corresponding
- * property is got from the <code> defaultInstance</code>
- * @param defaultInstance
- * the default instance of the class for fetching the default
- * values
+ * @param comp1
+ * @param comp2
+ * @return true if the heights of the components are equal
*/
- public static boolean assignAttribute(DesignSynchronizable component,
- String attribute, String value, DesignSynchronizable defaultInstance) {
- getLogger().info("Assigning attribute " + attribute + " -> " + value);
- // find setter for the property
- boolean success = false;
- try {
- Method setter = findSetterForAttribute(component.getClass(),
- attribute);
- if (setter == null) {
- // if we don't have the setter, there is no point in continuing
- success = false;
- } else if (value != null) {
- // we have a value from design attributes, let's use that
- getLogger().info("Setting the value from attributes");
- Object param = parseAttributeValue(
- setter.getParameterTypes()[0], value);
- setter.invoke(component, param);
- success = true;
- } else {
- // otherwise find the getter for the property
- Method getter = findGetter(component.getClass(), setter);
- if (getter != null) {
- // read the default value from defaults
- getLogger().info("Setting the default value");
- Object defaultValue = getter.invoke(defaultInstance);
- setter.invoke(component, defaultValue);
- success = true;
- }
- }
- } catch (Exception e) {
- getLogger().log(Level.WARNING,
- "Failed to set attribute " + attribute, e);
- }
- if (!success) {
- getLogger().info(
- "property " + attribute
- + " ignored by default attribute handler");
- }
- return success;
+ private static boolean areEqualHeight(Component comp1, Component comp2) {
+ return comp1.getHeight() == comp2.getHeight()
+ && comp1.getHeightUnits().equals(comp2.getHeightUnits());
}
/**
- * Assigns the width for the component based on the design attributes
+ * Test if the given components have equal size
*
* @since 7.4
- * @param component
- * the component to assign the width
- * @param attributes
- * the attributes to be used for determining the width
- * @param defaultInstance
- * the default instance of the class for fetching the default
- * value
+ * @param comp1
+ * @param comp2
+ * @return true if the widht and height of the components are equal
*/
- public static void assignWidth(DesignSynchronizable component,
- Attributes attributes, DesignSynchronizable defaultInstance) {
- if (attributes.hasKey("width-auto") || attributes.hasKey("size-auto")) {
- component.setWidth(null);
- } else if (attributes.hasKey("width-full")
- || attributes.hasKey("size-full")) {
- component.setWidth("100%");
- } else if (attributes.hasKey("width")) {
- component.setWidth(attributes.get("width"));
- } else {
- component.setWidth(defaultInstance.getWidth(),
- defaultInstance.getWidthUnits());
- }
+ private static boolean areEqualSize(Component comp1, Component comp2) {
+ return areEqualWidth(comp1, comp2) && areEqualHeight(comp1, comp2);
}
- /**
- * Assigns the height for the component based on the design attributes
- *
- * @since 7.4
- * @param component
- * the component to assign the height
- * @param attributes
- * the attributes to be used for determining the height
- * @param defaultInstance
- * the default instance of the class for fetching the default
- * value
- */
- public static void assignHeight(DesignSynchronizable component,
- Attributes attributes, DesignSynchronizable defaultInstance) {
- if (attributes.hasKey("height-auto") || attributes.hasKey("size-auto")) {
- component.setHeight(null);
- } else if (attributes.hasKey("height-full")
- || attributes.hasKey("size-full")) {
- component.setHeight("100%");
- } else if (attributes.hasKey("height")) {
- component.setHeight(attributes.get("height"));
- } else {
- component.setHeight(defaultInstance.getHeight(),
- defaultInstance.getHeightUnits());
- }
- }
}