source: protocol/src/main/java/geniusweb/protocol/session/amop/AMOPState.java@ 52

Last change on this file since 52 was 52, checked in by ruud, 14 months ago

Fixed small issues in domaineditor.

File size: 14.6 KB
Line 
1package geniusweb.protocol.session.amop;
2
3import java.util.Arrays;
4import java.util.Collections;
5import java.util.Comparator;
6import java.util.HashMap;
7import java.util.LinkedList;
8import java.util.List;
9import java.util.Map;
10import java.util.Set;
11import java.util.stream.Collectors;
12
13import geniusweb.actions.Action;
14import geniusweb.actions.EndNegotiation;
15import geniusweb.actions.Offer;
16import geniusweb.actions.PartyId;
17import geniusweb.actions.Vote;
18import geniusweb.actions.Votes;
19import geniusweb.inform.Agreements;
20import geniusweb.inform.YourTurn;
21import geniusweb.issuevalue.Bid;
22import geniusweb.progress.Progress;
23import geniusweb.progress.ProgressRounds;
24import geniusweb.protocol.ProtocolException;
25import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
26import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
27import geniusweb.protocol.session.SessionResult;
28import geniusweb.protocol.session.SessionState;
29import geniusweb.protocol.session.saop.SAOPSettings;
30import geniusweb.references.PartyWithProfile;
31import geniusweb.voting.CollectedVotes;
32
33public class AMOPState implements SessionState {
34 /**
35 * Phase determines what actions are allowed now. #isFinal terminates the
36 * state.
37 */
38 public enum Phase {
39 INIT, OFFER, VOTE;
40
41 public Phase next() {
42 return this == OFFER ? VOTE : OFFER;
43 }
44 }
45
46 private final Phase phase;
47 private final AMOPSettings settings;
48 private final Map<PartyId, PartyWithProfile> partyprofiles;
49 private final ProtocolToPartyConnections connections;
50 private final Agreements agreements;
51 private final List<PartyId> walkedAway;
52 private final List<Action> actions;
53 private final Progress progress;
54 private final Map<PartyId, ProtocolException> exceptions;
55
56 /**
57 * Creates the initial state from the given settings and progress=null
58 *
59 * @param settings the {@link SAOPSettings}
60 */
61 public AMOPState(AMOPSettings settings) {
62 this(Phase.INIT, Collections.emptyList(),
63 new ProtocolToPartyConnections(Collections.emptyList()), null,
64 settings, Collections.emptyMap(), new Agreements(),
65 Collections.emptyMap(), Collections.emptyList());
66
67 }
68
69 /**
70 * @param phase The Phase
71 * @param actions the legal actions that have been done in the
72 * negotiation. first action is the oldest. This MUST
73 * NOT contain illegal actions, otherwise we can not
74 * decypher the actions list anymore and find the
75 * proper phase boundaries. Instead parties doing
76 * illegal actions must be killed.
77 * @param conns the existing party connections. we assume ownership
78 * of this so it should not be modified although
79 * connections may of course break.
80 * @param progress the {@link Progress} line. can be null if not yet
81 * known
82 * @param settings the {@link SAOPSettings}
83 * @param partyprofiles map with the {@link PartyWithProfile} for connected
84 * parties. null is equivalent to an empty map.
85 * @param agreements the agreements reached.
86 * @param e Possibly empty map of {@link ProtocolException}, the
87 * keys are the party that failed to follow the
88 * protocol.
89 * @param walkedAway list of parties that walked away from the
90 * negotiation.
91 */
92 protected AMOPState(Phase phase, List<Action> actions,
93 ProtocolToPartyConnections conns, Progress progress,
94 AMOPSettings settings, Map<PartyId, PartyWithProfile> partyprofiles,
95 Agreements agreements, Map<PartyId, ProtocolException> e,
96 List<PartyId> walkedAway) {
97 this.phase = phase;
98 this.actions = actions;
99 this.connections = conns;
100 this.progress = progress;
101 this.settings = settings;
102 this.partyprofiles = partyprofiles;
103 this.agreements = agreements;
104 this.exceptions = e;
105 this.walkedAway = walkedAway;
106 }
107
108 @Override
109 public List<Action> getActions() {
110 return Collections.unmodifiableList(actions);
111 }
112
113 @Override
114 public Progress getProgress() {
115 return progress;
116 }
117
118 public Map<PartyId, PartyWithProfile> getPartyProfiles() {
119 return Collections.unmodifiableMap(partyprofiles);
120 }
121
122 /**
123 * @return all current/remaining active party connections. Finished/crashed
124 * parties should be removed immediately.
125 */
126 public ProtocolToPartyConnections getConnections() {
127 return connections;
128 }
129
130 /**
131 *
132 * @param connection the new {@link ProtocolToPartyConn}
133 * @param partyprofile the {@link PartyWithProfile} that is associated with
134 * this state
135 * @return new SessionState with the new connection added. This call ignores
136 * the progress (does not check isFinal) because we uses this during
137 * the setup where the deadline is not yet relevant.
138 */
139 protected AMOPState with(ProtocolToPartyConn connection,
140 PartyWithProfile partyprofile) {
141 ProtocolToPartyConnections newconns = getConnections().with(connection);
142 Map<PartyId, PartyWithProfile> newprofiles = new HashMap<>(
143 partyprofiles);
144 newprofiles.put(connection.getParty(), partyprofile);
145 return new AMOPState(Phase.INIT, actions, newconns, progress, settings,
146 newprofiles, agreements, exceptions, walkedAway);
147 }
148
149 /**
150 * @param id the {@link PartyId} of the party that failed.
151 * @param e the {@link ProtocolException} that occured in the party
152 * @return a new state with the error set.
153 */
154 public AMOPState with(PartyId id, ProtocolException e) {
155 Map<PartyId, ProtocolException> newExc = exceptions;
156 if (!exceptions.containsKey(id)) {
157 newExc = new HashMap<>(exceptions);
158 newExc.put(id, e);
159 }
160 return new AMOPState(phase, actions, connections, progress, settings,
161 partyprofiles, agreements, newExc, walkedAway);
162 }
163
164 /**
165 * Sets the progress for this session. Can be set only if progress=null.
166 * Should be set in INIT phase.
167 *
168 * @param newprogress the new progress
169 * @return new SAOPState with the progress set
170 */
171 public AMOPState with(Progress newprogress) {
172 if (progress != null || newprogress == null || phase != Phase.INIT) {
173 throw new IllegalArgumentException(
174 "progress must be null, newprogress must be not null and phase must be INIT");
175 }
176 return new AMOPState(phase, actions, connections, newprogress,
177 getSettings(), partyprofiles, agreements, exceptions,
178 walkedAway);
179 }
180
181 @Override
182 public Agreements getAgreements() {
183 return agreements;
184 }
185
186 @Override
187 public boolean isFinal(long currentTimeMs) {
188 boolean pastDeadline = progress != null
189 && progress.isPastDeadline(currentTimeMs);
190 return phase != Phase.INIT
191 && (pastDeadline || getActiveParties().size() < 2);
192 }
193
194 @Override
195 public AMOPSettings getSettings() {
196 return settings;
197 }
198
199 /**
200 * @param actor the actor that did this action. Can be used to check if
201 * action is valid. NOTICE caller has to make sure the current
202 * state is not final. MUST NOT be null.
203 * @param action the action that was proposed by actor. MUST NOT be null.
204 * @return new SessionState with the action added as last action.
205 * @throws ProtocolException if actor is violating the protocol
206 * @throws RuntimeException if we hit a bug.
207 */
208
209 public AMOPState with(PartyId actor, Action action)
210 throws ProtocolException {
211 if (!actor.equals(action.getActor())) {
212 throw new ProtocolException(
213 "act by " + actor + " contains wrong actorid: " + action,
214 actor);
215 }
216 if (!getActiveParties().contains(actor))
217 throw new ProtocolException("Deactivated actor tried to act",
218 actor);
219 if (getPhaseActions().containsKey(actor))
220 throw new ProtocolException(
221 "Attempt to act twice in phase:" + action, actor);
222
223 List<Action> newactions = new LinkedList<>(getActions());
224 newactions.add(action);
225 List<PartyId> newWalkedAway = walkedAway;
226
227 // check protocol is followed for specific actions
228 if (action instanceof Votes) {
229 if (phase != Phase.VOTE)
230 throw new ProtocolException(
231 "Vote can only be placed in VOTE phase", actor);
232 // Notice we don't check the votes. You can actually vote for
233 // other bids. But such action would be useless as others can't vote
234 // on it.
235 } else if (action instanceof Offer) {
236 if (phase != Phase.OFFER) {
237 throw new ProtocolException(
238 "Offer can only be placed in OFFER phase", actor);
239 }
240 } else if (action instanceof EndNegotiation) {
241 newWalkedAway = new LinkedList<>(walkedAway);
242 newWalkedAway.add(action.getActor());
243
244 } else {
245 throw new ProtocolException(
246 "Action " + action + " is not allowed in AMOP", actor);
247 }
248
249 return new AMOPState(phase, newactions, connections, progress, settings,
250 partyprofiles, agreements, exceptions, newWalkedAway);
251 }
252
253 @Override
254 public List<SessionResult> getResults() {
255 return Arrays.asList(new SessionResult(partyprofiles, getAgreements(),
256 Collections.emptyMap(), null));
257 }
258
259 /**
260 * @return true iff all parties acted as required in the current Phase. We
261 * search back until the last {@link YourTurn} if Phase=VOTE. /
262 * {@link Vote} if Phase=OFFER and check that all current
263 * connections have acted.
264 */
265 public boolean isAllPartiesActed() {
266 return getPhaseActions().keySet().containsAll(getActiveParties());
267 }
268
269 /**
270 * @return all actions of the current phase NOTE this assumes at least 1
271 * action is done in each phase, so that we can detect the previous
272 * phase actions in the actions list.
273 *
274 */
275 public Map<PartyId, Action> getPhaseActions() {
276 Map<PartyId, Action> newactions = new HashMap<>();
277 for (int n = actions.size() - 1; n >= 0; n--) {
278 Action act = actions.get(n);
279 if (act instanceof EndNegotiation)
280 continue;
281 if (phase == Phase.VOTE && !(act instanceof Votes))
282 break;
283 if (phase == Phase.OFFER && !(act instanceof Offer))
284 break;
285 newactions.put(act.getActor(), act);
286 }
287 return newactions;
288
289 }
290
291 /**
292 *
293 * @return all currently active parties. These are the parties that do not
294 * yet have an {@link #agreements} nor caused {@link #exceptions}
295 * nor {@link #walkedAway}
296 */
297 public List<PartyId> getActiveParties() {
298 List<PartyId> active = connections.stream().map(conn -> conn.getParty())
299 .collect(Collectors.toList());
300 active.removeAll(agreements.getMap().keySet());
301 active.removeAll(exceptions.keySet());
302 active.removeAll(walkedAway);
303 return active;
304 }
305
306 public Phase getPhase() {
307 return phase;
308 }
309
310 /**
311 *
312 * @return list of parties that walked away with {@link EndNegotiation}
313 */
314 public List<PartyId> getWalkedAway() {
315 return walkedAway;
316 }
317
318 /**
319 *
320 * @return new state with next phase selected and a updated list of
321 * agreements. This must be called to end a phase, it's not
322 * triggered by an action. If current phase is VOTE and next phase
323 * OFFER then progress is also incremented.
324 */
325 @SuppressWarnings({ "unchecked", "rawtypes" })
326 public AMOPState nextPhase() {
327 Agreements newagreements = agreements;
328 Progress newprogress = progress;
329 if (phase == Phase.VOTE) {
330 // hacky cast
331 newagreements = collectVotes((Map) getPhaseActions());
332 if (newprogress instanceof ProgressRounds) {
333 newprogress = ((ProgressRounds) newprogress).advance();
334 }
335 }
336 return new AMOPState(phase.next(), actions, connections, newprogress,
337 settings, partyprofiles, newagreements, exceptions, walkedAway);
338 }
339
340 /**
341 *
342 * @param allvotes the Votes of each party
343 * @return all agreements that were reached from the given votes
344 */
345 protected Agreements collectVotes(Map<PartyId, Votes> allvotes) {
346 Agreements newagreements = agreements;
347 while (true) {
348 // power is 1 for each known party.
349 Map<PartyId, Integer> powers = allvotes.keySet().stream()
350 .collect(Collectors.toMap(p -> p, p -> 1));
351 Map<Bid, Set<PartyId>> agrees = new CollectedVotes(allvotes, powers)
352 .getMaxAgreements();
353 if (agrees.isEmpty())
354 break;
355 // find the best one
356 Bid maxbid = agrees.keySet().stream()
357 .max(Comparator.comparingInt(bid -> agrees.get(bid).size()))
358 .get();
359 newagreements = newagreements
360 .with(new Agreements(maxbid, agrees.get(maxbid)));
361 for (PartyId party : agrees.get(maxbid)) {
362 allvotes.remove(party);
363 }
364 }
365 return newagreements;
366 }
367
368 @Override
369 public String toString() {
370 return "AMOPState[" + phase + "," + settings + "," + partyprofiles + ","
371 + connections + "," + agreements + "," + walkedAway + ","
372 + actions + "," + progress + "," + exceptions + "," + "]";
373 }
374
375 @Override
376 public int hashCode() {
377 final int prime = 31;
378 int result = 1;
379 result = prime * result + ((actions == null) ? 0 : actions.hashCode());
380 result = prime * result
381 + ((agreements == null) ? 0 : agreements.hashCode());
382 result = prime * result
383 + ((connections == null) ? 0 : connections.hashCode());
384 result = prime * result
385 + ((exceptions == null) ? 0 : exceptions.hashCode());
386 result = prime * result
387 + ((partyprofiles == null) ? 0 : partyprofiles.hashCode());
388 result = prime * result + ((phase == null) ? 0 : phase.hashCode());
389 result = prime * result
390 + ((progress == null) ? 0 : progress.hashCode());
391 result = prime * result
392 + ((settings == null) ? 0 : settings.hashCode());
393 result = prime * result
394 + ((walkedAway == null) ? 0 : walkedAway.hashCode());
395 return result;
396 }
397
398 @Override
399 public boolean equals(Object obj) {
400 if (this == obj)
401 return true;
402 if (obj == null)
403 return false;
404 if (getClass() != obj.getClass())
405 return false;
406 AMOPState other = (AMOPState) obj;
407 if (actions == null) {
408 if (other.actions != null)
409 return false;
410 } else if (!actions.equals(other.actions))
411 return false;
412 if (agreements == null) {
413 if (other.agreements != null)
414 return false;
415 } else if (!agreements.equals(other.agreements))
416 return false;
417 if (connections == null) {
418 if (other.connections != null)
419 return false;
420 } else if (!connections.equals(other.connections))
421 return false;
422 if (exceptions == null) {
423 if (other.exceptions != null)
424 return false;
425 } else if (!exceptions.equals(other.exceptions))
426 return false;
427 if (partyprofiles == null) {
428 if (other.partyprofiles != null)
429 return false;
430 } else if (!partyprofiles.equals(other.partyprofiles))
431 return false;
432 if (phase != other.phase)
433 return false;
434 if (progress == null) {
435 if (other.progress != null)
436 return false;
437 } else if (!progress.equals(other.progress))
438 return false;
439 if (settings == null) {
440 if (other.settings != null)
441 return false;
442 } else if (!settings.equals(other.settings))
443 return false;
444 if (walkedAway == null) {
445 if (other.walkedAway != null)
446 return false;
447 } else if (!walkedAway.equals(other.walkedAway))
448 return false;
449 return true;
450 }
451
452}
Note: See TracBrowser for help on using the repository browser.