Index: src/test/java/org/apache/hadoop/hbase/io/TestHbaseObjectWritable.java =================================================================== --- src/test/java/org/apache/hadoop/hbase/io/TestHbaseObjectWritable.java (revision 1297245) +++ src/test/java/org/apache/hadoop/hbase/io/TestHbaseObjectWritable.java (working copy) @@ -19,7 +19,14 @@ */ package org.apache.hadoop.hbase.io; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -33,11 +40,14 @@ import org.apache.hadoop.hbase.filter.FilterList; import org.apache.hadoop.hbase.filter.PrefixFilter; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableComparator; import org.junit.Assert; +import com.google.common.collect.Lists; + public class TestHbaseObjectWritable extends TestCase { @Override @@ -56,14 +66,14 @@ /* * This is the code used to generate byte[] where * HbaseObjectWritable used byte for code - * + * ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(byteStream); HbaseObjectWritable.writeObject(out, bytes, byte[].class, conf); byte[] ba = byteStream.toByteArray(); out.close(); */ - + /* * byte array generated by the folowing call * HbaseObjectWritable.writeObject(out, new Text("Old"), Text.class, conf); @@ -72,7 +82,7 @@ Text txt = (Text)readByteArray(conf, baForText); Text oldTxt = new Text("Old"); assertEquals(txt, oldTxt); - + final byte A = 'A'; byte [] bytes = new byte[1]; bytes[0] = A; @@ -84,7 +94,7 @@ byte[] baOut = (byte[])readByteArray(conf, baForByteArray); assertTrue(Bytes.equals(baOut, bytes)); } - + /* * helper method which reads byte array using HbaseObjectWritable.readObject() */ @@ -97,7 +107,7 @@ dis.close(); return product; } - + @SuppressWarnings("boxing") public void testReadObjectDataInputConfiguration() throws IOException { Configuration conf = HBaseConfiguration.create(); @@ -170,7 +180,7 @@ assertTrue(child instanceof CustomFilter); assertEquals("mykey", ((CustomFilter)child).getKey()); } - + public void testCustomSerializable() throws Exception { Configuration conf = HBaseConfiguration.create(); @@ -196,26 +206,111 @@ dis.close(); return product; } - + + public static class A extends IntWritable { + public A() {} + public A(int a) {super(a);} + } + + public static class B extends A { + int b; + public B() { } + public B(int a, int b) { + super(a); + this.b = b; + } + @Override + public void write(DataOutput out) throws IOException { + super.write(out); + out.writeInt(b); + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + this.b = in.readInt(); + } + @Override + public boolean equals(Object o) { + if (o instanceof B) { + return this.get() == ((B) o).get() && this.b == ((B) o).b; + } + return false; + } + } + + /** Tests for serialization of List and Arrays */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void testPolymorphismInSequences() throws Exception { + Configuration conf = HBaseConfiguration.create(); + Object ret; + + //test with lists + List list = Lists.newArrayList(new A(42), new B(10, 100)); + ret = doType(conf, list, list.getClass()); + assertEquals(ret, list); + + //test with Writable[] + Writable[] warr = new Writable[] {new A(42), new B(10, 100)}; + ret = doType(conf, warr, warr.getClass()); + Assert.assertArrayEquals((Writable[])ret, warr); + + //test with arrays + A[] arr = new A[] {new A(42), new B(10, 100)}; + ret = doType(conf, arr, arr.getClass()); + Assert.assertArrayEquals((A[])ret, arr); + + //test with double array + A[][] darr = new A[][] {new A[] { new A(42), new B(10, 100)}, new A[] {new A(12)}}; + ret = doType(conf, darr, darr.getClass()); + Assert.assertArrayEquals((A[][])ret, darr); + + //test with List of arrays + List larr = Lists.newArrayList(arr, new A[] {new A(99)}); + ret = doType(conf, larr, larr.getClass()); + List lret = (List) ret; + assertEquals(larr.size(), lret.size()); + for (int i=0; i declaredClass; @@ -329,26 +348,33 @@ } } - /** - * Write out the code byte for passed Class. - * @param out - * @param c - * @throws IOException - */ - static void writeClassCode(final DataOutput out, final Class c) + static Integer getClassCode(final Class c) throws IOException { Integer code = CLASS_TO_CODE.get(c); if (code == null ) { - if ( List.class.isAssignableFrom(c)) { + if (List.class.isAssignableFrom(c)) { code = CLASS_TO_CODE.get(List.class); - } - else if (Writable.class.isAssignableFrom(c)) { + } else if (Writable.class.isAssignableFrom(c)) { code = CLASS_TO_CODE.get(Writable.class); - } - else if (Serializable.class.isAssignableFrom(c)){ + } else if (c.isArray()) { + code = CLASS_TO_CODE.get(Array.class); + } else if (Serializable.class.isAssignableFrom(c)){ code = CLASS_TO_CODE.get(Serializable.class); } } + return code; + } + + /** + * Write out the code for passed Class. + * @param out + * @param c + * @throws IOException + */ + static void writeClassCode(final DataOutput out, final Class c) + throws IOException { + Integer code = getClassCode(c); + if (code == null) { LOG.error("Unsupported type " + c); StackTraceElement[] els = new Exception().getStackTrace(); @@ -360,7 +386,6 @@ WritableUtils.writeVInt(out, code); } - public static long getWritableSize(Object instance, Class declaredClass, Configuration conf) { long size = Bytes.SIZEOF_BYTE; // code @@ -412,11 +437,18 @@ } else if(declClass.equals(Result [].class)) { Result.writeArray(out, (Result [])instanceObj); } else { + //if it is a Generic array, write the element's type + if (getClassCode(declaredClass) == GENERIC_ARRAY_CODE) { + Class componentType = declaredClass.getComponentType(); + writeClass(out, componentType); + } + int length = Array.getLength(instanceObj); out.writeInt(length); for (int i = 0; i < length; i++) { - writeObject(out, Array.get(instanceObj, i), - declClass.getComponentType(), conf); + Object item = Array.get(instanceObj, i); + writeObject(out, item, + item.getClass(), conf); } } } else if (List.class.isAssignableFrom(declClass)) { @@ -489,7 +521,37 @@ } } + /** Writes the encoded class code as defined in CLASS_TO_CODE, or + * the whole class name if not defined in the mapping. + */ + static void writeClass(DataOutput out, Class c) throws IOException { + Integer code = CLASS_TO_CODE.get(c); + if (code == null) { + WritableUtils.writeVInt(out, NOT_ENCODED); + Text.writeString(out, c.getName()); + } else { + WritableUtils.writeVInt(out, code); + } + } + /** Reads and returns the class as written by {@link #writeClass(DataOutput, Class)} */ + static Class readClass(Configuration conf, DataInput in) throws IOException { + Class instanceClass = null; + int b = (byte)WritableUtils.readVInt(in); + if (b == NOT_ENCODED) { + String className = Text.readString(in); + try { + instanceClass = getClassByName(conf, className); + } catch (ClassNotFoundException e) { + LOG.error("Can't find class " + className, e); + throw new IOException("Can't find class " + className, e); + } + } else { + instanceClass = CODE_TO_CLASS.get(b); + } + return instanceClass; + } + /** * Read a {@link Writable}, {@link String}, primitive type, or an array of * the preceding. @@ -552,6 +614,13 @@ Array.set(instance, i, readObject(in, conf)); } } + } else if (declaredClass.equals(Array.class)) { //an array not declared in CLASS_TO_CODE + Class componentType = readClass(conf, in); + int length = in.readInt(); + instance = Array.newInstance(componentType, length); + for (int i = 0; i < length; i++) { + Array.set(instance, i, readObject(in, conf)); + } } else if (List.class.isAssignableFrom(declaredClass)) { // List int length = in.readInt(); instance = new ArrayList(length);