Skip to content
This repository has been archived by the owner on Jun 27, 2021. It is now read-only.

Latest commit

 

History

History
493 lines (393 loc) · 20 KB

UserGuide.adoc

File metadata and controls

493 lines (393 loc) · 20 KB

Java Money and Currency API — Backport for Java 7


javamoney cup

Version Information:

Specification: Java Money and Currency API for Java 7
Version:       1.0
Status:        Final
Release Date:  April 2015
Copyright:     2012-2015
               Credit Suisse AG
               P.O.Box
               8070 Zurich
               Switzerland
               All rights reserved.
License:       Spec Evaluation/Implementation Licence

1. Introduction

This document is the user guide of the Java API for Money and Currency of JSR 354 targeting Java 7. The technical objective is to provide a money and currency API for Java, targeted at all users of currencies and monetary amounts, compatible with Android and Java 7. The API will provide support for standard [ISO-4217] and custom currencies, and a model for monetary amounts and rounding. It will have extension points for adding additional features like currency exchange, financial calculations and formulas. For detailed information on the API and its usage, we recommend reading the JSR’s specification, which can be downloaded from the JCP page http://jcp.org/en/jsr/detail?id=35[JSR 354 on jcp.org .

The APIs for both Java 7 and Java 8 are basically the same. Java 8 additionally provides full support for default methods, which adds lots of comfort implementing the SPIs. For Java 7 users the reference implementation provides a set of common base classes, that provide the same functionality as provided by default methods in the Java 8 version of the API:

public class MyImplClass implements CurrencyProviderSpi{
  ...
}

you simply have to rewrite it as:

public class MyImplClass extends BaseCurrencyProviderSpi{
  ...
}

…​and it will run similarly in Java 7.

2. Package and Project Structure

2.1. Package Overview

Similar to JSR 354 Java 8 API this the Java 7 API defines 4 main packages:

javax.money

contains the main artifacts, such as CurrencyUnit, MonetaryAmount, MonetaryContext, MonetaryOperator, MonetaryQuery, MonetaryRounding, and the singleton accessor Monetary.

javax.money.conversion

contains the conversion artifacts ExchangeRate, ExchangeRateProvider, CurrencyConversion and the according MonetaryConversions accessor singleton.

javax.money.format

contains the formatting artifacts MonetaryAmountFormat, AmountFormatContext and the according MonetaryFormats accessor singleton.

javax.money.spi

contains the SPI interfaces provided by the JSR 354 API and the bootstrap logic, to support different runtime environments and component loading mechanisms.

2.2. Module/Repository Overview

The JSR’s source code repository under [source] provides several modules:

jsr354-api-bp

contains the API backport compatible with Java g and 7.

jsr354-ri-bp

contains the 'moneta' implementation, based on Java 7.

jsr354-api

contains the JSR 354 API for use with Java 8. We highly recommend using this version, if possible, to benefit from the power of Java 8.

jsr354-ri

contains the 'moneta' reference implementation, based on Java 8.

jsr354-tck

contains the technical compatibility kit (TCK) of the JSR, requiring as well Java 7. The TCK is the same for Java 7 and Java 8.

javamoney-parent

is a root “POM” project for all modules under org.javamoney. This includes the RI/TCK projects, but not jsr354-api and javamoney-api-bp (which are standalone).

javamoney-library

contains a financial library (JavaMoney) adding comprehensive support for several extended functionality, built on top of the JSR’s API, but not part of the JSR.

javamoney-examples

finally contains the examples and demos, and also is not part of this JSR.

3. Examples

The following sections illustrate the API usage in more detail.

3.1. Working with javax.money.Money

The Java 7 based implementation of JSR 354 has to provide value type classes for monetary amounts, hereby implementing javax.money.MonetaryAmount, and registering at least one implementation class with the javax.money.Monetary singleton by implementing and registering a corresponding javax.money.MonetayAmountFactory instance.

As an example the reference implementation provides a class org.javamoney.moneta.Money, which is using java.math.BigDecimal internally:

Class Money
public final class Money
implements MonetaryAmount, Comparable<MonetaryAmount>, Serializable, CurrencySupplier {
  ...
}

Since a corresponding MonetaryAmountFactory is registered, a new instance can be created using the typed factory:

Example Usage of MonetaryAmountFactory
MonetaryAmountFactory<Money> fact = Monetary.getAmountFactory(Money.class);
Money m = fact.withCurrency("USD").with(200.50).create();

Also a generic MonetaryAmount instance can be accessed using a raw factory (hereby it depends on the configured default amount factory, which effective type instance is returned):

Example Usage MonetaryAmountFactory
MonetaryAmount amt = Monetary.getDefaultAmountFactory()
                       .withCurrency("USD").with(200.50).create();

Still we can evaluate the effective amount’s type effectively:

