org.jgroups.util.MessageBatch Maven / Gradle / Ivy
package org.jgroups.util;
import org.jgroups.Address;
import org.jgroups.Message;
import java.util.*;
import java.util.function.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Represents a message batch; multiple messages from the same sender to the same receiver(s). This class is unsynchronized.
* @author Bela Ban
* @since 3.3
*/
public class MessageBatch implements Iterable {
/** The destination address. Null if this is a multicast message batch, non-null if the batch is sent to a specific member */
protected Address dest;
/** The sender of the message batch */
protected Address sender;
/** The name of the cluster in which the message batch is sent, this is equivalent to TpHeader.cluster_name */
protected AsciiString cluster_name;
/** The storage of the messages; removed messages have a null element */
protected Message[] messages;
/** Index of the next message to be inserted */
protected int index;
/** Whether all messages have dest == null (multicast) or not */
protected boolean multicast;
/** Whether this message batch contains only OOB messages, or only regular messages */
protected Mode mode=Mode.REG;
protected static final int INCR=5; // number of elements to add when resizing
protected static final ToIntBiFunction length_visitor=(msg, batch) -> msg != null? msg.getLength() : 0;
protected static final ToLongBiFunction total_size_visitor=(msg, batch) -> msg != null? msg.size() : 0;
public MessageBatch(int capacity) {
this.messages=new Message[capacity];
}
public MessageBatch(Collection msgs) {
messages=new Message[msgs.size()];
for(Message msg: msgs)
messages[index++]=msg;
mode=determineMode();
}
public MessageBatch(Address dest, Address sender, AsciiString cluster_name, boolean multicast, Collection msgs) {
this(dest, sender, cluster_name, multicast, msgs, null);
}
public MessageBatch(Address dest, Address sender, AsciiString cluster_name, boolean multicast,
Collection msgs, Predicate filter) {
messages=new Message[msgs.size()];
for(Message msg: msgs) {
if(filter != null && !filter.test(msg))
continue;
messages[index++]=msg;
}
this.dest=dest;
this.sender=sender;
this.cluster_name=cluster_name;
this.multicast=multicast;
this.mode=determineMode();
}
public MessageBatch(Address dest, Address sender, AsciiString cluster_name, boolean multicast, Mode mode, int capacity) {
this(capacity);
this.dest=dest;
this.sender=sender;
this.cluster_name=cluster_name;
this.multicast=multicast;
this.mode=mode;
}
public Address getDest() {return dest;}
public Address dest() {return dest;}
public MessageBatch setDest(Address dest) {this.dest=dest; return this;}
public MessageBatch dest(Address dest) {this.dest=dest; return this;}
public Address getSender() {return sender;}
public Address sender() {return sender;}
public MessageBatch setSender(Address sender) {this.sender=sender; return this;}
public MessageBatch sender(Address sender) {this.sender=sender; return this;}
public AsciiString getClusterName() {return cluster_name;}
public AsciiString clusterName() {return cluster_name;}
public MessageBatch setClusterName(AsciiString name) {this.cluster_name=name; return this;}
public MessageBatch clusterName(AsciiString name) {this.cluster_name=name; return this;}
public boolean isMulticast() {return multicast;}
public boolean multicast() {return multicast;}
public MessageBatch multicast(boolean flag) {multicast=flag; return this;}
public Mode getMode() {return mode;}
public Mode mode() {return mode;}
public MessageBatch setMode(Mode mode) {this.mode=mode; return this;}
public MessageBatch mode(Mode mode) {this.mode=mode; return this;}
public int getCapacity() {return messages.length;}
public int capacity() {return messages.length;}
public int index() {return index;}
/** Returns the underlying message array. This is only intended for testing ! */
public Message[] array() {
return messages;
}
public Message first() {
for(int i=0; i < index; i++)
if(messages[i] != null)
return messages[i];
return null;
}
public Message last() {
for(int i=index -1; i >= 0; i--)
if(messages[i] != null)
return messages[i];
return null;
}
public MessageBatch add(final Message msg) {
add(msg, true);
return this;
}
/** Adds a message to the table
* @param msg the message
* @param resize whether or not to resize the table. If true, the method will always return 1
* @return always 1 if resize==true, else 1 if the message was added or 0 if not
*/
public int add(final Message msg, boolean resize) {
if(msg == null) return 0;
if(index >= messages.length) {
if(!resize)
return 0;
resize();
}
messages[index++]=msg;
return 1;
}
public MessageBatch add(final MessageBatch batch) {
add(batch, true);
return this;
}
/**
* Adds another batch to this one
* @param batch the batch to add to this batch
* @param resize when true, this batch will be resized to accommodate the other batch
* @return the number of messages from the other batch that were added successfully. Will always be batch.size()
* unless resize==0: in this case, the number of messages that were added successfully is returned
*/
public int add(final MessageBatch batch, boolean resize) {
if(batch == null) return 0;
if(this == batch)
throw new IllegalArgumentException("cannot add batch to itself");
int batch_size=batch.size();
if(index+batch_size >= messages.length && resize)
resize(messages.length + batch_size + 1);
int cnt=0;
for(Message msg: batch) {
if(index >= messages.length)
return cnt;
messages[index++]=msg;
cnt++;
}
return cnt;
}
/**
* Replaces a message in the batch with another one
* @param existing_msg The message to be replaced. The message has to be non-null and is found by identity (==)
* comparison
* @param new_msg The message to replace the existing message with, can be null
* @return
*/
public MessageBatch replace(Message existing_msg, Message new_msg) {
if(existing_msg == null)
return this;
for(int i=0; i < index; i++) {
if(messages[i] != null && messages[i] == existing_msg) {
messages[i]=new_msg;
break;
}
}
return this;
}
/**
* Replaces all messages which match a given filter with a replacement message
* @param filter the filter. If null, no changes take place. Note that filter needs to be able to handle null msgs
* @param replacement the replacement message. Can be null, which essentially removes all messages matching filter
* @param match_all whether to replace the first or all matches
* @return the MessageBatch
*/
public MessageBatch replace(Predicate filter, Message replacement, boolean match_all) {
replaceIf(filter, replacement, match_all);
return this;
}
/**
* Replaces all messages that match a given filter with a replacement message
* @param filter the filter. If null, no changes take place. Note that filter needs to be able to handle null msgs
* @param replacement the replacement message. Can be null, which essentially removes all messages matching filter
* @param match_all whether to replace the first or all matches
* @return the number of matched messages
*/
public int replaceIf(Predicate filter, Message replacement, boolean match_all) {
if(filter == null)
return 0;
int matched=0;
for(int i=0; i < index; i++) {
if(filter.test(messages[i])) {
messages[i]=replacement;
matched++;
if(!match_all)
break;
}
}
return matched;
}
/**
* Transfers messages from other to this batch. Optionally clears the other batch after the transfer
* @param other the other batch
* @param clear If true, the transferred messages are removed from the other batch
* @return the number of transferred messages (may be 0 if the other batch was empty)
*/
public int transferFrom(MessageBatch other, boolean clear) {
if(other == null || this == other)
return 0;
int capacity=messages.length, other_size=other.size();
if(other_size == 0)
return 0;
if(capacity < other_size)
messages=new Message[other_size];
System.arraycopy(other.messages, 0, this.messages, 0, other_size);
if(this.index > other_size)
for(int i=other_size; i < this.index; i++)
messages[i]=null;
this.index=other_size;
if(clear)
other.clear();
return other_size;
}
/**
* Removes the current message (found by indentity (==)) by nulling it in the message array
* @param msg
* @return
*/
public MessageBatch remove(Message msg) {
return replace(msg, null);
}
/**
* Removes all messages which match filter
* @param filter the filter. If null, no removal takes place
* @return the MessageBatch
*/
public MessageBatch remove(Predicate filter) {
return replace(filter, null, true);
}
public MessageBatch clear() {
for(int i=0; i < index; i++)
messages[i]=null;
index=0;
return this;
}
public MessageBatch reset() {
index=0;
return this;
}
/** Removes and returns all messages which have a header with ID == id */
public Collection getMatchingMessages(final short id, boolean remove) {
return map((msg, batch) -> {
if(msg != null && msg.getHeader(id) != null) {
if(remove)
batch.remove(msg);
return msg;
}
return null;
});
}
/** Applies a function to all messages and returns a list of the function results */
public Collection map(BiFunction visitor) {
Collection retval=null;
for(int i=0; i < index; i++) {
try {
T result=visitor.apply(messages[i], this);
if(result != null) {
if(retval == null)
retval=new ArrayList<>();
retval.add(result);
}
}
catch(Throwable t) {
}
}
return retval;
}
public void forEach(BiConsumer consumer) {
for(int i=0; i < index; i++) {
try {
consumer.accept(messages[i], this);
}
catch(Throwable t) {
}
}
}
/** Returns the number of non-null messages */
public int size() {
int retval=0;
for(int i=0; i < index; i++)
if(messages[i] != null)
retval++;
return retval;
}
public boolean isEmpty() {
for(int i=0; i < index; i++)
if(messages[i] != null)
return false;
return true;
}
public Mode determineMode() {
int num_oob=0, num_reg=0, num_internal=0;
for(int i=0; i < index; i++) {
if(messages[i] == null)
continue;
if(messages[i].isFlagSet(Message.Flag.OOB))
num_oob++;
else if(messages[i].isFlagSet(Message.Flag.INTERNAL))
num_internal++;
else
num_reg++;
}
if(num_internal > 0 && num_oob == 0 && num_reg == 0)
return Mode.INTERNAL;
if(num_oob > 0 && num_internal == 0 && num_reg == 0)
return Mode.OOB;
if(num_reg > 0 && num_oob == 0 && num_internal == 0)
return Mode.REG;
return Mode.MIXED;
}
/** Returns the size of the message batch (by calling {@link org.jgroups.Message#size()} on all messages) */
public long totalSize() {
long retval=0;
for(int i=0; i < index; i++)
retval+=total_size_visitor.applyAsLong(messages[i], this);
return retval;
}
/** Returns the total number of bytes of the message batch (by calling {@link org.jgroups.Message#getLength()} on all messages) */
public int length() {
int retval=0;
for(int i=0; i < index; i++)
retval+=length_visitor.applyAsInt(messages[i], this);
return retval;
}
/** Iterator which iterates only over non-null messages, skipping null messages */
public Iterator iterator() {
return new BatchIterator(index);
}
public Stream stream() {
Spliterator sp=Spliterators.spliterator(iterator(), size(), 0);
return StreamSupport.stream(sp, false);
}
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("dest=" + dest);
if(sender != null)
sb.append(", sender=").append(sender);
sb.append(", mode=" + mode);
if(cluster_name != null)
sb.append(", cluster=").append(cluster_name);
if(sb.length() > 0)
sb.append(", ");
sb.append(size() + " messages [capacity=" + messages.length + "]");
return sb.toString();
}
public String printHeaders() {
StringBuilder sb=new StringBuilder().append("dest=" + dest);
if(sender != null)
sb.append(", sender=").append(sender);
sb.append("\n").append(size()).append(":\n");
int count=1;
for(Message msg: this)
sb.append("#").append(count++).append(": ").append(msg.printHeaders()).append("\n");
return sb.toString();
}
protected void resize() {
resize(messages.length + INCR);
}
protected void resize(int new_capacity) {
if(new_capacity <= messages.length)
return;
Message[] tmp=new Message[new_capacity];
System.arraycopy(messages,0,tmp,0,messages.length);
messages=tmp;
}
public enum Mode {OOB, REG, INTERNAL, MIXED}
/** Iterates over non-null elements of a batch, skipping null elements */
protected class BatchIterator implements Iterator {
protected int current_index=-1;
protected final int saved_index; // index at creation time of the iterator
public BatchIterator(int saved_index) {
this.saved_index=saved_index;
}
public boolean hasNext() {
// skip null elements
while(current_index +1 < saved_index && messages[current_index+1] == null)
current_index++;
return current_index +1 < saved_index;
}
public Message next() {
if(current_index +1 >= messages.length)
throw new NoSuchElementException();
return messages[++current_index];
}
public void remove() {
if(current_index >= 0)
messages[current_index]=null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy