001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package org.apache.isis.core.progmodel.facets.value.money;
021
022 import java.text.DecimalFormat;
023 import java.text.NumberFormat;
024 import java.text.ParseException;
025 import java.util.Currency;
026
027 import org.apache.isis.applib.adapters.EncoderDecoder;
028 import org.apache.isis.applib.adapters.Parser;
029 import org.apache.isis.applib.profiles.Localization;
030 import org.apache.isis.applib.value.Money;
031 import org.apache.isis.core.commons.config.ConfigurationConstants;
032 import org.apache.isis.core.commons.config.IsisConfiguration;
033 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
034 import org.apache.isis.core.metamodel.facetapi.Facet;
035 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
036 import org.apache.isis.core.metamodel.facets.object.parseable.TextEntryParseException;
037 import org.apache.isis.core.metamodel.facets.properties.defaults.PropertyDefaultFacet;
038 import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderAndFacetAbstract;
039 import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderContext;
040
041 public class MoneyValueSemanticsProvider extends ValueSemanticsProviderAndFacetAbstract<Money> implements
042 MoneyValueFacet {
043
044 private static Class<? extends Facet> type() {
045 return MoneyValueFacet.class;
046 }
047
048 private static final NumberFormat DEFAULT_NUMBER_FORMAT;
049 private static final NumberFormat DEFAULT_CURRENCY_FORMAT;
050 private static final String LOCAL_CURRENCY_CODE;
051 private static final int TYPICAL_LENGTH = 18;
052 private static final boolean IMMUTABLE = true;
053 private static final boolean EQUAL_BY_CONTENT = true;
054 private static final Money DEFAULT_VALUE = null; // no default
055
056 private final String defaultCurrencyCode;
057
058 static {
059 DEFAULT_NUMBER_FORMAT = NumberFormat.getNumberInstance();
060 DEFAULT_CURRENCY_FORMAT = NumberFormat.getCurrencyInstance();
061 DEFAULT_NUMBER_FORMAT.setMinimumFractionDigits(DEFAULT_CURRENCY_FORMAT.getMinimumFractionDigits());
062 DEFAULT_NUMBER_FORMAT.setMaximumFractionDigits(DEFAULT_CURRENCY_FORMAT.getMaximumFractionDigits());
063 LOCAL_CURRENCY_CODE = getDefaultCurrencyCode();
064 }
065
066 static final boolean isAPropertyDefaultFacet() {
067 return PropertyDefaultFacet.class.isAssignableFrom(MoneyValueSemanticsProvider.class);
068 }
069
070 private static String getDefaultCurrencyCode() {
071 try {
072 return DEFAULT_CURRENCY_FORMAT.getCurrency().getCurrencyCode();
073 } catch (final UnsupportedOperationException e) {
074 return "";
075 }
076 }
077
078 /**
079 * Required because implementation of {@link Parser} and {@link EncoderDecoder}.
080 */
081 public MoneyValueSemanticsProvider() {
082 this(null, null, null);
083 }
084
085 public MoneyValueSemanticsProvider(final FacetHolder holder, final IsisConfiguration configuration,
086 final ValueSemanticsProviderContext context) {
087 super(type(), holder, Money.class, TYPICAL_LENGTH, IMMUTABLE, EQUAL_BY_CONTENT, DEFAULT_VALUE, configuration,
088 context);
089
090 final String property = ConfigurationConstants.ROOT + "value.money.currency";
091 defaultCurrencyCode = configuration.getString(property, LOCAL_CURRENCY_CODE);
092 }
093
094 // //////////////////////////////////////////////////////////////////
095 // Parser
096 // //////////////////////////////////////////////////////////////////
097
098 @Override
099 protected Money doParse(final Object context, final String text) {
100 final String entry = text.trim();
101 final int pos = entry.lastIndexOf(' ');
102 if (endsWithCurrencyCode(entry, pos)) {
103 final String value = entry.substring(0, pos);
104 final String code = entry.substring(pos + 1);
105 return parseNumberAndCurrencyCode(value, code);
106 } else {
107 return parseDerivedValue(context, entry);
108 }
109 }
110
111 private boolean endsWithCurrencyCode(final String entry, final int pos) {
112 final String suffix = entry.substring(pos + 1);
113 final boolean isCurrencyCode =
114 suffix.length() == 3 && Character.isLetter(suffix.charAt(0)) && Character.isLetter(suffix.charAt(1))
115 && Character.isLetter(suffix.charAt(2));
116 return isCurrencyCode;
117 }
118
119 private Money parseDerivedValue(final Object original, final String entry) {
120 Money money = (Money) original;
121 if (money == null || money.getCurrency().equals(LOCAL_CURRENCY_CODE)) {
122 try {
123 final double value = DEFAULT_CURRENCY_FORMAT.parse(entry).doubleValue();
124 money = new Money(value, LOCAL_CURRENCY_CODE);
125 return money;
126 } catch (final ParseException ignore) {
127 }
128 }
129
130 try {
131 final double value = DEFAULT_NUMBER_FORMAT.parse(entry).doubleValue();
132 final String currencyCode = money == null ? defaultCurrencyCode : money.getCurrency();
133 money = new Money(value, currencyCode);
134 return money;
135 } catch (final ParseException ex) {
136 throw new TextEntryParseException("Not a distinguishable money value " + entry, ex);
137 }
138 }
139
140 private Money parseNumberAndCurrencyCode(final String amount, final String code) {
141 final String currencyCode = code.toUpperCase();
142 try {
143 Currency.getInstance(currencyCode.toUpperCase());
144 } catch (final IllegalArgumentException e) {
145 throw new TextEntryParseException("Invalid currency code " + currencyCode, e);
146 }
147 try {
148 final Money money = new Money(DEFAULT_NUMBER_FORMAT.parse(amount).doubleValue(), currencyCode);
149 return money;
150 } catch (final ParseException e) {
151 throw new TextEntryParseException("Invalid money entry", e);
152 }
153 }
154
155 @Override
156 public String titleString(final Object object, final Localization localization) {
157 if (object == null) {
158 return "";
159 }
160 final Money money = (Money) object;
161 final boolean localCurrency = LOCAL_CURRENCY_CODE.equals(money.getCurrency());
162 if (localCurrency) {
163 return DEFAULT_CURRENCY_FORMAT.format(money.doubleValue());
164 } else {
165 return DEFAULT_NUMBER_FORMAT.format(money.doubleValue()) + " " + money.getCurrency();
166 }
167 }
168
169 @Override
170 public String titleStringWithMask(final Object value, final String usingMask) {
171 if (value == null) {
172 return "";
173 }
174 final Money money = (Money) value;
175 return new DecimalFormat(usingMask).format(money.doubleValue());
176 }
177
178 // //////////////////////////////////////////////////////////////////
179 // EncoderDecoder
180 // //////////////////////////////////////////////////////////////////
181
182 @Override
183 protected String doEncode(final Object object) {
184 final Money money = (Money) object;
185 final String value = String.valueOf(money.doubleValue()) + " " + money.getCurrency();
186 return value;
187 }
188
189 @Override
190 protected Money doRestore(final String data) {
191 final String dataString = data;
192 final int pos = dataString.indexOf(' ');
193 final String amount = dataString.substring(0, pos);
194 final String currency = dataString.substring(pos + 1);
195 return new Money(Double.valueOf(amount).doubleValue(), currency);
196 }
197
198 // //////////////////////////////////////////////////////////////////
199 // MoneyValueFacet
200 // //////////////////////////////////////////////////////////////////
201
202 @Override
203 public float getAmount(final ObjectAdapter object) {
204 final Money money = (Money) object.getObject();
205 if (money == null) {
206 return 0.0f;
207 } else {
208 return money.floatValue();
209 }
210 }
211
212 @Override
213 public String getCurrencyCode(final ObjectAdapter object) {
214 final Money money = (Money) object.getObject();
215 if (money == null) {
216 return "";
217 } else {
218 return money.getCurrency();
219 }
220 }
221
222 @Override
223 public ObjectAdapter createValue(final float amount, final String currencyCode) {
224 return getAdapterMap().adapterFor(new Money(amount, currencyCode));
225 }
226
227 // /////// toString ///////
228
229 @Override
230 public String toString() {
231 return "MoneyValueSemanticsProvider: " + getDefaultCurrencyCode();
232 }
233
234 }