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}