[Mulgara-svn] r1084 - trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa

pag at mulgara.org pag at mulgara.org
Wed Jul 16 14:23:19 UTC 2008


Author: pag
Date: 2008-07-16 07:23:17 -0700 (Wed, 16 Jul 2008)
New Revision: 1084

Added:
   trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalUnitTest.java
Modified:
   trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalImpl.java
Log:
Updated xsd:decimal to handle floating point numbers as Doubles. If no decimal point is seen in the lexical form, then it still uses Longs. Also, remembers the number of decimal places in the lexical form for floating point values. This fixes #128

Modified: trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalImpl.java
===================================================================
--- trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalImpl.java	2008-07-16 01:30:49 UTC (rev 1083)
+++ trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalImpl.java	2008-07-16 14:23:17 UTC (rev 1084)
@@ -34,7 +34,6 @@
 import org.apache.log4j.Logger;
 
 // Locally written packages
-import org.mulgara.query.rdf.XSD;
 import org.mulgara.store.stringpool.*;
 import org.mulgara.util.Constants;
 
@@ -43,6 +42,8 @@
  * An SPTypedLiteral that represents xsd:decimal literals.
  * TODO This is currently implemented as a 64 bit (long) value.  xsd:decimal
  * and xsd:integer are not properly supported by this class.
+ * Also, decimal places are not held for floating point values, meaning
+ * that these values are not round-tripped.
  *
  * @created 2004-10-05
  *
@@ -63,34 +64,67 @@
  */
 public final class SPDecimalImpl extends AbstractSPTypedLiteral {
 
+  @SuppressWarnings("unused")
   private final static Logger logger = Logger.getLogger(SPDecimalImpl.class);
 
-  private long l;
+  private Number n;
+  
+  private byte decPlaces = 0;
 
   static final int TYPE_ID = 2; // Unique ID
 
 
   SPDecimalImpl(int subtypeId, URI typeURI, long l) {
     super(TYPE_ID, subtypeId, typeURI);
-    this.l = l;
+    n = l;
   }
 
 
+  SPDecimalImpl(int subtypeId, URI typeURI, double d) {
+    super(TYPE_ID, subtypeId, typeURI);
+    n = d;
+  }
+
+
+  SPDecimalImpl(int subtypeId, URI typeURI, Number n) {
+    super(TYPE_ID, subtypeId, typeURI);
+    this.n = n;
+  }
+
+
   SPDecimalImpl(int subtypeId, URI typeURI, ByteBuffer data) {
-    this(subtypeId, typeURI, data.getLong());
+    super(TYPE_ID, subtypeId, typeURI);
+    // decimal places stored + 1 so that 0 can indicate a Long
+    decPlaces = (byte)(data.get(Constants.SIZEOF_LONG) - 1);
+    if (decPlaces < 0) this.n = data.getLong();
+    else this.n = data.getDouble();
   }
 
 
   SPDecimalImpl(int subtypeId, URI typeURI, String lexicalForm) {
-    this(subtypeId, typeURI, Long.parseLong(lexicalForm));
+    super(TYPE_ID, subtypeId, typeURI);
+    int decPos = lexicalForm.indexOf('.');
+    if (decPos < 0) {
+      n = Long.valueOf(lexicalForm);
+    } else {
+      n = Double.valueOf(lexicalForm);
+      decPlaces = (byte)(lexicalForm.length() - decPos - 1);
+    }
   }
 
 
   /* from SPObject interface. */
 
   public ByteBuffer getData() {
-    ByteBuffer data = ByteBuffer.allocate(Constants.SIZEOF_LONG);
-    data.putLong(l);
+    ByteBuffer data = ByteBuffer.allocate(Constants.SIZEOF_LONG + 1);
+    // decimal places stored + 1 so that 0 can indicate a Long
+    if (n instanceof Long) {
+      data.putLong((Long)n);
+      data.put((byte)0);
+    } else {
+      data.putDouble((Double)n);
+      data.put((byte)(decPlaces + 1));
+    }
     data.flip();
     return data;
   }
@@ -102,7 +136,10 @@
 
 
   public String getLexicalForm() {
-    return Long.toString(l);
+    if (n instanceof Long) return n.toString();
+    String result = String.format("%." + decPlaces + "f", (Double)n);
+    if (decPlaces == 0) result += ".";
+    return result;
   }
 
 
@@ -114,14 +151,20 @@
     if (c != 0) return c;
 
     // Compare the longs.
-    return compare(l, ((SPDecimalImpl)o).l);
+    SPDecimalImpl di = (SPDecimalImpl)o;
+    // unequal type comparisons
+    if (n instanceof Long && di.n instanceof Long) {
+      return compare(n.longValue(), di.n.longValue());
+    } else {
+      return compare(n.doubleValue(), di.n.doubleValue());
+    }
   }
 
 
   /* from Object. */
 
   public int hashCode() {
-    return (int)(l * 7) | (int)(l >> 32);
+    return (int)(n.longValue() * 7) | (int)(n.longValue() >> 32);
   }
 
 
