package geniusweb.protocol.session; import java.util.ArrayList; 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 geniusweb.actions.Action; import geniusweb.actions.PartyId; import geniusweb.progress.Progress; import geniusweb.protocol.ProtocolException; import geniusweb.references.PartyWithProfile; /** * */ /** * * The default current state of the session. immutable. * * @param

the actual SessionState object * @param the actual SessionSettings object */ public abstract class DefaultSessionState

, S extends SessionSettings> implements SessionState { private final List actions; private final List connections; private final Progress progress; private final S settings; private final ProtocolException error; private final Map partyprofiles; /** * * @param actions value for actions done so far. null equals to empty * list * @param conns the currently existing connections. Can be empty. If * null it is assumed to be empty. Each connection * represents another party. Normally there is exactly * 1 connection for every party. The protocol should * check this. * @param progr the {@link Progress} that governs this session. Can * be null if session did not yet start. * @param settings the settings used for the session * @param partyprofiles map with the {@link PartyWithProfile} for connected * parties. null is equivalent to an empty map. * @param e the exception that occured, usually null. All errors * occuring due to faulty {@link Action}s translate to * {@link ProtocolException}s. All errors in our own * code are bugs (not ProtocolExceptions) and should * result in a throw and terminate the session. */ public DefaultSessionState(List actions, List conns, Progress progr, S settings, Map partyprofiles, ProtocolException e) { if (partyprofiles == null) { this.partyprofiles = Collections.emptyMap(); } else { this.partyprofiles = new HashMap<>(partyprofiles); } if (conns == null) { this.connections = Collections.emptyList(); } else { this.connections = new LinkedList<>(conns); } if (actions == null) { this.actions = new LinkedList<>(); } else { this.actions = new LinkedList<>(actions); } if (connections.size() != new HashSet(connections).size()) { throw new IllegalArgumentException( "There can not be multiple connections for a party:" + connections); } if (settings == null) throw new IllegalArgumentException("Settings must be not null"); this.progress = progr; this.settings = settings; this.error = e; } /** * Construct a new session state, where the DefaultSessionState changes * while the rest of the state remains unchanged. Notice the return type P. * * @param actions1 the new {@link Action}s * @param conns the new connected {@link PartyId}s * @param progress1 the new {@link Progress} * @param settings1 the new {@link SessionSettings}. Normally this is * constant during a session. * @param partyprofiles1 the new {@link PartyWithProfile}s for all parties. * Normally this remains constant during a session. * @param e an error that occured that caused the session to * reach its terminated/final state. If an error does * not cause termination, only a warning should be * logged. * @return the new state of the derived sessionstate. */ abstract public P with(List actions1, List conns, Progress progress1, S settings1, Map partyprofiles1, ProtocolException e); /** * @return existing connections. */ public List getConnections() { return Collections.unmodifiableList(connections); } /** * @return map with {@link PartyWithProfile} for the parties. May be an * incomplete map, as more parties with their profiles may be set * only later. */ public Map getPartyProfiles() { return Collections.unmodifiableMap(partyprofiles); } /** * * @return unmodifyable list of actions done so far. */ @Override public List getActions() { return Collections.unmodifiableList(actions); } @Override public Progress getProgress() { return progress; } @Override public S getSettings() { return settings; } @Override public boolean isFinal(long currentTimeMs) { return error != null || (progress != null && progress.isPastDeadline(currentTimeMs)); } public ProtocolException getError() { return error; } @Override public String toString() { return this.getClass().getSimpleName() + "[" + actions + "," + connections + "," + progress + "," + settings + "," + error; } public P withDeadlineReached() { return with(actions, connections, progress, settings, partyprofiles, error); } public P withoutParty(PartyId party) { List newconn = new ArrayList<>(connections); newconn.remove(party); return with(actions, newconn, progress, settings, partyprofiles, error); } /** * * @param e the error that occured * @return a new state with the error set/updated. */ public P with(ProtocolException e) { return with(actions, connections, progress, settings, partyprofiles, e); } public P with(PartyId connection, PartyWithProfile partyprofile) { List newconns = new ArrayList<>(getConnections()); newconns.add(connection); Map newprofiles = new HashMap<>( getPartyProfiles()); newprofiles.put(connection, partyprofile); return with(getActions(), newconns, getProgress(), getSettings(), newprofiles, null); } /** * Sets the new progress for this session. * * @param newprogress the new progress * @return new SAOPState with the progress set to new value */ public P with(Progress newprogress) { if (newprogress == null) { throw new IllegalArgumentException("newprogress must be not null"); } return with(actions, connections, newprogress, settings, partyprofiles, error); } /** * @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. * @param action the action that was proposed by actor. * @return new SAOPState with the action added as last action. */ public P with(PartyId actor, Action action) { String msg = checkAction(actor, action); if (msg != null) throw new IllegalArgumentException(msg); List newactions = new LinkedList<>(getActions()); newactions.add(action); return with(newactions, connections, progress, settings, partyprofiles, error); } /** * * @param actor the known real actor that did this action * @param action an {@link Action} * @return null if action seems ok, or message explaining why not. */ public String checkAction(PartyId actor, Action action) { if (actor == null) { // this is a bug return "actor must not be null"; } if (action == null) { return "action is null"; } if (!actor.equals(action.getActor())) return "act contains wrong credentials: " + action; return null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((actions == null) ? 0 : actions.hashCode()); result = prime * result + ((connections == null) ? 0 : connections.hashCode()); result = prime * result + ((error == null) ? 0 : error.hashCode()); result = prime * result + ((partyprofiles == null) ? 0 : partyprofiles.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; DefaultSessionState other = (DefaultSessionState) obj; if (actions == null) { if (other.actions != null) return false; } else if (!actions.equals(other.actions)) return false; if (connections == null) { if (other.connections != null) return false; } else if (!connections.equals(other.connections)) return false; if (error == null) { if (other.error != null) return false; } else if (!error.equals(other.error)) return false; if (partyprofiles == null) { if (other.partyprofiles != null) return false; } else if (!partyprofiles.equals(other.partyprofiles)) 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; } }