THRIFT-446. java: PartialDeserialization in Java

This patch adds a partialDeserialize method to TDeserializer that allows you to request a specific subfield of the serialized data.



git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@820786 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/java/build.xml b/lib/java/build.xml
index dbbaf6b..c626948 100644
--- a/lib/java/build.xml
+++ b/lib/java/build.xml
@@ -182,6 +182,8 @@
       classpathref="test.classpath" failonerror="true" />
     <java classname="org.apache.thrift.test.UnionTest"
       classpathref="test.classpath" failonerror="true" />
+    <java classname="org.apache.thrift.test.PartialDeserializeTest"
+      classpathref="test.classpath" failonerror="true" />
   </target>
 
   <target name="generate">
diff --git a/lib/java/src/org/apache/thrift/TDeserializer.java b/lib/java/src/org/apache/thrift/TDeserializer.java
index d6dd5d4..7b7d51d 100644
--- a/lib/java/src/org/apache/thrift/TDeserializer.java
+++ b/lib/java/src/org/apache/thrift/TDeserializer.java
@@ -23,7 +23,11 @@
 import java.io.UnsupportedEncodingException;
 
 import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TField;
+import org.apache.thrift.protocol.TProtocol;
 import org.apache.thrift.protocol.TProtocolFactory;
+import org.apache.thrift.protocol.TProtocolUtil;
+import org.apache.thrift.protocol.TType;
 import org.apache.thrift.transport.TIOStreamTransport;
 
 /**
@@ -81,6 +85,59 @@
   }
 
   /**
+   * Deserialize only a single Thrift object (addressed by recursively using field id)
+   * from a byte record.
+   * @param record The object to read from
+   * @param tb The object to read into
+   * @param fieldIdPath The FieldId's that define a path tb
+   * @throws TException 
+   */
+  public void partialDeserialize(TBase tb, byte[] bytes, int ... fieldIdPath) throws TException {
+    // if there are no elements in the path, then the user is looking for the 
+    // regular deserialize method
+    // TODO: it might be nice not to have to do this check every time to save
+    // some performance.
+    if (fieldIdPath.length == 0) {
+      deserialize(tb, bytes);
+      return;
+    }
+
+    TProtocol iprot = protocolFactory_.getProtocol(
+        new TIOStreamTransport(
+          new ByteArrayInputStream(bytes))); 
+
+    // index into field ID path being currently searched for
+    int curPathIndex = 0;
+
+    iprot.readStructBegin();
+
+    while (curPathIndex < fieldIdPath.length) {
+      TField field = iprot.readFieldBegin();
+      // we can stop searching if we either see a stop or we go past the field 
+      // id we're looking for (since fields should now be serialized in asc
+      // order).
+      if (field.type == TType.STOP || field.id > fieldIdPath[curPathIndex]) { 
+        return;
+      }
+
+      if (field.id != fieldIdPath[curPathIndex]) {
+        // Not the field we're looking for. Skip field.
+        TProtocolUtil.skip(iprot, field.type);
+        iprot.readFieldEnd();
+      } else {
+        // This field is the next step in the path. Step into field.
+        curPathIndex++;
+        if (curPathIndex < fieldIdPath.length) {
+          iprot.readStructBegin();
+        }
+      }
+    }
+
+    // when this line is reached, iprot will be positioned at the start of tb.
+    tb.read(iprot);
+  }
+
+  /**
    * Deserialize the Thrift object from a Java string, using the default JVM
    * charset encoding.
    *
diff --git a/lib/java/test/org/apache/thrift/test/PartialDeserializeTest.java b/lib/java/test/org/apache/thrift/test/PartialDeserializeTest.java
new file mode 100644
index 0000000..d88a686
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/test/PartialDeserializeTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.thrift.test;
+
+import org.apache.thrift.TBase;
+import org.apache.thrift.TDeserializer;
+import org.apache.thrift.TException;
+import org.apache.thrift.TSerializer;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TCompactProtocol;
+import org.apache.thrift.protocol.TJSONProtocol;
+import org.apache.thrift.protocol.TProtocolFactory;
+
+import thrift.test.Backwards;
+import thrift.test.OneOfEach;
+import thrift.test.PrimitiveThenStruct;
+import thrift.test.StructWithAUnion;
+import thrift.test.TestUnion;
+
+public class PartialDeserializeTest {
+
+  private static final TProtocolFactory[] PROTOCOLS = new TProtocolFactory[] {
+    new TBinaryProtocol.Factory(), 
+    new TCompactProtocol.Factory(), 
+    new TJSONProtocol.Factory()
+  };
+
+  public static void main(String[] args) throws TException {
+    //Root:StructWithAUnion
+    //  1:Union
+    //    1.3:OneOfEach
+    OneOfEach Level3OneOfEach = Fixtures.oneOfEach;
+    TestUnion Level2TestUnion = new TestUnion(TestUnion.STRUCT_FIELD, Level3OneOfEach);
+    StructWithAUnion Level1SWU = new StructWithAUnion(Level2TestUnion);
+
+    Backwards bw = new Backwards(2, 1);
+    PrimitiveThenStruct pts = new PrimitiveThenStruct(12345, 67890, bw);
+
+    for (TProtocolFactory factory : PROTOCOLS) {
+      //Full deserialization test
+      testPartialDeserialize(factory, Level1SWU, new StructWithAUnion(), Level1SWU);
+
+      //Level 2 test
+      testPartialDeserialize(factory, Level1SWU, new TestUnion(), Level2TestUnion, StructWithAUnion.TEST_UNION);
+
+      //Level 3 on 3rd field test
+      testPartialDeserialize(factory, Level1SWU, new OneOfEach(), Level3OneOfEach, StructWithAUnion.TEST_UNION, TestUnion.STRUCT_FIELD);
+
+      //Test early termination when traversed path Field.id exceeds the one being searched for
+      testPartialDeserialize(factory, Level1SWU, new OneOfEach(), new OneOfEach(), StructWithAUnion.TEST_UNION, TestUnion.I32_FIELD);
+      
+      //Test that readStructBegin isn't called on primitive
+      testPartialDeserialize(factory, pts, new Backwards(), bw, PrimitiveThenStruct.BW);      
+    }
+  }
+
+  public static void testPartialDeserialize(TProtocolFactory protocolFactory, TBase input, TBase output, TBase expected, int ... fieldIdPath) throws TException {
+    byte[] record = new TSerializer(protocolFactory).serialize(input);
+    new TDeserializer(protocolFactory).partialDeserialize(output, record, fieldIdPath);
+    if(!output.equals(expected))
+      throw new RuntimeException("with " + protocolFactory.toString() + ", expected " + expected + " but got " + output);
+  }
+}
+
diff --git a/test/DebugProtoTest.thrift b/test/DebugProtoTest.thrift
index 9ba60a2..9b3952e 100644
--- a/test/DebugProtoTest.thrift
+++ b/test/DebugProtoTest.thrift
@@ -265,4 +265,10 @@
 
 struct StructWithAUnion {
   1: TestUnion test_union;
+}
+
+struct PrimitiveThenStruct {
+  1: i32 blah;
+  2: i32 blah2;
+  3: Backwards bw;
 }
\ No newline at end of file