diff options
author | Miki <miki@vaadin.com> | 2015-01-16 14:02:19 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2015-02-03 11:15:41 +0000 |
commit | a508ed7b4aa062334ad84d7967cb2bdd5d8ecc26 (patch) | |
tree | 5215ac87b3063efd9d7c8a67d5b3b008ff931b70 | |
parent | 293b62ab6ed402b188685027b100025597a37d00 (diff) | |
download | vaadin-framework-a508ed7b4aa062334ad84d7967cb2bdd5d8ecc26.tar.gz vaadin-framework-a508ed7b4aa062334ad84d7967cb2bdd5d8ecc26.zip |
Declarative for DateFields (and related) with ISO8601 (#16313)
DesignAttributeHandler supports method names that contains some words in
uppercase
DesignAttributeHandler and other components now use extensible Formatter
with Converters rather than static methods
Change-Id: I9f68414bd4821f47ff37a26375091d154cae9a93
16 files changed, 1445 insertions, 366 deletions
diff --git a/server/src/com/vaadin/ui/AbsoluteLayout.java b/server/src/com/vaadin/ui/AbsoluteLayout.java index 6353a4b25d..63bbe70157 100644 --- a/server/src/com/vaadin/ui/AbsoluteLayout.java +++ b/server/src/com/vaadin/ui/AbsoluteLayout.java @@ -759,8 +759,8 @@ public class AbsoluteLayout extends AbstractLayout implements private void writePositionAttribute(Node node, String key, String symbol, Float value) { if (value != null) { - String valueString = DesignAttributeHandler.formatFloat(value - .floatValue()); + String valueString = DesignAttributeHandler.getFormatter().format( + value); node.attr(key, valueString + symbol); } } diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java index 9ff6dff21e..ebe438b908 100644 --- a/server/src/com/vaadin/ui/AbstractComponent.java +++ b/server/src/com/vaadin/ui/AbstractComponent.java @@ -959,8 +959,8 @@ public abstract class AbstractComponent extends AbstractClientConnector } // handle immediate if (attr.hasKey("immediate")) { - setImmediate(DesignAttributeHandler.parseBoolean(attr - .get("immediate"))); + setImmediate(DesignAttributeHandler.getFormatter().parse( + attr.get("immediate"), Boolean.class)); } // handle locale @@ -984,8 +984,8 @@ public abstract class AbstractComponent extends AbstractClientConnector // handle responsive if (attr.hasKey("responsive")) { - setResponsive(DesignAttributeHandler.parseBoolean(attr - .get("responsive"))); + setResponsive(DesignAttributeHandler.getFormatter().parse( + attr.get("responsive"), Boolean.class)); } // check for unsupported attributes Set<String> supported = new HashSet<String>(); @@ -1138,9 +1138,8 @@ public abstract class AbstractComponent extends AbstractClientConnector } else if (widthAuto) { attributes.put("width-auto", "true"); } else { - String widthString = DesignAttributeHandler - .formatFloat(getWidth()) - + getWidthUnits().getSymbol(); + String widthString = DesignAttributeHandler.getFormatter() + .format(getWidth()) + getWidthUnits().getSymbol(); attributes.put("width", widthString); } @@ -1152,9 +1151,8 @@ public abstract class AbstractComponent extends AbstractClientConnector } else if (heightAuto) { attributes.put("height-auto", "true"); } else { - String heightString = DesignAttributeHandler - .formatFloat(getHeight()) - + getHeightUnits().getSymbol(); + String heightString = DesignAttributeHandler.getFormatter() + .format(getHeight()) + getHeightUnits().getSymbol(); attributes.put("height", heightString); } } diff --git a/server/src/com/vaadin/ui/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java index 67bcfc904c..3aec3b2d7a 100644 --- a/server/src/com/vaadin/ui/AbstractOrderedLayout.java +++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java @@ -563,8 +563,8 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements if (expandRatio == 1.0f) { childElement.attr(":expand", ""); } else if (expandRatio > 0) { - childElement.attr(":expand", - DesignAttributeHandler.formatFloat(expandRatio)); + childElement.attr(":expand", DesignAttributeHandler + .getFormatter().format(expandRatio)); } } } diff --git a/server/src/com/vaadin/ui/DateField.java b/server/src/com/vaadin/ui/DateField.java index 3d683f4902..422b1ffdd8 100644 --- a/server/src/com/vaadin/ui/DateField.java +++ b/server/src/com/vaadin/ui/DateField.java @@ -24,6 +24,9 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.logging.Logger; + +import org.jsoup.nodes.Element; import com.vaadin.data.Property; import com.vaadin.data.Validator; @@ -40,6 +43,8 @@ import com.vaadin.server.PaintTarget; import com.vaadin.shared.ui.datefield.DateFieldConstants; import com.vaadin.shared.ui.datefield.Resolution; import com.vaadin.shared.ui.datefield.TextualDateFieldState; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; /** * <p> @@ -1061,4 +1066,40 @@ public class DateField extends AbstractField<Date> implements } } + + @Override + public void readDesign(Element design, DesignContext designContext) { + super.readDesign(design, designContext); + if (design.hasAttr("value") && !design.attr("value").isEmpty()) { + Date date = DesignAttributeHandler.getFormatter().parse( + design.attr("value"), Date.class); + // formatting will return null if it cannot parse the string + if (date == null) { + Logger.getLogger(DateField.class.getName()).info( + "cannot parse " + design.attr("value") + " as date"); + } + this.setValue(date); + } + } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + if (getValue() != null) { + design.attr("value", + DesignAttributeHandler.getFormatter().format(getValue())); + } + } + + /** + * Returns current date-out-of-range error message. + * + * @see #setDateOutOfRangeMessage(String) + * @since 7.4 + * @return Current error message for dates out of range. + */ + public String getDateOutOfRangeMessage() { + return dateOutOfRangeMessage; + } + } diff --git a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java index be7d023ebf..3e2c01c881 100644 --- a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java +++ b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java @@ -19,35 +19,25 @@ import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; -import java.io.File; import java.io.Serializable; 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 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.event.ShortcutAction; -import com.vaadin.event.ShortcutAction.KeyCode; -import com.vaadin.event.ShortcutAction.ModifierKey; -import com.vaadin.server.ExternalResource; -import com.vaadin.server.FileResource; -import com.vaadin.server.FontAwesome; -import com.vaadin.server.Resource; -import com.vaadin.server.ThemeResource; +import com.vaadin.data.util.converter.Converter; import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Component; @@ -65,8 +55,21 @@ public class DesignAttributeHandler implements Serializable { return Logger.getLogger(DesignAttributeHandler.class.getName()); } - private static Map<Class, AttributeCacheEntry> cache = Collections - .synchronizedMap(new HashMap<Class, AttributeCacheEntry>()); + private static Map<Class<?>, AttributeCacheEntry> cache = Collections + .synchronizedMap(new HashMap<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 @@ -111,8 +114,8 @@ public class DesignAttributeHandler implements Serializable { success = false; } else { // we have a value from design attributes, let's use that - Object param = fromAttributeValue( - setter.getParameterTypes()[0], value); + Object param = getFormatter().parse(value, + setter.getParameterTypes()[0]); setter.invoke(target, param); success = true; } @@ -170,7 +173,7 @@ public class DesignAttributeHandler implements Serializable { Method getter = descriptor.getReadMethod(); Method setter = descriptor.getWriteMethod(); if (getter != null && setter != null - && isSupported(descriptor.getPropertyType())) { + && getFormatter().canConvert(descriptor.getPropertyType())) { String attribute = toAttributeName(descriptor.getName()); entry.addAttribute(attribute, getter, setter); } @@ -229,10 +232,9 @@ public class DesignAttributeHandler implements Serializable { * @return the attribute value or the default value if the attribute is not * found */ - @SuppressWarnings("unchecked") public static <T> T readAttribute(String attribute, Attributes attributes, Class<T> outputType) { - if (!isSupported(outputType)) { + if (!getFormatter().canConvert(outputType)) { throw new IllegalArgumentException("output type: " + outputType.getName() + " not supported"); } @@ -241,7 +243,7 @@ public class DesignAttributeHandler implements Serializable { } else { try { String value = attributes.get(attribute); - return (T) fromAttributeValue(outputType, value); + return getFormatter().parse(value, outputType); } catch (Exception e) { throw new DesignException("Failed to read attribute " + attribute, e); @@ -266,7 +268,7 @@ public class DesignAttributeHandler implements Serializable { */ public static <T> void writeAttribute(String attribute, Attributes attributes, T value, T defaultValue, Class<T> inputType) { - if (!isSupported(inputType)) { + if (!getFormatter().canConvert(inputType)) { throw new IllegalArgumentException("input type: " + inputType.getName() + " not supported"); } @@ -277,101 +279,6 @@ public class DesignAttributeHandler implements Serializable { } /** - * Formats the given design attribute value. The method is provided to - * ensure consistent number formatting for design attribute values - * - * @param number - * the number to be formatted - * @return the formatted number - */ - public static String formatFloat(float number) { - return getDecimalFormat().format(number); - } - - /** - * Formats the given design attribute value. The method is provided to - * ensure consistent number formatting for design attribute values - * - * @param number - * the number to be formatted - * @return the formatted number - */ - public static String formatDouble(double number) { - return getDecimalFormat().format(number); - } - - /** - * Convert ShortcutAction to attribute string presentation - * - * @param shortcut - * the shortcut action - * @return the action as attribute string presentation - */ - private static String formatShortcutAction(ShortcutAction shortcut) { - StringBuilder sb = new StringBuilder(); - // handle modifiers - if (shortcut.getModifiers() != null) { - for (int modifier : shortcut.getModifiers()) { - sb.append(ShortcutKeyMapper.getStringForKeycode(modifier)) - .append("-"); - } - } - // handle keycode - sb.append(ShortcutKeyMapper.getStringForKeycode(shortcut.getKeyCode())); - return sb.toString(); - } - - /** - * Reads shortcut action from attribute presentation - * - * @param attributeValue - * attribute presentation of shortcut action - * @return shortcut action with keycode and modifier keys from attribute - * value - */ - private static ShortcutAction readShortcutAction(String attributeValue) { - if (attributeValue.length() == 0) { - return null; - } - String[] parts = attributeValue.split("-"); - // handle keycode - String keyCodePart = parts[parts.length - 1]; - int keyCode = ShortcutKeyMapper.getKeycodeForString(keyCodePart); - if (keyCode < 0) { - throw new IllegalArgumentException("Invalid shortcut definition " - + attributeValue); - } - // handle modifiers - int[] modifiers = null; - if (parts.length > 1) { - modifiers = new int[parts.length - 1]; - } - for (int i = 0; i < parts.length - 1; i++) { - int modifier = ShortcutKeyMapper.getKeycodeForString(parts[i]); - if (modifier > 0) { - modifiers[i] = modifier; - } else { - throw new IllegalArgumentException( - "Invalid shortcut definition " + attributeValue); - } - } - return new ShortcutAction(null, keyCode, modifiers); - } - - /** - * Creates the decimal format used when writing attributes to the design. - * - * @return the decimal format - */ - private static DecimalFormat getDecimalFormat() { - DecimalFormatSymbols symbols = new DecimalFormatSymbols(new Locale( - "en_US")); - DecimalFormat fmt = new DecimalFormat("0.###", symbols); - fmt.setGroupingUsed(false); - return fmt; - } - - /** * 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> @@ -381,6 +288,7 @@ public class DesignAttributeHandler implements Serializable { * @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++) { @@ -393,56 +301,43 @@ public class DesignAttributeHandler implements Serializable { } /** - * Parses the given attribute value to specified target type + * 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 targetType - * the target type for the value - * @param value - * the parsed value - * @return the object of specified target type + * @param param + * Input string. + * @return Input string with sequences of UPPERCASE turned into Normalcase. */ - private static Object fromAttributeValue(Class<?> targetType, String value) { - if (targetType == String.class) { - return value; - } - // special handling for boolean type. The attribute evaluates to true if - // 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 parseBoolean(value); - } - if (targetType == Integer.TYPE || targetType == Integer.class) { - return Integer.valueOf(value); - } - if (targetType == Byte.TYPE || targetType == Byte.class) { - return Byte.valueOf(value); - } - if (targetType == Short.TYPE || targetType == Short.class) { - return Short.valueOf(value); - } - if (targetType == Long.TYPE || targetType == Long.class) { - return Long.valueOf(value); - } - if (targetType == Character.TYPE || targetType == Character.class) { - return value.charAt(0); - } - if (targetType == Float.TYPE || targetType == Float.class) { - return Float.valueOf(value); - } - if (targetType == Double.TYPE || targetType == Double.class) { - return Double.valueOf(value); - } - if (targetType == Resource.class) { - return parseResource(value); - } - if (Enum.class.isAssignableFrom(targetType)) { - return Enum.valueOf((Class<? extends Enum>) targetType, - value.toUpperCase()); - } - if (targetType == ShortcutAction.class) { - return readShortcutAction(value); + 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() } - return null; + matcher.appendTail(result); + return result.toString(); } /** @@ -460,57 +355,16 @@ public class DesignAttributeHandler implements Serializable { // value is not null. How to represent null value in attributes? return ""; } - 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) { - String path = ((FileResource) value).getSourceFile().getPath(); - if (File.separatorChar != '/') { - // make sure we use '/' as file separator in templates - return path.replace(File.separatorChar, '/'); - } else { - return path; - } - } else { - getLogger().warning( - "Unknown resource type " + value.getClass().getName()); - return null; - } - } else if (sourceType == Float.class || sourceType == Float.TYPE) { - return formatFloat(((Float) value).floatValue()); - } else if (sourceType == Double.class || sourceType == Double.TYPE) { - return formatDouble(((Double) value).doubleValue()); - } else if (sourceType == ShortcutAction.class) { - return formatShortcutAction((ShortcutAction) value); + Converter<String, Object> converter = getFormatter().findConverterFor( + sourceType); + if (converter != null) { + return converter.convertToPresentation(value, String.class, null); } else { return value.toString(); } } /** - * 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); - } else if (value.startsWith("theme://")) { - return new ThemeResource(value.substring(8)); - } else if (value.startsWith("font://")) { - return FontAwesome.valueOf(value.substring(7)); - } else { - return new FileResource(new File(value)); - } - } - - /** * Returns a setter that can be used for assigning the given design * attribute to the class * @@ -542,29 +396,6 @@ public class DesignAttributeHandler implements Serializable { 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, - Character.class, Float.class, Double.class, Resource.class, - ShortcutAction.class }); - - /** - * Returns true if the specified value type is supported by this class. - * Currently the handler supports primitives, {@link Locale.class} and - * {@link Resource.class}. - * - * @param valueType - * the value type to be tested - * @return true if the value type is supported, otherwise false - */ - private static boolean isSupported(Class<?> valueType) { - return valueType != null - && (valueType.isPrimitive() - || supportedClasses.contains(valueType) || Enum.class - .isAssignableFrom(valueType)); - } - /** * Cache object for caching supported attributes and their getters and * setters @@ -599,117 +430,4 @@ public class DesignAttributeHandler implements Serializable { } } - /** - * Provides mappings between shortcut keycodes and their representation in - * design attributes - * - * @author Vaadin Ltd - */ - private static class ShortcutKeyMapper implements Serializable { - - private static Map<Integer, String> keyCodeMap = Collections - .synchronizedMap(new HashMap<Integer, String>()); - private static Map<String, Integer> presentationMap = Collections - .synchronizedMap(new HashMap<String, Integer>()); - - static { - // map modifiers - mapKey(ModifierKey.ALT, "alt"); - mapKey(ModifierKey.CTRL, "ctrl"); - mapKey(ModifierKey.META, "meta"); - mapKey(ModifierKey.SHIFT, "shift"); - // map keys - mapKey(KeyCode.ENTER, "enter"); - mapKey(KeyCode.ESCAPE, "escape"); - mapKey(KeyCode.PAGE_UP, "pageup"); - mapKey(KeyCode.PAGE_DOWN, "pagedown"); - mapKey(KeyCode.TAB, "tab"); - mapKey(KeyCode.ARROW_LEFT, "left"); - mapKey(KeyCode.ARROW_UP, "up"); - mapKey(KeyCode.ARROW_RIGHT, "right"); - mapKey(KeyCode.ARROW_DOWN, "down"); - mapKey(KeyCode.BACKSPACE, "backspace"); - mapKey(KeyCode.DELETE, "delete"); - mapKey(KeyCode.INSERT, "insert"); - mapKey(KeyCode.END, "end"); - mapKey(KeyCode.HOME, "home"); - mapKey(KeyCode.F1, "f1"); - mapKey(KeyCode.F2, "f2"); - mapKey(KeyCode.F3, "f3"); - mapKey(KeyCode.F4, "f4"); - mapKey(KeyCode.F5, "f5"); - mapKey(KeyCode.F6, "f6"); - mapKey(KeyCode.F7, "f7"); - mapKey(KeyCode.F8, "f8"); - mapKey(KeyCode.F9, "f9"); - mapKey(KeyCode.F10, "f10"); - mapKey(KeyCode.F11, "f11"); - mapKey(KeyCode.F12, "f12"); - mapKey(KeyCode.NUM0, "0"); - mapKey(KeyCode.NUM1, "1"); - mapKey(KeyCode.NUM2, "2"); - mapKey(KeyCode.NUM3, "3"); - mapKey(KeyCode.NUM4, "4"); - mapKey(KeyCode.NUM5, "5"); - mapKey(KeyCode.NUM6, "6"); - mapKey(KeyCode.NUM7, "7"); - mapKey(KeyCode.NUM8, "8"); - mapKey(KeyCode.NUM9, "9"); - mapKey(KeyCode.SPACEBAR, "spacebar"); - mapKey(KeyCode.A, "a"); - mapKey(KeyCode.B, "b"); - mapKey(KeyCode.C, "c"); - mapKey(KeyCode.D, "d"); - mapKey(KeyCode.E, "e"); - mapKey(KeyCode.F, "f"); - mapKey(KeyCode.G, "g"); - mapKey(KeyCode.H, "h"); - mapKey(KeyCode.I, "i"); - mapKey(KeyCode.J, "j"); - mapKey(KeyCode.K, "k"); - mapKey(KeyCode.L, "l"); - mapKey(KeyCode.M, "m"); - mapKey(KeyCode.N, "n"); - mapKey(KeyCode.O, "o"); - mapKey(KeyCode.P, "p"); - mapKey(KeyCode.Q, "q"); - mapKey(KeyCode.R, "r"); - mapKey(KeyCode.S, "s"); - mapKey(KeyCode.T, "t"); - mapKey(KeyCode.U, "u"); - mapKey(KeyCode.V, "v"); - mapKey(KeyCode.X, "x"); - mapKey(KeyCode.Y, "y"); - mapKey(KeyCode.Z, "z"); - } - - private static void mapKey(int keyCode, String presentation) { - keyCodeMap.put(keyCode, presentation); - presentationMap.put(presentation, keyCode); - } - - private static int getKeycodeForString(String attributePresentation) { - Integer code = presentationMap.get(attributePresentation); - return code != null ? code.intValue() : -1; - } - - private static String getStringForKeycode(int keyCode) { - return keyCodeMap.get(keyCode); - } - } - - /** - * Converts the given string attribute value to its corresponding boolean. - * - * An empty string and "true" are considered to represent a true value and - * "false" to represent a false value. - * - * @param booleanValue - * the boolean value from an attribute - * @return the parsed boolean - */ - public static boolean parseBoolean(String booleanValue) { - return !booleanValue.equalsIgnoreCase("false"); - } - }
\ No newline at end of file diff --git a/server/src/com/vaadin/ui/declarative/DesignFormatter.java b/server/src/com/vaadin/ui/declarative/DesignFormatter.java new file mode 100644 index 0000000000..fdce563104 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/DesignFormatter.java @@ -0,0 +1,348 @@ +/* + * 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.io.Serializable; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.event.ShortcutAction; +import com.vaadin.server.Resource; +import com.vaadin.ui.declarative.converters.DesignDateConverter; +import com.vaadin.ui.declarative.converters.DesignFormatConverter; +import com.vaadin.ui.declarative.converters.DesignResourceConverter; +import com.vaadin.ui.declarative.converters.DesignShortcutActionConverter; +import com.vaadin.ui.declarative.converters.ShortcutKeyMapper; +import com.vaadin.ui.declarative.converters.DesignToStringConverter; + +/** + * Class focused on flexible and consistent formatting and parsing of different + * values throughout reading and writing {@link Design}. An instance of this + * class is used by {@link DesignAttributeHandler}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignFormatter implements Serializable { + + private final Map<Class<?>, Converter<String, ?>> converterMap = Collections + .synchronizedMap(new HashMap<Class<?>, Converter<String, ?>>()); + + /** + * Creates the formatter with default types already mapped. + */ + public DesignFormatter() { + mapDefaultTypes(); + } + + /** + * Maps default types to their converters. + * + */ + protected void mapDefaultTypes() { + // numbers use standard toString/valueOf approach + for (Class<?> c : new Class<?>[] { Integer.class, Byte.class, + Short.class, Long.class, BigDecimal.class }) { + DesignToStringConverter<?> conv = new DesignToStringConverter(c); + converterMap.put(c, conv); + try { + converterMap.put((Class<?>) c.getField("TYPE").get(null), conv); + } catch (Exception e) { + ; // this will never happen + } + } + // booleans use a bit different converter than the standard one + // "false" is boolean false, everything else is boolean true + Converter<String, Boolean> booleanConverter = new Converter<String, Boolean>() { + + @Override + public Boolean convertToModel(String value, + Class<? extends Boolean> targetType, Locale locale) + throws Converter.ConversionException { + return !value.equalsIgnoreCase("false"); + } + + @Override + public String convertToPresentation(Boolean value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return String.valueOf(value.booleanValue()); + } + + @Override + public Class<Boolean> getModelType() { + return Boolean.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + + }; + converterMap.put(Boolean.class, booleanConverter); + converterMap.put(boolean.class, booleanConverter); + + // floats and doubles use formatters + DecimalFormatSymbols symbols = new DecimalFormatSymbols(new Locale( + "en_US")); + DecimalFormat fmt = new DecimalFormat("0.###", symbols); + fmt.setGroupingUsed(false); + converterMap.put(Float.class, new DesignFormatConverter<Float>( + Float.class, fmt)); + converterMap.put(Float.TYPE, new DesignFormatConverter<Float>( + Float.class, fmt)); + converterMap.put(Double.class, new DesignFormatConverter<Double>( + Double.class, fmt)); + converterMap.put(Double.TYPE, new DesignFormatConverter<Double>( + Double.class, fmt)); + + // strings do nothing + converterMap.put(String.class, new Converter<String, String>() { + + @Override + public String convertToModel(String value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return value; + } + + @Override + public String convertToPresentation(String value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return value; + } + + @Override + public Class<String> getModelType() { + return String.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + + }); + + // char takes the first character from the string + Converter<String, Character> charConverter = new DesignToStringConverter<Character>( + Character.class) { + + @Override + public Character convertToModel(String value, + Class<? extends Character> targetType, Locale locale) + throws Converter.ConversionException { + return value.charAt(0); + } + + }; + converterMap.put(Character.class, charConverter); + converterMap.put(Character.TYPE, charConverter); + + // date conversion has its own class + converterMap.put(Date.class, new DesignDateConverter()); + + // as shortcut action and resource + converterMap.put(ShortcutAction.class, + new DesignShortcutActionConverter(ShortcutKeyMapper.DEFAULT)); + + converterMap.put(Resource.class, new DesignResourceConverter()); + + // timezones use different static method and do not use toString() + converterMap.put(TimeZone.class, new DesignToStringConverter<TimeZone>( + TimeZone.class, "getTimeZone") { + @Override + public String convertToPresentation(TimeZone value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return value.getID(); + } + }); + } + + /** + * Adds a converter for a new type. + * + * @param converter + * Converter to add. + */ + protected <T> void addConverter(Converter<String, T> converter) { + converterMap.put(converter.getModelType(), converter); + } + + /** + * Adds a converter for a given type. + * + * @param type + * Type to convert to/from. + * @param converter + * Converter. + */ + protected <T> void addConverter(Class<?> type, + Converter<String, ?> converter) { + converterMap.put(type, converter); + } + + /** + * Removes the converter for given type, if it was present. + * + * @param type + * Type to remove converter for. + */ + protected void removeConverter(Class<?> type) { + converterMap.remove(type); + } + + /** + * Returns a set of classes that have a converter registered. This is <b>not + * the same</b> as the list of supported classes - subclasses of classes in + * this set are also supported. + * + * @return An unmodifiable set of classes that have a converter registered. + */ + protected Set<Class<?>> getRegisteredClasses() { + return Collections.unmodifiableSet(converterMap.keySet()); + } + + /** + * Parses a given string as a value of given type + * + * @param value + * String value to convert. + * @param type + * Expected result type. + * @return String converted to the expected result type using a registered + * converter for that type. + */ + public <T> T parse(String value, Class<? extends T> type) { + Converter<String, T> converter = findConverterFor(type); + if (converter != null) { + return converter.convertToModel(value, type, null); + } else { + return null; + } + } + + /** + * Finds a formatter for a given object and attempts to format it. + * + * @param object + * Object to format. + * @return String representation of the object, as returned by the + * registered converter. + */ + public String format(Object object) { + return format(object, object == null ? Object.class : object.getClass()); + } + + /** + * Formats an object according to a converter suitable for a given type. + * + * @param object + * Object to format. + * @param type + * Type of the object. + * @return String representation of the object, as returned by the + * registered converter. + */ + public <T> String format(T object, Class<? extends T> type) { + if (object == null) { + return null; + } else { + return findConverterFor(object.getClass()).convertToPresentation( + object, String.class, null); + } + } + + /** + * Checks whether or not a value of a given type can be converted. If a + * converter for a superclass is found, this will return true. + * + * @param type + * Type to check. + * @return <b>true</b> when either a given type or its supertype has a + * converter, <b>false</b> otherwise. + */ + public boolean canConvert(Class<?> type) { + return findConverterFor(type) != null; + } + + /** + * Finds a converter for a given type. May return a converter for a + * superclass instead, if one is found and {@code strict} is false. + * + * @param sourceType + * Type to find a converter for. + * @param strict + * Whether or not search should be strict. When this is + * <b>false</b>, a converter for a superclass of given type may + * be returned. + * @return A valid converter for a given type or its supertype, <b>null</b> + * if it was not found. + */ + @SuppressWarnings("unchecked") + protected <T> Converter<String, T> findConverterFor( + Class<? extends T> sourceType, boolean strict) { + if (sourceType.isEnum()) { + // enums can be read in lowercase + return new DesignToStringConverter<T>(sourceType) { + + @Override + public T convertToModel(String value, + Class<? extends T> targetType, Locale locale) + throws Converter.ConversionException { + return super.convertToModel(value.toUpperCase(), + targetType, locale); + } + }; + } else if (converterMap.containsKey(sourceType)) { + return ((Converter<String, T>) converterMap.get(sourceType)); + } else if (!strict) { + for (Class<?> supported : converterMap.keySet()) { + if (supported.isAssignableFrom(sourceType)) { + return ((Converter<String, T>) converterMap.get(supported)); + } + } + } + return null; + } + + /** + * Finds a converter for a given type. May return a converter for a + * superclass instead, if one is found. + * + * @param sourceType + * Type to find a converter for. + * @return A valid converter for a given type or its subtype, <b>null</b> if + * it was not found. + */ + protected <T> Converter<String, T> findConverterFor( + Class<? extends T> sourceType) { + return findConverterFor(sourceType, false); + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignDateConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignDateConverter.java new file mode 100644 index 0000000000..d2d63ad16e --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignDateConverter.java @@ -0,0 +1,67 @@ +/* + * 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.converters; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.ui.declarative.DesignAttributeHandler; + +/** + * A date converter to be used by {@link DesignAttributeHandler}. Provides + * ISO-compliant way of storing date and time. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignDateConverter implements Converter<String, Date> { + + @Override + public Date convertToModel(String value, Class<? extends Date> targetType, + Locale locale) throws Converter.ConversionException { + for (String pattern : new String[] { "yyyy-MM-dd HH:mm:ssZ", + "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH", + "yyyy-MM-dd", "yyyy-MM", "yyyy" }) { + try { + return new SimpleDateFormat(pattern).parse(value); + } catch (ParseException e) { + // not parseable, ignore and try another format + } + } + return null; + } + + @Override + public String convertToPresentation(Date value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ").format(value); + } + + @Override + public Class<Date> getModelType() { + return Date.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignFormatConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignFormatConverter.java new file mode 100644 index 0000000000..e9b26fce0b --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignFormatConverter.java @@ -0,0 +1,72 @@ +/* + * 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.converters; + +import java.text.Format; +import java.text.ParseException; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; + +/** + * Converter based on Java Formats rather than static methods. + * + * @since 7.4 + * @author Vaadin Ltd + * @param <TYPE> + * Type of the object to format. + */ +public class DesignFormatConverter<TYPE> implements Converter<String, TYPE> { + + private final Format format; + private final Class<? extends TYPE> type; + + /** + * Constructs an instance of the converter. + */ + public DesignFormatConverter(Class<? extends TYPE> type, Format format) { + this.type = type; + this.format = format; + } + + @Override + public TYPE convertToModel(String value, Class<? extends TYPE> targetType, + Locale locale) throws Converter.ConversionException { + try { + return targetType.cast(this.format.parseObject(value)); + } catch (ParseException e) { + throw new Converter.ConversionException(e); + } + } + + @Override + public String convertToPresentation(TYPE value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return this.format.format(value); + } + + @Override + public Class<TYPE> getModelType() { + return (Class<TYPE>) this.type; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java new file mode 100644 index 0000000000..70e46b8e7f --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java @@ -0,0 +1,87 @@ +/* + * 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.converters; + +import java.io.File; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.server.ExternalResource; +import com.vaadin.server.FileResource; +import com.vaadin.server.FontAwesome; +import com.vaadin.server.Resource; +import com.vaadin.server.ThemeResource; +import com.vaadin.ui.declarative.DesignAttributeHandler; + +/** + * A converter for {@link Resource} implementations supported by + * {@link DesignAttributeHandler}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignResourceConverter implements Converter<String, Resource> { + + @Override + public Resource convertToModel(String value, + Class<? extends Resource> targetType, Locale locale) + throws Converter.ConversionException { + if (value.startsWith("http://")) { + return new ExternalResource(value); + } else if (value.startsWith("theme://")) { + return new ThemeResource(value.substring(8)); + } else if (value.startsWith("font://")) { + return FontAwesome.valueOf(value.substring(7)); + } else { + return new FileResource(new File(value)); + } + } + + @Override + public String convertToPresentation(Resource value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + 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) { + String path = ((FileResource) value).getSourceFile().getPath(); + if (File.separatorChar != '/') { + // make sure we use '/' as file separator in templates + return path.replace(File.separatorChar, '/'); + } else { + return path; + } + } else { + throw new Converter.ConversionException("unknown Resource type - " + + value.getClass().getName()); + } + } + + @Override + public Class<Resource> getModelType() { + return Resource.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java new file mode 100644 index 0000000000..d9d84a1263 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java @@ -0,0 +1,121 @@ +/* + * 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.converters; + +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.event.ShortcutAction; + +/** + * Converter for {@link ShortcutActions}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignShortcutActionConverter implements + Converter<String, ShortcutAction> { + + /** + * Default instance of the shortcut key mapper. + */ + private final ShortcutKeyMapper keyMapper; + + /** + * Constructs the converter with given key mapper. + * + * @param mapper + * Key mapper to use. + */ + public DesignShortcutActionConverter(ShortcutKeyMapper mapper) { + keyMapper = mapper; + } + + @Override + public ShortcutAction convertToModel(String value, + Class<? extends ShortcutAction> targetType, Locale locale) + throws Converter.ConversionException { + if (value.length() == 0) { + return null; + } + String[] data = value.split(" ", 2); + + String[] parts = data[0].split("-"); + // handle keycode + String keyCodePart = parts[parts.length - 1]; + int keyCode = getKeyMapper().getKeycodeForString(keyCodePart); + if (keyCode < 0) { + throw new IllegalArgumentException("Invalid shortcut definition " + + value); + } + // handle modifiers + int[] modifiers = null; + if (parts.length > 1) { + modifiers = new int[parts.length - 1]; + } + for (int i = 0; i < parts.length - 1; i++) { + int modifier = getKeyMapper().getKeycodeForString(parts[i]); + if (modifier > 0) { + modifiers[i] = modifier; + } else { + throw new IllegalArgumentException( + "Invalid shortcut definition " + value); + } + } + return new ShortcutAction(data.length == 2 ? data[1] : null, keyCode, + modifiers); + } + + @Override + public String convertToPresentation(ShortcutAction value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + StringBuilder sb = new StringBuilder(); + // handle modifiers + if (value.getModifiers() != null) { + for (int modifier : value.getModifiers()) { + sb.append(getKeyMapper().getStringForKeycode(modifier)).append( + "-"); + } + } + // handle keycode + sb.append(getKeyMapper().getStringForKeycode(value.getKeyCode())); + if (value.getCaption() != null) { + sb.append(" ").append(value.getCaption()); + } + return sb.toString(); + } + + @Override + public Class<ShortcutAction> getModelType() { + return ShortcutAction.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + + /** + * Returns the currently used key mapper. + * + * @return Key mapper. + */ + public ShortcutKeyMapper getKeyMapper() { + return keyMapper; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignToStringConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignToStringConverter.java new file mode 100644 index 0000000000..d80119bea1 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignToStringConverter.java @@ -0,0 +1,115 @@ +/* + * 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.converters; + +import java.lang.reflect.InvocationTargetException; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.ui.declarative.DesignAttributeHandler; + +/** + * Utility class for {@link DesignAttributeHandler} that deals with converting + * various types to string. + * + * @since 7.4 + * @author Vaadin Ltd + * @param <TYPE> + * Type of the data being converted. + */ +public class DesignToStringConverter<TYPE> implements Converter<String, TYPE> { + + private final Class<? extends TYPE> type; + + private final String staticMethodName; + + /** + * A string that corresponds to how a null value is stored. + */ + public static final String NULL_VALUE_REPRESENTATION = ""; + + /** + * Constructs the converter for a given type. Implicitly requires that a + * static method {@code valueOf(String)} is present in the type to do the + * conversion. + * + * @param type + * Type of values to convert. + */ + public DesignToStringConverter(Class<? extends TYPE> type) { + this(type, "valueOf"); + } + + /** + * Constructs the converter for a given type, giving the name of the public + * static method that does the conversion from String. + * + * @param type + * Type to convert. + * @param staticMethodName + * Method to call when converting from String to this type. This + * must be public and static method that returns an object of + * passed type. + */ + public DesignToStringConverter(Class<? extends TYPE> type, String staticMethodName) { + this.type = type; + this.staticMethodName = staticMethodName; + } + + @Override + public TYPE convertToModel(String value, Class<? extends TYPE> targetType, + Locale locale) throws Converter.ConversionException { + try { + return type.cast(type + .getMethod(this.staticMethodName, String.class).invoke( + null, value)); + } catch (IllegalAccessException e) { + throw new Converter.ConversionException(e); + } catch (IllegalArgumentException e) { + throw new Converter.ConversionException(e); + } catch (InvocationTargetException e) { + throw new Converter.ConversionException(e); + } catch (NoSuchMethodException e) { + throw new Converter.ConversionException(e); + } catch (SecurityException e) { + throw new Converter.ConversionException(e); + } catch (RuntimeException e) { + throw new Converter.ConversionException(e); + } + } + + @Override + public String convertToPresentation(TYPE value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + if (value == null) { + return NULL_VALUE_REPRESENTATION; + } else { + return value.toString(); + } + } + + @Override + public Class<TYPE> getModelType() { + return (Class<TYPE>) this.type; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/ShortcutKeyMapper.java b/server/src/com/vaadin/ui/declarative/converters/ShortcutKeyMapper.java new file mode 100644 index 0000000000..46c38ce0e0 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/ShortcutKeyMapper.java @@ -0,0 +1,151 @@ +/* + * 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.converters; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutAction.ModifierKey; + +/** + * Provides mappings between shortcut keycodes and their representation in + * design attributes. Contains a default framework implementation as a field. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface ShortcutKeyMapper extends Serializable { + + /** + * Gets the key code for a given string. + * + * @param attributePresentation + * String + * @return Key code. + */ + public int getKeycodeForString(String attributePresentation); + + /** + * Returns a string for a given key code. + * + * @param keyCode + * Key code. + * @return String. + */ + public String getStringForKeycode(int keyCode); + + /** + * An instance of a default keymapper. + */ + public static final ShortcutKeyMapper DEFAULT = new ShortcutKeyMapper() { + + private final Map<Integer, String> keyCodeMap = Collections + .synchronizedMap(new HashMap<Integer, String>()); + private final Map<String, Integer> presentationMap = Collections + .synchronizedMap(new HashMap<String, Integer>()); + + { + // map modifiers + mapKey(ModifierKey.ALT, "alt"); + mapKey(ModifierKey.CTRL, "ctrl"); + mapKey(ModifierKey.META, "meta"); + mapKey(ModifierKey.SHIFT, "shift"); + // map keys + mapKey(KeyCode.ENTER, "enter"); + mapKey(KeyCode.ESCAPE, "escape"); + mapKey(KeyCode.PAGE_UP, "pageup"); + mapKey(KeyCode.PAGE_DOWN, "pagedown"); + mapKey(KeyCode.TAB, "tab"); + mapKey(KeyCode.ARROW_LEFT, "left"); + mapKey(KeyCode.ARROW_UP, "up"); + mapKey(KeyCode.ARROW_RIGHT, "right"); + mapKey(KeyCode.ARROW_DOWN, "down"); + mapKey(KeyCode.BACKSPACE, "backspace"); + mapKey(KeyCode.DELETE, "delete"); + mapKey(KeyCode.INSERT, "insert"); + mapKey(KeyCode.END, "end"); + mapKey(KeyCode.HOME, "home"); + mapKey(KeyCode.F1, "f1"); + mapKey(KeyCode.F2, "f2"); + mapKey(KeyCode.F3, "f3"); + mapKey(KeyCode.F4, "f4"); + mapKey(KeyCode.F5, "f5"); + mapKey(KeyCode.F6, "f6"); + mapKey(KeyCode.F7, "f7"); + mapKey(KeyCode.F8, "f8"); + mapKey(KeyCode.F9, "f9"); + mapKey(KeyCode.F10, "f10"); + mapKey(KeyCode.F11, "f11"); + mapKey(KeyCode.F12, "f12"); + mapKey(KeyCode.NUM0, "0"); + mapKey(KeyCode.NUM1, "1"); + mapKey(KeyCode.NUM2, "2"); + mapKey(KeyCode.NUM3, "3"); + mapKey(KeyCode.NUM4, "4"); + mapKey(KeyCode.NUM5, "5"); + mapKey(KeyCode.NUM6, "6"); + mapKey(KeyCode.NUM7, "7"); + mapKey(KeyCode.NUM8, "8"); + mapKey(KeyCode.NUM9, "9"); + mapKey(KeyCode.SPACEBAR, "spacebar"); + mapKey(KeyCode.A, "a"); + mapKey(KeyCode.B, "b"); + mapKey(KeyCode.C, "c"); + mapKey(KeyCode.D, "d"); + mapKey(KeyCode.E, "e"); + mapKey(KeyCode.F, "f"); + mapKey(KeyCode.G, "g"); + mapKey(KeyCode.H, "h"); + mapKey(KeyCode.I, "i"); + mapKey(KeyCode.J, "j"); + mapKey(KeyCode.K, "k"); + mapKey(KeyCode.L, "l"); + mapKey(KeyCode.M, "m"); + mapKey(KeyCode.N, "n"); + mapKey(KeyCode.O, "o"); + mapKey(KeyCode.P, "p"); + mapKey(KeyCode.Q, "q"); + mapKey(KeyCode.R, "r"); + mapKey(KeyCode.S, "s"); + mapKey(KeyCode.T, "t"); + mapKey(KeyCode.U, "u"); + mapKey(KeyCode.V, "v"); + mapKey(KeyCode.X, "x"); + mapKey(KeyCode.Y, "y"); + mapKey(KeyCode.Z, "z"); + } + + private void mapKey(int keyCode, String presentation) { + keyCodeMap.put(keyCode, presentation); + presentationMap.put(presentation, keyCode); + } + + @Override + public int getKeycodeForString(String attributePresentation) { + Integer code = presentationMap.get(attributePresentation); + return code != null ? code.intValue() : -1; + } + + @Override + public String getStringForKeycode(int keyCode) { + return keyCodeMap.get(keyCode); + } + + }; +}
\ No newline at end of file diff --git a/server/tests/src/com/vaadin/tests/design/DateFieldsTest.java b/server/tests/src/com/vaadin/tests/design/DateFieldsTest.java new file mode 100644 index 0000000000..b8aa6ad3c9 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/DateFieldsTest.java @@ -0,0 +1,99 @@ +/* + * 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.tests.design; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +import org.junit.Test; + +import com.vaadin.shared.ui.datefield.Resolution; +import com.vaadin.ui.DateField; +import com.vaadin.ui.InlineDateField; +import com.vaadin.ui.PopupDateField; +import com.vaadin.ui.declarative.Design; + +/** + * Tests the declarative support for implementations of {@link DateField}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DateFieldsTest { + + @Test + public void testInlineDateFieldToFromDesign() throws Exception { + InlineDateField field = new InlineDateField("Day is", + new SimpleDateFormat("yyyy-MM-dd").parse("2003-02-27")); + field.setResolution(Resolution.DAY); + field.setShowISOWeekNumbers(true); + field.setRangeStart(new SimpleDateFormat("yyyy-MM-dd") + .parse("2001-02-27")); + field.setRangeEnd(new SimpleDateFormat("yyyy-MM-dd") + .parse("2011-02-27")); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Design.write(field, bos); + + InlineDateField result = (InlineDateField) Design + .read(new ByteArrayInputStream(bos.toByteArray())); + assertEquals(field.getResolution(), result.getResolution()); + assertEquals(field.getCaption(), result.getCaption()); + assertEquals(field.getValue(), result.getValue()); + assertEquals(field.getRangeStart(), result.getRangeStart()); + assertEquals(field.getRangeEnd(), result.getRangeEnd()); + } + + @Test + public void testPopupDateFieldFromDesign() throws Exception { + ByteArrayInputStream bis = new ByteArrayInputStream( + "<!DOCTYPE html><html><head></head><body><v-popup-date-field show-iso-week-numbers caption=\"Day is\" resolution=\"MINUTE\" range-end=\"2019-01-15\" input-prompt=\"Pick a day\" value=\"2003-02-27 07:15\"></v-popup-date-field></body></html>" + .getBytes()); + PopupDateField result = (PopupDateField) Design.read(bis); + assertEquals(Resolution.MINUTE, result.getResolution()); + assertEquals("Day is", result.getCaption()); + assertTrue(result.isShowISOWeekNumbers()); + assertEquals("Pick a day", result.getInputPrompt()); + assertEquals( + new SimpleDateFormat("yyyy-MM-dd HH:mm") + .parse("2003-02-27 07:15"), + result.getValue()); + assertEquals(new SimpleDateFormat("yyyy-MM-dd").parse("2019-01-15"), + result.getRangeEnd()); + + } + + @Test + public void testPopupDateFieldFromDesignInTicket() throws Exception { + ByteArrayInputStream bis = new ByteArrayInputStream( + "<!DOCTYPE html><html><head></head><body><v-date-field range-start=\"2014-05-05\" range-end=\"2014-06-05\" date-out-of-range-message=\"Please select a sensible date\" resolution=\"day\" date-format=\"yyyy-MM-dd\" lenient show-iso-week-numbers parse-error-message=\"You are doing it wrong\" time-zone=\"GMT+5\" value=\"2014-05-15\"></v-date-field></body></html>" + .getBytes()); + DateField result = (DateField) Design.read(bis); + assertEquals(Resolution.DAY, result.getResolution()); + assertTrue(result.isShowISOWeekNumbers()); + assertEquals(new SimpleDateFormat("yyyy-MM-dd").parse("2014-05-15"), + result.getValue()); + assertEquals(new SimpleDateFormat("yyyy-MM-dd").parse("2014-06-05"), + result.getRangeEnd()); + assertEquals(TimeZone.getTimeZone("GMT+5"), result.getTimeZone()); + } + +} diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java new file mode 100644 index 0000000000..c7909751a1 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java @@ -0,0 +1,149 @@ +/* + * 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.tests.design; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.TimeZone; + +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.event.ShortcutAction; +import com.vaadin.server.ExternalResource; +import com.vaadin.server.FileResource; +import com.vaadin.server.Resource; +import com.vaadin.server.ThemeResource; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.ui.declarative.DesignFormatter; + +/** + * Various tests related to formatter. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignFormatterTest { + + private DesignFormatter formatter; + + @Before + public void setUp() { + // initialise with default classes + formatter = new DesignFormatter(); + } + + @Test + public void testSupportedClasses() { + + for (Class<?> type : new Class<?>[] { String.class, Boolean.class, + Integer.class, Float.class, Byte.class, Short.class, + Double.class, ShortcutAction.class, Date.class, + FileResource.class, ExternalResource.class, + ThemeResource.class, Resource.class, TimeZone.class }) { + assertTrue("not supported " + type.getSimpleName(), + formatter.canConvert(type)); + } + } + + @Test + public void testDate() throws Exception { + Date date = new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-17"); + String formatted = formatter.format(date); + Date result = formatter.parse(formatted, Date.class); + + // writing will always give full date string + assertEquals("2012-02-17 00:00:00+0200", formatted); + assertEquals(date, result); + + // try short date as well + result = formatter.parse("2012-02-17", Date.class); + assertEquals(date, result); + } + + @Test + public void testShortcutActions() { + ShortcutAction action = new ShortcutAction("&^d"); + String formatted = formatter.format(action); + // note the space here - it separates key combination from caption + assertEquals("alt-ctrl-d d", formatted); + + ShortcutAction result = formatter + .parse(formatted, ShortcutAction.class); + assertTrue(equals(action, result)); + } + + @Test + public void testShortcutActionNoCaption() { + ShortcutAction action = new ShortcutAction(null, + ShortcutAction.KeyCode.D, new int[] { + ShortcutAction.ModifierKey.ALT, + ShortcutAction.ModifierKey.CTRL }); + String formatted = formatter.format(action); + assertEquals("alt-ctrl-d", formatted); + + ShortcutAction result = formatter + .parse(formatted, ShortcutAction.class); + assertTrue(equals(action, result)); + } + + @Test + public void testTimeZone() { + TimeZone zone = TimeZone.getTimeZone("GMT+2"); + String formatted = formatter.format(zone); + assertEquals("GMT+02:00", formatted); + TimeZone result = formatter.parse(formatted, TimeZone.class); + assertEquals(zone, result); + // try shorthand notation as well + result = formatter.parse("GMT+2", TimeZone.class); + assertEquals(zone, result); + } + + /** + * A static method to allow comparison two different actions. + * + * @param act + * One action to compare. + * @param other + * Second action to compare. + * @return <b>true</b> when both actions are the same (caption, icon, and + * key combination). + */ + public static final boolean equals(ShortcutAction act, ShortcutAction other) { + if (SharedUtil.equals(other.getCaption(), act.getCaption()) + && SharedUtil.equals(other.getIcon(), act.getIcon()) + && act.getKeyCode() == other.getKeyCode() + && act.getModifiers().length == other.getModifiers().length) { + HashSet<Integer> thisSet = new HashSet<Integer>( + act.getModifiers().length); + // this is a bit tricky comparison, but there is no nice way of + // making int[] into a Set + for (int mod : act.getModifiers()) { + thisSet.add(mod); + } + for (int mod : other.getModifiers()) { + thisSet.remove(mod); + } + return thisSet.isEmpty(); + } + return false; + } + +} diff --git a/server/tests/src/com/vaadin/tests/design/LocaleTest.java b/server/tests/src/com/vaadin/tests/design/LocaleTest.java index 939080fbbc..8f0ef4d13e 100644 --- a/server/tests/src/com/vaadin/tests/design/LocaleTest.java +++ b/server/tests/src/com/vaadin/tests/design/LocaleTest.java @@ -15,15 +15,18 @@ */ package com.vaadin.tests.design; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.ByteArrayInputStream; import java.util.Locale; -import junit.framework.TestCase; - import org.jsoup.nodes.Document; import org.jsoup.nodes.DocumentType; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import org.junit.Before; +import org.junit.Test; import com.vaadin.ui.Button; import com.vaadin.ui.Component; @@ -32,11 +35,6 @@ import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.declarative.Design; import com.vaadin.ui.declarative.DesignContext; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; /** * Tests the handling of the locale property in parsing and html generation. diff --git a/server/tests/src/com/vaadin/tests/event/ShortcutActionTest.java b/server/tests/src/com/vaadin/tests/event/ShortcutActionTest.java new file mode 100644 index 0000000000..9af23b86b1 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/event/ShortcutActionTest.java @@ -0,0 +1,115 @@ +/* + * 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.tests.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashSet; + +import org.junit.Test; + +import com.vaadin.event.ShortcutAction; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.tests.design.DesignFormatterTest; + +/** + * Tests various things about shortcut actions. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class ShortcutActionTest { + + private static final String[] KEYS = "a b c d e f g h i j k l m n o p q r s t u v w x y z" + .split("\\s+"); + + @Test + public void testHashCodeUniqueness() { + HashSet<ShortcutAction> set = new HashSet<ShortcutAction>(); + for (String modifier : new String[] { "^", "&", "_", "&^", "&_", "_^", + "&^_" }) { + for (String key : KEYS) { + ShortcutAction action = new ShortcutAction(modifier + key); + for (ShortcutAction other : set) { + assertFalse(equals(action, other)); + } + set.add(action); + } + } + } + + @Test + public void testModifierOrderIrrelevant() { + for (String key : KEYS) { + // two modifiers + for (String modifier : new String[] { "&^", "&_", "_^" }) { + ShortcutAction action1 = new ShortcutAction(modifier + key); + ShortcutAction action2 = new ShortcutAction( + modifier.substring(1) + modifier.substring(0, 1) + key); + assertTrue(modifier + key, equals(action1, action2)); + } + // three modifiers + ShortcutAction action1 = new ShortcutAction("&^_" + key); + for (String modifier : new String[] { "&_^", "^&_", "^_&", "_^&", + "_&^" }) { + ShortcutAction action2 = new ShortcutAction(modifier + key); + assertTrue(modifier + key, equals(action1, action2)); + + } + } + } + + @Test + public void testSameKeycodeDifferentCaptions() { + ShortcutAction act1 = new ShortcutAction("E&xit"); + ShortcutAction act2 = new ShortcutAction("Lu&xtorpeda - Autystyczny"); + assertFalse(equals(act1, act2)); + } + + /** + * A static method to allow comparison two different actions. + * + * @see DesignFormatterTest + * + * @param act + * One action to compare. + * @param other + * Second action to compare. + * @return <b>true</b> when both actions are the same (caption, icon, and + * key combination). + */ + public static final boolean equals(ShortcutAction act, ShortcutAction other) { + if (SharedUtil.equals(other.getCaption(), act.getCaption()) + && SharedUtil.equals(other.getIcon(), act.getIcon()) + && act.getKeyCode() == other.getKeyCode() + && act.getModifiers().length == other.getModifiers().length) { + HashSet<Integer> thisSet = new HashSet<Integer>( + act.getModifiers().length); + // this is a bit tricky comparison, but there is no nice way of + // making int[] into a Set + for (int mod : act.getModifiers()) { + thisSet.add(mod); + } + for (int mod : other.getModifiers()) { + thisSet.remove(mod); + } + return thisSet.isEmpty(); + } + return false; + } + +} |