package geniusweb.protocol.session.mopac2; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import geniusweb.actions.Action; import geniusweb.actions.PartyId; import geniusweb.inform.Agreements; import geniusweb.inform.OptIn; import geniusweb.progress.Progress; import geniusweb.progress.ProgressRounds; import geniusweb.protocol.NegoProtocol; import geniusweb.protocol.ProtocolException; import geniusweb.protocol.session.SessionResult; import geniusweb.protocol.session.SessionState; import geniusweb.protocol.session.mopac2.phase.OfferPhase; import geniusweb.protocol.session.mopac2.phase.OptInPhase; import geniusweb.protocol.session.mopac2.phase.Phase; import geniusweb.protocol.session.saop.SAOPSettings; import geniusweb.references.PartyWithProfile; /** * Keeps track of the current {@link Phase}. Adds initializing stuff and * time/deadline checking. This state does not contain connections, this assumes * that someone else handles (i.e. {@link NegoProtocol} that and the connections * with the parties and that negotiation events are just pumped in from there. *

* This object is a bit tricky. It has two states *

    *
  1. The initial state, where phase=null and connections are being made to all * parties. At this point, problems can not be handled nicely yet because there * are no PartyId's for problematic parties *
  2. The working state, where all parties are connected and all problems can * be connected to an offending party. *
*/ public class MOPAC2State implements SessionState { private final Phase phase; // maybe null while initializing private final MOPAC2Settings settings; private final Map partyprofiles; private final List actions; private final Progress progress; /** * Creates the initial state from the given settings and progress=null * * @param settings the {@link SAOPSettings} */ public MOPAC2State(MOPAC2Settings settings) { this(null, Arrays.asList(), null, settings, Collections.emptyMap()); } /** * @param phase The Phase, or null if still initializing. Phase is * set something only after we have connections. * @param actions the legal actions that have been done in the * negotiation. {@link PartyStates#getActions()} is * called to collect the actions after the phase is * finished.first action in list is the oldest. This * will not contain actions that immediately led to an * agreement, because {@link PartyStates} removes the * actions that led to an agreement. This MUST NOT * contain illegal actions. Parties doing illegal * actions are killed and the offending action ends up * in the stacktrace. Previous actions of crashed * parties remain standing and valid. * @param progress the {@link Progress} line. can be null if not yet * known. null happens because during initialization * phase the protocol first add all connections. During * this time, parties may already enter the * failed/exception state. * @param settings the {@link SAOPSettings} * @param partyprofiles map with the {@link PartyWithProfile} for connected * parties. null is equivalent to an empty map. */ @JsonCreator protected MOPAC2State(@JsonProperty("phase") Phase phase, @JsonProperty("actions") List actions, @JsonProperty("progress") Progress progress, @JsonProperty("settings") MOPAC2Settings settings, @JsonProperty("partyprofiles") Map partyprofiles) { this.phase = phase; this.actions = actions; this.progress = progress; this.settings = settings; this.partyprofiles = partyprofiles; } /** * Sets the progress for this session and initial phase. Must be called * after all parties have been connected with * {@link #with(PartyId, PartyWithProfile)}. * * @param newprogress the initial {@link Progress} typically matching the * settings deadline object * @param now current time ms since 1970 * * @return state with the initial partystates , progress set. */ public MOPAC2State initPhase(Progress newprogress, long now) { if (progress != null || newprogress == null || phase != null) { throw new IllegalArgumentException( "progress must be null, newprogress must be not null and phase must be INIT"); } PartyStates partyStates = new PartyStates(getPowers()); Phase firstPhase = new OfferPhase(partyStates, now + getAvailablePhaseTime(newprogress, now), settings.getVotingEvaluation()); return new MOPAC2State(firstPhase, actions, newprogress, settings, partyprofiles); } @Override public List getActions() { return Collections.unmodifiableList(actions); } @Override public Progress getProgress() { return progress; } @Override public Agreements getAgreements() { return phase.getPartyStates().getAgreements(); } @Override public MOPAC2Settings getSettings() { return settings; } public Map getPartyProfiles() { return Collections.unmodifiableMap(partyprofiles); } @Override public boolean isFinal(long now) { return phase != null && phase.isFinal(now) && !isNewPhasePossible(now); } @Override public List getResults() { return Arrays.asList(new SessionResult(partyprofiles, getAgreements(), Collections.emptyMap(), null)); } /** * @param progress the Progress that needs to be checked * @param now current time ms since 1970 * @return the max possible duration in ms of a phase considering the * progress. */ private static Long getAvailablePhaseTime(Progress aprogress, long now) { return Math.min(aprogress.getTerminationTime().getTime() - now, Phase.PHASE_MAXTIME); } /** * @param id the new {@link PartyId} * @param partyprofile the {@link PartyWithProfile} that is associated with * this state * @return new {@link MOPAC2State} with the new party added. This call * ignores the progress (does not check isFinal) because we uses * this during the setup where the deadline is not yet relevant. */ protected MOPAC2State with(PartyId id, PartyWithProfile partyprofile) { if (phase != null) throw new IllegalStateException( "Adding connections only allowed while initializing"); Map newprofiles = new HashMap<>( partyprofiles); newprofiles.put(id, partyprofile); return new MOPAC2State(null, actions, progress, settings, newprofiles); } /** * @param e the {@link ProtocolException} that occured * @return a new state with the error set. You MUST have called * {@link #initPhase(Progress, long)} before using this */ public MOPAC2State with(ProtocolException e) { return new MOPAC2State(phase.with(e), actions, progress, settings, partyprofiles); } /** * Start the next phase. If new phase is OfferPhase, we increase progress. * Actions is reset to empty. does nothing if not * {@link #isNewPhasePossible(long)} * * @param now current time * @return new {@link MOPAC2State} with phase initialized for next phase. */ public MOPAC2State nextPhase(long now) { long remainingNegoTime = progress.getTerminationTime().getTime() - now; Phase newphase = phase.next(now, Math.min(remainingNegoTime, Phase.PHASE_MAXTIME)); return new MOPAC2State(newphase, actions, increment(progress, phase), getSettings(), partyprofiles); } /** * When this is called, all parties should have acted. * * @param now current time * @return true if there are still >2 parties active and we have enough * time for a new phase. */ public boolean isNewPhasePossible(long now) { // System.out.println("phase=" + phase); Progress newprogress = increment(progress, phase); if (newprogress.isPastDeadline(now + Phase.PHASE_MINTIME)) return false; return phase.getPartyStates().getNegotiatingParties().size() >= 2 && getAvailablePhaseTime(newprogress, now) > Phase.PHASE_MINTIME; } public Phase getPhase() { return phase; } /** * Check if action is allowed. Add action to the list of actions. Notice, * this does NOT check if we need to step to the next phase, because * deciding that is also depending on time-outs. * * @param actor the actor that did this action. Can be used to check if * action is valid. NOTICE caller has to make sure the current * state is not final. MUST NOT be null. * @param action the action that was proposed by actor. MUST NOT be null. * @param now the current time in ms since 1970, see * {@link System#currentTimeMillis()} * @return new {@link MOPAC2State} with the action checked and registered. * If the action is not allowed, the new state may be that the actor * is in the exception list. */ public MOPAC2State with(PartyId actor, Action action, long now) { return new MOPAC2State(phase.with(actor, action, now), actions, progress, settings, partyprofiles); } /** * @param aprogress the progress that might need to be advanced * @param aphase the phase * @return the next progress. Progress round advances if phase is * {@link OptIn}. */ private static Progress increment(Progress aprogress, Phase aphase) { if (aprogress instanceof ProgressRounds && aphase instanceof OptInPhase) return ((ProgressRounds) aprogress).advance(); return aprogress; } /** * @return the power of all parties as set in their parameters, default =1. * bad power values (non-integer, or <1) are ignored. */ private Map getPowers() { Map map = new HashMap<>(); for (PartyId pid : partyprofiles.keySet()) { Object power = partyprofiles.get(pid).getParty().getParameters() .get("power"); if (power == null || !(power instanceof Integer) || ((Integer) power) < 1) power = 1; map.put(pid, (Integer) power); } return map; } @Override public String toString() { return "MOPAC2State[" + phase + "," + settings + "," + partyprofiles + "," + progress + "]"; } /** * * @return a wrapped-up state, with all parties doen an action or kicked, * and agreements collected */ public MOPAC2State finishPhase() { Phase newphase = phase.finish(); LinkedList newactions = new LinkedList(actions); newactions.addAll(newphase.getPartyStates().getActions()); return new MOPAC2State(newphase, newactions, progress, settings, partyprofiles); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((actions == null) ? 0 : actions.hashCode()); result = prime * result + ((partyprofiles == null) ? 0 : partyprofiles.hashCode()); result = prime * result + ((phase == null) ? 0 : phase.hashCode()); result = prime * result + ((progress == null) ? 0 : progress.hashCode()); result = prime * result + ((settings == null) ? 0 : settings.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MOPAC2State other = (MOPAC2State) obj; if (actions == null) { if (other.actions != null) return false; } else if (!actions.equals(other.actions)) return false; if (partyprofiles == null) { if (other.partyprofiles != null) return false; } else if (!partyprofiles.equals(other.partyprofiles)) return false; if (phase == null) { if (other.phase != null) return false; } else if (!phase.equals(other.phase)) return false; if (progress == null) { if (other.progress != null) return false; } else if (!progress.equals(other.progress)) return false; if (settings == null) { if (other.settings != null) return false; } else if (!settings.equals(other.settings)) return false; return true; } }