diff --git a/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java b/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java index 6e435a1fd..919459ae8 100644 --- a/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java +++ b/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java @@ -16,6 +16,7 @@ package org.javamoney.moneta; import java.io.Serializable; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -259,7 +260,23 @@ public String toString() { */ @Override public int hashCode() { - return Objects.hash(base, conversionContext, factor, term, chain); + // Numerically equal factors should produce the same hash code, so hash a normalized + // version of the factor rather than the NumberValue object. + BigDecimal normalizedFactor = factor.numberValue(BigDecimal.class).stripTrailingZeros(); + + // The exchange rate chain includes a reference to "this" if the caller doesn't explicitly + // set a chain in the builder, so we can't naively hash the chain or we'll get infinite + // recursion. + int chainHash = 0; + for (ExchangeRate chainedRate : chain) { + if (chainedRate == this) { + // Use a constant to represent the presence of this object in the chain. + chainHash = Objects.hash(chainHash, "this"); + } else { + chainHash = Objects.hash(chainHash, chainedRate); + } + } + return Objects.hash(base, conversionContext, normalizedFactor, term, chainHash); } /* @@ -272,13 +289,11 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (obj instanceof DefaultExchangeRate) { - DefaultExchangeRate other = (DefaultExchangeRate) obj; - return Objects.equals(base, other.base) && - Objects.equals(conversionContext, other.conversionContext) && - Objects.equals(factor, other.factor) && Objects.equals(term, other.term); - } - return false; + DefaultExchangeRate other = (DefaultExchangeRate) obj; + return Objects.equals(base, other.base) && + Objects.equals(conversionContext, other.conversionContext) && + factor.compareTo(other.factor) == 0 && + Objects.equals(term, other.term); } /** diff --git a/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/convert/DefaultExchangeRate.java b/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/convert/DefaultExchangeRate.java index 43513b78a..c4dec334b 100644 --- a/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/convert/DefaultExchangeRate.java +++ b/moneta-convert/moneta-convert-base/src/main/java/org/javamoney/moneta/convert/DefaultExchangeRate.java @@ -289,14 +289,11 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (obj instanceof DefaultExchangeRate) { - DefaultExchangeRate other = (DefaultExchangeRate) obj; - return Objects.equals(base, other.base) && - Objects.equals(conversionContext, other.conversionContext) && - factor.compareTo(other.factor) == 0 && - Objects.equals(term, other.term); - } - return false; + DefaultExchangeRate other = (DefaultExchangeRate) obj; + return Objects.equals(base, other.base) && + Objects.equals(conversionContext, other.conversionContext) && + factor.compareTo(other.factor) == 0 && + Objects.equals(term, other.term); } /** diff --git a/moneta-core/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java b/moneta-core/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java index 6e435a1fd..919459ae8 100644 --- a/moneta-core/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java +++ b/moneta-core/src/main/java/org/javamoney/moneta/DefaultExchangeRate.java @@ -16,6 +16,7 @@ package org.javamoney.moneta; import java.io.Serializable; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -259,7 +260,23 @@ public String toString() { */ @Override public int hashCode() { - return Objects.hash(base, conversionContext, factor, term, chain); + // Numerically equal factors should produce the same hash code, so hash a normalized + // version of the factor rather than the NumberValue object. + BigDecimal normalizedFactor = factor.numberValue(BigDecimal.class).stripTrailingZeros(); + + // The exchange rate chain includes a reference to "this" if the caller doesn't explicitly + // set a chain in the builder, so we can't naively hash the chain or we'll get infinite + // recursion. + int chainHash = 0; + for (ExchangeRate chainedRate : chain) { + if (chainedRate == this) { + // Use a constant to represent the presence of this object in the chain. + chainHash = Objects.hash(chainHash, "this"); + } else { + chainHash = Objects.hash(chainHash, chainedRate); + } + } + return Objects.hash(base, conversionContext, normalizedFactor, term, chainHash); } /* @@ -272,13 +289,11 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (obj instanceof DefaultExchangeRate) { - DefaultExchangeRate other = (DefaultExchangeRate) obj; - return Objects.equals(base, other.base) && - Objects.equals(conversionContext, other.conversionContext) && - Objects.equals(factor, other.factor) && Objects.equals(term, other.term); - } - return false; + DefaultExchangeRate other = (DefaultExchangeRate) obj; + return Objects.equals(base, other.base) && + Objects.equals(conversionContext, other.conversionContext) && + factor.compareTo(other.factor) == 0 && + Objects.equals(term, other.term); } /**