diff options
author | Matti Hosio <mhosio@vaadin.com> | 2014-12-02 10:28:25 +0200 |
---|---|---|
committer | Matti Hosio <mhosio@vaadin.com> | 2014-12-02 10:28:25 +0200 |
commit | 070b7863cdbf121c74f7486e3e4e89cf57f0c427 (patch) | |
tree | d9b0b965d3ee18522812b2be86619fdb6e1798d1 /server/src/com/vaadin | |
parent | d54337ec3259fce95142227935e4471f3712ac68 (diff) | |
download | vaadin-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.java | 39 | ||||
-rw-r--r-- | server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java | 504 |
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()); - } - } } |