001/* 002 * Copyright 2002-2015 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.format.number.money; 018 019import java.text.ParseException; 020import java.util.Collections; 021import java.util.Currency; 022import java.util.Locale; 023import java.util.Set; 024import javax.money.CurrencyUnit; 025import javax.money.Monetary; 026import javax.money.MonetaryAmount; 027 028import org.springframework.context.support.EmbeddedValueResolutionSupport; 029import org.springframework.format.AnnotationFormatterFactory; 030import org.springframework.format.Formatter; 031import org.springframework.format.Parser; 032import org.springframework.format.Printer; 033import org.springframework.format.annotation.NumberFormat; 034import org.springframework.format.annotation.NumberFormat.Style; 035import org.springframework.format.number.CurrencyStyleFormatter; 036import org.springframework.format.number.NumberStyleFormatter; 037import org.springframework.format.number.PercentStyleFormatter; 038import org.springframework.util.StringUtils; 039 040/** 041 * Formats {@link javax.money.MonetaryAmount} fields annotated 042 * with Spring's common {@link NumberFormat} annotation. 043 * 044 * @author Juergen Hoeller 045 * @since 4.2 046 * @see NumberFormat 047 */ 048public class Jsr354NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport 049 implements AnnotationFormatterFactory<NumberFormat> { 050 051 private static final String CURRENCY_CODE_PATTERN = "\u00A4\u00A4"; 052 053 054 @Override 055 @SuppressWarnings("unchecked") 056 public Set<Class<?>> getFieldTypes() { 057 return (Set) Collections.singleton(MonetaryAmount.class); 058 } 059 060 @Override 061 public Printer<MonetaryAmount> getPrinter(NumberFormat annotation, Class<?> fieldType) { 062 return configureFormatterFrom(annotation); 063 } 064 065 @Override 066 public Parser<MonetaryAmount> getParser(NumberFormat annotation, Class<?> fieldType) { 067 return configureFormatterFrom(annotation); 068 } 069 070 071 private Formatter<MonetaryAmount> configureFormatterFrom(NumberFormat annotation) { 072 if (StringUtils.hasLength(annotation.pattern())) { 073 return new PatternDecoratingFormatter(resolveEmbeddedValue(annotation.pattern())); 074 } 075 else { 076 Style style = annotation.style(); 077 if (style == Style.NUMBER) { 078 return new NumberDecoratingFormatter(new NumberStyleFormatter()); 079 } 080 else if (style == Style.PERCENT) { 081 return new NumberDecoratingFormatter(new PercentStyleFormatter()); 082 } 083 else { 084 return new NumberDecoratingFormatter(new CurrencyStyleFormatter()); 085 } 086 } 087 } 088 089 090 private static class NumberDecoratingFormatter implements Formatter<MonetaryAmount> { 091 092 private final Formatter<Number> numberFormatter; 093 094 public NumberDecoratingFormatter(Formatter<Number> numberFormatter) { 095 this.numberFormatter = numberFormatter; 096 } 097 098 @Override 099 public String print(MonetaryAmount object, Locale locale) { 100 return this.numberFormatter.print(object.getNumber(), locale); 101 } 102 103 @Override 104 public MonetaryAmount parse(String text, Locale locale) throws ParseException { 105 CurrencyUnit currencyUnit = Monetary.getCurrency(locale); 106 Number numberValue = this.numberFormatter.parse(text, locale); 107 return Monetary.getDefaultAmountFactory().setNumber(numberValue).setCurrency(currencyUnit).create(); 108 } 109 } 110 111 112 private static class PatternDecoratingFormatter implements Formatter<MonetaryAmount> { 113 114 private final String pattern; 115 116 public PatternDecoratingFormatter(String pattern) { 117 this.pattern = pattern; 118 } 119 120 @Override 121 public String print(MonetaryAmount object, Locale locale) { 122 CurrencyStyleFormatter formatter = new CurrencyStyleFormatter(); 123 formatter.setCurrency(Currency.getInstance(object.getCurrency().getCurrencyCode())); 124 formatter.setPattern(this.pattern); 125 return formatter.print(object.getNumber(), locale); 126 } 127 128 @Override 129 public MonetaryAmount parse(String text, Locale locale) throws ParseException { 130 CurrencyStyleFormatter formatter = new CurrencyStyleFormatter(); 131 Currency currency = determineCurrency(text, locale); 132 CurrencyUnit currencyUnit = Monetary.getCurrency(currency.getCurrencyCode()); 133 formatter.setCurrency(currency); 134 formatter.setPattern(this.pattern); 135 Number numberValue = formatter.parse(text, locale); 136 return Monetary.getDefaultAmountFactory().setNumber(numberValue).setCurrency(currencyUnit).create(); 137 } 138 139 private Currency determineCurrency(String text, Locale locale) { 140 try { 141 if (text.length() < 3) { 142 // Could not possibly contain a currency code -> 143 // try with locale and likely let it fail on parse. 144 return Currency.getInstance(locale); 145 } 146 else if (this.pattern.startsWith(CURRENCY_CODE_PATTERN)) { 147 return Currency.getInstance(text.substring(0, 3)); 148 } 149 else if (this.pattern.endsWith(CURRENCY_CODE_PATTERN)) { 150 return Currency.getInstance(text.substring(text.length() - 3)); 151 } 152 else { 153 // A pattern without a currency code... 154 return Currency.getInstance(locale); 155 } 156 } 157 catch (IllegalArgumentException ex) { 158 throw new IllegalArgumentException("Cannot determine currency for number value [" + text + "]", ex); 159 } 160 } 161 } 162 163}