org.fusesource.hawtbuf.proto.CodedInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hawtbuf-proto Show documentation
Show all versions of hawtbuf-proto Show documentation
HawtBuf Proto: A protobuf library
The newest version!
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.
// http://code.google.com/p/protobuf/
//
// 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 org.fusesource.hawtbuf.proto;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.BufferInputStream;
/**
* Reads and decodes protocol message fields.
*
* This class contains two kinds of methods: methods that read specific protocol
* message constructs and field types (e.g. {@link #readTag()} and
* {@link #readInt32()}) and methods that read low-level values (e.g.
* {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading
* encoded protocol messages, you should use the former methods, but if you are
* reading some other format of your own design, use the latter.
*
* @author [email protected] Kenton Varda
*/
public final class CodedInputStream extends FilterInputStream {
private int lastTag = 0;
private int limit = Integer.MAX_VALUE;
private int pos;
private BufferInputStream bis;
public CodedInputStream(InputStream in) {
super(in);
if( in.getClass() == BufferInputStream.class ) {
bis = (BufferInputStream)in;
}
}
public CodedInputStream(Buffer data) {
this(new BufferInputStream(data));
limit = data.length;
}
public CodedInputStream(byte[] data) {
this(new BufferInputStream(data));
limit = data.length;
}
/**
* Attempt to read a field tag, returning zero if we have reached EOF.
* Protocol message parsers use this to read tags, since a protocol message
* may legally end wherever a tag occurs, and zero is not a valid tag
* number.
*/
public int readTag() throws IOException {
if( pos >= limit ) {
lastTag=0;
return 0;
}
try {
lastTag = readRawVarint32();
if (lastTag == 0) {
// If we actually read zero, that's not a valid tag.
throw InvalidProtocolBufferException.invalidTag();
}
return lastTag;
} catch (EOFException e) {
lastTag=0;
return 0;
}
}
/**
* Verifies that the last call to readTag() returned the given tag value.
* This is used to verify that a nested group ended with the correct end
* tag.
*
* @throws InvalidProtocolBufferException
* {@code value} does not match the last tag.
*/
public void checkLastTagWas(int value) throws InvalidProtocolBufferException {
if (lastTag != value) {
throw InvalidProtocolBufferException.invalidEndTag();
}
}
/**
* Reads and discards a single field, given its tag value.
*
* @return {@code false} if the tag is an endgroup tag, in which case
* nothing is skipped. Otherwise, returns {@code true}.
*/
public boolean skipField(int tag) throws IOException {
switch (WireFormat.getTagWireType(tag)) {
case WireFormat.WIRETYPE_VARINT:
readInt32();
return true;
case WireFormat.WIRETYPE_FIXED64:
readRawLittleEndian64();
return true;
case WireFormat.WIRETYPE_LENGTH_DELIMITED:
skipRawBytes(readRawVarint32());
return true;
case WireFormat.WIRETYPE_START_GROUP:
skipMessage();
checkLastTagWas(WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP));
return true;
case WireFormat.WIRETYPE_END_GROUP:
return false;
case WireFormat.WIRETYPE_FIXED32:
readRawLittleEndian32();
return true;
default:
throw InvalidProtocolBufferException.invalidWireType();
}
}
/**
* Reads and discards an entire message. This will read either until EOF or
* until an endgroup tag, whichever comes first.
*/
public void skipMessage() throws IOException {
while (true) {
int tag = readTag();
if (tag == 0 || !skipField(tag))
return;
}
}
// -----------------------------------------------------------------
/** Read a {@code double} field value from the stream. */
public double readDouble() throws IOException {
return Double.longBitsToDouble(readRawLittleEndian64());
}
/** Read a {@code float} field value from the stream. */
public float readFloat() throws IOException {
return Float.intBitsToFloat(readRawLittleEndian32());
}
/** Read a {@code uint64} field value from the stream. */
public long readUInt64() throws IOException {
return readRawVarint64();
}
/** Read an {@code int64} field value from the stream. */
public long readInt64() throws IOException {
return readRawVarint64();
}
/** Read an {@code int32} field value from the stream. */
public int readInt32() throws IOException {
return readRawVarint32();
}
/** Read a {@code fixed64} field value from the stream. */
public long readFixed64() throws IOException {
return readRawLittleEndian64();
}
/** Read a {@code fixed32} field value from the stream. */
public int readFixed32() throws IOException {
return readRawLittleEndian32();
}
/** Read a {@code bool} field value from the stream. */
public boolean readBool() throws IOException {
return readRawVarint32() != 0;
}
/** Read a {@code string} field value from the stream. */
public String readString() throws IOException {
int size = readRawVarint32();
Buffer data = readRawBytes(size);
return new String(data.data, data.offset, data.length, "UTF-8");
}
/** Read a {@code bytes} field value from the stream. */
public Buffer readBytes() throws IOException {
int size = readRawVarint32();
return readRawBytes(size);
}
/** Read a {@code uint32} field value from the stream. */
public int readUInt32() throws IOException {
return readRawVarint32();
}
/**
* Read an enum field value from the stream. Caller is responsible for
* converting the numeric value to an actual enum.
*/
public int readEnum() throws IOException {
return readRawVarint32();
}
/** Read an {@code sfixed32} field value from the stream. */
public int readSFixed32() throws IOException {
return readRawLittleEndian32();
}
/** Read an {@code sfixed64} field value from the stream. */
public long readSFixed64() throws IOException {
return readRawLittleEndian64();
}
/** Read an {@code sint32} field value from the stream. */
public int readSInt32() throws IOException {
return decodeZigZag32(readRawVarint32());
}
/** Read an {@code sint64} field value from the stream. */
public long readSInt64() throws IOException {
return decodeZigZag64(readRawVarint64());
}
// =================================================================
/**
* Read a raw Varint from the stream. If larger than 32 bits, discard the
* upper bits.
*/
public int readRawVarint32() throws IOException {
byte tmp = readRawByte();
if (tmp >= 0) {
return tmp;
}
int result = tmp & 0x7f;
if ((tmp = readRawByte()) >= 0) {
result |= tmp << 7;
} else {
result |= (tmp & 0x7f) << 7;
if ((tmp = readRawByte()) >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 0x7f) << 14;
if ((tmp = readRawByte()) >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 0x7f) << 21;
result |= (tmp = readRawByte()) << 28;
if (tmp < 0) {
// Discard upper 32 bits.
for (int i = 0; i < 5; i++) {
if (readRawByte() >= 0)
return result;
}
throw InvalidProtocolBufferException.malformedVarint();
}
}
}
}
return result;
}
/** Read a raw Varint from the stream. */
public long readRawVarint64() throws IOException {
int shift = 0;
long result = 0;
while (shift < 64) {
byte b = readRawByte();
result |= (long) (b & 0x7F) << shift;
if ((b & 0x80) == 0)
return result;
shift += 7;
}
throw InvalidProtocolBufferException.malformedVarint();
}
/** Read a 32-bit little-endian integer from the stream. */
public int readRawLittleEndian32() throws IOException {
byte b1 = readRawByte();
byte b2 = readRawByte();
byte b3 = readRawByte();
byte b4 = readRawByte();
return (((int) b1 & 0xff)) | (((int) b2 & 0xff) << 8) | (((int) b3 & 0xff) << 16) | (((int) b4 & 0xff) << 24);
}
/** Read a 64-bit little-endian integer from the stream. */
public long readRawLittleEndian64() throws IOException {
byte b1 = readRawByte();
byte b2 = readRawByte();
byte b3 = readRawByte();
byte b4 = readRawByte();
byte b5 = readRawByte();
byte b6 = readRawByte();
byte b7 = readRawByte();
byte b8 = readRawByte();
return (((long) b1 & 0xff)) | (((long) b2 & 0xff) << 8) | (((long) b3 & 0xff) << 16) | (((long) b4 & 0xff) << 24) | (((long) b5 & 0xff) << 32) | (((long) b6 & 0xff) << 40) | (((long) b7 & 0xff) << 48) | (((long) b8 & 0xff) << 56);
}
/**
* Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into
* values that can be efficiently encoded with varint. (Otherwise, negative
* values must be sign-extended to 64 bits to be varint encoded, thus always
* taking 10 bytes on the wire.)
*
* @param n
* An unsigned 32-bit integer, stored in a signed int because
* Java has no explicit unsigned support.
* @return A signed 32-bit integer.
*/
public static int decodeZigZag32(int n) {
return (n >>> 1) ^ -(n & 1);
}
/**
* Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into
* values that can be efficiently encoded with varint. (Otherwise, negative
* values must be sign-extended to 64 bits to be varint encoded, thus always
* taking 10 bytes on the wire.)
*
* @param n
* An unsigned 64-bit integer, stored in a signed int because
* Java has no explicit unsigned support.
* @return A signed 64-bit integer.
*/
public static long decodeZigZag64(long n) {
return (n >>> 1) ^ -(n & 1);
}
/**
* Read one byte from the input.
*
* @throws InvalidProtocolBufferException
* The end of the stream or the current limit was reached.
*/
public byte readRawByte() throws IOException {
if( pos >= limit ) {
throw new EOFException();
}
int rc = in.read();
if( rc < 0 ) {
throw new EOFException();
}
pos++;
return (byte)( rc & 0xFF);
}
/**
* Read a fixed size of bytes from the input.
*
* @throws InvalidProtocolBufferException
* The end of the stream or the current limit was reached.
*/
public Buffer readRawBytes(int size) throws IOException {
if( size == 0) {
return new Buffer(new byte[]{});
}
if( this.pos+size > limit ) {
throw new EOFException();
}
// If the underlying stream is a ByteArrayInputStream
// then we can avoid an array copy.
if( bis!=null ) {
Buffer rc = bis.readBuffer(size);
if( rc==null || rc.getLength() < size ) {
throw new EOFException();
}
this.pos += rc.getLength();
return rc;
}
// Otherwise we, have to do it the old fasioned way
byte[] rc = new byte[size];
int c;
int pos=0;
while( pos < size ) {
c = in.read(rc, pos, size-pos);
if( c < 0 ) {
throw new EOFException();
}
this.pos += c;
pos += c;
}
return new Buffer(rc);
}
/**
* Reads and discards {@code size} bytes.
*
* @throws InvalidProtocolBufferException
* The end of the stream or the current limit was reached.
*/
public void skipRawBytes(int size) throws IOException {
int pos = 0;
while (pos < size) {
int n = (int) in.skip(size - pos);
pos += n;
}
}
public int pushLimit(int limit) {
int rc = this.limit;
this.limit = pos+limit;
return rc;
}
public void popLimit(int limit) {
this.limit = limit;
}
}