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;
}
}