You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2009/05/28 17:09:29 UTC

svn commit: r779618 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/query/lucene/ test/java/org/apache/jackrabbit/core/query/lucene/

Author: thomasm
Date: Thu May 28 15:09:29 2009
New Revision: 779618

URL: http://svn.apache.org/viewvc?rev=779618&view=rev
Log:
JCR-1609:  new Property Types (BigDecimal to sortable String and back)

Added:
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DecimalField.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DecimalField.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DecimalField.java?rev=779618&r1=779617&r2=779618&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DecimalField.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DecimalField.java Thu May 28 15:09:29 2009
@@ -17,27 +17,147 @@
 package org.apache.jackrabbit.core.query.lucene;
 
 import java.math.BigDecimal;
+import java.math.BigInteger;
 
 /**
- * The <code>DecimalField</code> class is a utility to convert <code>javas.math.BigDecimal</code>
- * values into <code>String</code> values that are lexicographically ordered
- * according to the decimal value.
- *
- * TODO lexicographical string representation of BigDecimal
- * see e.g. http://www.mail-archive.com/java-user@lucene.apache.org/msg23632.html
+ * The <code>DecimalField</code> class is a utility to convert
+ * <code>java.math.BigDecimal</code> values into <code>String</code> 
+ * values that are lexicographically sortable according to the decimal value.
+ * <p>
+ * The string format only uses the digits '0' to '9' (except the last character 
+ * for negative values) and contains the following elements:
+ * <pre>
+ * { signum of value + 2 (1 character; decimal) } 
+ * { signum of exponent + 2 (1 character; decimal) } 
+ * { length of exponent - 1 (1 character; decimal) } 
+ * { unsigned exponent (decimal) } 
+ * { unsigned value (decimal) }
+ * { 'n' if negated }
+ * </pre>
+ * If the signum is zero, then the value is zero and that's it. The same goes
+ * for the exponent: it is only encoded if the signum of the exponent is not 0.
+ * If the signum is -1, the rest of the string is "negated" character by
+ * character as follows: '0' is converted to '9', '1' to '8', and so on. The
+ * same applies to the exponent.
+ * <p>
+ * Examples: 
+ * Decimal 0: String "2"
+ * Decimal 2: String "322": Signum 1; exponent 0; value 2)
+ * Decimal 123: String "3302123": Signum 1; exponent 2 which is
+ *      encoded as signum 3, length 1, value 2; value 123).
+ * Decimal -1: String "178n": Signum -1, the rest is negated;
+ *      exponent 0 ("2" negated); value 1 ("1" negated).
  */
 public class DecimalField {
-
-    private DecimalField() {
-    }
-
+    
+    /**
+     * Convert a BigDecimal to a String.
+     * 
+     * @param value the BigDecimal
+     * @return the String
+     */
     public static String decimalToString(BigDecimal value) {
-        // TODO implement
-        throw new UnsupportedOperationException("JCR-1609: new Property Types");
+        // sign (1: negative, 2: zero, 3: positive)
+        switch (value.signum()) {
+        case -1:
+            // without the 'n', the string representation of -101
+            // is larger than the string representation of -100
+            return "1" + negate(positiveDecimalToString(value.negate())) + "n";
+        case 0:
+            return "2";
+        default:
+            return "3" + positiveDecimalToString(value);
+        }
     }
-
+    
+    /**
+     * Convert a String to a BigDecimal.
+     * 
+     * @param value the String
+     * @return the BigDecimal
+     */
     public static BigDecimal stringToDecimal(String value) {
-        // TODO implement
-        throw new UnsupportedOperationException("JCR-1609: new Property Types");
+        int signum = value.charAt(0) - '2';
+        if (signum == 0) {
+            return BigDecimal.ZERO;
+        } else if (signum < 0) {
+            value = negate(value).substring(0, value.length() - 1);
+        }
+        int expSignum = value.charAt(1) - '2';
+        long exp;
+        if (expSignum == 0) {
+            exp = 0;
+            value = value.substring(2);
+        } else {
+            String e = value.substring(2, 3);
+            if (expSignum < 0) {
+                e = negate(e);
+            }
+            int expSize = e.charAt(0) - '0' + 1;
+            e = value.substring(3, 3 + expSize);
+            if (expSignum < 0) {
+                e = negate(e);
+            }
+            exp = Long.parseLong(e);
+            if (expSignum < 0) {
+                exp = -exp;
+            }
+            value = value.substring(3 + expSize);
+        }
+        int scale = (int) (value.length() - exp - 1);
+        BigInteger unscaled = new BigInteger(value.substring(0));
+        if (signum < 0) {
+            unscaled = unscaled.negate();
+        }
+        return new BigDecimal(unscaled, scale);
     }
+    
+    private static String positiveDecimalToString(BigDecimal value) {
+        StringBuilder buff = new StringBuilder();
+        int precision = value.precision();
+        int scale = value.scale();
+        long exp = precision - scale - 1;
+        // exponent signum and size
+        if (exp == 0) {
+            buff.append('2');
+        } else {
+            // exponent
+            String e = String.valueOf(Math.abs(exp));
+            // exponent size is prepended
+            e = String.valueOf(e.length() - 1) + e;
+            // exponent signum
+            if (exp > 0) {
+                buff.append('3');
+            } else {
+                buff.append('1');
+                // the exponent is negated
+                e = negate(e);
+            }
+            buff.append(e);
+        }
+        // the unscaled value
+        String s = value.unscaledValue().toString();
+        int max = s.length() - 1;
+        // remove trailing 0s
+        while (s.charAt(max) == '0') {
+            max--;
+        }
+        return buff.append(s.substring(0, max + 1)).toString();
+    }
+
+    /**
+     * "Negate" a number digit by digit (0 becomes 9, 9 becomes 0, and so on).
+     * 
+     * @param s the original string
+     * @return the negated string
+     */
+    private static String negate(String s) {
+        // negate character by character
+        char[] chars = s.toCharArray();
+        for (int i = 0; i < chars.length; i++) {
+            chars[i] = (char) ('9' - chars[i] + '0');
+        }
+        return String.valueOf(chars);
+    }
+
 }

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java?rev=779618&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java Thu May 28 15:09:29 2009
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Random;
+
+import org.apache.jackrabbit.test.JUnitTest;
+
+/**
+ * Tests converting BigDecimal to String and back.
+ */
+public class DecimalConvertTest extends JUnitTest {
+    
+    public void testCommon() {
+        // System.out.println(DecimalField.decimalToString(new BigDecimal(0)));
+        // System.out.println(DecimalField.decimalToString(new BigDecimal(2)));
+        // System.out.println(DecimalField.decimalToString(new BigDecimal(123)));
+        // System.out.println(DecimalField.decimalToString(new BigDecimal(-1)));
+        
+        ArrayList<BigDecimal> list = new ArrayList<BigDecimal>();
+        list.add(BigDecimal.ZERO);
+        list.add(BigDecimal.ONE);
+        list.add(BigDecimal.TEN);
+        list.add(BigDecimal.ONE.scaleByPowerOfTen(1));
+        list.add(new BigDecimal("100"));
+        list.add(new BigDecimal("1000"));
+        list.add(new BigDecimal("0.1"));
+        list.add(new BigDecimal("0.01"));
+        list.add(new BigDecimal("0.001"));
+        list.add(new BigDecimal("1.1"));
+        list.add(new BigDecimal("0.09"));
+        list.add(new BigDecimal("9.9"));
+        list.add(new BigDecimal("9.99"));
+        list.add(new BigDecimal("99"));
+        list.add(new BigDecimal("99.0"));
+        list.add(new BigDecimal("101"));
+        list.add(new BigDecimal("1000.0"));
+        list.add(new BigDecimal("-1.23E-10"));
+        testWithList(list);
+    }
+    
+    public void testRandomized() {
+        ArrayList<BigDecimal> list = new ArrayList<BigDecimal>();
+        list.add(BigDecimal.ZERO);
+        list.add(BigDecimal.ONE);
+        list.add(BigDecimal.TEN);
+        Random random = new Random(1);
+        // a few regular values
+        for (int i = 0; i < 10000; i++) {
+            list.add(new BigDecimal(i));
+        }
+        for (int i = 0; i < 100; i++) {
+            list.add(new BigDecimal(random.nextDouble()));
+        }
+        // scale -10 .. 10
+        for (int i = 0; i < 1000; i++) {
+            int scale = random.nextInt(20) - 10;
+            BigInteger value = BigInteger.valueOf(random.nextLong());
+            list.add(new BigDecimal(value, scale));
+        }
+        // extremely small and large values
+        for (int i = 0; i < 100; i++) {
+            int scale = random.nextInt(2000) - 1000;
+            BigInteger value = new BigInteger(1000, random);
+            list.add(new BigDecimal(value, scale));
+        }
+        testWithList(list);
+    }
+    
+    private void testWithList(ArrayList<BigDecimal> list) {
+        // add negative values
+        for (BigDecimal d : new ArrayList<BigDecimal>(list)) {
+            list.add(d.negate());
+        }
+        Collections.sort(list);
+        BigDecimal lastDecimal = null;
+        String lastString = null;
+        for (BigDecimal d : list) {
+            String s = DecimalField.decimalToString(d);
+            if (lastDecimal != null) {
+                int compDecimal = lastDecimal.compareTo(d);
+                int compString = (int) Math.signum(lastString.compareTo(s));
+                if (compDecimal != compString) {
+                    assertEquals(compDecimal, compString);
+                }
+            }
+            BigDecimal test = DecimalField.stringToDecimal(s);
+            assertEquals(d + "<>" + test, test.compareTo(d), 0);
+            lastDecimal = d;
+            lastString = s;
+        }
+    }
+    
+}

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java?rev=779618&r1=779617&r2=779618&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java Thu May 28 15:09:29 2009
@@ -36,6 +36,7 @@
         TestSuite suite = new TestSuite("Search tests");
 
         suite.addTestSuite(IndexingQueueTest.class);
+        suite.addTestSuite(DecimalConvertTest.class);
 
         return suite;
     }