if(Money.class==amt.getClass()){
  Money m = (Money)amt;
}

But in general, we do not need to know the exact implementation in most cases, since we can access amount meta-data as a MonetaryContext, This meta-data provides information, such as the maximal precision, maximal scale supported by the type’s implementation as well as other attributes. Refer to [MonetaryContext] for more details.

Example Usage MonetaryContext
MonetaryContext ctx = m.getMonetaryContext();
if(ctx.getMaxPrecision()==0){
   System.out.println("Unbounded maximal precision.");
}
if(ctx.getMaxScale()>=5){
   System.out.println("Sufficient scale for our use case, go for it.");
}

Finally performing arithmetic operations in both above scenarios works similar as it is when using java.math.BigDecimal:

Example Usage Monetary Arithmetic
MonetaryAmount amt = ...;
amt = amt.multiply(2.0).subtract(1.345);

Also the sample above illustrates how algorithmic operations can be chained together using a fluent API. As mentioned also external functionality can be chained, e.g. using instances of MonetaryOperator:

Example Function Chaining [1]
MonetaryAmount amt = ...;
amt = amt.multiply(2.12345).with(Monetary.getDefaultRounding())
        .with(MonetaryFunctions.minimal(100)).
        .multiply(2.12345).with(Monetary.getDefaultRounding())
        .with(MonetaryFunctions.percent(23));

3.1.1. Numeric Precision and Scale

Since the Money implementation class, which is part of the reference implementation, internally uses java.math.BigDecimal the numeric capabilities match exact the capabilities of BigDecimal. When accessing MonetaryAmountFactory instances it is possible to configure the MathContext effectively used (by default Money uses MathContext.DECIMAL64).:

Example Configuring a MonetaryAmountFactory, using the RI class Money as example.
MonetaryAmountFactory<Money> fact = Monetary.getAmountFactory(
   MonetaryAmountFactoryQueryBuilder.of(Money.class)
     .set(new MathContext(250, RoundingMode.HALF_DOWN)).build()
);
// Creates an instance of Money with the given MathContext
MonetaryAmount m1 = fact.setCurrency("CHF").setNumber(250.34).create();
Money m2 = fact.setCurrency("CHF").setNumber(250.34).create();

3.1.2. Extending the API

Now, one last thing to discuss is, how users can add their own functionality, e.g. by writing their own MonetaryOperator functions. Basically there are two distinct usage scenarios:

  • When the basic arithmetic defined on each MonetaryAmount are sufficient, it should be easy to implement such functionality, since its behaving like any other type, e.g.

public final class DuplicateOp implements MonetaryOperator{
  public <T extends MonetaryAmount> T apply(T amount){
    return (T) amount.multiply(2);
  }
}

Below is a rather academical example of a MonetaryOperator that simply converts any given amount to an amount with the same numeric value, but with XXX (undefined) as currency:

Simple example of a MonetaryOperator using the MonetaryAmountFactory provided.
public final class ToInvalid implements MonetaryOperator{
  public <T extends MonetaryAmount> T apply(T amount){
    return (T)amount.getFactory().setCurrency("XXX").create();
  }
}

3.2. Working with org.javamoney.moneta.FastMoney

This class implements a MonetaryAmount using long as numeric representation, whereas the full amount is interpreted as minor units, with a denumerator of 100000.

As an example CHF 2.5 is internally stored as CHF 250000. Addition and subtraction of values is trivial, whereas division and multiplication get complex with non integral values. Compared to Money the possible amounts to be modeled are limited to an overall precision of 18 and a fixed scale of 5 digits.

Beside that the overall handling of FastMoney is similar to Money. So we could rewrite the former example by just replacing FastMoney with Money:

Usage Example - FastMoney
MonetaryAmountFactory<FastMoney> fact = Monetary.getAmountFactory(FastMoney.class);
// Creates an instance of Money with the given MathContext
MonetaryAmount m1 = fact.setCurrency("CHF").setNumber(250.34).create();
FastMoney m2 = fact.setCurrency("CHF").setNumber(250.34).create();

Of course, the MonetaryContext is different than for Money:

The MonetaryContext of FastMoney
maxPrecision = 18;  // hard limit
maxScale = 5;       // fixed scale
numeric class = Long
attributes: RoundingMode.HALF_EVEN

3.3. Calculating a Total

A total of amounts can be calculated in multiple ways, one way is simply to chain the amounts with add(MonetaryAmount):

Usage Example Calculating a Total
MonetaryAmountFactory factory = Monetary.getDefaultAmountFactory().setCurrency("CHF");
MonetaryAmount[] params = new MonetaryAmount[]{
                   factory.setNumber(100).create(),
                   factory.setNumber(10.20).create(),
                   factory.setNumber(1.15).create(),};
