diff options
author | Matti Hosio <mhosio@vaadin.com> | 2014-12-04 11:19:20 +0200 |
---|---|---|
committer | Matti Hosio <mhosio@vaadin.com> | 2014-12-04 13:00:28 +0200 |
commit | 9730b6def11af6f0bcb1e8b0535e0ec081ac84dd (patch) | |
tree | 8fc0165a79dc6e8f972bbc3bb6e0961dc19f13c3 /server/src/com | |
parent | 405ae14a6bc561fc1fc431929d9b132415a6b3bd (diff) | |
download | vaadin-framework-9730b6def11af6f0bcb1e8b0535e0ec081ac84dd.tar.gz vaadin-framework-9730b6def11af6f0bcb1e8b0535e0ec081ac84dd.zip |
Declarative feature improvements (#7749)
Change-Id: Ie04db36ad08f686bf6b173241652836f639d3bd9
Diffstat (limited to 'server/src/com')
3 files changed, 316 insertions, 318 deletions
diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java index b1927f8580..b5721a0bba 100644 --- a/server/src/com/vaadin/ui/AbstractComponent.java +++ b/server/src/com/vaadin/ui/AbstractComponent.java @@ -19,13 +19,19 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.StringTokenizer; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; @@ -38,6 +44,7 @@ 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.Sizeable; import com.vaadin.server.UserError; import com.vaadin.server.VaadinSession; import com.vaadin.shared.AbstractComponentState; @@ -922,8 +929,7 @@ public abstract class AbstractComponent extends AbstractClientConnector DesignAttributeHandler.readAttribute(this, attribute, attr, def); } // handle width and height - DesignAttributeHandler.readWidth(this, attr, def); - DesignAttributeHandler.readHeight(this, attr, def); + readSize(attr, def); // handle component error if (attr.hasKey("error")) { UserError error = new UserError(attr.get("error"), @@ -932,28 +938,187 @@ public abstract class AbstractComponent extends AbstractClientConnector } else { setComponentError(def.getComponentError()); } + // check for unsupported attributes + Set<String> supported = new HashSet<String>(); + supported.addAll(getDefaultAttributes()); + supported.addAll(getCustomAttributes()); + for (Attribute a : attr) { + if (!a.getKey().startsWith(":") && !supported.contains(a.getKey())) { + getLogger().info( + "Unsupported attribute found when synchronizing from design : " + + a.getKey()); + } + } + } + + /** + * Synchronizes the size of this component from the given design attributes. + * If the attributes do not contain relevant size information, defaults is + * consulted. + * + * @param attributes + * the design attributes + * @param defaultInstance + * instance of the class that has default sizing. + */ + private void readSize(Attributes attributes, + AbstractComponent defaultInstance) { + // read width + if (attributes.hasKey("width-auto") || attributes.hasKey("size-auto")) { + this.setWidth(null); + } else if (attributes.hasKey("width-full") + || attributes.hasKey("size-full")) { + this.setWidth("100%"); + } else if (attributes.hasKey("width")) { + this.setWidth(attributes.get("width")); + } else { + this.setWidth(defaultInstance.getWidth(), + defaultInstance.getWidthUnits()); + } + + // read height + if (attributes.hasKey("height-auto") || attributes.hasKey("size-auto")) { + this.setHeight(null); + } else if (attributes.hasKey("height-full") + || attributes.hasKey("size-full")) { + this.setHeight("100%"); + } else if (attributes.hasKey("height")) { + this.setHeight(attributes.get("height")); + } else { + this.setHeight(defaultInstance.getHeight(), + defaultInstance.getHeightUnits()); + } + } + + /** + * Writes the size related attributes for the component if they differ from + * the defaults + * + * @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 + */ + private void writeSize(Attributes attributes, + DesignSynchronizable defaultInstance) { + if (hasEqualSize(defaultInstance)) { + // we have default values -> ignore + return; + } + boolean widthFull = getWidth() == 100f + && getWidthUnits().equals(Sizeable.Unit.PERCENTAGE); + boolean heightFull = getHeight() == 100f + && getHeightUnits().equals(Sizeable.Unit.PERCENTAGE); + boolean widthAuto = getWidth() == -1; + boolean heightAuto = 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 (!hasEqualWidth(defaultInstance)) { + if (widthFull) { + attributes.put("width-full", "true"); + } else if (widthAuto) { + attributes.put("width-auto", "true"); + } else { + String widthString = DesignAttributeHandler + .formatDesignAttribute(getWidth()) + + getWidthUnits().getSymbol(); + attributes.put("width", widthString); + + } + } + if (!hasEqualHeight(defaultInstance)) { + // handle height + if (heightFull) { + attributes.put("height-full", "true"); + } else if (heightAuto) { + attributes.put("height-auto", "true"); + } else { + String heightString = DesignAttributeHandler + .formatDesignAttribute(getHeight()) + + getHeightUnits().getSymbol(); + attributes.put("height", heightString); + } + } + } } /** - * Returns the list of attributes that do not require custom handling when - * synchronizing from design. These are typically attributes of some + * Test if the given component has equal width with this instance + * + * @param component + * the component for the width comparison + * @return true if the widths are equal + */ + private boolean hasEqualWidth(Component component) { + return getWidth() == component.getWidth() + && getWidthUnits().equals(component.getWidthUnits()); + } + + /** + * Test if the given component has equal height with this instance + * + * @param component + * the component for the height comparison + * @return true if the heights are equal + */ + private boolean hasEqualHeight(Component component) { + return getHeight() == component.getHeight() + && getHeightUnits().equals(component.getHeightUnits()); + } + + /** + * Test if the given components has equal size with this instance + * + * @param component + * the component for the size comparison + * @return true if the sizes are equal + */ + private boolean hasEqualSize(Component component) { + return hasEqualWidth(component) && hasEqualHeight(component); + } + + /** + * Returns a collection of attributes that do not require custom handling + * when synchronizing from design. These are typically attributes of some * primitive type. The default implementation searches setters with * primitive values * - * @since 7.4 - * @return the list of attributes that can be synchronized from design using - * the default approach. - */ - protected List<String> getDefaultAttributes() { - List<String> attributes = DesignAttributeHandler - .findSupportedAttributes(this.getClass()); - // we want to handle width and height in a custom way - attributes.remove("width"); - attributes.remove("height"); - attributes.remove("debug-id"); + * @return a collection of attributes that can be synchronized from design + * using the default approach. + */ + private Collection<String> getDefaultAttributes() { + Collection<String> attributes = DesignAttributeHandler + .getSupportedAttributes(this.getClass()); + attributes.removeAll(getCustomAttributes()); return attributes; } + /** + * Returns a collection of attributes that should not be handled by the + * basic implementation of the {@link synhronizeFromDesign} and + * {@link synchronizeToDesign} methods. Typically these are handled in a + * custom way in the overridden versions of the above methods + * + * @return the collection of attributes that are not handled by the basic + * implementation + */ + protected Collection<String> getCustomAttributes() { + return new ArrayList<String>(Arrays.asList(customAttributes)); + } + + private static final String[] customAttributes = new String[] { "width", + "height", "debug-id", "error", "width-auto", "height-auto", + "width-full", "height-full", "size-auto", "size-full" }; + /* * (non-Javadoc) * @@ -963,8 +1128,8 @@ public abstract class AbstractComponent extends AbstractClientConnector */ @Override public void synchronizeToDesign(Element design, DesignContext designContext) { - // clear node contents - DesignAttributeHandler.clearNode(design); + // clear element contents + DesignAttributeHandler.clearElement(design); AbstractComponent def = designContext.getDefaultInstance(this .getClass()); Attributes attr = design.attributes(); @@ -973,10 +1138,14 @@ public abstract class AbstractComponent extends AbstractClientConnector DesignAttributeHandler.writeAttribute(this, attribute, attr, def); } // handle size - DesignAttributeHandler.writeSize(this, attr, def); + writeSize(attr, def); // handle component error - if (getComponentError() != null) { - attr.put("error", getComponentError().getFormattedHtmlMessage()); + String errorMsg = getComponentError() != null ? getComponentError() + .getFormattedHtmlMessage() : null; + String defErrorMsg = def.getComponentError() != null ? def + .getComponentError().getFormattedHtmlMessage() : null; + if (!SharedUtil.equals(errorMsg, defErrorMsg)) { + attr.put("error", errorMsg); } } @@ -1096,4 +1265,8 @@ public abstract class AbstractComponent extends AbstractClientConnector } return false; } + + private static final Logger getLogger() { + return Logger.getLogger(AbstractComponent.class.getName()); + } } diff --git a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java index 1e5ff47742..423b2fb60b 100644 --- a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java +++ b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java @@ -15,29 +15,37 @@ */ package com.vaadin.ui.declarative; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.io.File; import java.lang.reflect.Method; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; 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.Element; 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.shared.util.SharedUtil; import com.vaadin.ui.DesignSynchronizable; /** @@ -54,14 +62,16 @@ public class DesignAttributeHandler { return Logger.getLogger(DesignAttributeHandler.class.getName()); } + private static Map<Class, AttributeCacheEntry> cache = Collections + .synchronizedMap(new HashMap<Class, AttributeCacheEntry>()); + /** - * Clears the children and attributes of the given node + * Clears the children and attributes of the given element * - * @since 7.4 * @param design - * the node to be cleared + * the element to be cleared */ - public static void clearNode(Node design) { + public static void clearElement(Element design) { Attributes attr = design.attributes(); for (Attribute a : attr.asList()) { attr.remove(a.getKey()); @@ -78,7 +88,6 @@ public class DesignAttributeHandler { * 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 @@ -94,13 +103,15 @@ public class DesignAttributeHandler { public static boolean readAttribute(DesignSynchronizable component, String attribute, Attributes attributes, DesignSynchronizable defaultInstance) { - String value = null; + if (component == null || attribute == null || attributes == null + || defaultInstance == null) { + throw new IllegalArgumentException( + "Parameters with null value not allowed"); + } if (attributes.hasKey(attribute)) { value = attributes.get(attribute); } - - // find setter for the property boolean success = false; try { Method setter = findSetterForAttribute(component.getClass(), @@ -115,15 +126,13 @@ public class DesignAttributeHandler { 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; - } + // otherwise find the getter for the attribute + Method getter = findGetterForAttribute(component.getClass(), + attribute); + // read the default value from defaults + Object defaultValue = getter.invoke(defaultInstance); + setter.invoke(component, defaultValue); + success = true; } } catch (Exception e) { getLogger().log(Level.WARNING, @@ -141,86 +150,56 @@ public class DesignAttributeHandler { * 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; + public static Collection<String> getSupportedAttributes(Class<?> clazz) { + resolveSupportedAttributes(clazz); + return cache.get(clazz).getAttributes(); } /** - * Assigns the width for the component based on the design attributes + * Resolves the supported attributes and corresponding getters and setters + * for the class using introspection. After resolving, the information is + * cached internally by this class * - * @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 clazz + * the class to resolve the supported attributes for */ - 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()); + private static void resolveSupportedAttributes(Class<?> clazz) { + if (clazz == null) { + throw new IllegalArgumentException("The clazz can not be null"); } - } - - /** - * 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()); + if (cache.containsKey(clazz.getCanonicalName())) { + // NO-OP + return; } + BeanInfo beanInfo; + try { + beanInfo = Introspector.getBeanInfo(clazz); + } catch (IntrospectionException e) { + throw new RuntimeException( + "Could not get supported attributes for class " + + clazz.getName()); + } + AttributeCacheEntry entry = new AttributeCacheEntry(); + for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) { + Method getter = descriptor.getReadMethod(); + Method setter = descriptor.getWriteMethod(); + if (getter != null && setter != null + && isSupported(descriptor.getPropertyType())) { + String attribute = toAttributeName(descriptor.getName()); + entry.addAttribute(attribute, getter, setter); + } + } + cache.put(clazz, entry); } /** * 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 @@ -243,9 +222,7 @@ public class DesignAttributeHandler { 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 { + if (!SharedUtil.equals(value, defaultValue)) { String attributeValue = toAttributeValue( getter.getReturnType(), value); attr.put(attribute, attributeValue); @@ -260,69 +237,9 @@ public class DesignAttributeHandler { } /** - * 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 { - attributes.put("width", - formatDesignAttribute(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 { - attributes.put("height", - formatDesignAttribute(component.getHeight()) - + component.getHeightUnits().getSymbol()); - } - } - } - } - - /** * Formats the given design attribute value. The method is provided to * ensure consistent number formatting for design attribute values * - * @since 7.4 * @param number * the number to be formatted * @return the formatted number @@ -340,16 +257,14 @@ public class DesignAttributeHandler { * For example given a method name <code>setPrimaryStyleName</code> the * return value would be <code>primary-style-name</code> * - * @since 7.4 - * @param methodName - * the method name + * @param propertyName + * the property name returned by {@link IntroSpector} * @return the design attribute name corresponding the given method name */ - private static String toAttributeName(String methodName) { - String[] words = methodName.split("(?<!^)(?=[A-Z])"); + private static String toAttributeName(String propertyName) { + String[] words = propertyName.split("(?<!^)(?=[A-Z])"); StringBuilder builder = new StringBuilder(); - // ignore first token ("set") - for (int i = 1; i < words.length; i++) { + for (int i = 0; i < words.length; i++) { if (builder.length() > 0) { builder.append("-"); } @@ -359,56 +274,8 @@ public class DesignAttributeHandler { } /** - * Returns the setter method name corresponding the given design attribute - * name. For example given a attribute name <code>primary-style-name</code> - * the return value would be <code>setPrimaryStyleName</code>. - * - * @since 7.4 - * @param designAttributeName - * the design attribute name - * @return the setter method name corresponding the given design attribute - * name - */ - private static String toSetterName(String designAttributeName) { - String[] parts = designAttributeName.split("-"); - StringBuilder builder = new StringBuilder(); - builder.append("set"); - for (String part : parts) { - builder.append(part.substring(0, 1).toUpperCase()); - builder.append(part.substring(1)); - } - return builder.toString(); - } - - /** - * 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 * @param targetType * the target type for the value * @param value @@ -423,7 +290,7 @@ public class DesignAttributeHandler { // it is present and the value is not "false" or "FALSE". Thus empty // value evaluates to true. if (targetType == Boolean.TYPE || targetType == Boolean.class) { - return value == null || !value.equalsIgnoreCase("false"); + return !value.equalsIgnoreCase("false"); } if (targetType == Integer.TYPE || targetType == Integer.class) { return Integer.valueOf(value); @@ -457,9 +324,7 @@ public class DesignAttributeHandler { /** * Serializes the given value to valid design attribute representation - * (string) * - * @since 7.4 * @param sourceType * the type of the value * @param value @@ -467,6 +332,11 @@ public class DesignAttributeHandler { * @return the given value as design attribute representation */ private static String toAttributeValue(Class<?> sourceType, Object value) { + if (value == null) { + // TODO: Handle corner case where sourceType is String and default + // value is not null. How to represent null value in attributes? + return ""; + } if (sourceType == Locale.class) { return value != null ? ((Locale) value).toString() : null; } else if (sourceType == Resource.class) { @@ -485,6 +355,8 @@ public class DesignAttributeHandler { return path; } } else { + getLogger().warning( + "Unknown resource type " + value.getClass().getName()); return null; } } else { @@ -492,6 +364,13 @@ public class DesignAttributeHandler { } } + /** + * Parses the given attribute value as resource + * + * @param value + * the attribute value to be parsed + * @return resource instance based on the attribute value + */ private static Resource parseResource(String value) { if (value.startsWith("http://")) { return new ExternalResource("value"); @@ -505,40 +384,9 @@ public class DesignAttributeHandler { } /** - * Finds a corresponding getter method for the given setter method - * - * @since 7.4 - * @param clazz - * the class to search methods from - * @param setter - * the setter that is used to find the matching getter - * @return the matching getter or null if not found - */ - private static Method findGetterForSetter(Class<?> clazz, Method setter) { - String propertyName = setter.getName().substring(3); - Class<?> returnType = setter.getParameterTypes()[0]; - for (Method method : clazz.getMethods()) { - if (isGetterForProperty(method, propertyName) - && method.getParameterTypes().length == 0 - && method.getReturnType().equals(returnType)) { - return method; - } - } - return null; - } - - private static boolean isGetterForProperty(Method method, String property) { - String methodName = method.getName(); - return methodName.equals("get" + property) - || methodName.equals("is" + property) - || methodName.equals("has" + property); - } - - /** * Returns a setter that can be used for assigning the given design * attribute to the class * - * @since 7.4 * @param clazz * the class that is scanned for setters * @param attribute @@ -547,25 +395,14 @@ public class DesignAttributeHandler { */ private static Method findSetterForAttribute(Class<?> clazz, String attribute) { - String methodName = toSetterName(attribute); - for (Method method : clazz.getMethods()) { - if (method.getName().equals(methodName) - && method.getParameterTypes().length == 1 - && isSupported(method.getParameterTypes()[0])) { - return method; - } - } - getLogger().warning( - "Could not find setter with supported type for property " - + attribute); - return null; + resolveSupportedAttributes(clazz); + return cache.get(clazz).getSetter(attribute); } /** - * Returns a getter that can be used for reading the value of the given - * design attribute from the class + * Returns a getter that can be used for reading the given design attribute + * value from the class * - * @since 7.4 * @param clazz * the class that is scanned for getters * @param attribute @@ -574,20 +411,11 @@ public class DesignAttributeHandler { */ 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; + resolveSupportedAttributes(clazz); + return cache.get(clazz).getGetter(attribute); } + // supported property types private static final List<Class<?>> supportedClasses = Arrays .asList(new Class<?>[] { String.class, Boolean.class, Integer.class, Byte.class, Short.class, Long.class, @@ -599,7 +427,6 @@ public class DesignAttributeHandler { * Currently the handler supports primitives, {@link Locale.class} and * {@link Resource.class}. * - * @since 7.4 * @param valueType * the value type to be tested * @return true if the value type is supported, otherwise false @@ -611,41 +438,37 @@ public class DesignAttributeHandler { } /** - * Test if the given components have equal width + * Cache object for caching supported attributes and their getters and + * setters * - * @since 7.4 - * @param comp1 - * @param comp2 - * @return true if the widths of the components are equal + * @author Vaadin Ltd */ - private static boolean areEqualWidth(Component comp1, Component comp2) { - return comp1.getWidth() == comp2.getWidth() - && comp1.getWidthUnits().equals(comp2.getWidthUnits()); - } + private static class AttributeCacheEntry { + private Map<String, Method[]> accessMethods = Collections + .synchronizedMap(new HashMap<String, Method[]>()); + + private void addAttribute(String attribute, Method getter, Method setter) { + Method[] methods = new Method[2]; + methods[0] = getter; + methods[1] = setter; + accessMethods.put(attribute, methods); + } - /** - * Tests if the given components have equal height - * - * @since 7.4 - * @param comp1 - * @param comp2 - * @return true if the heights of the components are equal - */ - private static boolean areEqualHeight(Component comp1, Component comp2) { - return comp1.getHeight() == comp2.getHeight() - && comp1.getHeightUnits().equals(comp2.getHeightUnits()); - } + private Collection<String> getAttributes() { + ArrayList<String> attributes = new ArrayList<String>(); + attributes.addAll(accessMethods.keySet()); + return attributes; + } - /** - * Test if the given components have equal size - * - * @since 7.4 - * @param comp1 - * @param comp2 - * @return true if the widht and height of the components are equal - */ - private static boolean areEqualSize(Component comp1, Component comp2) { - return areEqualWidth(comp1, comp2) && areEqualHeight(comp1, comp2); + private Method getGetter(String attribute) { + Method[] methods = accessMethods.get(attribute); + return (methods != null && methods.length > 0) ? methods[0] : null; + } + + private Method getSetter(String attribute) { + Method[] methods = accessMethods.get(attribute); + return (methods != null && methods.length > 1) ? methods[1] : null; + } } } diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java index 1af8fc4fcd..961affd5b0 100644 --- a/server/src/com/vaadin/ui/declarative/DesignContext.java +++ b/server/src/com/vaadin/ui/declarative/DesignContext.java @@ -215,21 +215,23 @@ public class DesignContext { * Returns the default instance for the given class. The instance must not * be modified by the caller. * - * @since * @param instanceClass - * @return + * @return the default instance for the given class. The return value must + * not be modified by the caller */ public <T> T getDefaultInstance(Class<T> instanceClass) { T instance = (T) instanceCache.get(instanceClass); if (instance == null) { try { instance = instanceClass.newInstance(); + instanceCache.put(instanceClass, instance); } catch (InstantiationException e) { - e.printStackTrace(); + throw new RuntimeException("Could not instantiate " + + instanceClass.getName()); } catch (IllegalAccessException e) { - e.printStackTrace(); + throw new RuntimeException("Could not instantiate " + + instanceClass.getName()); } - instanceCache.put(instanceClass, instance); } return instance; } |