org.jgroups.View Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
package org.jgroups;
import org.jgroups.annotations.Immutable;
import org.jgroups.util.ArrayIterator;
import org.jgroups.util.SizeStreamable;
import org.jgroups.util.Util;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* A view is a local representation of the current membership of a group. Only one view is installed
* in a channel at a time. Views contain the address of its creator, an ID and a list of member
* addresses. These addresses are ordered, and the first address is always the coordinator of the
* view. This way, each member of the group knows who the new coordinator will be if the current one
* crashes or leaves the group. The views are sent between members using the VIEW_CHANGE event
*
* @since 2.0
* @author Bela Ban
*/
@Immutable
public class View implements Comparable, SizeStreamable, Iterable, Constructable {
/**
* A view is uniquely identified by its ViewID. The view id contains the creator address and a
* Lamport time. The Lamport time is the highest timestamp seen or sent from a view. if a view
* change comes in with a lower Lamport time, the event is discarded.
*/
protected ViewId view_id;
/**
* An array containing all the members of the view. This array is always ordered, with the
* coordinator being the first member. The second member will be the new coordinator if the
* current one disappears or leaves the group.
*/
protected Address[] members;
protected static final boolean suppress_view_size=Boolean.getBoolean(Global.SUPPRESS_VIEW_SIZE);
/**
* Creates an empty view, should not be used, only used by (de-)serialization
*/
public View() {
}
/**
* Creates a new view
*
* @param view_id The view id of this view (can not be null)
* @param members Contains a list of all the members in the view, can be empty but not null.
*/
public View(ViewId view_id, Collection members) {
this.view_id=view_id;
if(members == null)
throw new IllegalArgumentException("members cannot be null");
this.members=new Address[members.size()];
int index=0;
for(Address member: members)
this.members[index++]=member;
}
/**
* Creates a new view.
* @param view_id The new view-id
* @param members The members. Note that the parameter is not copied.
*/
public View(ViewId view_id, Address[] members) {
this.view_id=view_id;
this.members=members;
if(members == null)
throw new IllegalArgumentException("members cannot be null");
}
/**
* Creates a new view
*
* @param creator The creator of this view (can not be null)
* @param id The lamport timestamp of this view
* @param members Contains a list of all the members in the view, can be empty but not null.
*/
public View(Address creator, long id, List members) {
this(new ViewId(creator, id), members);
}
public static View create(Address coord, long id, Address ... members) {
return new View(new ViewId(coord, id), members);
}
public static View create(Address coord, long id, Collection members) {
return new View(new ViewId(coord, id), members);
}
public Supplier extends View> create() {
return View::new;
}
/**
* Returns the view ID of this view
* if this view was created with the empty constructur, null will be returned
* @return the view ID of this view
*/
public ViewId getViewId() {return view_id;}
/**
* Returns the creator of this view
* if this view was created with the empty constructur, null will be returned
* @return the creator of this view in form of an Address object
*/
public Address getCreator() {return view_id.getCreator();}
public Address getCoord() {return members.length > 0? members[0] : null;}
/**
* Returns the member list
* @return an immutable list of the members
*/
public List getMembers() {
// do *NOT* replace with List.of(): we need to be able to do getMembers().contains(m) where m == null!
return Collections.unmodifiableList(Arrays.asList(members));
}
/** Returns the underlying array. The caller must not modify the contents. Should not be used by
* application code ! This method may be removed at any time, so don't use it !
*/
public Address[] getMembersRaw() {
return members;
}
/**
* Returns true if this view contains a certain member
* @param mbr - the address of the member,
* @return true if this view contains the member, false if it doesn't
*/
public boolean containsMember(Address mbr) {
if(mbr == null || members == null)
return false;
for(Address member: members)
if(Objects.equals(member, mbr))
return true;
return false;
}
/** Returns true if all mbrs are elements of this view, false otherwise */
public boolean containsMembers(Address ... mbrs) {
if(mbrs == null || members == null)
return false;
for(Address mbr: mbrs) {
if(!containsMember(mbr))
return false;
}
return true;
}
public int compareTo(View o) {
return view_id.compareTo(o.view_id);
}
public boolean equals(Object obj) {
return obj instanceof View && (this == obj || compareTo((View)obj) == 0);
}
public boolean deepEquals(View other) {
return this == other || equals(other) && Arrays.equals(members, other.members);
}
public int hashCode() {
return view_id.hashCode();
}
/**
* Returns the number of members in this view
* @return the number of members in this view 0..n
*/
public int size() {
return members.length;
}
public String toString() {
StringBuilder sb=new StringBuilder(64);
sb.append(view_id);
if(members != null) {
if(!suppress_view_size)
sb.append(" (").append(members.length).append(")");
sb.append(" [").append(Util.printListWithDelimiter(members,", ",Util.MAX_LIST_PRINT_SIZE)).append("]");
}
return sb.toString();
}
@Override
public void writeTo(DataOutput out) throws IOException {
view_id.writeTo(out);
Util.writeAddresses(members,out);
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
view_id=new ViewId();
view_id.readFrom(in);
members=Util.readAddresses(in);
}
@Override
public int serializedSize() {
return (int)(view_id.serializedSize() + Util.size(members));
}
/**
* Returns a list of members which left from view one to two
* @param one
* @param two
*/
public static List leftMembers(View one, View two) {
if(one == null || two == null)
return null;
List retval=new ArrayList<>(one.getMembers());
retval.removeAll(two.getMembers());
return retval;
}
public static List newMembers(View old, View new_view) {
if(new_view == null)
return null;
List retval=new ArrayList<>(new_view.getMembers());
if(old != null)
retval.removeAll(old.getMembers());
return retval;
}
/**
* Returns the difference between 2 views from and to. It is assumed that view 'from' is logically prior to view 'to'.
* @param from The first view
* @param to The second view
* @return an array of 2 Address arrays: index 0 has the addresses of the joined member, index 1 those of the left members
*/
public static Address[][] diff(final View from, final View to) {
if(to == null)
throw new IllegalArgumentException("the second view cannot be null");
if(from == to)
return new Address[][]{{},{}};
if(from == null) {
Address[] joined=new Address[to.size()];
int index=0;
for(Address addr: to.getMembers())
joined[index++]=addr;
return new Address[][]{joined,{}};
}
Address[] joined=null, left=null;
int num_joiners=0, num_left=0;
// determine joiners
for(Address addr: to)
if(!from.containsMember(addr))
num_joiners++;
if(num_joiners > 0) {
joined=new Address[num_joiners];
int index=0;
for(Address addr: to)
if(!from.containsMember(addr))
joined[index++]=addr;
}
// determine leavers
for(Address addr: from)
if(!to.containsMember(addr))
num_left++;
if(num_left > 0) {
left=new Address[num_left];
int index=0;
for(Address addr: from)
if(!to.containsMember(addr))
left[index++]=addr;
}
return new Address[][]{joined != null? joined : new Address[]{}, left != null? left : new Address[]{}};
}
public static String printDiff(Address[][] diff) {
if(diff == null)
return "";
Address[] joined=diff[0], left=diff[1];
if(joined.length == 0 && left.length == 0)
return "";
if(joined.length > 0 && left.length > 0)
return String.format("(%s joined, %s left)", Util.print(joined), Util.print(left));
if(joined.length > 0)
return String.format("(%s joined)", Util.print(joined));
if(left.length > 0)
return String.format("(%s left)", Util.print(left));
return "";
}
/** Returns true if all views are the same. Uses the view IDs for comparison */
public static boolean sameViews(View ... views) {
ViewId first_view_id=views[0].getViewId();
return Stream.of(views).allMatch(v -> v.getViewId().equals(first_view_id));
}
public static boolean sameViews(Collection views) {
ViewId first_view_id=views.iterator().next().getViewId();
return views.stream().allMatch(v -> v.getViewId().equals(first_view_id));
}
/** Checks if two views have the same members regardless of order. E.g. {A,B,C} and {B,A,C} returns true */
public static boolean sameMembers(View v1, View v2) {
if(v1 == v2)
return true;
if(v1.size() != v2.size())
return false;
Address[][] diff=diff(v1, v2);
return diff[0].length == 0 && diff[1].length == 0;
}
/** Checks if two views have the same members observing order. E.g. {A,B,C} and {B,A,C} returns false,
* {A,C,B} and {A,C,B} returns true */
public static boolean sameMembersOrdered(View v1, View v2) {
return Arrays.equals(v1.getMembersRaw(), v2.getMembersRaw());
}
public Iterator iterator() {
return new ArrayIterator<>(this.members);
}
}