Index: src/java/org/apache/lucene/util/Simple9.java
===================================================================
--- src/java/org/apache/lucene/util/Simple9.java	(revision 0)
+++ src/java/org/apache/lucene/util/Simple9.java	(revision 0)
@@ -0,0 +1,430 @@
+package org.apache.lucene.util;
+
+/**
+ * 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.
+ */
+
+
+/** Simple9 methods for compressing integers into a single 32-bit integer
+ * and for the corresponding decompression.
+ * 
+ * The Simple9 compression method is taken from this paper:
+ * <br>
+ * Jiangong Zhang, Xiaohui Long, Torsten Suel,
+ * "Performance of Compressed Inverted List Caching in Search Engines",
+ * WWW 2008 / Refereed Track: Search - Corpus Characterization & Search
+ * Performance, Beijing, China
+ * <p>
+ * Adapted quote from this paper:
+ * <br>
+ * The compressed word contains 4 status bits and 28 data bits.
+ * There are nine different ways of dividing up the 28
+ * data bits: 28 1-bit numbers, 14 2-bit numbers, 9 3-bit numbers (one
+ * bit unused), 7 4-bit numbers, 5 5-numbers (three bits unused), 4
+ * 7-bit numbers, 3 9-bit numbers (one bit unused), 2 14-bit numbers,
+ * or 1 28-bit number. We use the four status bits to store which of the
+ * 9 cases is used. Decompression can be done in a highly efﬁcient
+ * manner by doing a switch operation on the status bits, where each
+ * of the 9 cases applies a ﬁxed bit mask to extract the integers.
+ * <br>
+ * End of quote.
+ * <p>
+ * Simple9 leaves some room to choose the encoding variants, and preference
+ * is given here to cases in which increasing positive
+ * integers are compressed.
+ * <br> The reason for this is that it is expected
+ * that small sets of increasing positive integers will be useful for
+ * encoding of the indexes of exceptions in a variant of PForDelta that is
+ * described in the same paper mentioned above.
+ */
+ 
+public class Simple9 {
+  /** No instances needed, only static members */
+  private Simple9() {}
+  
+  private static final int NUM_DATA_BITS = 28;
+  private static final int BITS_28_MASK = (1 << 28) - 1;
+  private static final int BITS_14_MASK = (1 << 14) - 1;
+  private static final int BITS_10_MASK = (1 << 10) - 1;
+  private static final int BITS_9_MASK = (1 << 9) - 1;
+  private static final int BITS_7_MASK = (1 << 7) - 1;
+  private static final int BITS_6_MASK = (1 << 6) - 1;
+  private static final int BITS_5_MASK = (1 << 5) - 1;
+  private static final int BITS_4_MASK = (1 << 4) - 1;
+  private static final int BITS_3_MASK = (1 << 3) - 1;
+  private static final int BITS_2_MASK = (1 << 2) - 1;
+  private static final int BITS_1_MASK = (1 << 1) - 1;
+
+  private static final int NUM_STATUS_BITS = 4;
+  private static final int STATUS_1_NUM_28_BITS = 1; // 1 number of 28 bits
+  private static final int STATUS_2_NUM_14_BITS = 2; // 2 numbers of 14 bits
+  private static final int STATUS_3_NUM_99_10_BITS = 3; // 3 numbers of 9, 9 and 10 bits
+  private static final int STATUS_4_NUM_7_BITS = 4; // 4 numbers of 7 bits
+  private static final int STATUS_5_NUM_55_666_BITS = 5; // 5 numbers of 5, 5, 6, 6 and 6 bits
+  private static final int STATUS_7_NUM_4_BITS = 6; // 7 numbers of 4 bits
+  private static final int STATUS_9_NUM_33333333_4_BITS = 7; // 9 numbers, 8 of 3 bits, 1 of 4 bits
+  private static final int STATUS_14_NUM_2_BITS = 8; // 14 numbers of 2 bits
+  private static final int STATUS_28_NUM_1_BITS = 9; // 28 numbers of 1 bits
+
+  /** Encode positive integers from an uncompressed array at a given inOffset
+   * into a single 32 bit integer at a given output offset using a Simple9 method.
+   * The maximum number in the input array should not exceed 2**28 - 1.
+   * The input array should have at least 28 numbers available from the given inOffset.
+   * At most inSize numbers will be compressed. When inSize is smaller than 29
+   * some bits may be taken from at most 13 remaining elements of the given array.
+   * These remaining 13 elements (pads) should be zero.
+   * Return the number of input integers that where compressed. This will
+   * be at least 1 and at most 28, independent of the value of inSize.
+   *
+   * FIXME: encoding should never use more elements than available.
+   */
+  public static int compressSingle(int[] uncompressed, int inOffset, int inSize,
+                                   int[] compressed, int outOffset) {
+    if (inSize < 1) {
+      throw new IllegalArgumentException("Cannot compress input with non positive size " + inSize);
+    }
+    int inputCompressable = 1;
+    int minBits = 1;
+    int maxFitPlus1 = (1 << minBits);
+    int nextData;
+    do {
+      nextData = uncompressed[inOffset+inputCompressable-1];
+      if (nextData < 0) {
+        throw new IllegalArgumentException("Cannot compress negative input " + nextData 
+                                            + " (at index " + (inOffset+inputCompressable-1) + ")");
+      }
+      while ((nextData >= maxFitPlus1) && (minBits < NUM_DATA_BITS))
+      { // Give preference to input cases with increasing numbers:
+        if ((minBits == 3) && (inputCompressable == 9)
+            && (nextData < (maxFitPlus1 << 1))) {
+          /* STATUS_9_NUM_33333333_4_BITS, nextData fits in 4 bits, no need to increase minBits */
+          break;
+        } else if ((minBits == 5) && (inputCompressable >= 3)
+                   && (nextData < (maxFitPlus1 << 1))) {
+          /* STATUS_5_NUM_55_666_BITS, nextData fits in 6 bits, no need to increase minBits */
+          break;
+        } else if ((minBits == 9) && (inputCompressable == 3)
+                   && (nextData < (maxFitPlus1 << 1))) {
+          /* STATUS_3_NUM_99_10_BITS, nextData fits in 10 bits, no need to increase minBits */
+          break;
+        } else {
+          minBits++;
+          maxFitPlus1 <<= 1;
+          if ((inputCompressable * minBits) > NUM_DATA_BITS) {
+            break;
+          }
+        }
+      }
+      inputCompressable++;
+    } while ( ((inputCompressable * minBits) <= NUM_DATA_BITS)
+             && (inputCompressable <= inSize) );
+ 
+    inputCompressable--;
+    if (inputCompressable == 0) { // 20100105: untested
+      throw new IllegalArgumentException("Cannot compress input " + nextData
+                                          + " with more than " + NUM_DATA_BITS
+                                          + " bits (at offSet " + inOffset + ")");
+    }
+
+    // Check whether a bigger number of bits can be used:
+    while ((inputCompressable * (minBits+1)) <= NUM_DATA_BITS) {
+      minBits++;
+    }
+
+    if (((inputCompressable+1) * minBits) <= NUM_DATA_BITS) {
+      // not enough input available for minBits
+      minBits++;
+      // do not compress all available input
+      inputCompressable = NUM_DATA_BITS / minBits;
+    } 
+
+    // Put compression method in status bits and encode input data
+    int s9 = uncompressed[inOffset]; // init with first input value
+    switch (minBits) { // add status bits and later input values
+    case 28:
+      s9 |= STATUS_1_NUM_28_BITS << NUM_DATA_BITS;
+      compressed[outOffset] = s9;
+      return 1;
+    case 14:
+      s9 |= STATUS_2_NUM_14_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 14;
+      compressed[outOffset] = s9;
+      return 2;
+    case 9:
+      s9 |= STATUS_3_NUM_99_10_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 9;
+      s9 |= uncompressed[inOffset+2] << 18;
+      compressed[outOffset] = s9;
+      return 3;
+    case 7:
+      s9 |= STATUS_4_NUM_7_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 7;
+      s9 |= uncompressed[inOffset+2] << 14;
+      s9 |= uncompressed[inOffset+3] << 21;
+      compressed[outOffset] = s9;
+      return 4;
+    case 5:
+      s9 |= STATUS_5_NUM_55_666_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 5;
+      s9 |= uncompressed[inOffset+2] << 10;
+      s9 |= uncompressed[inOffset+3] << 16;
+      s9 |= uncompressed[inOffset+4] << 22;
+      compressed[outOffset] = s9;
+      return 5;
+    case 4:
+      s9 |= STATUS_7_NUM_4_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 4;
+      s9 |= uncompressed[inOffset+2] << 8;
+      s9 |= uncompressed[inOffset+3] << 12;
+      s9 |= uncompressed[inOffset+4] << 16;
+      s9 |= uncompressed[inOffset+5] << 20;
+      s9 |= uncompressed[inOffset+6] << 24;
+      compressed[outOffset] = s9;
+      return 7;
+    case 3:
+      s9 |= STATUS_9_NUM_33333333_4_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 3;
+      s9 |= uncompressed[inOffset+2] << 6;
+      s9 |= uncompressed[inOffset+3] << 9;
+      s9 |= uncompressed[inOffset+4] << 12;
+      s9 |= uncompressed[inOffset+5] << 15;
+      s9 |= uncompressed[inOffset+6] << 18;
+      s9 |= uncompressed[inOffset+7] << 21;
+      s9 |= uncompressed[inOffset+8] << 24;
+      compressed[outOffset] = s9;
+      return 9;
+    case 2:
+      s9 |= STATUS_14_NUM_2_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 2;
+      s9 |= uncompressed[inOffset+2] << 4;
+      s9 |= uncompressed[inOffset+3] << 6;
+      s9 |= uncompressed[inOffset+4] << 8;
+      s9 |= uncompressed[inOffset+5] << 10;
+      s9 |= uncompressed[inOffset+6] << 12;
+      s9 |= uncompressed[inOffset+7] << 14;
+      s9 |= uncompressed[inOffset+8] << 16;
+      s9 |= uncompressed[inOffset+9] << 18;
+      s9 |= uncompressed[inOffset+10] << 20;
+      s9 |= uncompressed[inOffset+11] << 22;
+      s9 |= uncompressed[inOffset+12] << 24;
+      s9 |= uncompressed[inOffset+13] << 26;
+      compressed[outOffset] = s9;
+      return 14;
+    case 1:
+      s9 |= STATUS_28_NUM_1_BITS << NUM_DATA_BITS;
+      s9 |= uncompressed[inOffset+1] << 1;
+      s9 |= uncompressed[inOffset+2] << 2;
+      s9 |= uncompressed[inOffset+3] << 3;
+      s9 |= uncompressed[inOffset+4] << 4;
+      s9 |= uncompressed[inOffset+5] << 5;
+      s9 |= uncompressed[inOffset+6] << 6;
+      s9 |= uncompressed[inOffset+7] << 7;
+      s9 |= uncompressed[inOffset+8] << 8;
+      s9 |= uncompressed[inOffset+9] << 9;
+      s9 |= uncompressed[inOffset+10] << 10;
+      s9 |= uncompressed[inOffset+11] << 11;
+      s9 |= uncompressed[inOffset+12] << 12;
+      s9 |= uncompressed[inOffset+13] << 13;
+      s9 |= uncompressed[inOffset+14] << 14;
+      s9 |= uncompressed[inOffset+15] << 15;
+      s9 |= uncompressed[inOffset+16] << 16;
+      s9 |= uncompressed[inOffset+17] << 17;
+      s9 |= uncompressed[inOffset+18] << 18;
+      s9 |= uncompressed[inOffset+19] << 19;
+      s9 |= uncompressed[inOffset+20] << 20;
+      s9 |= uncompressed[inOffset+21] << 21;
+      s9 |= uncompressed[inOffset+22] << 22;
+      s9 |= uncompressed[inOffset+23] << 23;
+      s9 |= uncompressed[inOffset+24] << 24;
+      s9 |= uncompressed[inOffset+25] << 25;
+      s9 |= uncompressed[inOffset+26] << 26;
+      s9 |= uncompressed[inOffset+27] << 27;
+      compressed[outOffset] = s9;
+      return 28;
+    default:
+      throw new Error("Simple9.compressSingle internal error: unknown minBits: " + minBits);
+    }
+  }
+
+ 
+  /** The decompressed array should have at least 28 elements from the given offset. */
+  public static int decompressSingle(int s9, int[] decompressed, int outOffset) {
+    switch (s9 >>> NUM_DATA_BITS) {
+    case STATUS_1_NUM_28_BITS:
+      decompressed[outOffset] = s9 & BITS_28_MASK;
+      return 1;
+    case STATUS_2_NUM_14_BITS:
+      decompressed[outOffset] = s9 & BITS_14_MASK;
+      decompressed[outOffset+1] = (s9 >>> 14) & BITS_14_MASK;
+      return 2;
+    case STATUS_3_NUM_99_10_BITS:
+      decompressed[outOffset] = s9 & BITS_9_MASK;
+      decompressed[outOffset+1] = (s9 >>> 9) & BITS_9_MASK;
+      decompressed[outOffset+2] = (s9 >>> 18) & BITS_10_MASK;
+      return 3;
+    case STATUS_4_NUM_7_BITS:
+      decompressed[outOffset] = s9 & BITS_7_MASK;
+      decompressed[outOffset+1] = (s9 >>> 7) & BITS_7_MASK;
+      decompressed[outOffset+2] = (s9 >>> 14) & BITS_7_MASK;
+      decompressed[outOffset+3] = (s9 >>> 21) & BITS_7_MASK;
+      return 4;
+    case STATUS_5_NUM_55_666_BITS:
+      decompressed[outOffset] = s9 & BITS_5_MASK;
+      decompressed[outOffset+1] = (s9 >>> 5) & BITS_5_MASK;
+      decompressed[outOffset+2] = (s9 >>> 10) & BITS_6_MASK; 
+      decompressed[outOffset+3] = (s9 >>> 16) & BITS_6_MASK; 
+      decompressed[outOffset+4] = (s9 >>> 22) & BITS_6_MASK;
+      return 5;
+    case STATUS_7_NUM_4_BITS:
+      decompressed[outOffset] = s9 & BITS_4_MASK;
+      decompressed[outOffset+1] = (s9 >>> 4) & BITS_4_MASK;
+      decompressed[outOffset+2] = (s9 >>> 8) & BITS_4_MASK; 
+      decompressed[outOffset+3] = (s9 >>> 12) & BITS_4_MASK; 
+      decompressed[outOffset+4] = (s9 >>> 16) & BITS_4_MASK;
+      decompressed[outOffset+5] = (s9 >>> 20) & BITS_4_MASK;
+      decompressed[outOffset+6] = (s9 >>> 24) & BITS_4_MASK;
+      return 7;
+    case STATUS_9_NUM_33333333_4_BITS:
+      decompressed[outOffset] = s9 & BITS_3_MASK;
+      decompressed[outOffset+1] = (s9 >>> 3) & BITS_3_MASK;
+      decompressed[outOffset+2] = (s9 >>> 6) & BITS_3_MASK; 
+      decompressed[outOffset+3] = (s9 >>> 9) & BITS_3_MASK; 
+      decompressed[outOffset+4] = (s9 >>> 12) & BITS_3_MASK;
+      decompressed[outOffset+5] = (s9 >>> 15) & BITS_3_MASK;
+      decompressed[outOffset+6] = (s9 >>> 18) & BITS_3_MASK;
+      decompressed[outOffset+7] = (s9 >>> 21) & BITS_3_MASK;
+      decompressed[outOffset+8] = (s9 >>> 24) & BITS_4_MASK;
+      return 9;
+    case STATUS_14_NUM_2_BITS:
+      decompressed[outOffset] = s9 & BITS_2_MASK;
+      decompressed[outOffset+1] = (s9 >>> 2) & BITS_2_MASK;
+      decompressed[outOffset+2] = (s9 >>> 4) & BITS_2_MASK;
+      decompressed[outOffset+3] = (s9 >>> 6) & BITS_2_MASK;
+      decompressed[outOffset+4] = (s9 >>> 8) & BITS_2_MASK;
+      decompressed[outOffset+5] = (s9 >>> 10) & BITS_2_MASK;
+      decompressed[outOffset+6] = (s9 >>> 12) & BITS_2_MASK;
+      decompressed[outOffset+7] = (s9 >>> 14) & BITS_2_MASK;
+      decompressed[outOffset+8] = (s9 >>> 16) & BITS_2_MASK;
+      decompressed[outOffset+9] = (s9 >>> 18) & BITS_2_MASK;
+      decompressed[outOffset+10] = (s9 >>> 20) & BITS_2_MASK;
+      decompressed[outOffset+11] = (s9 >>> 22) & BITS_2_MASK;
+      decompressed[outOffset+12] = (s9 >>> 24) & BITS_2_MASK;
+      decompressed[outOffset+13] = (s9 >>> 26) & BITS_2_MASK;
+      return 14;
+    case STATUS_28_NUM_1_BITS:
+      decompressed[outOffset] = s9 & BITS_1_MASK;
+      decompressed[outOffset+1] = (s9 >>> 1) & BITS_1_MASK;
+      decompressed[outOffset+2] = (s9 >>> 2) & BITS_1_MASK;
+      decompressed[outOffset+3] = (s9 >>> 3) & BITS_1_MASK;
+      decompressed[outOffset+4] = (s9 >>> 4) & BITS_1_MASK;
+      decompressed[outOffset+5] = (s9 >>> 5) & BITS_1_MASK;
+      decompressed[outOffset+6] = (s9 >>> 6) & BITS_1_MASK;
+      decompressed[outOffset+7] = (s9 >>> 7) & BITS_1_MASK;
+      decompressed[outOffset+8] = (s9 >>> 8) & BITS_1_MASK;
+      decompressed[outOffset+9] = (s9 >>> 9) & BITS_1_MASK;
+      decompressed[outOffset+10] = (s9 >>> 10) & BITS_1_MASK;
+      decompressed[outOffset+11] = (s9 >>> 11) & BITS_1_MASK;
+      decompressed[outOffset+12] = (s9 >>> 12) & BITS_1_MASK;
+      decompressed[outOffset+13] = (s9 >>> 13) & BITS_1_MASK;
+      decompressed[outOffset+14] = (s9 >>> 14) & BITS_1_MASK;
+      decompressed[outOffset+15] = (s9 >>> 15) & BITS_1_MASK;
+      decompressed[outOffset+16] = (s9 >>> 16) & BITS_1_MASK;
+      decompressed[outOffset+17] = (s9 >>> 17) & BITS_1_MASK;
+      decompressed[outOffset+18] = (s9 >>> 18) & BITS_1_MASK;
+      decompressed[outOffset+19] = (s9 >>> 19) & BITS_1_MASK;
+      decompressed[outOffset+20] = (s9 >>> 20) & BITS_1_MASK;
+      decompressed[outOffset+21] = (s9 >>> 21) & BITS_1_MASK;
+      decompressed[outOffset+22] = (s9 >>> 22) & BITS_1_MASK;
+      decompressed[outOffset+23] = (s9 >>> 23) & BITS_1_MASK;
+      decompressed[outOffset+24] = (s9 >>> 24) & BITS_1_MASK;
+      decompressed[outOffset+25] = (s9 >>> 25) & BITS_1_MASK;
+      decompressed[outOffset+26] = (s9 >>> 26) & BITS_1_MASK;
+      decompressed[outOffset+27] = (s9 >>> 27) & BITS_1_MASK;
+      return 28;
+    default:
+      throw new IllegalArgumentException("Unknown Simple9 status: " + (s9 >>> 28));
+    }
+  }
+
+    
+  /** Encode positive integers from an uncompressed array at a given inOffset
+   * into an array of 32 bit integers at a given output offset using the
+   * <code>compressSingle</code> method.
+   * The maximum input array element value should not exceed 2**28 - 1.
+   * Return the number of integers that where added to the compressed array
+   * from the given outOffset.
+   * The output array should be at least as big as the input array,
+   * a smaller array may cause an IndexOutOfBoundsException.
+   */
+  public static int compressArray(int[] uncompressed, int inOffset, int inSize,
+                                  int[] compressed, int outOffset)
+  {
+    int totalOut = 0;
+    while (inSize > 0) {
+      int encoded = compressSingle(uncompressed, inOffset, inSize,
+                                   compressed, (outOffset + totalOut));
+  System.out.println("compressArray inSize " + inSize + ", encoded " + encoded);
+      inOffset += encoded;
+      inSize -= encoded;
+      totalOut += 1;
+    }
+    return totalOut;
+  }
+    
+  /** Decompress integers from an array of compressed integers at a given
+   * offset and size into a integers at a given output offset.
+   * The encoding is supposed to have been done by the
+   * <code>compressArray</code> method.
+   * Return the number of output integers that where added to the
+   * uncompressed array.
+   */
+  public static int decompressArray(int[] compressed, int inOffset, int inSize,
+                                    int[] decompressed, int outOffset)
+  {
+    int totalOut = 0;
+    while (inSize > 0) {
+      int decoded = decompressSingle(compressed[inOffset],
+                                     decompressed, (outOffset + totalOut));
+      inOffset += 1;
+      inSize -= 1;
+      totalOut += decoded;
+    }
+    return totalOut;
+  }
+ 
+  /** Decompress integers from an array of compressed integers at a given
+   * offset into a integers at a given output offset and size.
+   * The encoding is supposed to have been done by the
+   * <code>compressArray</code> method.
+   * Return the number of output integers that where actually added to the
+   * uncompressed array, this may be up to 27 larger than the given outSize.
+   * The input array should contain at least as many compressed integers at
+   * the given inOffset as needed to decompress the given output size,
+   * a smaller input array will cause an IndexOutOfBoundsException.
+   */
+  public static int decompressArray(int[] compressed, int inOffset,
+                                    int[] decompressed, int outOffset, int outSize)
+  {
+    int totalOut = 0;
+    while (totalOut < outSize) {
+      int decoded = decompressSingle(compressed[inOffset],
+                                     decompressed, (outOffset + totalOut));
+      inOffset += 1;
+      totalOut += decoded;
+    }
+    return totalOut;
+  }
+}
Index: src/test/org/apache/lucene/util/TestSimple9Single.java
===================================================================
--- src/test/org/apache/lucene/util/TestSimple9Single.java	(revision 0)
+++ src/test/org/apache/lucene/util/TestSimple9Single.java	(revision 0)
@@ -0,0 +1,253 @@
+package org.apache.lucene.util;
+
+/**
+ * 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.
+ */
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test cases for basic (de)compression methods of class Simple9.
+ * Assumes JUnit 4.0 or later.
+ */
+public class TestSimple9Single { // does not need anything from LuceneTestCaseJ4
+  
+  private String dump(int[] data) {
+    StringBuilder r = new StringBuilder();
+    r.append('[');
+    for (int i = 0; i < data.length; i++) {
+      if (i > 0) r.append(',');
+      r.append(data[i]);
+    }
+    r.append(']');
+    return r.toString();
+  }
+  
+  private String dumpDiffBits(int a, int b) {
+    int d = a ^ b;
+    StringBuilder r = new StringBuilder();
+    r.append("0b");
+    for (int i = 0; i < 32; i++) {
+      r.append((d >>> (31-i)) & 1);
+    }
+    return r.toString();
+  }
+
+  private void singleS9Test(int[] data, int simple9, int numCompressed, int inSize) {
+    int[] compressed = new int[1];
+    int actualCompressed = Simple9.compressSingle(data, 0, inSize, compressed, 0);
+    assertEquals("Number of compressed data", numCompressed, actualCompressed);
+    int[] decompressed = new int[numCompressed];
+    int numDecompressed = Simple9.decompressSingle(compressed[0], decompressed, 0);
+    assertEquals("Number of decompressed data", numDecompressed, numCompressed);
+    for (int i = 0; i < numDecompressed; i++) {
+      assertEquals("Decompressed data element " + i, decompressed[i], data[i]);
+    }
+    // This could be done directly after compression but the decompression
+    // failure message is normally more useful:
+    assertEquals("Simple9 of " + dump(data)
+                + ", diff bits: " + dumpDiffBits(compressed[0], simple9),
+                compressed[0], simple9);
+  }
+
+  private void singleS9Test(int[] data, int simple9, int numCompressed) {
+    singleS9Test(data, simple9, numCompressed, data.length);
+  }
+
+  private void singleS9Test(int[] data, int simple9) {
+    singleS9Test(data, simple9, data.length);
+  }
+
+
+  @Test public void num01_28bit01() {
+    singleS9Test(new int[] {0x0fffffff}, 0x1fffffff);
+  }
+  @Test public void num01_28bit02() {
+    singleS9Test(new int[] {0x00000004}, 0x10000004);
+  }
+  @Test public void num01_28bit03() {
+    singleS9Test(new int[] {0x00000000}, 0x10000000);
+  }
+  @Test public void num01_28bitx01() {
+    singleS9Test(new int[] {0x0fffffff,0x0}, 0x1fffffff, 1); // compress only first number
+  }
+  @Test public void num01_28bitx02() {
+    singleS9Test(new int[] {0x07fff,0x0,0x0}, 0x10007fff, 1);
+  }
+
+  @Test public void num02_14bit01() {
+    singleS9Test(new int[] {0x3fff,0x3fff}, 0x2fffffff);
+  }
+  @Test public void num02_14bit02() {
+    singleS9Test(new int[] {0x3fff,0x0000}, 0x20003fff);
+  }
+  @Test public void num02_14bit03() {
+    singleS9Test(new int[] {0x0000,0x3fff}, 0x2fffc000);
+  }
+  @Test public void num02_14bit04() {
+    singleS9Test(new int[] {0x0000,0x0000}, 0x20000000);
+  }
+  @Test public void num02_14bitx01() {
+    singleS9Test(new int[] {0x3fff,0x0000,0x0000}, 0x20003fff, 2);
+  }
+  @Test public void num02_14bitx02() {
+    singleS9Test(new int[] {0x3ff,0x0000,0x0000}, 0x200003ff, 2);
+  }
+
+  @Test public void num03_9bit01() {
+    singleS9Test(new int[] {0x1ff,0x1ff,0x1ff}, 0x37ffffff);
+  }
+  @Test public void num03_9bit02() {
+    singleS9Test(new int[] {0x000,0x000,0x000}, 0x30000000);
+  }
+  @Test public void num03_9bit03() {
+    singleS9Test(new int[] {0x000,0x000,0x0ff}, 0x33fc0000);
+  }
+  @Test public void num03_9bit04() {
+    singleS9Test(new int[] {0x000,0x000,0x03f}, 0x30fc0000);
+  }
+  @Test public void num03_99_10bit01() {
+    singleS9Test(new int[] {0x1ff,0x1ff,0x3ff}, 0x3fffffff);
+  }
+  @Test public void num03_99_10bit02() {
+    singleS9Test(new int[] {0x02f,0x000,0x3ff}, 0x3ffc002f);
+  }
+  @Test public void num03_99_10bit03() {
+    singleS9Test(new int[] {0x000,0x000,0x3ff}, 0x3ffc0000);
+  }
+  @Test public void num03_9bitx01() {
+    singleS9Test(new int[] {0x1ff,0x000,0x000,0x000}, 0x300001ff, 3);
+  }
+  @Test public void num03_9bitx02() {
+    singleS9Test(new int[] {0x0ff,0x000,0x000,0x000}, 0x300000ff, 3);
+  }
+
+  
+  @Test public void num04_7bit01() {
+    singleS9Test(new int[] {0x7f,0x7f,0x7f,0x7f}, 0x4fffffff);
+  }
+  @Test public void num04_7bit02() {
+    singleS9Test(new int[] {0x00,0x7f,0x00,0x00}, 0x40003f80);
+  }
+  @Test public void num04_7bitx01() {
+    singleS9Test(new int[] {0x7f,0x00,0x00,0x00,0x00}, 0x4000007f, 4);
+  }
+  @Test public void num04_7bitx02() {
+    singleS9Test(new int[] {0x1f,0x00,0x7f,0x00,0x00}, 0x401fc01f, 4); // not 55_666
+  }
+
+  @Test public void num05_5bit01() {
+    singleS9Test(new int[] {0x1f,0x1f,0x1f,0x1f,0x1f}, 0x57df7fff);
+  }
+  @Test public void num05_5bit02() {
+    singleS9Test(new int[] {0x00,0x00,0x00,0x00,0x1f}, 0x57c00000);
+  }
+  @Test public void num05_5bit03() {
+    singleS9Test(new int[] {0x1f,0x00,0x00,0x00,0x00}, 0x5000001f);
+  }
+  @Test public void num05_5bitx01() {
+    singleS9Test(new int[] {0x1f,0x1f,0x1f,0x00,0x00,0x00}, 0x50007fff, 5);
+  }
+  @Test public void num05_5bitx02() {
+    singleS9Test(new int[] {0xf,0xf,0xf,0xf,0xf,0xf,0x0}, 0x53cf3def, 5, 5); // 5 compressed, 5 available input
+  }
+  @Test public void num05_5bitx03() {
+    singleS9Test(new int[] {0xf,0xf,0xf,0xf,0xf,0xf,0x0}, 0x53cf3def, 5, 6); // 5 compressed, 6 available input
+  }
+  @Test public void num05_55_666bit01() {
+    singleS9Test(new int[] {0x1f,0x00,0x3f,0x00,0x00}, 0x5000fc1f);
+  }
+  @Test public void num05_55_666bit02() {
+    singleS9Test(new int[] {0x1f,0x00,0x00,0x3f,0x00}, 0x503f001f);
+  }
+  @Test public void num05_55_666bit03() {
+    singleS9Test(new int[] {0x1f,0x00,0x00,0x00,0x3f}, 0x5fc0001f);
+  }
+  @Test public void num05_55_666bitx03() {
+    singleS9Test(new int[] {0x1f,0x00,0x00,0x00,0x3f,0x00}, 0x5fc0001f, 5);
+  }
+
+  @Test public void num07_4bit01() {
+    singleS9Test(new int[] {0xf,0xf,0xf,0xf,0xf,0xf,0xf}, 0x6fffffff);
+  }
+  @Test public void num07_4bit03() {
+    singleS9Test(new int[] {0x0,0xf,0x0,0xf,0x0,0xf,0x0}, 0x60f0f0f0);
+  }
+  @Test public void num07_4bit04() {
+    singleS9Test(new int[] {0xf,0x0,0xf,0x0,0xf,0x0,0xf}, 0x6f0f0f0f);
+  }
+  @Test public void num07_4bitx01() {
+    singleS9Test(new int[] {0x7,0x7,0x7,0x7,0x7,0x7,0x7,0x7,0x0}, 0x67777777, 7, 8); // 7 compressed, 8 available input
+  }
+
+  @Test public void num09_3bit01() {
+    singleS9Test(new int[] {0x7,0x7,0x7,0x7,0x7,0x7,0x7,0x7,0x7}, 0x77ffffff);
+  }
+  @Test public void num09_3_4bit01() {
+    singleS9Test(new int[] {0x7,0x7,0x7,0x7,0x7,0x7,0x7,0x7,0xf}, 0x7fffffff);
+  }
+  @Test public void num09_3_4bit02() {
+    singleS9Test(new int[] {0x0,0x7,0x0,0x7,0x0,0x7,0x0,0x7,0x0}, 0x70e38e38);
+  }
+  @Test public void num09_3_4bit03() {
+    singleS9Test(new int[] {0x7,0x0,0x7,0x0,0x7,0x0,0x7,0x0,0xf}, 0x7f1c71c7);
+  }
+  @Test public void num09_3bitx02() {
+    singleS9Test(new int[] {3,3,3,3,3,3,3,3,3,0,0,0,0,0}, 0x736db6db, 9, 9); // 9 compressed, 9 available input
+  }
+  @Test public void num09_3bitx03() {
+    singleS9Test(new int[] {2,3,3,3,3,3,3,3,3,3,0,0,0,0}, 0x736db6da, 9, 10); // 9 compressed, 10 available input
+  }
+
+  @Test public void num14_2bit01() {
+    singleS9Test(new int[] {3,3,3,3,3,3,3,3,3,3,3,3,3,3}, 0x8fffffff);
+  }
+  @Test public void num14_2bit02() {
+    singleS9Test(new int[] {3,0,3,0,3,0,3,0,3,0,3,0,3,0}, 0x83333333);
+  }
+  @Test public void num14_2bit03() {
+    singleS9Test(new int[] {0,3,0,3,0,3,0,3,0,3,0,3,0,3}, 0x8ccccccc);
+  }
+  @Test public void num14_2bitx02() {
+    singleS9Test(new int[] {1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0x85555555, 14, 14); // 14 compressed, 14 available input
+  }
+  @Test public void num14_2bitx03() {
+    singleS9Test(new int[] {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0x85555554, 14, 27); // 14 compressed, 27 available input
+  }
+  @Test public void num14_2bitx04() {
+    singleS9Test(new int[] {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0xffff}, 0x80000004, 14, 28); // 27 0/1's, and 0xffff: 14
+  }
+
+  @Test public void num28_1bit01() {
+    singleS9Test(new int[] {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, 0x9fffffff);
+  }
+  @Test public void num28_1bit02() {
+    singleS9Test(new int[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0x90000000);
+  }
+  @Test public void num28_1bit03() {
+    singleS9Test(new int[] {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0}, 0x95555555);
+  }
+  @Test public void num28_1bit04() {
+    singleS9Test(new int[] {0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1}, 0x9aaaaaaa);
+  }
+
+
+/*
+ Still to be tested: out of bounds offSets, sizes.
+ An example for this from the junit docs: @Test(expected=IndexOutOfBoundsException.class)
+ */
+}
Index: src/test/org/apache/lucene/util/TestSimple9Arrays.java
===================================================================
--- src/test/org/apache/lucene/util/TestSimple9Arrays.java	(revision 0)
+++ src/test/org/apache/lucene/util/TestSimple9Arrays.java	(revision 0)
@@ -0,0 +1,72 @@
+package org.apache.lucene.util;
+
+/**
+ * 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.
+ */
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test cases for array methods of class Simple9.
+ * Assumes JUnit 4.0 or later.
+ */
+public class TestSimple9Arrays { // does not need anything from LuceneTestCaseJ4
+
+  private void arrayS9Test(int[] data, int compressedSize) {
+    int inSize = data.length;
+    int[] compressed = new int[compressedSize];
+    int actualCompressed = Simple9.compressArray(data, 0, inSize, compressed, 0);
+    assertEquals("Compressed size", compressedSize, actualCompressed);
+    
+    int[] decompressed1 = new int[inSize];
+    int numDecompressed1 = Simple9.decompressArray(compressed, 0, actualCompressed, decompressed1, 0);
+    assertEquals("Number of decompressed(1) data", numDecompressed1, inSize);
+    for (int i = 0; i < inSize; i++) {
+      assertEquals("Decompressed(1) data element " + i, decompressed1[i], data[i]);
+    }
+
+    int[] decompressed2 = new int[inSize];
+    int numDecompressed2 = Simple9.decompressArray(compressed, 0, decompressed2, 0, inSize);
+    assertEquals("Number of decompressed(2) data", numDecompressed2, inSize);
+    for (int i = 0; i < inSize; i++) {
+      assertEquals("Decompressed(2) data element " + i, decompressed2[i], data[i]);
+    }
+  }
+
+  private void arrayS9Test(int[] data) {
+    arrayS9Test(data, data.length);
+  }
+
+  @Test public void array01() {
+    arrayS9Test(new int[] {0x0fffffff}, 1);
+  }
+  @Test public void array02() {
+    arrayS9Test(new int[] {0x0}, 1);
+  }
+  @Test public void array03() {
+    arrayS9Test(new int[] {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, 1); // 28 0/1's
+  }
+  @Test public void array04() {
+    arrayS9Test(new int[] {0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0}, 3); // 27 0/1's: 14 9 4
+  }
+  @Test public void array05() {
+    arrayS9Test(new int[] {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0xffff}, 4); // 27 0/1's, and 2: 14 9 3 1
+  }
+
+/* Still to be done: offset testing */
+}
