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.dom.Element; 018import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 019import org.apache.tapestry5.ioc.services.ThreadLocale; 020import org.apache.tapestry5.ioc.services.TypeCoercer; 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}