/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 net.formio.format; import java.math.BigDecimal; import java.math.BigInteger; 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.concurrent.ConcurrentHashMap; /** * Transforms objects of common type(s) to a String and back from a String. * Different subclasses with different registered formatters can be * created: Method {@link #registerFormatters()} can be overridden. * * @author Radek Beran */ public class BasicFormatters implements Formatters { // -- Public API -- public BasicFormatters() { this.formatters = registerFormatters(); } @Override public <T> T parseFromString(String str, Class<T> destClass, String formatPattern, Location loc) { Formatter<T> formatter = (Formatter<T>) formatters.get(destClass); if (formatter == null) { // fallback for enumerations if (Enum.class.isAssignableFrom(destClass)) { formatter = (Formatter<T>) ENUM_FORMATTER; } } if (formatter == null) { throw new FormatterNotFoundException(destClass); } return formatter.parseFromString(str, destClass, formatPattern, loc); } public <T> T parseFromString(String str, Class<T> cls, Location loc) { return parseFromString(str, cls, null, loc); } @Override public <T> String makeString(T value, String formatPattern, Location loc) { if (value == null) return null; Formatter<T> formatter = (Formatter<T>) formatters.get(value.getClass()); if (formatter == null) { // fallback to common string maker formatter = (Formatter<T>)COMMON_STR_MAKER; } return formatter.makeString(value, formatPattern, loc); } public <T> String makeString(T value, Location loc) { return makeString(value, (String)null, loc); } @Override public boolean canHandle(Class<?> cls) { return cls.isAssignableFrom(String.class) || cls.isEnum() || this.formatters.containsKey(cls); } // -- API to override -- /** * Returns all formatters available. * * @return */ protected Map<Class<?>, Formatter<?>> registerFormatters() { final Class<? extends Formatters> formattersKey = getClass(); Map<Class<?>, Formatter<?>> formatters = FORMATTERS_CACHE.get(formattersKey); if (formatters == null) { formatters = new HashMap<Class<?>, Formatter<?>>(); formatters.put(Boolean.class, BOOLEAN_FORMATTER); formatters.put(boolean.class, BOOLEAN_FORMATTER); formatters.put(String.class, STRING_FORMATTER); final Formatter<Date> dateFormatter = new Formatter<Date>() { @Override public Date parseFromString(String str, Class<Date> destClass, String formatPattern, Location loc) { try { return FormatsCache.getOrCreateDateFormat(formatPattern, loc).parse(str); } catch (Exception ex) { throw new StringParseException(Date.class, str, ex); } } @Override public String makeString(Date value, String formatPattern, Location loc) { return FormatsCache.getOrCreateDateFormat( formatPattern, loc).format(value); } }; formatters.put(Date.class, dateFormatter); final Formatter<Byte> byteFormatter = new Formatter<Byte>() { @Override public Byte parseFromString(String str, Class<Byte> destClass, String formatPattern, Location loc) { try { String amendedStr = removeDecimalPart(str, loc.getLocale()); return Byte.valueOf(FormatsCache.getOrCreateDecimalFormat(formatPattern, loc) .parse(amendedStr).byteValue()); } catch (Exception ex) { throw new StringParseException(Byte.class, str, ex); } } @Override public String makeString(Byte value, String formatPattern, Location loc) { return COMMON_STR_MAKER.makeString(value, formatPattern, loc); } }; formatters.put(Byte.class, byteFormatter); formatters.put(byte.class, byteFormatter); final Formatter<Short> shortFormatter = new Formatter<Short>() { @Override public Short parseFromString(String str, Class<Short> destClass, String formatPattern, Location loc) { try { String amendedStr = removeDecimalPart(str, loc.getLocale()); return Short.valueOf(FormatsCache.getOrCreateDecimalFormat(formatPattern, loc) .parse(amendedStr).shortValue()); } catch (Exception ex) { throw new StringParseException(Short.class, str, ex); } } @Override public String makeString(Short value, String formatPattern, Location loc) { return COMMON_STR_MAKER.makeString(value, formatPattern, loc); } }; formatters.put(Short.class, shortFormatter); formatters.put(short.class, shortFormatter); // NOPMD by Radek on 2.3.14 19:10 final Formatter<Integer> integerFormatter = new Formatter<Integer>() { @Override public Integer parseFromString(String str, Class<Integer> destClass, String formatPattern, Location loc) { try { String amendedStr = removeDecimalPart(str, loc.getLocale()); return Integer.valueOf(FormatsCache.getOrCreateDecimalFormat(formatPattern, loc) .parse(amendedStr).intValue()); } catch (Exception ex) { throw new StringParseException(Integer.class, str, ex); } } @Override public String makeString(Integer value, String formatPattern, Location loc) { return COMMON_STR_MAKER.makeString(value, formatPattern, loc); } }; formatters.put(Integer.class, integerFormatter); formatters.put(int.class, integerFormatter); final Formatter<Long> longFormatter = new Formatter<Long>() { @Override public Long parseFromString(String str, Class<Long> destClass, String formatPattern, Location loc) { try { String amendedStr = removeDecimalPart(str, loc.getLocale()); return Long.valueOf(FormatsCache.getOrCreateDecimalFormat(formatPattern, loc) .parse(amendedStr).longValue()); } catch (Exception ex) { throw new StringParseException(Long.class, str, ex); } } @Override public String makeString(Long value, String formatPattern, Location loc) { return COMMON_STR_MAKER.makeString(value, formatPattern, loc); } }; formatters.put(Long.class, longFormatter); formatters.put(long.class, longFormatter); final Formatter<BigInteger> bigIntegerFormatter = new Formatter<BigInteger>() { @Override public BigInteger parseFromString(String str, Class<BigInteger> destClass, String formatPattern, Location loc) { try { return new BigInteger(removeDecimalPart(str, loc.getLocale())); } catch (Exception ex) { throw new StringParseException(BigInteger.class, str, ex); } } @Override public String makeString(BigInteger value, String formatPattern, Location loc) { return COMMON_STR_MAKER.makeString(value, formatPattern, loc); } }; formatters.put(BigInteger.class, bigIntegerFormatter); final Formatter<Double> doubleFormatter = new Formatter<Double>() { @Override public Double parseFromString(String str, Class<Double> destClass, String formatPattern, Location loc) { try { return Double.valueOf(FormatsCache.getOrCreateDecimalFormat(formatPattern, loc) .parse(str).doubleValue()); } catch (Exception ex) { throw new StringParseException(Double.class, str, ex); } } @Override public String makeString(Double value, String formatPattern, Location loc) { return FormatsCache.getOrCreateDecimalFormat(formatPattern, loc).format(value); } }; formatters.put(Double.class, doubleFormatter); formatters.put(double.class, doubleFormatter); final Formatter<BigDecimal> bigDecimalFormatter = new Formatter<BigDecimal>() { @Override public BigDecimal parseFromString(String str, Class<BigDecimal> destClass, String formatPattern, Location loc) { BigDecimal bd = null; try { DecimalFormat format = FormatsCache.getOrCreateDecimalFormat(formatPattern, loc); format.setParseBigDecimal(true); bd = (BigDecimal) format.parseObject(str); } catch (Exception ex) { throw new StringParseException(BigDecimal.class, str, ex); } if (bd == null && str != null) { throw new StringParseException(BigDecimal.class, str, null); } return bd; } @Override public String makeString(BigDecimal value, String formatPattern, Location loc) { return FormatsCache.getOrCreateDecimalFormat(formatPattern, loc).format(value); } }; formatters.put(BigDecimal.class, bigDecimalFormatter); formatters = Collections.unmodifiableMap(formatters); FORMATTERS_CACHE.put(formattersKey, formatters); } return formatters; } protected static final Formatter<Boolean> BOOLEAN_FORMATTER = new Formatter<Boolean>() { @Override public Boolean parseFromString(String str, Class<Boolean> destClass, String formatPattern, Location loc) { if (str == null || str.isEmpty()) return Boolean.FALSE; String amendedStr = str.toLowerCase(); return Boolean.valueOf(amendedStr.equals("t") || amendedStr.equals("y") || amendedStr.equals("true") || amendedStr.equals("1") || amendedStr.equals("on")); } @Override public String makeString(Boolean value, String formatPattern, Location loc) { return COMMON_STR_MAKER.makeString(value, formatPattern, loc); } }; protected static final Formatter<String> STRING_FORMATTER = new Formatter<String>() { @Override public String parseFromString(String str, Class<String> destClass, String formatPattern, Location loc) { return str; } @Override public String makeString(String value, String formatPattern, Location loc) { return value; } }; /** * Enumeration formatter. Does not convert string to uppercase (not all * enumerations have their constants in uppercase form. */ protected static class EnumFormatter<E extends Enum<E>> implements Formatter<E> { @Override public E parseFromString(String str, Class<E> destClass, String formatPattern, Location loc) { try { if (str == null || str.isEmpty()) return null; return Enum.valueOf(destClass, str); } catch (Exception ex) { throw new StringParseException(destClass, str, ex); } } @Override public String makeString(E value, String formatPattern, Location loc) { return value != null ? value.name() : ""; } } protected static final EnumFormatter<?> ENUM_FORMATTER = new EnumFormatter(); /** * Auxiliary formatter used only for converting objects to strings using toString method. */ protected static final Formatter<Object> COMMON_STR_MAKER = new Formatter<Object>() { @Override public String makeString(Object value, String formatPattern, Location loc) { return value != null ? "" + value : ""; } @Override public Object parseFromString(String str, Class<Object> destClass, String formatPattern, Location loc) { return null; } }; // -- Internal implementation -- private final Map<Class<?>, Formatter<?>> formatters; private static final Map<Class<? extends Formatters>, Map<Class<?>, Formatter<?>>> FORMATTERS_CACHE = new ConcurrentHashMap<Class<? extends Formatters>, Map<Class<?>, Formatter<?>>>(); static String removeDecimalPart(String str, Locale locale) { char decimalSep = '.'; if (locale != null) { DecimalFormatSymbols dfs = new DecimalFormatSymbols(locale); decimalSep = dfs.getDecimalSeparator(); } String amendedStr = str; if (amendedStr != null && !amendedStr.isEmpty()) { int pointIndex = amendedStr.indexOf(decimalSep); if (pointIndex == -1 && locale != null && locale.getLanguage().toLowerCase().equals("cs")) { // Czech language does not use thousand separators... pointIndex = amendedStr.indexOf("."); } if (pointIndex > -1) { amendedStr = amendedStr.substring(0, pointIndex); } } return amendedStr; } }