All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.ui.declarative.DesignFormatter Maven / Gradle / Ivy

There is a newer version: 8.27.3
Show newest version
/*
 * Copyright (C) 2000-2024 Vaadin Ltd
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  for the full
 * license.
 */
package com.vaadin.ui.declarative;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;

import org.jsoup.parser.Parser;

import com.vaadin.data.Converter;
import com.vaadin.data.Result;
import com.vaadin.data.ValueContext;
import com.vaadin.data.converter.StringToBigDecimalConverter;
import com.vaadin.data.converter.StringToDoubleConverter;
import com.vaadin.data.converter.StringToFloatConverter;
import com.vaadin.event.ShortcutAction;
import com.vaadin.server.Resource;
import com.vaadin.ui.declarative.converters.DesignDateConverter;
import com.vaadin.ui.declarative.converters.DesignEnumConverter;
import com.vaadin.ui.declarative.converters.DesignLocalDateConverter;
import com.vaadin.ui.declarative.converters.DesignLocalDateTimeConverter;
import com.vaadin.ui.declarative.converters.DesignObjectConverter;
import com.vaadin.ui.declarative.converters.DesignResourceConverter;
import com.vaadin.ui.declarative.converters.DesignShortcutActionConverter;
import com.vaadin.ui.declarative.converters.DesignTimeZoneConverter;
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, Converter> converterMap = new ConcurrentHashMap<>();
    private final Converter stringObjectConverter = new DesignObjectConverter();

    /**
     * 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[] { Byte.class, Short.class,
                Integer.class, Long.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 booleanConverter = new Converter() {

            @Override
            public Result convertToModel(String value,
                    ValueContext context) {
                return Result.ok(!value.equalsIgnoreCase("false"));
            }

            @Override
            public String convertToPresentation(Boolean value,
                    ValueContext context) {
                if (value.booleanValue()) {
                    return "";
                } else {
                    return "false";
                }
            }

        };
        converterMap.put(Boolean.class, booleanConverter);
        converterMap.put(boolean.class, booleanConverter);

        // floats and doubles use formatters
        final DecimalFormatSymbols symbols = new DecimalFormatSymbols(
                new Locale("en_US"));
        final DecimalFormat fmt = new DecimalFormat("0.###", symbols);
        fmt.setGroupingUsed(false);

        Converter floatConverter = new StringToFloatConverter(
                "Error converting value") {
            @Override
            protected NumberFormat getFormat(Locale locale) {
                return fmt;
            };
        };
        converterMap.put(Float.class, floatConverter);
        converterMap.put(float.class, floatConverter);

        Converter doubleConverter = new StringToDoubleConverter(
                "Error converting value") {
            @Override
            protected NumberFormat getFormat(Locale locale) {
                return fmt;
            };
        };
        converterMap.put(Double.class, doubleConverter);
        converterMap.put(double.class, doubleConverter);

        final DecimalFormat bigDecimalFmt = new DecimalFormat("0.###", symbols);
        bigDecimalFmt.setGroupingUsed(false);
        bigDecimalFmt.setParseBigDecimal(true);
        converterMap.put(BigDecimal.class,
                new StringToBigDecimalConverter("Error converting value") {
                    @Override
                    protected NumberFormat getFormat(Locale locale) {
                        return bigDecimalFmt;
                    };
                });

        // strings do nothing
        converterMap.put(String.class, new Converter() {

            @Override
            public Result convertToModel(String value,
                    ValueContext context) {
                return Result.ok(value);
            }

            @Override
            public String convertToPresentation(String value,
                    ValueContext context) {
                return value;
            }

        });

        // char takes the first character from the string
        Converter charConverter = new DesignToStringConverter(
                Character.class) {

            @Override
            public Result convertToModel(String value,
                    ValueContext context) {
                return Result.ok(value.charAt(0));
            }

        };
        converterMap.put(Character.class, charConverter);
        converterMap.put(char.class, charConverter);

        converterMap.put(Date.class, new DesignDateConverter());
        converterMap.put(LocalDate.class, new DesignLocalDateConverter());
        converterMap.put(LocalDateTime.class,
                new DesignLocalDateTimeConverter());
        converterMap.put(ShortcutAction.class,
                new DesignShortcutActionConverter());
        converterMap.put(Resource.class, new DesignResourceConverter());
        converterMap.put(TimeZone.class, new DesignTimeZoneConverter());
    }

    /**
     * Adds a converter for a given type.
     *
     * @param type
     *            Type to convert to/from.
     * @param converter
     *            Converter.
     * @since 8.0
     */
    protected  void addConverter(Class type,
            Converter 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 not
     * the same 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> 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 parse(String value, Class type) {
        Converter converter = findConverterFor(type);
        if (converter != null) {
            Result result = converter.convertToModel(value,
                    new ValueContext());
            return result.getOrThrow(msg -> new IllegalArgumentException(msg));
        } 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  String format(T object, Class type) {
        if (object == null) {
            return null;
        } else {
            Converter converter = findConverterFor(
                    object.getClass());
            return converter.convertToPresentation(object, new ValueContext());
        }
    }

    /**
     * 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 true when either a given type or its supertype has a
     *         converter, false 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
     *            false, a converter for a superclass of given type may
     *            be returned.
     * @return A valid converter for a given type or its supertype, null
     *         if it was not found.
     * @since 8.0
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected  Converter findConverterFor(
            Class sourceType, boolean strict) {
        if (sourceType == Object.class) {
            // Use for propertyIds, itemIds and such. Only string type objects
            // are really supported if no special logic is implemented in the
            // component.
            return (Converter) stringObjectConverter;
        }

        if (converterMap.containsKey(sourceType)) {
            return ((Converter) converterMap.get(sourceType));
        } else if (!strict) {
            for (Class supported : converterMap.keySet()) {
                if (supported.isAssignableFrom(sourceType)) {
                    return ((Converter) converterMap.get(supported));
                }
            }
        }

        if (sourceType.isEnum()) {
            return new DesignEnumConverter(sourceType);
        }
        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, null if
     *         it was not found.
     * @since 8.0
     */
    protected  Converter findConverterFor(
            Class sourceType) {
        return findConverterFor(sourceType, false);
    }

    /**
     * 

* Encodes some special characters in a given input String to make * it ready to be written as contents of a text node. WARNING: this will * e.g. encode "<someTag>" to "&lt;someTag&gt;" as this method * doesn't do any parsing and assumes that there are no intended HTML * elements in the input. Only some entities are actually encoded: * &,<, > It's assumed that other entities are taken care of by * Jsoup. *

*

* Typically, this method will be used by components to encode data (like * option items in {@code AbstractSelect}) when dumping to HTML format *

* * @since 7.5.7 * @param input * String to be encoded * @return String with &,< and > replaced with their HTML entities */ public static String encodeForTextNode(String input) { if (input == null) { return null; } return input.replace("&", "&").replace(">", ">").replace("<", "<"); } /** *

* Decodes HTML entities in a text from text node and replaces them with * actual characters. *

* *

* Typically this method will be used by components to read back data (like * option items in {@code AbstractSelect}) from HTML. Note that this method * unencodes more characters than {@link #encodeForTextNode(String)} encodes *

* * @since 7.6 * @param input * @return */ public static String decodeFromTextNode(String input) { return Parser.unescapeEntities(input, false); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy