package geniusweb.protocol.session.mopac2; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import geniusweb.actions.Action; import geniusweb.actions.EndNegotiation; import geniusweb.actions.PartyId; import geniusweb.inform.Agreements; import geniusweb.protocol.ProtocolException; /** * Invariant: contains the current state of all participating parties. A party * either notYetActed, did action, reached an agreement, walked away or made a * {@link ProtocolException}. *

* After a phase completes, the actions are filtered/handled to collect * agreements and move remaining parties back to notYetActed. * */ @JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE) public class PartyStates { // all parties must be in exactly one state at all times. private final Set notYetActed; private final List actions; // chrono order private final Agreements agreements; private final List walkedAway; private final Map exceptions; private final Map powers; @JsonCreator protected PartyStates(@JsonProperty("notYetActed") Set notYetActed, @JsonProperty("actions") List actions, @JsonProperty("agreements") Agreements agreements, @JsonProperty("walkedAway") List walkedAway, @JsonProperty("exceptions") Map exceptions, @JsonProperty("powers") Map powers) { this.notYetActed = notYetActed; this.actions = actions; this.agreements = agreements; this.walkedAway = walkedAway; this.exceptions = exceptions; this.powers = powers; } /** * Initial constructor. * * @param powers the powers of all parties. */ public PartyStates(Map powers) { this.notYetActed = new HashSet<>(powers.keySet()); this.actions = Collections.emptyList(); this.agreements = new Agreements(); this.walkedAway = Collections.emptyList(); this.exceptions = Collections.emptyMap(); this.powers = new HashMap<>(powers); } /** * * @param action the action done by some party. The correctness of action, * particularly of {@link Action#getActor()}, must be correct. * @return new state with party done given action. This just accepts any * given action and is not considering number of rounds, time or * whether an action is allowed. * * @throws IllegalArgumentException if party doing action already acted. * This is just a safety check as legality * of actions should be checked before * calling this. */ public PartyStates with(Action action) { if (!(notYetActed.contains(action.getActor()))) throw new IllegalArgumentException( "actor already acted: " + action); if (action instanceof EndNegotiation) return withWalkAway(action.getActor()); List newActions = new LinkedList(actions); newActions.add(action); return new PartyStates(removeParty(action.getActor()), newActions, agreements, walkedAway, exceptions, powers); } /** * * @param newAgree a new agreements to be merged with existing agreements. * The parties in the agreement must have acted and thus in * actions. * @return new PartyStates with agreeing parties removed from actions and * added to Agreements. */ public PartyStates with(Agreements newAgree) { Set parties = newAgree.getMap().keySet(); List newActions = actions.stream() .filter(act -> !(parties.contains(act.getActor()))) .collect(Collectors.toList()); return new PartyStates(notYetActed, newActions, agreements.with(newAgree), walkedAway, exceptions, powers); } public PartyStates withWalkAway(PartyId actor) { if (!(notYetActed.contains(actor))) throw new IllegalArgumentException("actor already acted: " + actor); LinkedList newWalkAway = new LinkedList<>(walkedAway); newWalkAway.add(actor); return new PartyStates(removeParty(actor), actions, agreements, newWalkAway, exceptions, powers); } /** * Move party from active to exceptions. * * @param e the exception that the party caused * @return new PartyStates with party that caused the exception in the * exceptions list and removed from the active list. Nothing happens * if party is not active. */ public PartyStates with(ProtocolException e) { if (!notYetActed.contains(e.getParty())) { // complex case. Party did valid action but now is messing around. // Easiest seems to completely ignore it. CHECK. return this; } Map newExc = new HashMap<>(exceptions); newExc.put(e.getParty(), e); return new PartyStates(removeParty(e.getParty()), actions, agreements, walkedAway, newExc, powers); } /** * * @return parties that have not yet acted */ public Set getNotYetActed() { return notYetActed; } /** * * @return parties that are still in the negotiation. These are the parties * that not yet acted plus the ones that did an action. */ public Set getNegotiatingParties() { Set parties = actions.stream().map(act -> act.getActor()) .collect(Collectors.toSet()); parties.addAll(notYetActed); return parties; } /** * @param actor * @return {@link #notYetActed} with actor removed * @throws IllegalArgumentException if party already acted (not in * {@link #notYetActed}). */ private Set removeParty(PartyId party) { Set newActiveParties = new HashSet<>(notYetActed); if (!newActiveParties.remove(party)) throw new IllegalArgumentException( "Party " + party + " is not active, can't be removed"); return newActiveParties; } public Agreements getAgreements() { return agreements; } public Map getPowers() { return Collections.unmodifiableMap(powers); } public Map getExceptions() { return Collections.unmodifiableMap(exceptions); } /** * @return new state where all {@link #notYetActed} are moved to the * exceptions list. */ public PartyStates finish() { PartyStates newstate = this; for (PartyId party : notYetActed) { newstate = newstate .with(new ProtocolException("Party did not act", party)); } return newstate; } public List getActions() { return Collections.unmodifiableList(actions); } /** * * @return all parties that walked away */ public List getWalkedAway() { return walkedAway; } /** * @param the type of objects requested * @param type the type of actions to extract. Must be of type T, needed * because of java's type erasure. * * @return all actions of type done in this phase */ public List getActions(Class type) { return actions.stream().filter(act -> type.isInstance(act)) .map(act -> (T) act).collect(Collectors.toList()); } @Override public String toString() { return "PartyStates[" + notYetActed + "," + actions + "," + agreements + "," + walkedAway + "," + exceptions + "]"; } /** * * @return states with all parties that are in {@link #actions} moved back * to {@link #notYetActed}. This state is then ready for a next * phase. * @throws IllegalStateException if {@link #notYetActed} is not empty */ public PartyStates flush() { if (!notYetActed.isEmpty()) throw new IllegalStateException( "Some parties did not yet act:" + notYetActed); return new PartyStates(getNegotiatingParties(), Collections.emptyList(), agreements, walkedAway, exceptions, powers); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((actions == null) ? 0 : actions.hashCode()); result = prime * result + ((agreements == null) ? 0 : agreements.hashCode()); result = prime * result + ((exceptions == null) ? 0 : exceptions.hashCode()); result = prime * result + ((notYetActed == null) ? 0 : notYetActed.hashCode()); result = prime * result + ((powers == null) ? 0 : powers.hashCode()); result = prime * result + ((walkedAway == null) ? 0 : walkedAway.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; PartyStates other = (PartyStates) obj; if (actions == null) { if (other.actions != null) return false; } else if (!actions.equals(other.actions)) return false; if (agreements == null) { if (other.agreements != null) return false; } else if (!agreements.equals(other.agreements)) return false; if (exceptions == null) { if (other.exceptions != null) return false; } else if (!exceptions.equals(other.exceptions)) return false; if (notYetActed == null) { if (other.notYetActed != null) return false; } else if (!notYetActed.equals(other.notYetActed)) return false; if (powers == null) { if (other.powers != null) return false; } else if (!powers.equals(other.powers)) return false; if (walkedAway == null) { if (other.walkedAway != null) return false; } else if (!walkedAway.equals(other.walkedAway)) return false; return true; } }