blob: aa5d5d1fb2b24b1318fd09b2fc94bc56f42d2722 [file] [log] [blame]
// Copyright 2009 Google Inc.
//
// 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.google.gwtorm.protobuf;
import com.google.gwtorm.client.Column;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Encode and decode an arbitrary Java object as a Protobuf message.
*
* <p>The object must use the {@link Column} annotations to denote the fields that should be encoded
* or decoded.
*/
public abstract class ProtobufCodec<T> {
/** Encode the object into an immutable byte string. */
public ByteString encodeToByteString(T obj) {
return ByteString.copyFrom(encodeToByteBuffer(obj));
}
/** Encode the object into an immutable byte string. */
public ByteBuffer encodeToByteBuffer(T obj) {
ByteBuffer data = ByteBuffer.allocate(sizeof(obj));
encode(obj, data);
data.flip();
return data;
}
/** Encode the object into a byte array. */
public byte[] encodeToByteArray(T obj) {
byte[] data = new byte[sizeof(obj)];
encode(obj, data);
return data;
}
/** Encode the object into a byte array. */
public void encode(T obj, final byte[] data) {
encode(obj, data, 0, data.length);
}
/** Encode the object into a byte array. */
public void encode(T obj, final byte[] data, int offset, int length) {
CodedOutputStream out = CodedOutputStream.newInstance(data, offset, length);
try {
encode(obj, out);
out.flush();
} catch (IOException err) {
throw new RuntimeException("Cannot encode message", err);
}
}
/** Encode the object into a ByteBuffer. */
public void encode(T obj, ByteBuffer buf) {
if (buf.hasArray()) {
CodedOutputStream out =
CodedOutputStream.newInstance( //
buf.array(), //
buf.position(), //
buf.remaining());
try {
encode(obj, out);
out.flush();
} catch (IOException err) {
throw new RuntimeException("Cannot encode message", err);
}
buf.position(buf.position() + (buf.remaining() - out.spaceLeft()));
} else {
CodedOutputStream out = CodedOutputStream.newInstance(newStream(buf));
try {
encode(obj, out);
out.flush();
} catch (IOException err) {
throw new RuntimeException("Cannot encode message", err);
}
}
}
/**
* Encodes the object, prefixed by its encoded length.
*
* <p>The length is encoded as a raw varint with no tag.
*
* @param obj the object to encode.
* @param out stream that will receive the object's data.
* @throws IOException the stream failed to write data.
*/
public void encodeWithSize(T obj, OutputStream out) throws IOException {
CodedOutputStream cos = CodedOutputStream.newInstance(out);
cos.writeRawVarint32(sizeof(obj));
encode(obj, cos);
cos.flush();
}
private static ByteBufferOutputStream newStream(ByteBuffer buf) {
return new ByteBufferOutputStream(buf);
}
/**
* Encode the object to the supplied output stream.
*
* <p>The stream {@code out} is not flushed by this method. Callers that need the entire byte
* representation after invoking encode must flush the stream to ensure its intermediate buffers
* have been written to the backing store.
*
* @param obj the object to encode.
* @param out the stream to encode the object onto.
* @throws IOException the underlying stream cannot be written to.
*/
public abstract void encode(T obj, CodedOutputStream out) throws IOException;
/** Compute the number of bytes of the encoded form of the object. */
public abstract int sizeof(T obj);
/** Create a new uninitialized instance of the object type. */
public abstract T newInstance();
/** Decode a byte string into an object instance. */
public T decode(ByteString buf) {
T obj = newInstance();
mergeFrom(buf, obj);
return obj;
}
/** Decode a byte array into an object instance. */
public T decode(byte[] data) {
T obj = newInstance();
mergeFrom(data, obj);
return obj;
}
/** Decode a byte array into an object instance. */
public T decode(byte[] data, int offset, int length) {
T obj = newInstance();
mergeFrom(data, offset, length, obj);
return obj;
}
/** Decode a byte buffer into an object instance. */
public T decode(ByteBuffer buf) {
T obj = newInstance();
mergeFrom(buf, obj);
return obj;
}
/**
* Decode an object by reading it from the stream.
*
* @throws IOException the underlying stream cannot be read.
*/
public T decode(CodedInputStream in) throws IOException {
T obj = newInstance();
mergeFrom(in, obj);
return obj;
}
/** Decode an object that is prefixed by its encoded length. */
public T decodeWithSize(InputStream in) throws IOException {
T obj = newInstance();
mergeFromWithSize(in, obj);
return obj;
}
/** Decode a byte string into an existing object instance. */
public void mergeFrom(ByteString buf, T obj) {
try {
mergeFrom(buf.newCodedInput(), obj);
} catch (IOException err) {
throw new RuntimeException("Cannot decode message", err);
}
}
/** Decode a byte array into an existing object instance. */
public void mergeFrom(byte[] data, T obj) {
mergeFrom(data, 0, data.length, obj);
}
/** Decode a byte array into an existing object instance. */
public void mergeFrom(byte[] data, int offset, int length, T obj) {
try {
mergeFrom(CodedInputStream.newInstance(data, offset, length), obj);
} catch (IOException err) {
throw new RuntimeException("Cannot decode message", err);
}
}
/** Decode a byte buffer into an existing object instance. */
public void mergeFrom(ByteBuffer buf, T obj) {
if (buf.hasArray()) {
CodedInputStream in =
CodedInputStream.newInstance( //
buf.array(), //
buf.position(), //
buf.remaining());
try {
mergeFrom(in, obj);
} catch (IOException err) {
throw new RuntimeException("Cannot decode message", err);
}
buf.position(buf.position() + in.getTotalBytesRead());
} else {
mergeFrom(ByteString.copyFrom(buf), obj);
}
}
/** Decode an object that is prefixed by its encoded length. */
public void mergeFromWithSize(InputStream in, T obj) throws IOException {
int sz = readRawVarint32(in);
mergeFrom(CodedInputStream.newInstance(new CappedInputStream(in, sz)), obj);
}
/**
* Decode an input stream into an existing object instance.
*
* @throws IOException the underlying stream cannot be read.
*/
public abstract void mergeFrom(CodedInputStream in, T obj) throws IOException;
private static int readRawVarint32(InputStream in) throws IOException {
int b = in.read();
if (b == -1) {
throw new InvalidProtocolBufferException("Truncated input");
}
if ((b & 0x80) == 0) {
return b;
}
int result = b & 0x7f;
int offset = 7;
for (; offset < 32; offset += 7) {
b = in.read();
if (b == -1) {
throw new InvalidProtocolBufferException("Truncated input");
}
result |= (b & 0x7f) << offset;
if ((b & 0x80) == 0) {
return result;
}
}
// Keep reading up to 64 bits.
for (; offset < 64; offset += 7) {
b = in.read();
if (b == -1) {
throw new InvalidProtocolBufferException("Truncated input");
}
if ((b & 0x80) == 0) {
return result;
}
}
throw new InvalidProtocolBufferException("Malformed varint");
}
}