    001// Copyright 2009, 2010, 2012 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.internal.translator;
    016
    017import org.apache.tapestry5.commons.services.TypeCoercer;
    018import org.apache.tapestry5.commons.util.CollectionFactory;
    019import org.apache.tapestry5.dom.Element;
    020import org.apache.tapestry5.ioc.services.ThreadLocale;
    021import org.apache.tapestry5.services.javascript.JavaScriptSupport;
    022
    023import java.math.BigDecimal;
    024import java.math.BigInteger;
    025import java.text.DecimalFormat;
    026import java.text.DecimalFormatSymbols;
    027import java.text.NumberFormat;
    028import java.text.ParseException;
    029import java.util.Locale;
    030import java.util.Set;
    031
    032public class NumericTranslatorSupportImpl implements NumericTranslatorSupport
    033{
    034    private final TypeCoercer typeCoercer;
    035
    036    private final ThreadLocale threadLocale;
    037
    038    private final JavaScriptSupport javascriptSupport;
    039
    040    private final Set<Class> integerTypes = CollectionFactory.newSet();
    041
    042    public NumericTranslatorSupportImpl(TypeCoercer typeCoercer, ThreadLocale threadLocale,
    043                                        JavaScriptSupport javascriptSupport)
    044    {
    045        this.typeCoercer = typeCoercer;
    046        this.threadLocale = threadLocale;
    047        this.javascriptSupport = javascriptSupport;
    048
    049        Class[] integerTypes =
    050                {Byte.class, Short.class, Integer.class, Long.class, BigInteger.class};
    051
    052        for (Class c : integerTypes)
    053        {
    054            this.integerTypes.add(c);
    055        }
    056
    057    }
    058
    059    public <T extends Number> void setupTranslation(Class<T> type, Element element, String message)
    060    {
    061        String translation = isIntegerType(type) ? "integer" : "numeric";
    062
    063        javascriptSupport.require("t5/core/validation");
    064
    065        element.attributes("data-validation", "true",
    066                "data-translation", translation,
    067                "data-translation-message", message);
    068    }
    069
    070    private boolean isIntegerType(Class type)
    071    {
    072        return integerTypes.contains(type);
    073    }
    074
    075    public <T extends Number> T parseClient(Class<T> type, String clientValue) throws ParseException
    076    {
    077        NumericFormatter formatter = getParseFormatter(type);
    078
    079        Number number = formatter.parse(clientValue.trim());
    080
    081        return typeCoercer.coerce(number, type);
    082    }
    083
    084    private NumericFormatter getParseFormatter(Class type)
    085    {
    086        Locale locale = threadLocale.getLocale();
    087        DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
    088
    089        if (type.equals(BigInteger.class))
    090            return new BigIntegerNumericFormatter(symbols);
    091
    092        if (type.equals(BigDecimal.class))
    093            return new BigDecimalNumericFormatter(symbols);
    094
    095        // We don't cache NumberFormat instances because they are not thread safe.
    096        // Perhaps we should turn this service into a perthread so that we can cache
    097        // (for the duration of a request)?
    098
    099        // We don't cache the rest of these, because they are built on DecimalFormat which is
    100        // not thread safe.
    101
    102        if (isIntegerType(type))
    103        {
    104            NumberFormat format = NumberFormat.getIntegerInstance(locale);
    105            return new NumericFormatterImpl(format);
    106        }
    107
    108        DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(locale);
    109
    110        if (type.equals(BigDecimal.class))
    111            df.setParseBigDecimal(true);
    112
    113        return new NumericFormatterImpl(df);
    114    }
    115
    116    private NumericFormatter getOutputFormatter(Class type)
    117    {
    118        Locale locale = threadLocale.getLocale();
    119
    120        DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
    121
    122        if (type.equals(BigInteger.class))
    123            return new BigIntegerNumericFormatter(symbols);
    124
    125        if (type.equals(BigDecimal.class))
    126            return new BigDecimalNumericFormatter(symbols);
    127
    128        // We don't cache the rest of these, because they are built on DecimalFormat which is
    129        // not thread safe.
    130
    131        if (!isIntegerType(type))
    132        {
    133            NumberFormat format = NumberFormat.getNumberInstance(locale);
    134
    135            return new NumericFormatterImpl(format);
    136        }
    137
    138        DecimalFormat df = new DecimalFormat(toString(symbols.getZeroDigit()), symbols);
    139
    140        return new NumericFormatterImpl(df);
    141    }
    142
    143    public <T extends Number> String toClient(Class<T> type, T value)
    144    {
    145        return getOutputFormatter(type).toClient(value);
    146    }
    147
    148    public <T extends Number> String getMessageKey(Class<T> type)
    149    {
    150        return isIntegerType(type) ? "integer-format-exception" : "number-format-exception";
    151    }
    152
    153    private static String toString(char ch)
    154    {
    155        return String.valueOf(ch);
    156    }
    157}