@@ -130,19 +173,23 @@
     if (obj == null) return false;
 
     try {
-      return l == ((SPDecimalImpl)obj).l;
+      SPDecimalImpl di = (SPDecimalImpl)obj;
+      return n.equals(di.n);
     } catch (ClassCastException ex) {
       // obj was not an SPDecimalImpl.
       return false;
     }
   }
 
-
   static int compare(long l1, long l2) {
     return l1 < l2 ? -1 : (l1 > l2 ? 1 : 0);
   }
 
+  static int compare(double d1, double d2) {
+    return d1 < d2 ? -1 : (d1 > d2 ? 1 : 0);
+  }
 
+
   /** Compares the binary representations of two SPDecimalImpl objects. */
   public static class SPDecimalComparator implements SPComparator {
 
@@ -157,7 +204,19 @@
     }
 
     public int compare(ByteBuffer d1, ByteBuffer d2) {
-      return SPDecimalImpl.compare(d1.getLong(), d2.getLong());
+      if (d1.get(Constants.SIZEOF_LONG) == 0) {
+        if (d2.get(Constants.SIZEOF_LONG) == 0) {
+          return SPDecimalImpl.compare(d1.getLong(), d2.getLong());
+        } else {
+          return SPDecimalImpl.compare(d1.getLong(), d2.getDouble());
+        }
+      } else {
+        if (d2.get(Constants.SIZEOF_LONG) == 0) {
+          return SPDecimalImpl.compare(d1.getDouble(), d2.getLong());
+        } else {
+          return SPDecimalImpl.compare(d1.getDouble(), d2.getDouble());
+        }
+      }
     }
 
   }

