package geniusweb.protocol.session.saop; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import geniusweb.actions.Accept; import geniusweb.actions.Action; import geniusweb.actions.EndNegotiation; import geniusweb.actions.Offer; import geniusweb.actions.PartyId; import geniusweb.inform.Agreements; import geniusweb.issuevalue.Bid; import geniusweb.progress.Progress; import geniusweb.progress.ProgressRounds; import geniusweb.protocol.ProtocolException; import geniusweb.protocol.session.DefaultSessionState; import geniusweb.protocol.session.SessionResult; import geniusweb.references.PartyWithProfile; /** * Immutable. */ public class SAOPState extends DefaultSessionState { /** * * @param actions the actions done by the parties * @param connections the existing party connections. If null, default * empty list is used. Can be less than 2 parties in * the first phases of the setup or after parties * disconnected. * @param progress the {@link Progress} line. can be null * @param settings the {@link SAOPSettings} * @param partyprofiles map with the {@link PartyWithProfile} for connected * parties. null is equivalent to an empty map. * @param error the {@link ProtocolException}, or null if none * occurred. */ @JsonCreator public SAOPState(@JsonProperty("actions") List actions, @JsonProperty("connections") List connections, @JsonProperty("progress") Progress progress, @JsonProperty("settings") SAOPSettings settings, @JsonProperty("partyprofiles") Map partyprofiles, @JsonProperty("error") ProtocolException error) { super(actions, connections, progress, settings, partyprofiles, error); } @Override public SAOPState with(List actions, List conns, Progress progr, SAOPSettings settings, Map partyprofiles, ProtocolException e) { return new SAOPState(actions, conns, progr, settings, partyprofiles, e); } /** * Creates the initial state from the given settings and progress=null * * @param settings the {@link SAOPSettings} */ public SAOPState(SAOPSettings settings) { this(Collections.emptyList(), Collections.emptyList(), null, settings, null, null); } @Override public Agreements getAgreements() { Agreements agree = new Agreements(); List acts = getActions(); int nparticipants = getConnections().size(); if (nparticipants < 2 || acts.size() < nparticipants) { return agree; } Action offer = acts.get(acts.size() - nparticipants); if (!(offer instanceof Offer)) return agree; Bid bid = ((Offer) offer).getBid(); // check that the last n-1 are accepts. boolean allaccept = acts.stream().skip(acts.size() - nparticipants + 1) .allMatch(act -> act instanceof Accept && bid.equals(((Accept) act).getBid())); if (allaccept) agree = agree .with(new Agreements(bid, new HashSet<>(getParties()))); return agree; } /** * * @return all currently connected parties. */ public List getParties() { return getConnections(); } @Override public boolean isFinal(long currentTimeMs) { List acts = getActions(); return super.isFinal(currentTimeMs) || !getAgreements().getMap().isEmpty() || (!acts.isEmpty() && acts.get(acts.size() - 1) instanceof EndNegotiation); } @Override public SAOPState with(PartyId actor, Action action) { return super.with(actor, action).with(advanceProgress()); } @Override public String checkAction(PartyId actor, Action action) { String msg = super.checkAction(actor, action); if (msg != null) return msg; if (!actor.equals(getNextActor())) return "Party does not have the turn "; // check protocol is followed for specific actions if (action instanceof Accept) { Bid bid = getLastBid(); if (bid == null) return "Accept without a recent offer"; if (!bid.equals(((Accept) action).getBid())) return "Party accepts a bid differing from the last offer =" + bid + ", action=" + action + ")"; return null; } else if (action instanceof Offer) { return null; } else if (action instanceof EndNegotiation) { return null; } return "Action " + action + " is not allowed in SAOP"; } /** * Check up to nparticipants-1 steps back if there was an offer. * * @return Bid from the most recent offer, or null if no such offer */ private Bid getLastBid() { int nparticipants = getConnections().size(); List acts = getActions(); for (int n = acts.size() - 1; n > acts.size() - nparticipants && n >= 0; n--) { Action action = acts.get(n); if (action instanceof Offer) { return ((Offer) action).getBid(); } } return null; } /** * * @return the next actor in the current state. Assumes 1 action per actor * every time. NOTE if party disconnects this would jump wildly but * nego stops then anyway */ protected PartyId getNextActor() { return getConnections() .get(getActions().size() % getConnections().size()); } /** * @return new progress state */ public Progress advanceProgress() { Progress newprogress = getProgress(); if (newprogress instanceof ProgressRounds && isLastActor()) { newprogress = ((ProgressRounds) newprogress).advance(); } return newprogress; } /** * @return true if the current actor is the last actor in the list */ private boolean isLastActor() { int nparticipants = getConnections().size(); return getActions().size() % nparticipants == nparticipants - 1; } @Override public List getResults() { return Arrays.asList(new SessionResult(getPartyProfiles(), getAgreements(), Collections.emptyMap(), getError())); } }