All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgroups.nio.Buffers Maven / Gradle / Ivy

There is a newer version: 5.3.13.Final
Show newest version
package org.jgroups.nio;

import org.jgroups.util.Util;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Class to do scattering reads or gathering writes on a sequence of {@link ByteBuffer} instances. The buffers are
 * kept in an array with fixed capacity (max Short.MAX_VALUE).
 * Buffers can be added and removed dynamically, but they're dropped when the capacity is exceeded. This
 * means that, if a we have configured a capacity of 5 buffers, and want to add 6, none of the 6 buffers will
 * be added!
 * 
* A read is successful when all non-null buffers from left to right are filled, i.e. all {@link ByteBuffer#remaining()} * methods return 0. *
* Same for writes: when all non-null buffers (from left to right) have been written ({@link ByteBuffer#remaining()} == 0), * a write is considered successful; otherwise it is partial. *
* Individual buffers can be accessed; e.g. for reading its value after a read. It is also possible to add buffers * dynamically, e.g. after reading a 'length' buffer, a user may want to add a new buffer allocated for reading * 'length' bytes. *
* This class is not synchronized. * @author Bela Ban * @since 3.6.5 */ public class Buffers implements Iterable { protected final ByteBuffer[] bufs; // the buffers to be written or read protected short position; // points to the next buffer in bufs to be read or written protected short limit; // points beyond the last buffer in bufs that was read or written protected short next_to_copy; // index of the next buffer to copy, set by copy(): position <= last_copied <= limit protected int max_length; // max number of bytes to read (JGRP-2523) /** * Creates a new instance with an array of capacity buffers * @param capacity Must be an unsigned positive short [1 .. Short.MAX_VALUE] */ public Buffers(int capacity) { bufs=new ByteBuffer[toPositiveUnsignedShort(capacity)]; } public Buffers(ByteBuffer ... data) { if(data == null) throw new IllegalArgumentException("null buffer array"); assertPositiveUnsignedShort(data.length); this.bufs=data; for(ByteBuffer b: this.bufs) { if(b == null) break; limit++; } } public int position() {return position;} public Buffers position(int new_pos) {this.position=toPositiveUnsignedShort(new_pos); nextToCopy(new_pos); return this;} public int limit() {return limit;} public Buffers limit(int new_limit) {this.limit=toPositiveUnsignedShort(new_limit); return this;} public int nextToCopy() {return next_to_copy;} public Buffers nextToCopy(int next) {next_to_copy=toPositiveUnsignedShort(next); return this;} public int maxLength() {return max_length;} public Buffers maxLength(int len) {max_length=len; return this;} public int remaining() { int remaining=0; for(int i=position; i < limit; i++) { ByteBuffer buf=bufs[i]; if(buf != null) remaining+=buf.remaining(); } return remaining; } public boolean hasRemaining() { for(int i=position; i < limit; i++) { ByteBuffer buf=bufs[i]; if(buf != null && buf.hasRemaining()) return true; } return false; } /** * Adds a number of buffers. Note that if the buffers cannot be added as a whole, * e.g. because of exceeding the capacity, none of them will be added! */ public Buffers add(ByteBuffer ... buffers) { if(buffers == null) return this; assertPositiveUnsignedShort(buffers.length); int len=buffers.length; if(spaceAvailable(len) || (makeSpace() && spaceAvailable(len))) { for(ByteBuffer buf: buffers) bufs[limit++]=buf; } return this; } /** Adds a buffer. If there's no capacity, the buffer will not be added and will be silently dropped */ public Buffers add(ByteBuffer buf) { if(buf == null) return this; if(spaceAvailable(1) || (makeSpace() && spaceAvailable(1))) bufs[limit++]=buf; return this; } public ByteBuffer get(int index) { return this.bufs[index]; } public Buffers set(int index, ByteBuffer buf) { this.bufs[index]=buf; return this; } /** Nulls the buffer at index */ public Buffers remove(int index) { return set(index, null); } /** * Reads length and then length bytes into the data buffer, which is grown if needed. * @param ch The channel to read data from * @return The data buffer (position is 0 and limit is length), or null if not all data could be read. */ public ByteBuffer readLengthAndData(SocketChannel ch) throws Exception { if(bufs[0].hasRemaining() && ch.read(bufs[0]) < 0) throw new EOFException(); if(bufs[0].hasRemaining()) return null; int len=bufs[0].getInt(0); // https://issues.redhat.com/browse/JGRP-2523: check if max_length has been exceeded if(max_length > 0 && len > max_length) throw new IllegalStateException(String.format("the length of a message (%s) from %s is bigger than the " + "max accepted length (%s): discarding the message", Util.printBytes(len), ch.getRemoteAddress(), Util.printBytes(max_length))); if(bufs[1] == null || len > bufs[1].capacity()) bufs[1]=ByteBuffer.allocate(len); // Workaround for JDK8 compatibility // limit() returns java.nio.Buffer in JDK8, but java.nio.ByteBuffer since JDK9. ((java.nio.Buffer) bufs[1]).limit(len); if(bufs[1].hasRemaining() && ch.read(bufs[1]) < 0) throw new EOFException(); if(bufs[1].hasRemaining()) return null; try { // Workaround for JDK8 compatibility // flip() returns java.nio.Buffer in JDK8, but java.nio.ByteBuffer since JDK9. return (ByteBuffer) ((java.nio.Buffer) bufs[1].duplicate()).flip(); } finally { bufs[0].clear(); bufs[1].clear(); } } /** * Performs a scattering read into all (contiguous) non-null buffers in range [position .. limit]. Returns true if * the scattering read was successful, else false. Note that to read the contents of the individual buffers, * {@link ByteBuffer#clear()} has to be called (all buffers have their position == limit on a successful read). */ public boolean read(SocketChannel ch) throws Exception { long bytes=ch.read(bufs, position, limit-position); if(bytes == -1) throw new EOFException(); return adjustPosition(false); } /** Helper method which adds the buffers passed as arguments and then calls write() */ public boolean write(GatheringByteChannel ch, ByteBuffer ... buffers) throws Exception { return add(buffers).write(ch); } /** * Writes the buffers from position to limit to the given channel. Note that all buffers need to have * their {@link ByteBuffer#position()} at the start of the data to be written * @param ch The channel to write to * @return True if all the bytes of the buffer were written successfully, false otherwise (partial write). * @throws Exception Thrown if the write failed */ public boolean write(GatheringByteChannel ch) throws Exception { int num_buffers_to_write=size(); if(num_buffers_to_write == 0) return true; if(ch != null) { try { ch.write(bufs, position, num_buffers_to_write); } catch(ClosedChannelException closed_ex) { throw closed_ex; } catch(NotYetConnectedException | IOException others) { ; // ignore, we'll queue writes up to Buffers.capacity } } return nullData(); } /** Copies the data that has not yet been written and moves last_copied. Typically done after an unsuccessful * write, if copying is required. This is typically needed if the output buffer is reused. Note that * direct buffers will be converted to heap-based buffers */ public Buffers copy() { for(int i=Math.max(position, next_to_copy); i < limit; i++) { this.bufs[i]=copyBuffer(this.bufs[i]); next_to_copy=(short)(i+1); } return this; } /** Returns the number of elements that have not yet been read or written */ public int size() { return limit - position; } public String toString() { return String.format("[%d bufs pos=%d lim=%d cap=%d rem=%d]", size(), position, limit, bufs.length, remaining()); } protected boolean spaceAvailable(int num_buffers) { return bufs.length - limit >= num_buffers; } protected boolean makeSpace() { if(position == limit) { // easy case: no pending writes, but pos and limit are not at the head of the buffer position=limit=next_to_copy=0; // redundant if position == 0, but who cares return true; } // limit > position: copy to head of buffer if position > 0 if(position == 0) // cannot copy to head as position is already at head return false; // move data to the head of the buffer int buffers_to_move=size(); for(int dest_index=0, src_index=position; dest_index < buffers_to_move; dest_index++, src_index++) { bufs[dest_index]=bufs[src_index]; } // null buffers for(int i=buffers_to_move; i < limit; i++) bufs[i]=null; next_to_copy-=position; limit=(short)buffers_to_move; // same as limit-=position position=0; next_to_copy=(short)Math.max(next_to_copy, position); return true; } /** Looks at all buffers in range [position .. limit-1] and nulls buffers that have no remaining data. * Returns true if all buffers could be nulled, and false otherwise */ protected boolean nullData() { if(!adjustPosition(true)) return false; if(position >= bufs.length) makeSpace(); return true; } protected boolean adjustPosition(boolean null_complete_data) { while(position < limit) { ByteBuffer buf=bufs[position]; if(buf.remaining() > 0) return false; if(null_complete_data) bufs[position]=null; position++; if(next_to_copy < position) next_to_copy=position; } return true; } protected static short toPositiveUnsignedShort(int num) { return (short)assertPositiveUnsignedShort(num); } protected static int assertPositiveUnsignedShort(int num) { if(num < 1 || num > Short.MAX_VALUE) { short tmp=(short)num; throw new IllegalArgumentException(String.format("number %d must be a positive unsigned short", tmp)); } return num; } /** Copies a ByteBuffer by copying and wrapping the underlying array of a heap-based buffer. Direct buffers are converted to heap-based buffers */ public static ByteBuffer copyBuffer(final ByteBuffer buf) { if(buf == null) return null; int offset=buf.hasArray()? buf.arrayOffset() + buf.position() : buf.position(), len=buf.remaining(); byte[] tmp=new byte[len]; if(!buf.isDirect()) System.arraycopy(buf.array(), offset, tmp, 0, len); else { for(int i=0; i < len; i++) tmp[i]=buf.get(i+offset); } return ByteBuffer.wrap(tmp); } @Override public Iterator iterator() { return new BuffersIterator(); } /** Iterates over the non-null buffers */ protected class BuffersIterator implements Iterator { protected int index=-1; @Override public boolean hasNext() { for(int i=index+1; i < bufs.length; i++) if(bufs[i] != null) return true; return false; } // @SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException") @Override public ByteBuffer next() { while(true) { if(++index >= bufs.length) throw new NoSuchElementException(String.format("index %d is out of range (%d buffers)", index, bufs.length)); if(bufs[index] != null) return bufs[index]; } } @Override public void remove() { // todo: remove when baselining on Java 8 ; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy