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 020package org.apache.isis.core.progmodel.facets.value.money; 021 022import java.text.DecimalFormat; 023import java.text.NumberFormat; 024import java.text.ParseException; 025import java.util.Currency; 026 027import org.apache.isis.applib.adapters.EncoderDecoder; 028import org.apache.isis.applib.adapters.Parser; 029import org.apache.isis.applib.profiles.Localization; 030import org.apache.isis.applib.value.Money; 031import org.apache.isis.core.commons.config.ConfigurationConstants; 032import org.apache.isis.core.commons.config.IsisConfiguration; 033import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 034import org.apache.isis.core.metamodel.facetapi.Facet; 035import org.apache.isis.core.metamodel.facetapi.FacetHolder; 036import org.apache.isis.core.metamodel.facets.object.parseable.TextEntryParseException; 037import org.apache.isis.core.metamodel.facets.properties.defaults.PropertyDefaultFacet; 038import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderAndFacetAbstract; 039import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderContext; 040 041public class MoneyValueSemanticsProvider extends ValueSemanticsProviderAndFacetAbstract<Money> implements MoneyValueFacet { 042 043 private static Class<? extends Facet> type() { 044 return MoneyValueFacet.class; 045 } 046 047 private static final NumberFormat DEFAULT_NUMBER_FORMAT; 048 private static final NumberFormat DEFAULT_CURRENCY_FORMAT; 049 private static final String LOCAL_CURRENCY_CODE; 050 private static final int TYPICAL_LENGTH = 18; 051 private static final Money DEFAULT_VALUE = null; // no default 052 053 private final String defaultCurrencyCode; 054 055 static { 056 DEFAULT_NUMBER_FORMAT = NumberFormat.getNumberInstance(); 057 DEFAULT_CURRENCY_FORMAT = NumberFormat.getCurrencyInstance(); 058 DEFAULT_NUMBER_FORMAT.setMinimumFractionDigits(DEFAULT_CURRENCY_FORMAT.getMinimumFractionDigits()); 059 DEFAULT_NUMBER_FORMAT.setMaximumFractionDigits(DEFAULT_CURRENCY_FORMAT.getMaximumFractionDigits()); 060 LOCAL_CURRENCY_CODE = getDefaultCurrencyCode(); 061 } 062 063 static final boolean isAPropertyDefaultFacet() { 064 return PropertyDefaultFacet.class.isAssignableFrom(MoneyValueSemanticsProvider.class); 065 } 066 067 private static String getDefaultCurrencyCode() { 068 try { 069 return DEFAULT_CURRENCY_FORMAT.getCurrency().getCurrencyCode(); 070 } catch (final UnsupportedOperationException e) { 071 return ""; 072 } 073 } 074 075 /** 076 * Required because implementation of {@link Parser} and 077 * {@link EncoderDecoder}. 078 */ 079 public MoneyValueSemanticsProvider() { 080 this(null, null, null); 081 } 082 083 public MoneyValueSemanticsProvider(final FacetHolder holder, final IsisConfiguration configuration, final ValueSemanticsProviderContext context) { 084 super(type(), holder, Money.class, TYPICAL_LENGTH, Immutability.IMMUTABLE, EqualByContent.HONOURED, DEFAULT_VALUE, configuration, context); 085 086 final String property = ConfigurationConstants.ROOT + "value.money.currency"; 087 defaultCurrencyCode = configuration.getString(property, LOCAL_CURRENCY_CODE); 088 } 089 090 // ////////////////////////////////////////////////////////////////// 091 // Parser 092 // ////////////////////////////////////////////////////////////////// 093 094 @Override 095 protected Money doParse(final Object context, final String text) { 096 final String entry = text.trim(); 097 final int pos = entry.lastIndexOf(' '); 098 if (endsWithCurrencyCode(entry, pos)) { 099 final String value = entry.substring(0, pos); 100 final String code = entry.substring(pos + 1); 101 return parseNumberAndCurrencyCode(value, code); 102 } else { 103 return parseDerivedValue(context, entry); 104 } 105 } 106 107 private boolean endsWithCurrencyCode(final String entry, final int pos) { 108 final String suffix = entry.substring(pos + 1); 109 final boolean isCurrencyCode = suffix.length() == 3 && Character.isLetter(suffix.charAt(0)) && Character.isLetter(suffix.charAt(1)) && Character.isLetter(suffix.charAt(2)); 110 return isCurrencyCode; 111 } 112 113 private Money parseDerivedValue(final Object original, final String entry) { 114 Money money = (Money) original; 115 if (money == null || money.getCurrency().equals(LOCAL_CURRENCY_CODE)) { 116 try { 117 final double value = DEFAULT_CURRENCY_FORMAT.parse(entry).doubleValue(); 118 money = new Money(value, LOCAL_CURRENCY_CODE); 119 return money; 120 } catch (final ParseException ignore) { 121 } 122 } 123 124 try { 125 final double value = DEFAULT_NUMBER_FORMAT.parse(entry).doubleValue(); 126 final String currencyCode = money == null ? defaultCurrencyCode : money.getCurrency(); 127 money = new Money(value, currencyCode); 128 return money; 129 } catch (final ParseException ex) { 130 throw new TextEntryParseException("Not a distinguishable money value " + entry, ex); 131 } 132 } 133 134 private Money parseNumberAndCurrencyCode(final String amount, final String code) { 135 final String currencyCode = code.toUpperCase(); 136 try { 137 Currency.getInstance(currencyCode.toUpperCase()); 138 } catch (final IllegalArgumentException e) { 139 throw new TextEntryParseException("Invalid currency code " + currencyCode, e); 140 } 141 try { 142 final Money money = new Money(DEFAULT_NUMBER_FORMAT.parse(amount).doubleValue(), currencyCode); 143 return money; 144 } catch (final ParseException e) { 145 throw new TextEntryParseException("Invalid money entry", e); 146 } 147 } 148 149 @Override 150 public String titleString(final Object object, final Localization localization) { 151 if (object == null) { 152 return ""; 153 } 154 final Money money = (Money) object; 155 final boolean localCurrency = LOCAL_CURRENCY_CODE.equals(money.getCurrency()); 156 if (localCurrency) { 157 return DEFAULT_CURRENCY_FORMAT.format(money.doubleValue()); 158 } else { 159 return DEFAULT_NUMBER_FORMAT.format(money.doubleValue()) + " " + money.getCurrency(); 160 } 161 } 162 163 @Override 164 public String titleStringWithMask(final Object value, final String usingMask) { 165 if (value == null) { 166 return ""; 167 } 168 final Money money = (Money) value; 169 return new DecimalFormat(usingMask).format(money.doubleValue()); 170 } 171 172 // ////////////////////////////////////////////////////////////////// 173 // EncoderDecoder 174 // ////////////////////////////////////////////////////////////////// 175 176 @Override 177 protected String doEncode(final Object object) { 178 final Money money = (Money) object; 179 final String value = String.valueOf(money.doubleValue()) + " " + money.getCurrency(); 180 return value; 181 } 182 183 @Override 184 protected Money doRestore(final String data) { 185 final String dataString = data; 186 final int pos = dataString.indexOf(' '); 187 final String amount = dataString.substring(0, pos); 188 final String currency = dataString.substring(pos + 1); 189 return new Money(Double.valueOf(amount).doubleValue(), currency); 190 } 191 192 // ////////////////////////////////////////////////////////////////// 193 // MoneyValueFacet 194 // ////////////////////////////////////////////////////////////////// 195 196 @Override 197 public float getAmount(final ObjectAdapter object) { 198 final Money money = (Money) object.getObject(); 199 if (money == null) { 200 return 0.0f; 201 } else { 202 return money.floatValue(); 203 } 204 } 205 206 @Override 207 public String getCurrencyCode(final ObjectAdapter object) { 208 final Money money = (Money) object.getObject(); 209 if (money == null) { 210 return ""; 211 } else { 212 return money.getCurrency(); 213 } 214 } 215 216 @Override 217 public ObjectAdapter createValue(final float amount, final String currencyCode) { 218 return getAdapterManager().adapterFor(new Money(amount, currencyCode)); 219 } 220 221 // /////// toString /////// 222 223 @Override 224 public String toString() { 225 return "MoneyValueSemanticsProvider: " + getDefaultCurrencyCode(); 226 } 227 228}