MonetaryAmount total = params[0];
for(int i=1; i<params.length;i++){
   total = total.add(params[i]);
}

As an alternate it is also possible to define a MonetaryOperator, which can be passed to all amounts:

Example of total/add method
public class Total implements MonetaryOperator{
   private MonetaryAmount total;

   public <T extends MonetaryAmount<T>> T apply(T amount){
      if(total==null){
         total = amount;
      }
      else{
         total = total.add(amount);
      }
      // ensure to return correct type, since different implementations
      // can be passed as amount parameter
      return amount.getFactory().with(total).create();
   }


   public MonetaryAmount getTotal(){
      return total;
   }

   public <T extends MonetaryAmount> T getTotal(Class<T> amountType){
      return Monetary.getAmountFactory(amountType).with(total).create();
   }

}
Important

We are well aware of the fact that this implementation still has some severe drawbacks, but we decided for simplicity to not add the following features to this example:

  • the implementation can only handle one currency, a better implementation could also be multi-currency capable.

  • The implementation above is not thread-safe.

Now with the MonetaryOperator totalizing looks as follows:

Example Using Total/add method
Total total = new Total();
for(int i=1; i<params.length;i++){
  total.with(params[i]);
}
System.out.println("TOTAL: " + total.getTotal());

A similar approach can also be used for other multi value calculations as used in statistics, e.g. average, median etc.

3.4. Calculating a Present Value

The present value (abbreviated PV) shows how financial formulas can be implemented based on the JSR 354 API. A PV models the current value of a financial in- or outflow in the future, weighted with a calculatory interest rate. The PV is defined as follows:

C / ((1+r)^n)

Hereby

  • n is the time of the cash flow (in periods)

  • r is the discount rate (the rate of return that could be earned on an investment in the financial markets with similar risk.); the opportunity cost of capital.

  • C is the net cash flow i.e. cash inflow – cash outflow, at time t . For educational purposes,

The same financial function now can be implemented for example as follows:

Example Using Total/add method
public <T extends MonetaryAmount> T presentValue(
                                T amt, BigDecimal rate, int periods){
  BigDecimal divisor = BigDecimal.ONE.add(rate).pow(periods);
  // cast should be safe for implementations that adhere to this spec
  return (T)amt.divide(divisor);
}

This algorithm can be implemented as MonetaryOperator:

Example Implementing a MonetaryOperator
public final class PresentValue implements MonetaryOperator{
  private BigDecimal rate;
  private int periods;
  private BigDecimal divisor;

  public PresentValue(BigDecimal rate, int periods){
    Objects.requireNotNull(rate);
    this.rate = rate;
    this.periods = periods;
    this.divisor = BigDecimal.ONE.add(periods).power(periods);
  }

  public int getPeriods(){ return periods; }

  public BigDecimal getRate(){ return rate; }

  public <T extends MonetaryAmount> T apply(T amount){
     // cast should be safe for implementations that adhere to this spec
     return (T)amount.divide(divisor);
  }

  public String toString(){...}
}

For simplicity we did not add additional feature such as caching of PresentValue instances using a static factory method, or pre-calculation of divisor matrices. Now given the MonetaryOperator a present value can be calculated as follows:

Example Using a Financial Function
Money m = Monetary.getAmountFactory(Money.class).setCurrency("CHF").setNumber(1000).create();
// present value for an amount of 100, available in two periods,
// with a rate of 5%.
Money pv = m.with(new PresentValue(new BigDecimal("0.05"), 2));

3.5. Performing Currency Conversion

Currency Conversion also is a special case of a MonetaryOperator since it creates a new amount based on another amount. Hereby by the conversion the resulting amount will typically have a different currency and a different numeric amount:

Example Currency Conversion
MonetaryAmount inCHF =...;
CurrencyConversion conv = MonetaryConversions.getConversion("EUR");
MonetaryAmount inEUR = inCHF.with(conv);

Also we can define the providers to be used for currency conversion by passing the provider names explicitly:

CurrencyConversion conv = MonetaryConversions.getConversion("EUR", "EZB", "IMF");

To cover also more complex usage scenarios we can also pass a ConversionQuery with additional parameters for conversion, e.g.:

MonetaryAmount inCHF =...;
CurrencyConversion conv = MonetaryConversions.getConversion(ConversionQueryBuilder.of()
        .setProviders("CS", "EZB", "IMF")
        .setTermCurrency("EUR")
        .set(MonetaryAmount.class, inCHF, MonetaryAmount.class)
        .set(LocalDate.of(2008, 1, 1))
        .setRateType(RateType.HISTORIC)
        .set(StockExchange.NYSE) // custom type
        .set("contractId", "AA-1234.2")
        .build());
MonetaryAmount inEUR = inCHF.with(conv);

1. MonetaryFunctions is not part of the JSR, its just for illustration purposes.