Return-Path: Delivered-To: apmail-commons-issues-archive@minotaur.apache.org Received: (qmail 66126 invoked from network); 24 Oct 2009 13:03:23 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 24 Oct 2009 13:03:23 -0000 Received: (qmail 6580 invoked by uid 500); 24 Oct 2009 13:03:23 -0000 Delivered-To: apmail-commons-issues-archive@commons.apache.org Received: (qmail 6484 invoked by uid 500); 24 Oct 2009 13:03:23 -0000 Mailing-List: contact issues-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: issues@commons.apache.org Delivered-To: mailing list issues@commons.apache.org Received: (qmail 6474 invoked by uid 99); 24 Oct 2009 13:03:23 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 24 Oct 2009 13:03:23 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.140] (HELO brutus.apache.org) (140.211.11.140) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 24 Oct 2009 13:03:20 +0000 Received: from brutus (localhost [127.0.0.1]) by brutus.apache.org (Postfix) with ESMTP id 61A8C234C045 for ; Sat, 24 Oct 2009 06:02:59 -0700 (PDT) Message-ID: <1203143649.1256389379388.JavaMail.jira@brutus> Date: Sat, 24 Oct 2009 13:02:59 +0000 (UTC) From: "Stephen Colebourne (JIRA)" To: issues@commons.apache.org Subject: [jira] Updated: (LANG-467) EqualsBuilder and HashCodeBuilder treat java.math.BigDecimal inconsistantly and break general contract of hashCode In-Reply-To: <1146294641.1225101284518.JavaMail.jira@brutus> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit X-JIRA-FingerPrint: 30527f35849b9dde25b450d4833f0394 X-Virus-Checked: Checked by ClamAV on apache.org [ https://issues.apache.org/jira/browse/LANG-467?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ] Stephen Colebourne updated LANG-467: ------------------------------------ Priority: Major (was: Minor) We can't have EqualsBuilder and HashCodeBuilder out of line. My preferred fix would be to revert the invalid change to EqualsBuilder. > EqualsBuilder and HashCodeBuilder treat java.math.BigDecimal inconsistantly and break general contract of hashCode > ------------------------------------------------------------------------------------------------------------------ > > Key: LANG-467 > URL: https://issues.apache.org/jira/browse/LANG-467 > Project: Commons Lang > Issue Type: Bug > Affects Versions: 2.4 > Reporter: David Jones > Fix For: 3.0 > > > A POJO with a BigDecimal field and equals() and hashCode() methods implemented using EqualsBuilder and HashCodeBuilder breaks the general contract of Object.hashCode(); > EqualsBuilder treats BigDecimal specially by comparing it using BigDecimal.compareTo() == 0 rather than BigDecimal.equals() > Using BigDecimal.compareTo() ignores the scale of the BigDecimal() > However the append(Object o) method of HashCodeBuilder uses BigDecimal.hashCode() in the case that o is a BigDecimal, which takes the scale into account when generating the hashCode. > The following test case shows the problem! > {code:title=TestApacheCommonsLangHashCodeBuilder.java|borderStyle=solid} > // package declaration and imports not shown > public class TestApacheCommonsLangHashCodeBuilder extends TestCase { > > public void testHashCode() { > MyPojo myPojo1 = new MyPojo(new String("foo"), new BigDecimal("10.2")); > MyPojo myPojo2 = new MyPojo(new String("foo"), new BigDecimal("10.20")); > > // equals method ignores the scale of the big decimal > // so this test passes > assertTrue(myPojo1.equals(myPojo2)); > > // however in the case the equals returns true the > // hashCode must be the same according to the contract > // TEST FAILS AT THIS LINE > assertEquals(myPojo1.hashCode(), myPojo2.hashCode()); > } > > private class MyPojo { > private String name; > private BigDecimal value; > > public MyPojo(String name, BigDecimal value) { > this.name = name; > this.value = value; > } > > public String getName() { > return name; > } > public BigDecimal getValue() { > return value; > } > /** > * equals method implemented using EqualsBuilder > * as documented by apache commons > */ > @Override public boolean equals(Object obj) { > if(this == obj) { > return true; > } > > if(!(obj instanceof MyPojo)) { > return false; > } > > MyPojo other = (MyPojo) obj; > return new EqualsBuilder() > .append(name, other.getName()) > .append(value, other.getValue()) > .isEquals(); > } > > /** > * hashCode method implemented using HashCodeBuilder > * as documented by apache commons > */ > @Override public int hashCode() { > HashCodeBuilder hcb = new HashCodeBuilder(17, 31); > return hcb > .append(name) > .append(value) > .toHashCode(); > } > } > } > {code} > Note that the only reason I haven't provided a patch is because I could not think of one which I thought was reasonable. > *Option 1* > Always set the scale to some value and then get the hashCode() > Example change in HashCodeBuilder.append(Object) add the following: > {code} > else if (object instanceof BigDecimal) { > append(((BigDecimal) object).setScale(DEFAULT_BIGDECIMAL_SCALE, RoundingMode.DOWN).hashCode()); > } > {code} > However what is a reasonable scale for calculating this hashCode? I cannot see a reasonanble scale to choose, that depends on the circumstance > *Option 2* > stripTrailingZeros() before calculating the hashCode() > Example change in HashCodeBuilder.append(Object) add the following: > {code} > else if (object instanceof BigDecimal) { > append(((BigDecimal) object).stripTrailingZeros().hashCode()); > } > {code} > The performance of this method under different circumstances is not documented. > *Option 3* > Document the problem and propose that the client solves the problem. > For example change HashCodeBuilder documentation as follows > {code} > /* > * ... > * public class Person { > * String name; > * int age; > * boolean smoker; > * BigDecimal netWorth; > * ... > * > * public int hashCode() { > * // you pick a hard-coded, randomly chosen, non-zero, odd number > * // ideally different for each class > * return new HashCodeBuilder(17, 37). > * append(name). > * append(age). > * append(smoker). > * // take special care when using BigDecimal as scale takes > * // is included in the hashCode calculation breaking hashCode contract > * // choose a scale which is reasonable for hashCode calculation > * append(netWorth == null ? null : netWorth.setScale(2)). > * toHashCode(); > * } > * } > * ... > */ > {code} -- This message is automatically generated by JIRA. - You can reply to this email to add a comment to the issue online.