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}