Added: trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalUnitTest.java
===================================================================
--- trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalUnitTest.java	                        (rev 0)
+++ trunk/src/jar/store-stringpool/java/org/mulgara/store/stringpool/xa/SPDecimalUnitTest.java	2008-07-16 14:23:17 UTC (rev 1084)
@@ -0,0 +1,252 @@
+/*
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * The Original Code is the Kowari Metadata Store.
+ *
+ * The Initial Developer of the Original Code is Edwin Shin.
+ * Copyright (C) 2005. All Rights Reserved.
+ *
+ * Contributor(s): N/A.
+ *
+ * [NOTE: The text of this Exhibit A may differ slightly from the text
+ * of the notices in the Source Code files of the Original Code. You
+ * should use the text of this Exhibit A rather than the text found in the
+ * Original Code Source Code for Your Modifications.]
+ *
+ */
+
+package org.mulgara.store.stringpool.xa;
+
+// Standard Java
+import java.nio.ByteBuffer;
+
+// JUnit
+import junit.framework.*;
+
+// Internal Packages
+import org.mulgara.query.rdf.XSD;
+import org.mulgara.util.Constants;
+
+/**
+ * Unit test for testing an xsd:decimal wrapper.
+ *
+ * @created Jul 15, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class SPDecimalUnitTest extends TestCase {
+
+  private static final String VALID_XSD_DECIMAL1 = "1001";
+
+  private static final String VALID_XSD_DECIMAL2 = "1001.";
+
+  private static final String VALID_XSD_DECIMAL3 = "1001.0";
+
+  private static final String VALID_XSD_DECIMAL4 = "1001.2";
+
+  private static final String VALID_XSD_DECIMAL5 = "1001.230";
+
+  private static final String VALID_XSD_DECIMAL6 = "0.1234";
+
+  private static final String VALID_XSD_DECIMAL7 = "0.123400";
+
+  private static final String VALID_XSD_DECIMAL8 = "-1";
+
+  private static final String VALID_XSD_DECIMAL9 = "0";
+
+  private static final String INVALID_XSD_DECIMAL = "x";
+  private static final String INVALID_XSD_DECIMAL2 = "0x";
+  private static final String INVALID_XSD_DECIMAL3 = " 10";
+
+  /**
+   * Constructs a new test with the given name.
+   *
+   * @param name the name of the test
+   */
+  public SPDecimalUnitTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Hook for test runner to obtain a test suite from.
+   *
+   * @return The test suite
+   */
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    suite.addTest(new SPDecimalUnitTest("testValid"));
+    suite.addTest(new SPDecimalUnitTest("testInvalid"));
+    suite.addTest(new SPDecimalUnitTest("testCompare"));
+
+    return suite;
+  }
+
+  /**
+   * Default test runner.
+   *
+   * @param args The command line arguments
+   * @throws Exception
+   */
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+
+  /**
+   * Tests that valid xsd:decimal values are accepted and processed correctly.
+   */
+  public void testValid() throws Exception {
+    // Create a new factory
+    SPDecimalFactory factory = new SPDecimalFactory();
+
+    validTest(VALID_XSD_DECIMAL1, factory);
+    validTest(VALID_XSD_DECIMAL2, factory);
+    validTest(VALID_XSD_DECIMAL3, factory);
+    validTest(VALID_XSD_DECIMAL4, factory);
+    validTest(VALID_XSD_DECIMAL5, factory);
+    validTest(VALID_XSD_DECIMAL6, factory);
+    validTest(VALID_XSD_DECIMAL7, factory);
+    validTest(VALID_XSD_DECIMAL8, factory);
+    validTest(VALID_XSD_DECIMAL9, factory);
+
+    SPDecimalImpl dec = (SPDecimalImpl)factory.newSPTypedLiteral(XSD.DECIMAL_URI, VALID_XSD_DECIMAL1);
+
+    // Retrieve the byte data of the decimal object
+    ByteBuffer dtBytes = dec.getData();
+
+    long dLong = dtBytes.getLong();
+    byte dByte = dtBytes.get();
+
+    // Test the correct value is stored
+    assertEquals(VALID_XSD_DECIMAL1, Long.toString(dLong));
+    assertEquals((byte)0, dByte);
+
+    // Byte buffer to hold our decimal information
+    ByteBuffer buffer = ByteBuffer.wrap(new byte[Constants.SIZEOF_LONG + 1]);
+
+    // If the previous step passed then we know the long value is what we want,
+    // so store it in our buffer
+    buffer.putLong(dLong);
+    buffer.put((byte)0);
+
+    // Reset the buffer for reading
+    buffer.flip();
+
+    // Create a decimal object by byte buffer
+    dec = (SPDecimalImpl)factory.newSPTypedLiteral(0, buffer);
+
+    // Test that the lexical form of the decimal is correct
+    assertEquals(VALID_XSD_DECIMAL1, dec.getLexicalForm());
+
+    dec = (SPDecimalImpl)factory.newSPTypedLiteral(XSD.DECIMAL_URI, VALID_XSD_DECIMAL2);
+
+    // Retrieve the byte data of the decimal object
+    dtBytes = dec.getData();
+
+    double dDbl = dtBytes.getDouble();
+    dByte = dtBytes.get();
+
+    // Test the correct value is stored
+    assertEquals(VALID_XSD_DECIMAL2, String.format("%.0f.", dDbl));
+    assertEquals((byte)1, dByte);
+  }
+
+  private void validTest(String number, SPDecimalFactory factory) throws Exception {
+    // Create a decimal object by lexical string
+    SPDecimalImpl d = (SPDecimalImpl)factory.newSPTypedLiteral(XSD.DECIMAL_URI, number);
+
+    // Test that the lexical form of the decimal is correct
+    assertEquals(number, d.getLexicalForm());
+
+    // Retrieve the byte data of the decimal object
+    ByteBuffer dtBytes = d.getData();
+
+    // Retrieve the long and in values from the buffer
+    byte places = (byte)(dtBytes.get(Constants.SIZEOF_LONG) - 1);
+
+    // Create a decimal object from the decimal's long
+    SPDecimalImpl dDec;
+    if (places < 0) {
+      dDec = new SPDecimalImpl(0, XSD.DECIMAL_URI, dtBytes.getLong());
+    } else {
+      dDec = new SPDecimalImpl(0, XSD.DECIMAL_URI, dtBytes.getDouble());
+    }
+
+    assertEquals(d, dDec);
+
+    dtBytes.rewind();
+    SPDecimalImpl rebuilt = new SPDecimalImpl(0, XSD.DECIMAL_URI, dtBytes);
+    assertEquals(d, rebuilt);
+  }
+
+  /**
+   * Tests invalid xsd:decimal values.
+   */
+  public void testInvalid() throws Exception {
+    // Create a new factory
+    SPDecimalFactory factory = new SPDecimalFactory();
+
+    invalidTest(INVALID_XSD_DECIMAL, factory);
+    invalidTest(INVALID_XSD_DECIMAL2, factory);
+    invalidTest(INVALID_XSD_DECIMAL3, factory);
+  }
+
+  private void invalidTest(String dec, SPDecimalFactory factory) throws Exception {
+    try {
+      SPDecimalImpl d = (SPDecimalImpl)factory.newSPTypedLiteral(XSD.DECIMAL_URI, dec);
+      fail("Successfully parsed an invalid decimal: " + dec + " -> " + d);
+    } catch (IllegalArgumentException e) {
+    }
+  }
+
+  public void testCompare() throws Exception {
+    // Create a new factory
+    SPDecimalFactory factory = new SPDecimalFactory();
+
+    // 6 < 1 < 5
+    SPDecimalImpl t1, t2, t3;
+
+    t1 = (SPDecimalImpl) factory.newSPTypedLiteral(XSD.DECIMAL_URI, VALID_XSD_DECIMAL6);
+    t2 = (SPDecimalImpl) factory.newSPTypedLiteral(XSD.DECIMAL_URI, VALID_XSD_DECIMAL1);
+    t3 = (SPDecimalImpl) factory.newSPTypedLiteral(XSD.DECIMAL_URI, VALID_XSD_DECIMAL5);
+
+    assertTrue(t1.compareTo(t1) == 0);
+    assertTrue(t1.compareTo(t2) == -1);
+    assertTrue(t1.compareTo(t3) == -1);
+
+    assertTrue(t2.compareTo(t1) == 1);
+    assertTrue(t2.compareTo(t2) == 0);
+    assertTrue(t2.compareTo(t3) == -1);
+
+    assertTrue(t3.compareTo(t1) == 1);
+    assertTrue(t3.compareTo(t2) == 1);
+    assertTrue(t3.compareTo(t3) == 0);
+  }
+
+  /**
+   * Initialise members.
+   *
+   * @throws Exception if something goes wrong
+   */
+  public void setUp() throws Exception {
+    super.setUp();
+  }
+
+  /**
+   * The teardown method for JUnit
+   *
+   * @throws Exception EXCEPTION TO DO
+   */
+  public void tearDown() throws Exception {
+
+    super.tearDown();
+  }
+}




More information about the Mulgara-svn mailing list