TJSONProtocol writing support in Java

Summary: TJSONProtocol for Java with write support and a TSerializer utility for easier conversion of Thrift objects into byte[] or strings.

Reviewed By: dreiss

Test Plan: Included a basic piece of this in test/ client for Java.

Revert: OK

DiffCamp Revision: 3890


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665367 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/java/src/TSerializer.java b/lib/java/src/TSerializer.java
new file mode 100644
index 0000000..2fb3794
--- /dev/null
+++ b/lib/java/src/TSerializer.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2006- Facebook
+// Distributed under the Thrift Software License
+//
+// See accompanying file LICENSE or visit the Thrift site at:
+// http://developers.facebook.com/thrift/
+
+package com.facebook.thrift;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+
+import com.facebook.thrift.protocol.TBinaryProtocol;
+import com.facebook.thrift.protocol.TProtocol;
+import com.facebook.thrift.protocol.TProtocolFactory;
+import com.facebook.thrift.transport.TIOStreamTransport;
+import com.facebook.thrift.transport.TTransport;
+
+/**
+ * Generic utility for easily serializing objects into a byte array.
+ *
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+public class TSerializer {
+
+  private static class TByteArrayTransport extends TIOStreamTransport {
+
+    private final ByteArrayOutputStream baos_ = new ByteArrayOutputStream();
+
+    public TByteArrayTransport() {
+      outputStream_ = baos_;
+    }
+
+    public byte[] get() {
+      return baos_.toByteArray();
+    }
+
+    public void reset() {
+      baos_.reset();
+    }
+  }
+
+  private TProtocol protocol_;
+
+  private final TByteArrayTransport transport_ = new TByteArrayTransport();
+
+  public TSerializer() {
+    this(new TBinaryProtocol.Factory());
+  }
+
+  public TSerializer(TProtocolFactory protocolFactory) {
+    protocol_ = protocolFactory.getProtocol(transport_);
+  }
+
+  public byte[] serialize(TBase base) throws TException {
+    transport_.reset();
+    base.write(protocol_);
+    byte[] data = transport_.get();
+    return data;
+  }
+
+  public String toString(TBase base, String charset) throws TException {
+    try {
+      return new String(serialize(base), charset);
+    } catch (UnsupportedEncodingException uex) {
+      throw new TException("JVM DOES NOT SUPPORT ENCODING");
+    }
+  }
+
+  public String toString(TBase base) throws TException {
+    return new String(serialize(base));
+  }
+}
+
diff --git a/lib/java/src/protocol/TJSONProtocol.java b/lib/java/src/protocol/TJSONProtocol.java
new file mode 100644
index 0000000..07dee63
--- /dev/null
+++ b/lib/java/src/protocol/TJSONProtocol.java
@@ -0,0 +1,337 @@
+// Copyright (c) 2006- Facebook
+// Distributed under the Thrift Software License
+//
+// See accompanying file LICENSE or visit the Thrift site at:
+// http://developers.facebook.com/thrift/
+
+package com.facebook.thrift.protocol;
+
+import com.facebook.thrift.TException;
+import com.facebook.thrift.transport.TTransport;
+import java.io.UnsupportedEncodingException;
+import java.util.Stack;
+
+/**
+ * JSON protocol implementation for thrift.
+ *
+ * @author Joydeep Sen Sarma <jssarma@facebook.com>
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+public class TJSONProtocol extends TProtocol {
+
+  /**
+   * Factory
+   */
+  public static class Factory implements TProtocolFactory {
+    public TProtocol getProtocol(TTransport trans) {
+      return new TJSONProtocol(trans);
+    }
+  }
+
+  public static final byte[] COMMA = new byte[] {','};
+  public static final byte[] COLON = new byte[] {':'};
+  public static final byte[] QUOTE = new byte[] {'"'};
+  public static final byte[] LBRACE = new byte[] {'{'};
+  public static final byte[] RBRACE = new byte[] {'}'};
+  public static final byte[] LBRACKET = new byte[] {'['};
+  public static final byte[] RBRACKET = new byte[] {']'};
+
+  protected class Context {
+    protected void write() throws TException {}
+  }
+
+  protected class ListContext extends Context {
+    protected boolean first_ = true;
+
+    protected void write() throws TException {
+      if (first_) {
+        first_ = false;
+      } else {
+        trans_.write(COMMA);
+      }
+    }
+  }
+
+  protected class StructContext extends Context {
+    protected boolean first_ = true;
+    protected boolean colon_ = true;
+
+    protected void write() throws TException {
+      if (first_) {
+        first_ = false;
+        colon_ = true;
+      } else {
+        trans_.write(colon_ ? COLON : COMMA);
+        colon_ = !colon_;
+      }
+    }
+  }
+
+  protected final Context BASE_CONTEXT = new Context();
+
+  /**
+   * Stack of nested contexts that we may be in.
+   */
+  protected Stack<Context> writeContextStack_ = new Stack<Context>();
+
+  /**
+   * Current context that we are in
+   */
+  protected Context writeContext_ = BASE_CONTEXT;
+
+  /**
+   * Push a new write context onto the stack.
+   */
+  protected void pushWriteContext(Context c) {
+    writeContextStack_.push(writeContext_);
+    writeContext_ = c;
+  }
+
+  /**
+   * Pop the last write context off the stack
+   */
+  protected void popWriteContext() {
+    writeContext_ = writeContextStack_.pop();
+  }
+
+  /**
+   * Constructor
+   */
+  public TJSONProtocol(TTransport trans) {
+    super(trans);
+  }
+
+  public void writeMessageBegin(TMessage message) throws TException {
+    trans_.write(LBRACKET);
+    pushWriteContext(new ListContext());
+    writeString(message.name);
+    writeByte(message.type);
+    writeI32(message.seqid);
+  }
+
+  public void writeMessageEnd() throws TException {
+    popWriteContext();
+    trans_.write(RBRACKET);
+  }
+
+  public void writeStructBegin(TStruct struct) throws TException {
+    writeContext_.write();
+    trans_.write(LBRACE);
+    pushWriteContext(new StructContext());
+  }
+
+  public void writeStructEnd() throws TException {
+    popWriteContext();
+    trans_.write(RBRACE);
+  }
+
+  public void writeFieldBegin(TField field) throws TException {
+    // Note that extra type information is omitted in JSON!
+    writeString(field.name);
+  }
+
+  public void writeFieldEnd() {}
+
+  public void writeFieldStop() {}
+
+  public void writeMapBegin(TMap map) throws TException {
+    writeContext_.write();
+    trans_.write(LBRACE);
+    pushWriteContext(new StructContext());
+    // No metadata!
+  }
+
+  public void writeMapEnd() throws TException {
+    popWriteContext();
+    trans_.write(RBRACE);
+  }
+
+  public void writeListBegin(TList list) throws TException {
+    writeContext_.write();
+    trans_.write(LBRACKET);
+    pushWriteContext(new ListContext());
+    // No metadata!
+  }
+
+  public void writeListEnd() throws TException {
+    popWriteContext();
+    trans_.write(RBRACKET);
+  }
+
+  public void writeSetBegin(TSet set) throws TException {
+    writeContext_.write();
+    trans_.write(LBRACKET);
+    pushWriteContext(new ListContext());
+    // No metadata!
+  }
+
+  public void writeSetEnd() throws TException {
+    popWriteContext();
+    trans_.write(RBRACKET);
+  }
+
+  public void writeBool(boolean b) throws TException {
+    writeByte(b ? (byte)1 : (byte)0);
+  }
+
+  public void writeByte(byte b) throws TException {
+    writeI32(b);
+  }
+
+  public void writeI16(short i16) throws TException {
+    writeI32(i16);
+  }
+
+  public void writeI32(int i32) throws TException {
+    writeContext_.write();
+    _writeStringData(Integer.toString(i32));
+  }
+
+  public void _writeStringData(String s) throws TException {
+    try {
+      byte[] b = s.getBytes("UTF-8");
+      trans_.write(b);
+    } catch (UnsupportedEncodingException uex) {
+      throw new TException("JVM DOES NOT SUPPORT UTF-8");
+    }
+  }
+
+  public void writeI64(long i64) throws TException {
+    writeContext_.write();
+    _writeStringData(Long.toString(i64));
+  }
+
+  public void writeDouble(double dub) throws TException {
+    writeContext_.write();
+    _writeStringData(Double.toString(dub));
+  }
+
+  public void writeString(String str) throws TException {
+    writeContext_.write();
+    trans_.write(QUOTE);
+
+    int length = str.length();
+    StringBuffer escape = new StringBuffer(length);
+    char c;
+    for (int i = 0; i < length; ++i) {
+      c = str.charAt(i);
+      switch (c) {
+      case '"':
+      case '\\':
+        escape.append('\\');
+        escape.append(c);
+        break;
+      default:
+        escape.append(c);
+        break;
+      }
+    }
+    _writeStringData(escape.toString());
+    trans_.write(QUOTE);
+  }
+
+  public void writeBinary(byte[] bin) throws TException {
+    try {
+      // TODO(mcslee): Fix this
+      writeString(new String(bin, "UTF-8"));
+    } catch (UnsupportedEncodingException uex) {
+      throw new TException("JVM DOES NOT SUPPORT UTF-8");
+    }
+  }
+
+  /**
+   * Reading methods.
+   */
+
+  public TMessage readMessageBegin() throws TException {
+    TMessage message = new TMessage();
+    // TODO(mcslee): implement
+    return message;
+  }
+
+  public void readMessageEnd() {}
+
+  public TStruct readStructBegin() {
+    // TODO(mcslee): implement
+    return new TStruct();
+  }
+
+  public void readStructEnd() {}
+
+  public TField readFieldBegin() throws TException {
+    TField field = new TField();
+    // TODO(mcslee): implement
+    return field;
+  }
+
+  public void readFieldEnd() {}
+
+  public TMap readMapBegin() throws TException {
+    TMap map = new TMap();
+    // TODO(mcslee): implement
+    return map;
+  }
+
+  public void readMapEnd() {}
+
+  public TList readListBegin() throws TException {
+    TList list = new TList();
+    // TODO(mcslee): implement
+    return list;
+  }
+
+  public void readListEnd() {}
+
+  public TSet readSetBegin() throws TException {
+    TSet set = new TSet();
+    // TODO(mcslee): implement
+    return set;
+  }
+
+  public void readSetEnd() {}
+
+  public boolean readBool() throws TException {
+    return (readByte() == 1);
+  }
+
+  public byte readByte() throws TException {
+    // TODO(mcslee): implement
+    return 0;
+  }
+
+  public short readI16() throws TException {
+    // TODO(mcslee): implement
+    return 0;
+  }
+
+  public int readI32() throws TException {
+    // TODO(mcslee): implement
+    return 0;
+  }
+
+  public long readI64() throws TException {
+    // TODO(mcslee): implement
+    return 0;
+  }
+
+  public double readDouble() throws TException {
+    // TODO(mcslee): implement
+    return 0;
+  }
+
+  public String readString() throws TException {
+    // TODO(mcslee): implement
+    return "";
+  }
+
+  public String readStringBody(int size) throws TException {
+    // TODO(mcslee): implement
+    return "";
+  }
+
+  public byte[] readBinary() throws TException {
+    // TODO(mcslee): implement
+    return new byte[0];
+  }
+
+}
diff --git a/lib/java/src/transport/TTransport.java b/lib/java/src/transport/TTransport.java
index 8322b33..2c10870 100644
--- a/lib/java/src/transport/TTransport.java
+++ b/lib/java/src/transport/TTransport.java
@@ -56,7 +56,7 @@
     throws TTransportException;
 
   /**
-   * Guarantees that all of len bytes are 
+   * Guarantees that all of len bytes are
    *
    * @param buf Array to read into
    * @param off Index to start reading at
@@ -79,6 +79,16 @@
   }
 
   /**
+   * Writes the buffer to the output
+   *
+   * @param buf The output data buffer
+   * @throws TTransportException if an error occurs writing data
+   */
+  public void write(byte[] buf) throws TTransportException {
+    write(buf, 0, buf.length);
+  }
+
+  /**
    * Writes up to len bytes from the buffer.
    *
    * @param buf The output data buffer
@@ -94,6 +104,6 @@
    *
    * @throws TTransportException if there was an error writing out data.
    */
-  public void flush() 
+  public void flush()
     throws TTransportException {}
 }