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

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

Faster example parties

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