/*
 * Copyright 2008-present MongoDB, 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 org.bson.io;

import org.bson.BsonSerializationException;
import org.bson.ByteBuf;
import org.bson.types.ObjectId;

import java.nio.ByteOrder;
import java.nio.charset.Charset;

import static java.lang.String.format;

An implementation of BsonInput that is backed by a ByteBuf.
Since:3.0
/** * An implementation of {@code BsonInput} that is backed by a {@code ByteBuf}. * * @since 3.0 */
public class ByteBufferBsonInput implements BsonInput { private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); private static final String[] ONE_BYTE_ASCII_STRINGS = new String[Byte.MAX_VALUE + 1]; static { for (int b = 0; b < ONE_BYTE_ASCII_STRINGS.length; b++) { ONE_BYTE_ASCII_STRINGS[b] = String.valueOf((char) b); } } private ByteBuf buffer; private int mark = -1;
Construct an instance with the given byte buffer. The stream takes over ownership of the buffer and closes it when this instance is closed.
Params:
  • buffer – the byte buffer
/** * Construct an instance with the given byte buffer. The stream takes over ownership of the buffer and closes it when this instance is * closed. * * @param buffer the byte buffer */
public ByteBufferBsonInput(final ByteBuf buffer) { if (buffer == null) { throw new IllegalArgumentException("buffer can not be null"); } this.buffer = buffer; buffer.order(ByteOrder.LITTLE_ENDIAN); } @Override public int getPosition() { ensureOpen(); return buffer.position(); } @Override public byte readByte() { ensureOpen(); ensureAvailable(1); return buffer.get(); } @Override public void readBytes(final byte[] bytes) { ensureOpen(); ensureAvailable(bytes.length); buffer.get(bytes); } @Override public void readBytes(final byte[] bytes, final int offset, final int length) { ensureOpen(); ensureAvailable(length); buffer.get(bytes, offset, length); } @Override public long readInt64() { ensureOpen(); ensureAvailable(8); return buffer.getLong(); } @Override public double readDouble() { ensureOpen(); ensureAvailable(8); return buffer.getDouble(); } @Override public int readInt32() { ensureOpen(); ensureAvailable(4); return buffer.getInt(); } @Override public ObjectId readObjectId() { ensureOpen(); byte[] bytes = new byte[12]; readBytes(bytes); return new ObjectId(bytes); } @Override public String readString() { ensureOpen(); int size = readInt32(); if (size <= 0) { throw new BsonSerializationException(format("While decoding a BSON string found a size that is not a positive number: %d", size)); } return readString(size); } @Override public String readCString() { ensureOpen(); // TODO: potentially optimize this int mark = buffer.position(); readUntilNullByte(); int size = buffer.position() - mark; buffer.position(mark); return readString(size); } private String readString(final int size) { if (size == 2) { byte asciiByte = readByte(); // if only one byte in the string, it must be ascii. byte nullByte = readByte(); // read null terminator if (nullByte != 0) { throw new BsonSerializationException("Found a BSON string that is not null-terminated"); } if (asciiByte < 0) { return UTF8_CHARSET.newDecoder().replacement(); } return ONE_BYTE_ASCII_STRINGS[asciiByte]; // this will throw if asciiByte is negative } else { byte[] bytes = new byte[size - 1]; readBytes(bytes); byte nullByte = readByte(); if (nullByte != 0) { throw new BsonSerializationException("Found a BSON string that is not null-terminated"); } return new String(bytes, UTF8_CHARSET); } } private void readUntilNullByte() { //CHECKSTYLE:OFF while (readByte() != 0) { //NOPMD //do nothing - checkstyle & PMD hate this, not surprisingly } //CHECKSTYLE:ON } @Override public void skipCString() { ensureOpen(); readUntilNullByte(); } @Override public void skip(final int numBytes) { ensureOpen(); buffer.position(buffer.position() + numBytes); } @Deprecated @Override public void mark(final int readLimit) { ensureOpen(); mark = buffer.position(); } @Override public BsonInputMark getMark(final int readLimit) { return new BsonInputMark() { private int mark = buffer.position(); @Override public void reset() { ensureOpen(); buffer.position(mark); } }; } @Deprecated @Override public void reset() { ensureOpen(); if (mark == -1) { throw new IllegalStateException("Mark not set"); } buffer.position(mark); } @Override public boolean hasRemaining() { ensureOpen(); return buffer.hasRemaining(); } @Override public void close() { buffer.release(); buffer = null; } private void ensureOpen() { if (buffer == null) { throw new IllegalStateException("Stream is closed"); } } private void ensureAvailable(final int bytesNeeded) { if (buffer.remaining() < bytesNeeded) { throw new BsonSerializationException(format("While decoding a BSON document %d bytes were required, " + "but only %d remain", bytesNeeded, buffer.remaining())); } } }