aboutsummaryrefslogtreecommitdiffstats
path: root/server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java
diff options
context:
space:
mode:
authorTeemu Suo-Anttila <teemusa@vaadin.com>2016-03-10 22:40:51 +0200
committerTeemu Suo-Anttila <teemusa@vaadin.com>2016-03-14 07:59:12 +0200
commita6653d3fe49e6a97468ac09f7f2f4d621bea1078 (patch)
tree96c82e20ca6551ee4c14c8877f0258b25c63cddf /server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java
parentf7e57d77ce621ee39167369c31d989edc5633266 (diff)
downloadvaadin-framework-a6653d3fe49e6a97468ac09f7f2f4d621bea1078.tar.gz
vaadin-framework-a6653d3fe49e6a97468ac09f7f2f4d621bea1078.zip
Migrate vaadin-server build to maven
Change-Id: I5c740f4e9cb28103bab199f9a552153d82277e7e
Diffstat (limited to 'server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java')
-rw-r--r--server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java455
1 files changed, 455 insertions, 0 deletions
diff --git a/server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java b/server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java
new file mode 100644
index 0000000000..cee2ebe381
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java
@@ -0,0 +1,455 @@
+/*
+ * 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.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+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;
+import org.jsoup.nodes.Node;
+
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.shared.util.SharedUtil;
+
+/**
+ * Default attribute handler implementation used when parsing designs to
+ * component trees. Handles all the component attributes that do not require
+ * custom handling.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class DesignAttributeHandler implements Serializable {
+
+ private static Logger getLogger() {
+ return Logger.getLogger(DesignAttributeHandler.class.getName());
+ }
+
+ private static Map<Class<?>, AttributeCacheEntry> cache = new ConcurrentHashMap<Class<?>, AttributeCacheEntry>();
+
+ // translates string <-> object
+ private static DesignFormatter FORMATTER = new DesignFormatter();
+
+ /**
+ * Returns the currently used formatter. All primitive types and all types
+ * needed by Vaadin components are handled by that formatter.
+ *
+ * @return An instance of the formatter.
+ */
+ public static DesignFormatter getFormatter() {
+ return FORMATTER;
+ }
+
+ /**
+ * Clears the children and attributes of the given element
+ *
+ * @param design
+ * the element to be cleared
+ */
+ public static void clearElement(Element 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.
+ *
+ * @param target
+ * the target to which the attribute should be set
+ * @param attribute
+ * the name of the attribute to be set
+ * @param value
+ * the string value of the attribute
+ * @return true on success
+ */
+ public static boolean assignValue(Object target, String attribute,
+ String value) {
+ if (target == null || attribute == null || value == null) {
+ throw new IllegalArgumentException(
+ "Parameters with null value not allowed");
+ }
+ boolean success = false;
+ try {
+ Method setter = findSetterForAttribute(target.getClass(), attribute);
+ if (setter == null) {
+ // if we don't have the setter, there is no point in continuing
+ success = false;
+ } else {
+ // we have a value from design attributes, let's use that
+ Object param = getFormatter().parse(value,
+ setter.getParameterTypes()[0]);
+ setter.invoke(target, param);
+ success = true;
+ }
+ } catch (Exception e) {
+ getLogger().log(
+ Level.WARNING,
+ "Failed to set value \"" + value + "\" to 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
+ *
+ * @param clazz
+ * the class scanned for setters
+ * @return the list of supported design attributes
+ */
+ public static Collection<String> getSupportedAttributes(Class<?> clazz) {
+ resolveSupportedAttributes(clazz);
+ return cache.get(clazz).getAttributes();
+ }
+
+ /**
+ * Resolves the supported attributes and corresponding getters and setters
+ * for the class using introspection. After resolving, the information is
+ * cached internally by this class
+ *
+ * @param clazz
+ * the class to resolve the supported attributes for
+ */
+ private static void resolveSupportedAttributes(Class<?> clazz) {
+ if (clazz == null) {
+ throw new IllegalArgumentException("The clazz can not be null");
+ }
+ if (cache.containsKey(clazz)) {
+ // 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
+ && getFormatter().canConvert(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>
+ *
+ * @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
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static void writeAttribute(Object component, String attribute,
+ Attributes attr, Object 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);
+ writeAttribute(attribute, attr, value, defaultValue,
+ (Class) getter.getReturnType());
+ } catch (Exception e) {
+ getLogger()
+ .log(Level.SEVERE,
+ "Failed to invoke getter for attribute "
+ + attribute, e);
+ }
+ }
+ }
+
+ /**
+ * Writes the given attribute value to a set of attributes if it differs
+ * from the default attribute value.
+ *
+ * @param attribute
+ * the attribute key
+ * @param attributes
+ * the set of attributes where the new attribute is written
+ * @param value
+ * the attribute value
+ * @param defaultValue
+ * the default attribute value
+ * @param inputType
+ * the type of the input value
+ */
+ public static <T> void writeAttribute(String attribute,
+ Attributes attributes, T value, T defaultValue, Class<T> inputType) {
+ if (!getFormatter().canConvert(inputType)) {
+ throw new IllegalArgumentException("input type: "
+ + inputType.getName() + " not supported");
+ }
+ if (!SharedUtil.equals(value, defaultValue)) {
+ String attributeValue = toAttributeValue(inputType, value);
+ if ("".equals(attributeValue)
+ && (inputType == boolean.class || inputType == Boolean.class)) {
+ attributes.put(attribute, true);
+ } else {
+ attributes.put(attribute, attributeValue);
+ }
+ }
+ }
+
+ /**
+ * Reads the given attribute from a set of attributes. If attribute does not
+ * exist return a given default value.
+ *
+ * @param attribute
+ * the attribute key
+ * @param attributes
+ * the set of attributes to read from
+ * @param defaultValue
+ * the default value to return if attribute does not exist
+ * @param outputType
+ * the output type for the attribute
+ * @return the attribute value or the default value if the attribute is not
+ * found
+ */
+ public static <T> T readAttribute(String attribute, Attributes attributes,
+ T defaultValue, Class<T> outputType) {
+ T value = readAttribute(attribute, attributes, outputType);
+ if (value != null) {
+ return value;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Reads the given attribute from a set of attributes.
+ *
+ * @param attribute
+ * the attribute key
+ * @param attributes
+ * the set of attributes to read from
+ * @param outputType
+ * the output type for the attribute
+ * @return the attribute value or null
+ */
+ public static <T> T readAttribute(String attribute, Attributes attributes,
+ Class<T> outputType) {
+ if (!getFormatter().canConvert(outputType)) {
+ throw new IllegalArgumentException("output type: "
+ + outputType.getName() + " not supported");
+ }
+ if (!attributes.hasKey(attribute)) {
+ return null;
+ } else {
+ try {
+ String value = attributes.get(attribute);
+ return getFormatter().parse(value, outputType);
+ } catch (Exception e) {
+ throw new DesignException("Failed to read attribute "
+ + attribute, e);
+ }
+ }
+ }
+
+ /**
+ * 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>
+ *
+ * @param propertyName
+ * the property name returned by {@link IntroSpector}
+ * @return the design attribute name corresponding the given method name
+ */
+ private static String toAttributeName(String propertyName) {
+ propertyName = removeSubsequentUppercase(propertyName);
+ String[] words = propertyName.split("(?<!^)(?=[A-Z])");
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < words.length; i++) {
+ if (builder.length() > 0) {
+ builder.append("-");
+ }
+ builder.append(words[i].toLowerCase());
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Replaces subsequent UPPERCASE strings of length 2 or more followed either
+ * by another uppercase letter or an end of string. This is to generalise
+ * handling of method names like <tt>showISOWeekNumbers</tt>.
+ *
+ * @param param
+ * Input string.
+ * @return Input string with sequences of UPPERCASE turned into Normalcase.
+ */
+ private static String removeSubsequentUppercase(String param) {
+ StringBuffer result = new StringBuffer();
+ // match all two-or-more caps letters lead by a non-uppercase letter
+ // followed by either a capital letter or string end
+ Pattern pattern = Pattern.compile("(^|[^A-Z])([A-Z]{2,})([A-Z]|$)");
+ Matcher matcher = pattern.matcher(param);
+ while (matcher.find()) {
+ String matched = matcher.group(2);
+ // if this is a beginning of the string, the whole matched group is
+ // written in lower case
+ if (matcher.group(1).isEmpty()) {
+ matcher.appendReplacement(result, matched.toLowerCase()
+ + matcher.group(3));
+ // otherwise the first character of the group stays uppercase,
+ // while the others are lower case
+ } else {
+ matcher.appendReplacement(
+ result,
+ matcher.group(1) + matched.substring(0, 1)
+ + matched.substring(1).toLowerCase()
+ + matcher.group(3));
+ }
+ // in both cases the uppercase letter of the next word (or string's
+ // end) is added
+ // this implies there is at least one extra lowercase letter after
+ // it to be caught by the next call to find()
+ }
+ matcher.appendTail(result);
+ return result.toString();
+ }
+
+ /**
+ * Serializes the given value to valid design attribute representation
+ *
+ * @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 (value == null) {
+ // TODO: Handle corner case where sourceType is String and default
+ // value is not null. How to represent null value in attributes?
+ return "";
+ }
+ Converter<String, Object> converter = getFormatter().findConverterFor(
+ sourceType);
+ if (converter != null) {
+ return converter.convertToPresentation(value, String.class, null);
+ } else {
+ return value.toString();
+ }
+ }
+
+ /**
+ * Returns a setter that can be used for assigning the given design
+ * attribute to the class
+ *
+ * @param clazz
+ * the class that is scanned for setters
+ * @param attribute
+ * the design attribute to find setter for
+ * @return the setter method or null if not found
+ */
+ private static Method findSetterForAttribute(Class<?> clazz,
+ String attribute) {
+ resolveSupportedAttributes(clazz);
+ return cache.get(clazz).getSetter(attribute);
+ }
+
+ /**
+ * Returns a getter that can be used for reading the given design attribute
+ * value from the class
+ *
+ * @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) {
+ resolveSupportedAttributes(clazz);
+ return cache.get(clazz).getGetter(attribute);
+ }
+
+ /**
+ * Cache object for caching supported attributes and their getters and
+ * setters
+ *
+ * @author Vaadin Ltd
+ */
+ private static class AttributeCacheEntry implements Serializable {
+ private Map<String, Method[]> accessMethods = new ConcurrentHashMap<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);
+ }
+
+ private Collection<String> getAttributes() {
+ ArrayList<String> attributes = new ArrayList<String>();
+ attributes.addAll(accessMethods.keySet());
+ return attributes;
+ }
+
+ 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;
+ }
+ }
+}