diff options
| author | Brian S. O'Neill <bronee@gmail.com> | 2006-10-14 16:47:22 +0000 | 
|---|---|---|
| committer | Brian S. O'Neill <bronee@gmail.com> | 2006-10-14 16:47:22 +0000 | 
| commit | 3df73d90a65115d9fdcf4b8596971e957c6bce2d (patch) | |
| tree | 12e11eb3b3ab52d5bb86a2cc7fce1d60f54d5721 /src/test/java/com/amazon/carbonado/raw | |
Moved tests to separate project.
Diffstat (limited to 'src/test/java/com/amazon/carbonado/raw')
3 files changed, 1943 insertions, 0 deletions
diff --git a/src/test/java/com/amazon/carbonado/raw/TestDataEncoding.java b/src/test/java/com/amazon/carbonado/raw/TestDataEncoding.java new file mode 100644 index 0000000..bff5820 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/raw/TestDataEncoding.java @@ -0,0 +1,515 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.raw;
 +
 +import java.util.Random;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +/**
 + * Test case for {@link DataEncoder} and {@link DataDecoder}. 
 + * <p>
 + * It generates random data values, checks that the decoding produces the
 + * original results, and it checks that the order of the encoded bytes matches
 + * the order of the original data values.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestDataEncoding extends TestCase {
 +    private static final int SHORT_TEST = 100;
 +    private static final int MEDIUM_TEST = 500;
 +    private static final int LONG_TEST = 1000;
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestDataEncoding.class);
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayCompare(byte[] aa, byte[] ab) {
 +        int len = Math.min(aa.length, ab.length);
 +        int result = byteArrayCompare(aa, ab, len);
 +        if (result == 0 && aa.length != ab.length) {
 +            if (aa.length == len) {
 +                return -1;
 +            }
 +            if (ab.length == len) {
 +                return 1;
 +            }
 +        }
 +        return result;
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayCompare(byte[] aa, byte[] ab, int len) {
 +        for (int i=0; i<len; i++) {
 +            int a = aa[i] & 0xff;
 +            int b = ab[i] & 0xff;
 +            if (a < b) {
 +                return -1;
 +            }
 +            if (a > b) {
 +                return 1;
 +            }
 +        }
 +        return 0;
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayOrNullCompare(byte[] aa, byte[] ab) {
 +        if (aa == null) {
 +            if (ab == null) {
 +                return 0;
 +            } else {
 +                return 1;
 +            }
 +        } else if (ab == null) {
 +            return -1;
 +        } else {
 +            return byteArrayCompare(aa, ab);
 +        }
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayOrNullCompare(byte[] aa, byte[] ab, int len) {
 +        if (aa == null) {
 +            if (ab == null) {
 +                return 0;
 +            } else {
 +                return 1;
 +            }
 +        } else if (ab == null) {
 +            return -1;
 +        } else {
 +            return byteArrayCompare(aa, ab, len);
 +        }
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static <C extends Comparable> int compare(C a, C b) {
 +        if (a == null) {
 +            if (b == null) {
 +                return 0;
 +            } else {
 +                return 1;
 +            }
 +        } else if (b == null) {
 +            return -1;
 +        } else {
 +            return Integer.signum(a.compareTo(b));
 +        }
 +    }
 +
 +    private final long mSeed;
 +
 +    private Random mRandom;
 +
 +    public TestDataEncoding(String name) {
 +        super(name);
 +        mSeed = 5399777425345431L;
 +    }
 +
 +    protected void setUp() {
 +        mRandom = new Random(mSeed);
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void test_boolean() throws Exception {
 +        byte[] bytes = new byte[1];
 +        boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            boolean value = mRandom.nextBoolean();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeBoolean(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Boolean() throws Exception {
 +        byte[] bytes = new byte[1];
 +        Boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Boolean value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = mRandom.nextBoolean();
 +            }
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeBooleanObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byte() throws Exception {
 +        byte[] bytes = new byte[1];
 +        byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            byte value = (byte) mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeByte(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Byte() throws Exception {
 +        byte[] bytes = new byte[2];
 +        Byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Byte value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = (byte) mRandom.nextInt();
 +                assertEquals(2, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeByteObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_short() throws Exception {
 +        byte[] bytes = new byte[2];
 +        short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            short value = (short) mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeShort(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Short() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Short value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = (short) mRandom.nextInt();
 +                assertEquals(3, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeShortObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_char() throws Exception {
 +        byte[] bytes = new byte[2];
 +        char lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            char value = (char) mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeChar(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Character() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Character lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Character value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = (char) mRandom.nextInt();
 +                assertEquals(3, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeCharacterObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_int() throws Exception {
 +        byte[] bytes = new byte[4];
 +        int lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            int value = mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeInt(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Integer() throws Exception {
 +        byte[] bytes = new byte[5];
 +        Integer lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Integer value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextInt();
 +                assertEquals(5, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeIntegerObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_long() throws Exception {
 +        byte[] bytes = new byte[8];
 +        long lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            long value = mRandom.nextLong();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeLong(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Long() throws Exception {
 +        byte[] bytes = new byte[9];
 +        Long lastValue = 0L;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Long value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextLong();
 +                assertEquals(9, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeLongObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_float() throws Exception {
 +        byte[] bytes = new byte[4];
 +        float lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            float value = Float.intBitsToFloat(mRandom.nextInt());
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeFloat(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Float() throws Exception {
 +        byte[] bytes = new byte[4];
 +        Float lastValue = 0f;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Float value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Float.intBitsToFloat(mRandom.nextInt());
 +            }
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeFloatObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_double() throws Exception {
 +        byte[] bytes = new byte[8];
 +        double lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            double value = Double.longBitsToDouble(mRandom.nextLong());
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeDouble(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Double() throws Exception {
 +        byte[] bytes = new byte[8];
 +        Double lastValue = 0d;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Double value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Double.longBitsToDouble(mRandom.nextLong());
 +            }
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeDoubleObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_String() throws Exception {
 +        String[] ref = new String[1];
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            String value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length;
 +                switch (mRandom.nextInt(15)) {
 +                default:
 +                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
 +                    length = mRandom.nextInt(100);
 +                    break;
 +                case 8: case 9: case 10: case 11:
 +                    length = mRandom.nextInt(200);
 +                    break;
 +                case 12: case 13:
 +                    length = mRandom.nextInt(20000);
 +                    break;
 +                case 14:
 +                    length = mRandom.nextInt(3000000);
 +                    break;
 +                }
 +                char[] chars = new char[length];
 +                for (int j=0; j<length; j++) {
 +                    char c;
 +                    switch (mRandom.nextInt(7)) {
 +                    default:
 +                    case 0: case 1: case 2: case 3:
 +                        c = (char) mRandom.nextInt(128);
 +                        break;
 +                    case 4: case 5:
 +                        c = (char) mRandom.nextInt(4000);
 +                        break;
 +                    case 6:
 +                        c = (char) mRandom.nextInt();
 +                        break;
 +                    }
 +                    chars[j] = c;
 +                }
 +                value = new String(chars);
 +            }
 +
 +            byte[] bytes = new byte[DataEncoder.calculateEncodedStringLength(value)];
 +            assertEquals(bytes.length, DataEncoder.encode(value, bytes, 0));
 +            assertEquals(bytes.length, DataDecoder.decodeString(bytes, 0, ref));
 +            assertEquals(value, ref[0]);
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/raw/TestEncodingStrategy.java b/src/test/java/com/amazon/carbonado/raw/TestEncodingStrategy.java new file mode 100644 index 0000000..11a4eca --- /dev/null +++ b/src/test/java/com/amazon/carbonado/raw/TestEncodingStrategy.java @@ -0,0 +1,857 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.raw;
 +
 +import java.lang.reflect.*;
 +import java.util.*;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import org.cojen.classfile.*;
 +import org.cojen.util.*;
 +
 +import com.amazon.carbonado.*;
 +import com.amazon.carbonado.info.*;
 +import com.amazon.carbonado.spi.*;
 +
 +/**
 + * Test case for {@link GenericEncodingStrategy}.
 + * <p>
 + * It generates random selections of properties, encodes with random values,
 + * and checks that the decoding produces the original results. In addition, the
 + * proper ordering of encoded keys is checked.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestEncodingStrategy extends TestCase {
 +    private static final int SHORT_TEST = 100;
 +    private static final int MEDIUM_TEST = 500;
 +    private static final int LONG_TEST = 1000;
 +
 +    private static final int ENCODE_OBJECT_ARRAY = 0;
 +    private static final int DECODE_OBJECT_ARRAY = 1;
 +    private static final int ENCODE_OBJECT_ARRAY_PARTIAL = 2;
 +
 +    private static final int BOGUS_GENERATION = 99;
 +
 +    // Make sure BOGUS_GENERATION is not included.
 +    private static final int[] GENERATIONS = {-1, 0, 1, 2, 127, 128, 129, Integer.MAX_VALUE};
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestEncodingStrategy.class);
 +    }
 +
 +    private final long mSeed;
 +    private final StorableProperty<TestStorable>[] mProperties;
 +
 +    private Random mRandom;
 +
 +    public TestEncodingStrategy(String name) {
 +        super(name);
 +        mSeed = 986184829029842L;
 +        Collection<? extends StorableProperty<TestStorable>> properties =
 +            StorableIntrospector.examine(TestStorable.class).getAllProperties().values();
 +        mProperties = properties.toArray(new StorableProperty[0]);
 +    }
 +
 +    protected void setUp() {
 +        mRandom = new Random(mSeed);
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void test_dataEncoding_noProperties() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(0, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_noProperties_prefix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(5, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_noProperties_suffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(0, 7, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_noProperties_prefixAndSuffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(5, 7, generation);
 +        }
 +    }
 +
 +    private void test_dataEncoding_noProperties(int prefix, int suffix, int generation)
 +        throws Exception
 +    {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, 0, 0, prefix, suffix);
 +
 +        Method[] methods = generateCodecMethods
 +            (strategy, new StorableProperty[0], null, generation);
 +
 +        byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +            .invoke(null, new Object[] {new Object[0]});
 +
 +        int generationPrefix;
 +        if (generation < 0) {
 +            generationPrefix = 0;
 +        } else if (generation < 128) {
 +            generationPrefix = 1;
 +        } else {
 +            generationPrefix = 4;
 +        }
 +
 +        assertEquals(encoded.length, prefix + generationPrefix + suffix);
 +
 +        if (generation >= 0) {
 +            if (generationPrefix == 1) {
 +                assertEquals(generation, encoded[prefix]);
 +            } else {
 +                int actualGeneration = DataDecoder.decodeInt(encoded, prefix);
 +                assertEquals(generation, actualGeneration);
 +            }
 +        }
 +
 +        // Decode should not throw an exception.
 +        methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +
 +        // Generation mismatch should throw an exception.
 +        if (generation >= 0) {
 +            encoded[prefix] = BOGUS_GENERATION;
 +            try {
 +                methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +                fail();
 +            } catch (InvocationTargetException e) {
 +                Throwable cause = e.getCause();
 +                if (cause instanceof CorruptEncodingException) {
 +                    CorruptEncodingException cee = (CorruptEncodingException) cause;
 +                    // Make sure error message includes actual generation.
 +                    assertTrue(cee.getMessage().indexOf(String.valueOf(BOGUS_GENERATION)) >= 0);
 +                } else {
 +                    throw e;
 +                }
 +            }
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(0, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties_prefix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(5, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties_suffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(0, 7, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties_prefixAndSuffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(5, 7, generation);
 +        }
 +    }
 +
 +    /**
 +     * @param generation when non-negative, encode a storable layout generation
 +     * value in one or four bytes.
 +     */
 +    private void test_dataEncoding_multipleProperties(int prefix, int suffix, int generation)
 +        throws Exception
 +    {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, 0, 0, prefix, suffix);
 +
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
 +
 +            Method[] methods = generateCodecMethods(strategy, properties, null, generation);
 +            Object[] values = selectPropertyValues(properties);
 +
 +            byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values});
 +
 +            int generationPrefix;
 +            if (generation < 0) {
 +                generationPrefix = 0;
 +            } else if (generation < 128) {
 +                generationPrefix = 1;
 +            } else {
 +                generationPrefix = 4;
 +            }
 +
 +            assertTrue(encoded.length > (prefix + generationPrefix + suffix));
 +
 +            if (generation >= 0) {
 +                if (generationPrefix == 1) {
 +                    assertEquals(generation, encoded[prefix]);
 +                } else {
 +                    int actualGeneration = DataDecoder.decodeInt(encoded, prefix);
 +                    assertEquals(generation, actualGeneration);
 +                }
 +            }
 +
 +            if (prefix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<prefix; p++) {
 +                    encoded[p] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            if (suffix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<suffix; p++) {
 +                    encoded[encoded.length - p - 1] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            Object[] decodedValues = new Object[values.length];
 +            methods[DECODE_OBJECT_ARRAY].invoke(null, decodedValues, encoded);
 +
 +            for (int j=0; j<properties.length; j++) {
 +                Object a = values[j];
 +                Object b = decodedValues[j];
 +                if (properties[j].getType() == byte[].class) {
 +                    assertTrue(0 == TestDataEncoding.byteArrayOrNullCompare
 +                               ((byte[]) a, (byte[]) b));
 +                } else {
 +                    assertEquals(a, b);
 +                }
 +            }
 +
 +            // Generation mismatch should throw an exception.
 +            if (generation >= 0) {
 +                encoded[prefix] = BOGUS_GENERATION;
 +                try {
 +                    methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +                    fail();
 +                } catch (InvocationTargetException e) {
 +                    Throwable cause = e.getCause();
 +                    if (cause instanceof CorruptEncodingException) {
 +                        CorruptEncodingException cee = (CorruptEncodingException) cause;
 +                        // Make sure error message includes actual generation.
 +                        assertTrue
 +                            (cee.getMessage().indexOf(String.valueOf(BOGUS_GENERATION)) >= 0);
 +                    } else {
 +                        throw e;
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    public void test_keyEncoding_noProperties() throws Exception {
 +        test_keyEncoding_noProperties(0, 0);
 +    }
 +
 +    public void test_keyEncoding_noProperties_prefix() throws Exception {
 +        test_keyEncoding_noProperties(5, 0);
 +    }
 +
 +    public void test_keyEncoding_noProperties_suffix() throws Exception {
 +        test_keyEncoding_noProperties(0, 7);
 +    }
 +
 +    public void test_keyEncoding_noProperties_prefixAndSuffix() throws Exception {
 +        test_keyEncoding_noProperties(5, 7);
 +    }
 +
 +    private void test_keyEncoding_noProperties(int prefix, int suffix) throws Exception {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, prefix, suffix, 0, 0);
 +
 +        Method[] methods = generateCodecMethods
 +            (strategy, new StorableProperty[0], new Direction[0], -1);
 +
 +        // Encode should return an empty array.
 +        byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +            .invoke(null, new Object[] {new Object[0]});
 +        assertEquals(encoded.length, prefix + suffix);
 +
 +        // Decode should not throw an exception.
 +        methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties() throws Exception {
 +        test_keyEncoding_multipleProperties(0, 0);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties_prefix() throws Exception {
 +        test_keyEncoding_multipleProperties(5, 0);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties_suffix() throws Exception {
 +        test_keyEncoding_multipleProperties(0, 7);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties_prefixAndSuffix() throws Exception {
 +        test_keyEncoding_multipleProperties(5, 7);
 +    }
 +
 +    private void test_keyEncoding_multipleProperties(int prefix, int suffix) throws Exception {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, prefix, suffix, 0, 0);
 +
 +        for (int i=0; i<MEDIUM_TEST; i++) {
 +            StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
 +            Direction[] directions = selectDirections(properties.length);
 +
 +            Method[] methods = generateCodecMethods(strategy, properties, directions, -1);
 +            Object[] values = selectPropertyValues(properties);
 +
 +            byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values});
 +
 +            assertTrue(encoded.length > (prefix + suffix));
 +
 +            // Encode using partial encoding method, but do all
 +            // properties. Ensure that the encoding is exactly the same.
 +            byte[] encoded2 = (byte[]) methods[ENCODE_OBJECT_ARRAY_PARTIAL]
 +                .invoke(null, new Object[] {values, 0, properties.length});
 +            assertTrue(Arrays.equals(encoded, encoded2));
 +
 +            if (prefix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<prefix; p++) {
 +                    encoded[p] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            if (suffix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<suffix; p++) {
 +                    encoded[encoded.length - p - 1] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            Object[] decodedValues = new Object[values.length];
 +            methods[DECODE_OBJECT_ARRAY].invoke(null, decodedValues, encoded);
 +
 +            for (int j=0; j<properties.length; j++) {
 +                Object a = values[j];
 +                Object b = decodedValues[j];
 +                if (properties[j].getType() == byte[].class) {
 +                    assertTrue(0 == TestDataEncoding.byteArrayOrNullCompare
 +                               ((byte[]) a, (byte[]) b));
 +                } else {
 +                    assertEquals(a, b);
 +                }
 +            }
 +
 +            // Now test partial encoding of keys.
 +
 +            // Clear out random affixes, since we don't need specific values
 +            // anymore and it interferes with next test.
 +            if (prefix > 0) {
 +                for (int p=0; p<prefix; p++) {
 +                    encoded[p] = 0;
 +                }
 +            }
 +            if (suffix > 0) {
 +                for (int p=0; p<suffix; p++) {
 +                    encoded[encoded.length - p - 1] = 0;
 +                }
 +            }
 +
 +            for (int j=0; j<SHORT_TEST; j++) {
 +                int start, end;
 +                if (properties.length == 1) {
 +                    start = 0;
 +                    end = 1;
 +                } else {
 +                    start = mRandom.nextInt(properties.length - 1);
 +                    // Partial encoding doesn't support zero properties, so
 +                    // ensure randomly selected stride is more than zero.
 +                    int stride;
 +                    do {
 +                        stride = mRandom.nextInt(properties.length - start);
 +                    } while (stride == 0);
 +                    end = start + stride + 1;
 +                }
 +
 +                Object[] partialValues = new Object[end - start];
 +                System.arraycopy(values, start, partialValues, 0, partialValues.length);
 +                
 +                byte[] partial = (byte[]) methods[ENCODE_OBJECT_ARRAY_PARTIAL]
 +                    .invoke(null, new Object[] {partialValues, start, end});
 +                
 +                // Partial key must be substring of full key.
 +                int searchStart = start == 0 ? 0 : prefix;
 +                int index = indexOf(encoded, searchStart, encoded.length - searchStart,
 +                                    partial, 0, partial.length, 0);
 +
 +                if (start == 0) {
 +                    assertEquals(0, index);
 +                } else {
 +                    assertTrue(index > 0);
 +                }
 +
 +                if (properties.length == 1) {
 +                    break;
 +                }
 +            }
 +        }
 +    }
 +
 +    public void test_keyEncoding_ordering() throws Exception {
 +        GenericEncodingStrategy strategy =
 +            new GenericEncodingStrategy(TestStorable.class, null, 0, 0, 0, 0);
 +
 +        for (int i=0; i<MEDIUM_TEST; i++) {
 +            StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
 +            Direction[] directions = selectDirections(properties.length);
 +            Method[] methods = generateCodecMethods(strategy, properties, directions, -1);
 +
 +            Object[] values_1 = selectPropertyValues(properties);
 +            byte[] encoded_1 = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values_1});
 +
 +            Object[] values_2 = selectPropertyValues(properties);
 +            byte[] encoded_2 = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values_2});
 +
 +            int byteOrder = TestDataEncoding.byteArrayCompare(encoded_1, encoded_2);
 +            int valueOrder = compareValues(properties, directions, values_1, values_2);
 +
 +            assertEquals(valueOrder, byteOrder);
 +        }
 +    }
 +
 +    private int compareValues(StorableProperty<?>[] properties, Direction[] directions,
 +                              Object[] values_1, Object[] values_2) {
 +        int length = directions.length;
 +        for (int i=0; i<length; i++) {
 +            StorableProperty<?> property = properties[i];
 +            Direction direction = directions[i];
 +
 +            Object value_1 = values_1[i];
 +            Object value_2 = values_2[i];
 +
 +            int result;
 +            if (property.getType() == byte[].class) {
 +                result = TestDataEncoding.byteArrayOrNullCompare
 +                    ((byte[]) value_1, (byte[]) value_2);
 +            } else {
 +                if (value_1 == null) {
 +                    if (value_2 == null) {
 +                        result = 0;
 +                    } else {
 +                        result = 1;
 +                    }
 +                } else if (value_2 == null) {
 +                    result = -1;
 +                } else {
 +                    result = Integer.signum(((Comparable) value_1).compareTo(value_2));
 +                }
 +            }
 +
 +            if (result != 0) {
 +                if (direction == Direction.DESCENDING) {
 +                    result = -result;
 +                }
 +                return result;
 +            }
 +        }
 +
 +        return 0;
 +    }
 +
 +    // Method taken from String class and modified a bit.
 +    private static int indexOf(byte[] source, int sourceOffset, int sourceCount,
 +                               byte[] target, int targetOffset, int targetCount,
 +                               int fromIndex) {
 +        if (fromIndex >= sourceCount) {
 +            return (targetCount == 0 ? sourceCount : -1);
 +        }
 +        if (fromIndex < 0) {
 +            fromIndex = 0;
 +        }
 +        if (targetCount == 0) {
 +            return fromIndex;
 +        }
 +
 +        byte first  = target[targetOffset];
 +        int max = sourceOffset + (sourceCount - targetCount);
 +        
 +        for (int i = sourceOffset + fromIndex; i <= max; i++) {
 +            // Look for first byte.
 +            if (source[i] != first) {
 +                while (++i <= max && source[i] != first);
 +            }
 +            
 +            // Found first byte, now look at the rest of v2
 +            if (i <= max) {
 +                int j = i + 1;
 +                int end = j + targetCount - 1;
 +                for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++);
 +                
 +                if (j == end) {
 +                    // Found whole string.
 +                    return i - sourceOffset;
 +                }
 +            }
 +        }
 +        return -1;
 +    }
 +
 +    /**
 +     * First method is the encoder, second is the decoder. Both methods are
 +     * static. Encoder accepts an object array of property values, and it
 +     * returns a byte array. Decoder accepts an object array to receive
 +     * property values, an encoded byte array, and it returns void.
 +     *
 +     * <p>If generating key encoding and the property count is more than zero,
 +     * then third element in array is a key encoder that supports partial
 +     * encoding. In addition to the object array argument, it also accepts an
 +     * int start and an int end argument. The start of the range is inclusive,
 +     * and the end is exclusive.
 +     *
 +     * @param directions when supplied, build key encoding/decoding. Otherwise,
 +     * build data encoding/decoding.
 +     * @param generation when non-negative, encode a storable layout generation
 +     * value in one or four bytes.
 +     */
 +    private Method[] generateCodecMethods(GenericEncodingStrategy strategy,
 +                                          StorableProperty<TestStorable>[] properties,
 +                                          Direction[] directions,
 +                                          int generation)
 +        throws Exception
 +    {
 +        ClassInjector ci = ClassInjector.create(TestStorable.class.getName(), null);
 +        ClassFile cf = new ClassFile(ci.getClassName());
 +        cf.markSynthetic();
 +        cf.setTarget("1.5");
 +
 +        cf.addDefaultConstructor();
 +
 +        TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
 +        TypeDesc objectArrayType = TypeDesc.forClass(Object[].class);
 +
 +        // Build encode method.
 +        MethodInfo mi = cf.addMethod(Modifiers.PUBLIC_STATIC, "encode", byteArrayType,
 +                                     new TypeDesc[] {objectArrayType});
 +        CodeBuilder b = new CodeBuilder(mi);
 +        LocalVariable encodedVar;
 +        if (directions != null) {
 +            OrderedProperty<TestStorable>[] ordered =
 +                makeOrderedProperties(properties, directions);
 +            encodedVar =
 +                strategy.buildKeyEncoding(b, ordered, b.getParameter(0), null, false, null, null);
 +        } else {
 +            encodedVar = strategy.buildDataEncoding
 +                (b, properties, b.getParameter(0), null, false, generation);
 +        }
 +        b.loadLocal(encodedVar);
 +        b.returnValue(byteArrayType);
 +
 +        // Build decode method.
 +        mi = cf.addMethod(Modifiers.PUBLIC_STATIC, "decode", null,
 +                          new TypeDesc[] {objectArrayType, byteArrayType});
 +        b = new CodeBuilder(mi);
 +        if (directions != null) {
 +            OrderedProperty<TestStorable>[] ordered =
 +                makeOrderedProperties(properties, directions);
 +            strategy.buildKeyDecoding
 +                (b, ordered, b.getParameter(0), null, false, b.getParameter(1));
 +        } else {
 +            strategy.buildDataDecoding
 +                (b, properties, b.getParameter(0), null, false,
 +                 generation, null, b.getParameter(1));
 +        }
 +        b.returnVoid();
 +
 +        if (directions != null && properties.length > 0) {
 +            // Build encode partial key method.
 +            mi = cf.addMethod
 +                (Modifiers.PUBLIC_STATIC, "encodePartial", byteArrayType,
 +                 new TypeDesc[] {objectArrayType, TypeDesc.INT, TypeDesc.INT});
 +            b = new CodeBuilder(mi);
 +            OrderedProperty<TestStorable>[] ordered =
 +                makeOrderedProperties(properties, directions);
 +            encodedVar =
 +                strategy.buildKeyEncoding(b, ordered, b.getParameter(0), null, false,
 +                                          b.getParameter(1), b.getParameter(2));
 +            b.loadLocal(encodedVar);
 +            b.returnValue(byteArrayType);
 +        }
 +
 +        Class<?> clazz = ci.defineClass(cf);
 +
 +        Method encode = clazz.getMethod("encode", Object[].class);
 +        Method decode = clazz.getMethod("decode", Object[].class, byte[].class);
 +        Method encodePartial = null;
 +        if (directions != null && properties.length > 0) {
 +            encodePartial = clazz.getMethod("encodePartial", Object[].class, int.class, int.class);
 +        }
 +
 +        Method[] methods = new Method[3];
 +        methods[ENCODE_OBJECT_ARRAY] = encode;
 +        methods[DECODE_OBJECT_ARRAY] = decode;
 +        methods[ENCODE_OBJECT_ARRAY_PARTIAL] = encodePartial;
 +
 +        return methods;
 +    }
 +
 +    private StorableProperty<TestStorable>[] selectProperties(int minCount, int maxCount) {
 +        int length = (minCount == maxCount) ? minCount
 +            : (mRandom.nextInt(maxCount - minCount + 1) + minCount);
 +
 +        StorableProperty<TestStorable>[] selection = new StorableProperty[length];
 +
 +        for (int i=length; --i>=0; ) {
 +            selection[i] = mProperties[mRandom.nextInt(mProperties.length)];
 +        }
 +
 +        return selection;
 +    }
 +
 +    private Direction[] selectDirections(int count) {
 +        Direction[] directions = new Direction[count];
 +
 +        for (int i=count; --i>=0; ) {
 +            Direction dir;
 +            switch (mRandom.nextInt(3)) {
 +            default:
 +                dir = Direction.UNSPECIFIED;
 +                break;
 +            case 1:
 +                dir = Direction.ASCENDING;
 +                break;
 +            case 2:
 +                dir = Direction.DESCENDING;
 +                break;
 +            }
 +            directions[i] = dir;
 +        }
 +
 +        return directions;
 +    }
 +
 +    private OrderedProperty<TestStorable>[] makeOrderedProperties
 +        (StorableProperty<TestStorable>[] properties, Direction[] directions) {
 +
 +        int length = properties.length;
 +        OrderedProperty<TestStorable>[] ordered = new OrderedProperty[length];
 +        for (int i=length; --i>=0; ) {
 +            ordered[i] = OrderedProperty.get(properties[i], directions[i]);
 +        }
 +
 +        return ordered;
 +    }
 +
 +    /**
 +     * Returns an array of the same size with randomly selected values filled
 +     * in that match the property type.
 +     */
 +    private Object[] selectPropertyValues(StorableProperty<?>[] properties) {
 +        int length = properties.length;
 +        Object[] values = new Object[length];
 +
 +        for (int i=length; --i>=0; ) {
 +            StorableProperty<?> property = properties[i];
 +            TypeDesc type = TypeDesc.forClass(property.getType());
 +
 +            Object value;
 +
 +            if (property.isNullable() && mRandom.nextInt(100) == 0) {
 +                value = null;
 +            } else {
 +                TypeDesc prim = type.toPrimitiveType();
 +                if (prim != null) {
 +                    switch (prim.getTypeCode()) {
 +                    case TypeDesc.BOOLEAN_CODE: default:
 +                        value = mRandom.nextBoolean();
 +                        break;
 +                    case TypeDesc.CHAR_CODE:
 +                        value = (char) mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.FLOAT_CODE:
 +                        value = Float.intBitsToFloat(mRandom.nextInt());
 +                        break;
 +                    case TypeDesc.DOUBLE_CODE:
 +                        value = Double.longBitsToDouble(mRandom.nextLong());
 +                        break;
 +                    case TypeDesc.BYTE_CODE:
 +                        value = (byte) mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.SHORT_CODE:
 +                        value = (short) mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.INT_CODE:
 +                        value = mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.LONG_CODE:
 +                        value = mRandom.nextLong();
 +                        break;
 +                    }
 +                } else if (type == TypeDesc.STRING) {
 +                    int len = mRandom.nextInt(100);
 +                    StringBuilder sb = new StringBuilder(len);
 +                    for (int j=0; j<len; j++) {
 +                        sb.append((char) mRandom.nextInt());
 +                    }
 +                    value = sb.toString();
 +                } else {
 +                    int len = mRandom.nextInt(100);
 +                    byte[] bytes = new byte[len];
 +                    for (int j=0; j<len; j++) {
 +                        bytes[j] = (byte) mRandom.nextInt();
 +                    }
 +                    value = bytes;
 +                }
 +            }
 +
 +            values[i] = value;
 +        }
 +
 +        return values;
 +    }
 +
 +    /**
 +     * Just a collection of storable properties. At least one property defined
 +     * per supported type.
 +     */
 +    @PrimaryKey("byte") // I don't really care what the primary key is.
 +    public static interface TestStorable extends Storable {
 +        byte getByte();
 +
 +        void setByte(byte b);
 +
 +        Byte getByteObj();
 +
 +        void setByteObj(Byte b);
 +
 +        @Nullable
 +        Byte getNullableByteObj();
 +
 +        void setNullableByteObj(Byte b);
 +
 +        short getShort();
 +
 +        void setShort(short s);
 +
 +        Short getShortObj();
 +
 +        void setShortObj(Short s);
 +
 +        @Nullable
 +        Short getNullableShortObj();
 +
 +        void setNullableShortObj(Short s);
 +
 +        char getChar();
 +
 +        void setChar(char c);
 +
 +        Character getCharacterObj();
 +
 +        void setCharacterObj(Character c);
 +
 +        @Nullable
 +        Character getNullableCharacterObj();
 +
 +        void setNullableCharacterObj(Character c);
 +
 +        int getInt();
 +
 +        void setInt(int i);
 +
 +        Integer getIntegerObj();
 +
 +        void setIntegerObj(Integer obj);
 +
 +        @Nullable
 +        Integer getNullableIntegerObj();
 +
 +        void setNullableIntegerObj(Integer obj);
 +
 +        long getLong();
 +
 +        void setLong(long i);
 +
 +        Long getLongObj();
 +
 +        void setLongObj(Long i);
 +
 +        @Nullable
 +        Long getNullableLongObj();
 +
 +        void setNullableLongObj(Long i);
 +
 +        float getFloat();
 +
 +        void setFloat(float f);
 +
 +        Float getFloatObj();
 +
 +        void setFloatObj(Float f);
 +
 +        @Nullable
 +        Float getNullableFloatObj();
 +
 +        void setNullableFloatObj(Float f);
 +
 +        double getDouble();
 +
 +        void setDouble(double d);
 +
 +        Double getDoubleObj();
 +
 +        void setDoubleObj(Double d);
 +
 +        @Nullable
 +        Double getNullableDoubleObj();
 +
 +        void setNullableDoubleObj(Double d);
 +
 +        byte[] getByteArray();
 +
 +        void setByteArray(byte[] b);
 +
 +        @Nullable
 +        byte[] getNullableByteArray();
 +
 +        void setNullableByteArray(byte[] b);
 +
 +        String getString();
 +
 +        void setString(String s);
 +
 +        @Nullable
 +        String getNullableString();
 +
 +        void setNullableString(String s);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/raw/TestKeyEncoding.java b/src/test/java/com/amazon/carbonado/raw/TestKeyEncoding.java new file mode 100644 index 0000000..3dba00e --- /dev/null +++ b/src/test/java/com/amazon/carbonado/raw/TestKeyEncoding.java @@ -0,0 +1,571 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.raw;
 +
 +import java.util.Random;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +/**
 + * Test case for {@link KeyEncoder} and {@link KeyDecoder}. 
 + * <p>
 + * It generates random data values, checks that the decoding produces the
 + * original results, and it checks that the order of the encoded bytes matches
 + * the order of the original data values.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestKeyEncoding extends TestCase {
 +    private static final int SHORT_TEST = 100;
 +    private static final int MEDIUM_TEST = 500;
 +    private static final int LONG_TEST = 1000;
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestKeyEncoding.class);
 +    }
 +
 +    private final long mSeed;
 +
 +    private Random mRandom;
 +
 +    public TestKeyEncoding(String name) {
 +        super(name);
 +        mSeed = 5399777425345431L;
 +    }
 +
 +    protected void setUp() {
 +        mRandom = new Random(mSeed);
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void test_booleanDesc() throws Exception {
 +        byte[] bytes = new byte[1];
 +        boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            boolean value = mRandom.nextBoolean();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeBooleanDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_BooleanDesc() throws Exception {
 +        byte[] bytes = new byte[1];
 +        Boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Boolean value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = mRandom.nextBoolean();
 +            }
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeBooleanObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byteDesc() throws Exception {
 +        byte[] bytes = new byte[1];
 +        byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            byte value = (byte) mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeByteDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_ByteDesc() throws Exception {
 +        byte[] bytes = new byte[2];
 +        Byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Byte value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = (byte) mRandom.nextInt();
 +                assertEquals(2, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeByteObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_shortDesc() throws Exception {
 +        byte[] bytes = new byte[2];
 +        short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            short value = (short) mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeShortDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_ShortDesc() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Short value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = (short) mRandom.nextInt();
 +                assertEquals(3, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeShortObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_charDesc() throws Exception {
 +        byte[] bytes = new byte[2];
 +        char lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            char value = (char) mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeCharDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_CharacterDesc() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Character lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Character value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = (char) mRandom.nextInt();
 +                assertEquals(3, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeCharacterObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_intDesc() throws Exception {
 +        byte[] bytes = new byte[4];
 +        int lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            int value = mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeIntDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_IntegerDesc() throws Exception {
 +        byte[] bytes = new byte[5];
 +        Integer lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Integer value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextInt();
 +                assertEquals(5, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeIntegerObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_longDesc() throws Exception {
 +        byte[] bytes = new byte[8];
 +        long lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            long value = mRandom.nextLong();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeLongDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_LongDesc() throws Exception {
 +        byte[] bytes = new byte[9];
 +        Long lastValue = 0L;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Long value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextLong();
 +                assertEquals(9, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeLongObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_floatDesc() throws Exception {
 +        byte[] bytes = new byte[4];
 +        float lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            float value = Float.intBitsToFloat(mRandom.nextInt());
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeFloatDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_FloatDesc() throws Exception {
 +        byte[] bytes = new byte[4];
 +        Float lastValue = 0f;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Float value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Float.intBitsToFloat(mRandom.nextInt());
 +            }
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeFloatObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_doubleDesc() throws Exception {
 +        byte[] bytes = new byte[8];
 +        double lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            double value = Double.longBitsToDouble(mRandom.nextLong());
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeDoubleDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_DoubleDesc() throws Exception {
 +        byte[] bytes = new byte[8];
 +        Double lastValue = 0d;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Double value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Double.longBitsToDouble(mRandom.nextLong());
 +            }
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeDoubleObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_String() throws Exception {
 +        String lastValue = null;
 +        byte[] lastBytes = null;
 +        String[] ref = new String[1];
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            String value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length;
 +                switch (mRandom.nextInt(15)) {
 +                default:
 +                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
 +                    length = mRandom.nextInt(100);
 +                    break;
 +                case 8: case 9: case 10: case 11:
 +                    length = mRandom.nextInt(200);
 +                    break;
 +                case 12: case 13:
 +                    length = mRandom.nextInt(20000);
 +                    break;
 +                case 14:
 +                    length = mRandom.nextInt(3000000);
 +                    break;
 +                }
 +                char[] chars = new char[length];
 +                for (int j=0; j<length; j++) {
 +                    char c;
 +                    switch (mRandom.nextInt(7)) {
 +                    default:
 +                    case 0: case 1: case 2: case 3:
 +                        c = (char) mRandom.nextInt(128);
 +                        break;
 +                    case 4: case 5:
 +                        c = (char) mRandom.nextInt(4000);
 +                        break;
 +                    case 6:
 +                        c = (char) mRandom.nextInt();
 +                        break;
 +                    }
 +                    chars[j] = c;
 +                }
 +                value = new String(chars);
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedStringLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encode(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decodeString(bytes, 0, ref));
 +            assertEquals(value, ref[0]);
 +
 +            if (lastBytes != null) {
 +                int sgn = TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_StringDesc() throws Exception {
 +        String lastValue = null;
 +        byte[] lastBytes = null;
 +        String[] ref = new String[1];
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            String value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length;
 +                switch (mRandom.nextInt(15)) {
 +                default:
 +                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
 +                    length = mRandom.nextInt(100);
 +                    break;
 +                case 8: case 9: case 10: case 11:
 +                    length = mRandom.nextInt(200);
 +                    break;
 +                case 12: case 13:
 +                    length = mRandom.nextInt(20000);
 +                    break;
 +                case 14:
 +                    length = mRandom.nextInt(3000000);
 +                    break;
 +                }
 +                char[] chars = new char[length];
 +                for (int j=0; j<length; j++) {
 +                    char c;
 +                    switch (mRandom.nextInt(7)) {
 +                    default:
 +                    case 0: case 1: case 2: case 3:
 +                        c = (char) mRandom.nextInt(128);
 +                        break;
 +                    case 4: case 5:
 +                        c = (char) mRandom.nextInt(4000);
 +                        break;
 +                    case 6:
 +                        c = (char) mRandom.nextInt();
 +                        break;
 +                    }
 +                    chars[j] = c;
 +                }
 +                value = new String(chars);
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedStringLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encodeDesc(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decodeStringDesc(bytes, 0, ref));
 +            assertEquals(value, ref[0]);
 +
 +
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byteArray() throws Exception {
 +        byte[] lastValue = null;
 +        byte[] lastBytes = null;
 +        byte[][] ref = new byte[1][];
 +        for (int i=0; i<LONG_TEST; i++) {
 +            byte[] value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length = mRandom.nextInt(4000);
 +                value = new byte[length];
 +                for (int j=0; j<length; j++) {
 +                    value[j] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encode(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decode(bytes, 0, ref));
 +            if (ref[0] == null) {
 +                assertEquals(value, null);
 +            } else if (value == null) {
 +                assertEquals(value, ref[0]);
 +            } else {
 +                assertEquals(0, TestDataEncoding.byteArrayCompare(value, ref[0], value.length));
 +            }
 +
 +            if (lastBytes != null) {
 +                int sgn = TestDataEncoding.byteArrayOrNullCompare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byteArrayDesc() throws Exception {
 +        byte[] lastValue = null;
 +        byte[] lastBytes = null;
 +        byte[][] ref = new byte[1][];
 +        for (int i=0; i<LONG_TEST; i++) {
 +            byte[] value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length = mRandom.nextInt(4000);
 +                value = new byte[length];
 +                for (int j=0; j<length; j++) {
 +                    value[j] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encodeDesc(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decodeDesc(bytes, 0, ref));
 +            if (ref[0] == null) {
 +                assertEquals(value, null);
 +            } else if (value == null) {
 +                assertEquals(value, ref[0]);
 +            } else {
 +                assertEquals(0, TestDataEncoding.byteArrayCompare(value, ref[0], value.length));
 +            }
 +
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.byteArrayOrNullCompare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +}
  | 
