-
Notifications
You must be signed in to change notification settings - Fork 470
/
View.java
320 lines (269 loc) · 10.3 KB
/
View.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
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<View>, SizeStreamable, Iterable<Address>, Constructable<View> {
/**
* 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<Address> 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 <em>not</em> 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<Address> 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<Address> 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<Address> getMembers() {
return Collections.unmodifiableList(Arrays.asList(members));
}
/** Returns the underlying array. The caller <em>must not</em> 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<Address> leftMembers(View one, View two) {
if(one == null || two == null)
return null;
List<Address> retval=new ArrayList<>(one.getMembers());
retval.removeAll(two.getMembers());
return retval;
}
public static List<Address> newMembers(View old, View new_view) {
if(old == null || new_view == null)
return null;
List<Address> retval=new ArrayList<>(new_view.getMembers());
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[]{}};
}
/** 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<View> 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<Address> iterator() {
return new ArrayIterator(this.members);
}
}