001/* 002 * Copyright 2002-2020 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; 024 025import javax.money.CurrencyUnit; 026import javax.money.Monetary; 027import javax.money.MonetaryAmount; 028 029import org.springframework.context.support.EmbeddedValueResolutionSupport; 030import org.springframework.format.AnnotationFormatterFactory; 031import org.springframework.format.Formatter; 032import org.springframework.format.Parser; 033import org.springframework.format.Printer; 034import org.springframework.format.annotation.NumberFormat; 035import org.springframework.format.annotation.NumberFormat.Style; 036import org.springframework.format.number.CurrencyStyleFormatter; 037import org.springframework.format.number.NumberStyleFormatter; 038import org.springframework.format.number.PercentStyleFormatter; 039import org.springframework.util.StringUtils; 040 041/** 042 * Formats {@link javax.money.MonetaryAmount} fields annotated 043 * with Spring's common {@link NumberFormat} annotation. 044 * 045 * @author Juergen Hoeller 046 * @since 4.2 047 * @see NumberFormat 048 */ 049public class Jsr354NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport 050 implements AnnotationFormatterFactory<NumberFormat> { 051 052 private static final String CURRENCY_CODE_PATTERN = "\u00A4\u00A4"; 053 054 055 @Override 056 public Set<Class<?>> getFieldTypes() { 057 return 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 String pattern = resolveEmbeddedValue(annotation.pattern()); 073 if (StringUtils.hasLength(pattern)) { 074 return new PatternDecoratingFormatter(pattern); 075 } 076 else { 077 Style style = annotation.style(); 078 if (style == Style.NUMBER) { 079 return new NumberDecoratingFormatter(new NumberStyleFormatter()); 080 } 081 else if (style == Style.PERCENT) { 082 return new NumberDecoratingFormatter(new PercentStyleFormatter()); 083 } 084 else { 085 return new NumberDecoratingFormatter(new CurrencyStyleFormatter()); 086 } 087 } 088 } 089 090 091 private static class NumberDecoratingFormatter implements Formatter<MonetaryAmount> { 092 093 private final Formatter<Number> numberFormatter; 094 095 public NumberDecoratingFormatter(Formatter<Number> numberFormatter) { 096 this.numberFormatter = numberFormatter; 097 } 098 099 @Override 100 public String print(MonetaryAmount object, Locale locale) { 101 return this.numberFormatter.print(object.getNumber(), locale); 102 } 103 104 @Override 105 public MonetaryAmount parse(String text, Locale locale) throws ParseException { 106 CurrencyUnit currencyUnit = Monetary.getCurrency(locale); 107 Number numberValue = this.numberFormatter.parse(text, locale); 108 return Monetary.getDefaultAmountFactory().setNumber(numberValue).setCurrency(currencyUnit).create(); 109 } 110 } 111 112 113 private static class PatternDecoratingFormatter implements Formatter<MonetaryAmount> { 114 115 private final String pattern; 116 117 public PatternDecoratingFormatter(String pattern) { 118 this.pattern = pattern; 119 } 120 121 @Override 122 public String print(MonetaryAmount object, Locale locale) { 123 CurrencyStyleFormatter formatter = new CurrencyStyleFormatter(); 124 formatter.setCurrency(Currency.getInstance(object.getCurrency().getCurrencyCode())); 125 formatter.setPattern(this.pattern); 126 return formatter.print(object.getNumber(), locale); 127 } 128 129 @Override 130 public MonetaryAmount parse(String text, Locale locale) throws ParseException { 131 CurrencyStyleFormatter formatter = new CurrencyStyleFormatter(); 132 Currency currency = determineCurrency(text, locale); 133 CurrencyUnit currencyUnit = Monetary.getCurrency(currency.getCurrencyCode()); 134 formatter.setCurrency(currency); 135 formatter.setPattern(this.pattern); 136 Number numberValue = formatter.parse(text, locale); 137 return Monetary.getDefaultAmountFactory().setNumber(numberValue).setCurrency(currencyUnit).create(); 138 } 139 140 private Currency determineCurrency(String text, Locale locale) { 141 try { 142 if (text.length() < 3) { 143 // Could not possibly contain a currency code -> 144 // try with locale and likely let it fail on parse. 145 return Currency.getInstance(locale); 146 } 147 else if (this.pattern.startsWith(CURRENCY_CODE_PATTERN)) { 148 return Currency.getInstance(text.substring(0, 3)); 149 } 150 else if (this.pattern.endsWith(CURRENCY_CODE_PATTERN)) { 151 return Currency.getInstance(text.substring(text.length() - 3)); 152 } 153 else { 154 // A pattern without a currency code... 155 return Currency.getInstance(locale); 156 } 157 } 158 catch (IllegalArgumentException ex) { 159 throw new IllegalArgumentException("Cannot determine currency for number value [" + text + "]", ex); 160 } 161 } 162 } 163 164}