Money.java

  1. /*
  2.  * #%L
  3.  * *********************************************************************************************************************
  4.  *
  5.  * blueHour
  6.  * http://bluehour.tidalwave.it - git clone git@bitbucket.org:tidalwave/bluehour-src.git
  7.  * %%
  8.  * Copyright (C) 2013 - 2023 Tidalwave s.a.s. (http://tidalwave.it)
  9.  * %%
  10.  * *********************************************************************************************************************
  11.  *
  12.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  13.  * the License. You may obtain a copy of the License at
  14.  *
  15.  *     http://www.apache.org/licenses/LICENSE-2.0
  16.  *
  17.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  18.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  19.  * specific language governing permissions and limitations under the License.
  20.  *
  21.  * *********************************************************************************************************************
  22.  *
  23.  *
  24.  * *********************************************************************************************************************
  25.  * #L%
  26.  */
  27. package it.tidalwave.accounting.model.types;

  28. import javax.annotation.Nonnegative;
  29. import javax.annotation.Nonnull;
  30. import javax.annotation.concurrent.Immutable;
  31. import java.math.BigDecimal;
  32. import java.text.DecimalFormat;
  33. import java.text.DecimalFormatSymbols;
  34. import java.text.ParseException;
  35. import lombok.AccessLevel;
  36. import lombok.EqualsAndHashCode;
  37. import lombok.Getter;
  38. import lombok.RequiredArgsConstructor;

  39. /***********************************************************************************************************************
  40.  *
  41.  * This class models an amount of money.
  42.  *
  43.  * @author  Fabrizio Giudici
  44.  *
  45.  **********************************************************************************************************************/
  46. @Immutable @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @EqualsAndHashCode
  47. public class Money implements Comparable<Money>
  48.   {
  49.     public static final Money ZERO = Money.of(BigDecimal.ZERO, "EUR");

  50.     @Getter @Nonnull
  51.     private final BigDecimal amount;

  52.     @Getter @Nonnull
  53.     private final String currency;

  54.     private Money (final long amount, @Nonnull final String currency)
  55.       {
  56.         this(BigDecimal.valueOf(amount), currency);
  57.       }

  58.     @Nonnull
  59.     public static Money of (final BigDecimal amount, @Nonnull final String currency)
  60.       {
  61.         return new Money(amount, currency);
  62.       }

  63.     @Nonnull
  64.     public static Money of (final long amount, @Nonnull final String currency)
  65.       {
  66.         return new Money(amount, currency);
  67.       }

  68.     @Nonnull
  69.     public static Money parse (@Nonnull final String string)
  70.       throws ParseException
  71.       {
  72.         final var parts = string.split(" ");
  73.         return Money.of((BigDecimal)getFormat().parse(parts[0]), parts[1]);
  74.       }

  75.     @Nonnull
  76.     public Money add (@Nonnull final Money other)
  77.       {
  78.         checkCurrencies(other);
  79.         return Money.of(amount.add(other.amount), currency);
  80.       }

  81.     @Nonnull
  82.     public Money subtract (@Nonnull final Money other)
  83.       {
  84.         checkCurrencies(other);
  85.         return Money.of(amount.subtract(other.amount), currency);
  86.       }

  87.     @Nonnegative
  88.     public double divided (@Nonnull final Money other)
  89.       {
  90.         checkCurrencies(other);
  91.         // Can fail with ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
  92. //        return amount.divide(other.amount).doubleValue();
  93.         return amount.doubleValue() / other.amount.doubleValue();
  94.       }

  95.     @Nonnull
  96.     public static DecimalFormat getFormat()
  97.       {
  98.         final var symbols = new DecimalFormatSymbols();
  99.         symbols.setDecimalSeparator('.');
  100.         final var pattern = "###0.00";
  101.         final var decimalFormat = new DecimalFormat(pattern, symbols);
  102.         decimalFormat.setParseBigDecimal(true);

  103.         return decimalFormat;
  104.       }

  105.     @Override
  106.     public int compareTo (@Nonnull final Money other)
  107.       {
  108.         checkCurrencies(other);
  109.         return this.amount.compareTo(other.amount);
  110.       }

  111.     public boolean isEqualTo (@Nonnull final Money other)
  112.       {
  113.         return compareTo(other) == 0;
  114.       }

  115.     public boolean greaterThan (@Nonnull final Money other)
  116.       {
  117.         return compareTo(other) > 0;
  118.       }

  119.     public boolean lowerThan (@Nonnull final Money other)
  120.       {
  121.         return compareTo(other) < 0;
  122.       }

  123.     @Override @Nonnull
  124.     public String toString()
  125.       {
  126.         return String.format("%s %s", getFormat().format(amount), currency);
  127.       }

  128.     private void checkCurrencies (@Nonnull final Money other)
  129.       {
  130.         if (!this.currency.equals(other.currency))
  131.           {
  132.             throw new IllegalArgumentException(String.format("Currency mismatch: %s vs %s",
  133.                                                              this.currency, other.currency));
  134.           }
  135.       }
  136.   }