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

Last change on this file since 44 was 44, checked in by bart, 2 years ago

Added time-dependent parties for python and simpleRunner-GUI for java

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