/*
* 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.sshd.common.util.buffer;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.function.IntUnaryOperator;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.Readable;
import org.apache.sshd.common.util.ValidateUtils;
/**
* Provides an implementation of {@link Buffer} using a backing byte array
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class ByteArrayBuffer extends Buffer {
public static final int DEFAULT_SIZE = 256;
public static final int MAX_LEN = 65536;
private byte[] data;
private int rpos;
private int wpos;
public ByteArrayBuffer() {
this(DEFAULT_SIZE);
}
public ByteArrayBuffer(int size) {
this(size, true);
}
public ByteArrayBuffer(int size, boolean roundOff) {
this(new byte[roundOff ? BufferUtils.getNextPowerOf2(size) : size], false);
}
public ByteArrayBuffer(byte[] data) {
this(data, 0, data.length, true);
}
public ByteArrayBuffer(byte[] data, boolean read) {
this(data, 0, data.length, read);
}
public ByteArrayBuffer(byte[] data, int off, int len) {
this(data, off, len, true);
}
public ByteArrayBuffer(byte[] data, int off, int len, boolean read) {
this.data = data;
this.rpos = off;
this.wpos = (read ? len : 0) + off;
}
@Override
public int rpos() {
return rpos;
}
@Override
public void rpos(int rpos) {
this.rpos = rpos;
}
@Override
public int wpos() {
return wpos;
}
@Override
public void wpos(int wpos) {
if (wpos > this.wpos) {
ensureCapacity(wpos - this.wpos);
}
this.wpos = wpos;
}
@Override
public int available() {
return wpos - rpos;
}
@Override
public int capacity() {
return data.length - wpos;
}
@Override
public byte[] array() {
return data;
}
@Override
public void compact() {
int avail = available();
if (avail > 0) {
System.arraycopy(data, rpos, data, 0, avail);
}
wpos -= rpos;
rpos = 0;
}
@Override
public void clear(boolean wipeData) {
rpos = 0;
wpos = 0;
if (wipeData) {
Arrays.fill(data, (byte) 0);
}
}
@Override
public byte getByte() {
ensureAvailable(Byte.BYTES);
return data[rpos++];
}
@Override
public void putByte(byte b) {
ensureCapacity(Byte.BYTES);
data[wpos++] = b;
}
@Override
public int putBuffer(Readable buffer, boolean expand) {
int r = expand ? buffer.available() : Math.min(buffer.available(), capacity());
ensureCapacity(r);
buffer.getRawBytes(data, wpos, r);
wpos += r;
return r;
}
@Override
public void putBuffer(ByteBuffer buffer) {
int required = buffer.remaining();
ensureCapacity(required + Integer.SIZE);
putInt(required);
buffer.get(data, wpos, required);
wpos += required;
}
@Override
public void putRawBytes(byte[] d, int off, int len) {
ValidateUtils.checkTrue(len >= 0, "Negative raw bytes length: %d", len);
ensureCapacity(len);
System.arraycopy(d, off, data, wpos, len);
wpos += len;
}
@Override
public String getString(Charset charset) {
int len = getInt();
if (len < 0) {
throw new BufferException("Bad item length: " + len);
}
ensureAvailable(len);
String s = new String(data, rpos, len, charset);
rpos += len;
return s;
}
@Override
public void getRawBytes(byte[] buf, int off, int len) {
ensureAvailable(len);
copyRawBytes(0, buf, off, len);
rpos += len;
}
@Override
protected void copyRawBytes(int offset, byte[] buf, int pos, int len) {
System.arraycopy(data, rpos + offset, buf, pos, len);
}
@Override
public void ensureCapacity(int capacity, IntUnaryOperator growthFactor) {
ValidateUtils.checkTrue(capacity >= 0, "Negative capacity requested: %d", capacity);
int maxSize = size();
int curPos = wpos();
int remaining = maxSize - curPos;
if (remaining < capacity) {
int minimum = curPos + capacity;
int actual = growthFactor.applyAsInt(minimum);
if (actual < minimum) {
throw new IllegalStateException("ensureCapacity(" + capacity + ") actual (" + actual + ") below min. (" + minimum + ")");
}
byte[] tmp = new byte[actual];
System.arraycopy(data, 0, tmp, 0, data.length);
data = tmp;
}
}
@Override
protected int size() {
return data.length;
}
/**
* Creates a compact buffer (i.e., one that starts at offset zero) containing a <U>copy</U>
* of the original data
*
* @param data The original data buffer
* @return A {@link ByteArrayBuffer} containing a <U>copy</U> of the original data
* starting at zero read position
* @see #getCompactClone(byte[], int, int)
*/
public static ByteArrayBuffer getCompactClone(byte[] data) {
return getCompactClone(data, 0, NumberUtils.length(data));
}
/**
* Creates a compact buffer (i.e., one that starts at offset zero) containing a <U>copy</U>
* of the original data
*
* @param data The original data buffer
* @param offset The offset of the valid data in the buffer
* @param len The size (in bytes) of of the valid data in the buffer
* @return A {@link ByteArrayBuffer} containing a <U>copy</U> of the original data
* starting at zero read position
*/
public static ByteArrayBuffer getCompactClone(byte[] data, int offset, int len) {
byte[] clone = (len > 0) ? new byte[len] : GenericUtils.EMPTY_BYTE_ARRAY;
if (len > 0) {
System.arraycopy(data, offset, clone, 0, len);
}
return new ByteArrayBuffer(clone, true);
}
}