source: protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java@ 26

Last change on this file since 26 was 26, checked in by bart, 4 years ago

Voting requests now contain Offers. Fixed windows whitespace issue. Partiesserver now supports up to 8 parties simultaneously.

File size: 8.4 KB
Line 
1package geniusweb.protocol.session.saop;
2
3import java.util.Collections;
4import java.util.HashMap;
5import java.util.LinkedList;
6import java.util.List;
7import java.util.Map;
8import java.util.Set;
9import java.util.stream.Collectors;
10
11import geniusweb.actions.Accept;
12import geniusweb.actions.Action;
13import geniusweb.actions.EndNegotiation;
14import geniusweb.actions.Offer;
15import geniusweb.actions.PartyId;
16import geniusweb.inform.Agreements;
17import geniusweb.issuevalue.Bid;
18import geniusweb.progress.Progress;
19import geniusweb.progress.ProgressRounds;
20import geniusweb.protocol.ProtocolException;
21import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
22import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
23import geniusweb.protocol.session.DefaultSessionState;
24import geniusweb.protocol.session.SessionResult;
25import geniusweb.protocol.session.SessionSettings;
26import geniusweb.references.PartyWithProfile;
27
28/**
29 * Immutable.
30 */
31public class SAOPState extends DefaultSessionState {
32
33 /**
34 *
35 * @param actions the actions done by the parties
36 * @param conns the existing party connections. we assume ownership
37 * of this so it should not be modified although
38 * connections may of course break. If null, default
39 * empty list is used. Can be less than 2 parties in
40 * the first phases of the setup.
41 * @param progress the {@link Progress} line. can be null
42 * @param settings the {@link SAOPSettings}
43 * @param partyprofiles map with the {@link PartyWithProfile} for connected
44 * parties. null is equivalent to an empty map.
45 * @param e the {@link ProtocolException}, or null if none
46 * occurred.
47 */
48 public SAOPState(List<Action> actions, ProtocolToPartyConnections conns,
49 Progress progress, SessionSettings settings,
50 Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
51 super(actions, conns, progress, settings, partyprofiles, e);
52 }
53
54 /**
55 * Creates the initial state from the given settings and progress=null
56 *
57 * @param settings the {@link SAOPSettings}
58 */
59 public SAOPState(SAOPSettings settings) {
60 this(Collections.emptyList(),
61 new ProtocolToPartyConnections(Collections.emptyList()), null,
62 settings, null, null);
63
64 }
65
66 /**
67 *
68 * @param connection the new {@link ProtocolToPartyConn}
69 * @param partyprofile the {@link PartyWithProfile} that is associated with
70 * this state
71 * @return new SessionState with the new connection added. This call ignores
72 * the progress (does not check isFinal) because SAOP uses this
73 * during the setup where the deadline is not yet relevant. The
74 * connection should be informed about the partyprofile but the
75 * state currently indicate if that already happened or not.
76 */
77 public SAOPState with(ProtocolToPartyConn connection,
78 PartyWithProfile partyprofile) {
79 // Only called from the SAOP initialization phase.
80 ProtocolToPartyConnections newconns = getConnections().with(connection);
81 Map<PartyId, PartyWithProfile> newprofiles = new HashMap<>(
82 getPartyProfiles());
83 newprofiles.put(connection.getParty(), partyprofile);
84 return new SAOPState(getActions(), newconns, getProgress(),
85 getSettings(), newprofiles, null);
86 }
87
88 /**
89 *
90 * @param e the error that occured
91 * @return a new state with the error set.
92 */
93 public SAOPState with(ProtocolException e) {
94 return new SAOPState(getActions(), getConnections(), getProgress(),
95 getSettings(), getPartyProfiles(), e);
96 }
97
98 /**
99 * Sets the progress for this session. Can be set only if progress=null.
100 *
101 * @param newprogress the new progress
102 * @return new SAOPState with the progress set
103 */
104 public SAOPState with(Progress newprogress) {
105 if (newprogress == null) {
106 throw new IllegalArgumentException("newprogress must be not null");
107 }
108 if (getProgress() != null) {
109 return this;
110 }
111 return new SAOPState(getActions(), getConnections(), newprogress,
112 getSettings(), getPartyProfiles(), getError());
113 }
114
115 @Override
116 public Agreements getAgreements() {
117 Agreements agree = new Agreements();
118 List<Action> acts = getActions();
119 int nparticipants = getConnections().size();
120 if (nparticipants < 2 || acts.size() < nparticipants) {
121 return agree;
122 }
123 Action offer = acts.get(acts.size() - nparticipants);
124 if (!(offer instanceof Offer))
125 return agree;
126 Bid bid = ((Offer) offer).getBid();
127
128 // check that the last n-1 are accepts.
129 boolean allaccept = acts.stream().skip(acts.size() - nparticipants + 1)
130 .allMatch(act -> act instanceof Accept
131 && bid.equals(((Accept) act).getBid()));
132 if (allaccept)
133 agree = agree.with(new Agreements(bid, getParties()));
134 return agree;
135 }
136
137 /**
138 *
139 * @return all currently connected parties.
140 */
141 private Set<PartyId> getParties() {
142 return getConnections().stream().map(conn -> conn.getParty())
143 .collect(Collectors.toSet());
144 }
145
146 @Override
147 public boolean isFinal(long currentTimeMs) {
148 List<Action> acts = getActions();
149 return super.isFinal(currentTimeMs)
150 || !getAgreements().getMap().isEmpty() || (!acts.isEmpty()
151 && acts.get(acts.size() - 1) instanceof EndNegotiation);
152 }
153
154 @Override
155 public SAOPSettings getSettings() {
156 return (SAOPSettings) super.getSettings();
157 }
158
159 /**
160 * @param actor the actor that did this action. Can be used to check if
161 * action is valid. NOTICE caller has to make sure the current
162 * state is not final.
163 * @param action the action that was proposed by actor.
164 * @return new SAOPState with the action added as last action.
165 */
166
167 public SAOPState with(PartyId actor, Action action) {
168
169 if (actor == null) { // this is a bug
170 throw new IllegalArgumentException("actor must not be null");
171 }
172 if (!actor.equals(getNextActor())) {
173 throw new IllegalArgumentException("Party does not have the turn ");
174
175 }
176 if (action == null) {
177 throw new IllegalArgumentException("action is null");
178 }
179 if (!actor.equals(action.getActor())) {
180 throw new IllegalArgumentException(
181 "act contains wrong credentials: " + action);
182 }
183
184 // check protocol is followed for specific actions
185 if (action instanceof Accept) {
186 Bid bid = getLastBid();
187 if (bid == null) {
188 throw new IllegalArgumentException(
189 "Accept without a recent offer");
190 }
191 if (!bid.equals(((Accept) action).getBid())) {
192 throw new IllegalArgumentException(
193 "Party accepts a bid differing from the last offer ="
194 + bid + ", action=" + action + ")");
195 }
196 } else if (action instanceof Offer) {
197 // offer is always fine. We don't check if bid is actually in domain
198 // or complete etc. We would need domain for that
199 } else if (action instanceof EndNegotiation) {
200 // just act, we will get into the final state then.
201 } else {
202 throw new IllegalArgumentException(
203 "Action " + action + " is not allowed in SAOP");
204 }
205
206 List<Action> newactions = new LinkedList<>(getActions());
207 newactions.add(action);
208 return new SAOPState(newactions, getConnections(), advanceProgress(),
209 getSettings(), getPartyProfiles(), null);
210 }
211
212 /**
213 * Check up to nparticipants-1 steps back if there was an offer.
214 *
215 * @return Bid from the most recent offer, or null if no such offer
216 */
217 private Bid getLastBid() {
218 int nparticipants = getConnections().size();
219 List<Action> acts = getActions();
220 for (int n = acts.size() - 1; n > acts.size() - nparticipants
221 && n >= 0; n--) {
222 Action action = acts.get(n);
223 if (action instanceof Offer) {
224 return ((Offer) action).getBid();
225 }
226 }
227 return null;
228
229 }
230
231 /**
232 *
233 * @return the next actor in the current state. Assumes 1 action per actor
234 * every time.
235 */
236 protected PartyId getNextActor() {
237 return getConnections()
238 .get(getActions().size() % getConnections().size()).getParty();
239 }
240
241 /**
242 *
243 * @param progress
244 * @return new progress state
245 */
246 private Progress advanceProgress() {
247 Progress newprogress = getProgress();
248 if (newprogress instanceof ProgressRounds && isLastActor()) {
249 newprogress = ((ProgressRounds) newprogress).advance();
250 }
251 return newprogress;
252 }
253
254 /**
255 * @return true if the current actor is the last actor in the list
256 */
257 private boolean isLastActor() {
258 int nparticipants = getConnections().size();
259 return getActions().size() % nparticipants == nparticipants - 1;
260 }
261
262 @Override
263 public SessionResult getResult() {
264 return new SessionResult(getPartyProfiles(), getAgreements(),
265 Collections.emptyMap(), getError());
266 }
267
268}
Note: See TracBrowser for help on using the repository browser.