Index: protocol/src/main/java/geniusweb/protocol/session/OnePartyTeam.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/OnePartyTeam.java	(revision 29)
+++ 	(revision )
@@ -1,30 +1,0 @@
-package geniusweb.protocol.session;
-
-import java.util.Arrays;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import geniusweb.references.PartyWithParameters;
-import geniusweb.references.PartyWithProfile;
-import geniusweb.references.ProfileRef;
-
-/**
- * Team that contains just 1 party.
- */
-public class OnePartyTeam extends PartyWithProfile
-		implements TeamOfPartiesAndProfiles {
-
-	@JsonCreator
-	public OnePartyTeam(@JsonProperty("party") PartyWithParameters party,
-			@JsonProperty("profile") ProfileRef profile) {
-		super(party, profile);
-	}
-
-	@Override
-	public List<PartyWithProfile> getAllParties() {
-		return Arrays.asList(this);
-	}
-
-}
Index: protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 29)
+++ protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 30)
@@ -8,6 +8,8 @@
 import geniusweb.protocol.session.amop.AMOPSettings;
 import geniusweb.protocol.session.mopac.MOPACSettings;
+import geniusweb.protocol.session.mopac2.MOPAC2Settings;
 import geniusweb.protocol.session.saop.SAOPSettings;
 import geniusweb.protocol.session.shaop.SHAOPSettings;
+import geniusweb.protocol.tournament.allpermutations.Team;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
@@ -19,13 +21,21 @@
 		@JsonSubTypes.Type(value = SHAOPSettings.class),
 		@JsonSubTypes.Type(value = AMOPSettings.class),
-		@JsonSubTypes.Type(value = MOPACSettings.class) })
+		@JsonSubTypes.Type(value = MOPACSettings.class),
+		@JsonSubTypes.Type(value = MOPAC2Settings.class) })
 public interface SessionSettings extends NegoSettings {
 
 	/**
-	 * Get participants
+	 * Get the {@link Team}s.
 	 * 
 	 * @return list of {@link PartyWithProfile} items.
 	 */
-	List<TeamOfPartiesAndProfiles> getTeams();
+	List<TeamInfo> getTeams();
+
+	/**
+	 * @return the size of each {@link Team}. Usually 1 (SAOP etc) or sometimes
+	 *         2 (SHAOP). Used eg by tournament runner to determine proper team
+	 *         construction.
+	 */
+	Integer getTeamSize();
 
 	/**
@@ -33,8 +43,8 @@
 	 * for tournament auto-configuration of sessions.
 	 * 
-	 * @param partyprofteam the {@link TeamOfPartiesAndProfiles} to be added
+	 * @param partyprofteam the {@link TeamInfo} to be added
 	 * @return new modified SessionSettings object
 	 */
-	SessionSettings with(TeamOfPartiesAndProfiles partyprofteam);
+	SessionSettings with(TeamInfo partyprofteam);
 
 	/**
Index: protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java	(revision 30)
@@ -0,0 +1,88 @@
+package geniusweb.protocol.session;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+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 com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+
+/**
+ * Contains a functional team, eg in SHAOP a team would be a SHAOP together with
+ * his COB partner. In SAOP this is just a SAOP party.
+ */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
+public class TeamInfo {
+
+	private List<PartyWithProfile> parties;
+
+	/**
+	 * Convenient constructor if team is only 1 party and profile
+	 * 
+	 * @param party   the {@link PartyWithParameters}
+	 * @param profile the ProfileRef to be used by party
+	 */
+	public TeamInfo(PartyWithParameters party, ProfileRef profile) {
+		this(Arrays.asList(new PartyWithProfile(party, profile)));
+	}
+
+	@JsonCreator
+	public TeamInfo(@JsonProperty("teams") List<PartyWithProfile> teams) {
+		this.parties = teams;
+	}
+
+	/**
+	 * 
+	 * @return the list of all parties involved, which is the parties in all
+	 *         teams combined.
+	 */
+	public List<PartyWithProfile> getAllParties() {
+		return Collections.unmodifiableList(parties);
+	}
+
+	/**
+	 * 
+	 * @return the number of parties in the team.
+	 */
+	public Integer getSize() {
+		return parties.size();
+	}
+
+	@Override
+	public String toString() {
+		return "TeamInfo[" + parties + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((parties == null) ? 0 : parties.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;
+		TeamInfo other = (TeamInfo) obj;
+		if (parties == null) {
+			if (other.parties != null)
+				return false;
+		} else if (!parties.equals(other.parties))
+			return false;
+		return true;
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/session/TeamOfPartiesAndProfiles.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/TeamOfPartiesAndProfiles.java	(revision 29)
+++ 	(revision )
@@ -1,33 +1,0 @@
-package geniusweb.protocol.session;
-
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-import com.fasterxml.jackson.annotation.JsonSubTypes;
-
-import geniusweb.protocol.session.shaop.ShaopTeam;
-import geniusweb.references.PartyWithProfile;
-
-/**
- * Contains a functional team, eg in SHAOP a team would be a SHAOP together with
- * his COB partner. In SAOP this is just a SAOP party.
- * 
- * This is needed to specify the general SessionSettings functionality in a
- * sufficient general way
- *
- */
-@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
-// we do not use JsonTypeInfo because we always use a specific Team within a protocol
-// and do not (de)serialize individual TeamOfPartiesAndProfiles.
-//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
-@JsonSubTypes({ @JsonSubTypes.Type(value = OnePartyTeam.class),
-		@JsonSubTypes.Type(value = ShaopTeam.class) })
-public interface TeamOfPartiesAndProfiles {
-	/**
-	 * 
-	 * @return the list of all parties involved, which is the parties in all
-	 *         teams combined.
-	 */
-	List<PartyWithProfile> getAllParties();
-}
Index: protocol/src/main/java/geniusweb/protocol/session/amop/AMOPSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/amop/AMOPSettings.java	(revision 29)
+++ protocol/src/main/java/geniusweb/protocol/session/amop/AMOPSettings.java	(revision 30)
@@ -10,8 +10,7 @@
 
 import geniusweb.deadline.Deadline;
-import geniusweb.protocol.session.OnePartyTeam;
 import geniusweb.protocol.session.SessionProtocol;
 import geniusweb.protocol.session.SessionSettings;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
@@ -21,5 +20,5 @@
  */
 public class AMOPSettings implements SessionSettings {
-	private final List<OnePartyTeam> participants;
+	private final List<TeamInfo> participants;
 	private final Deadline deadline;
 
@@ -34,5 +33,5 @@
 	@JsonCreator
 	public AMOPSettings(
-			@JsonProperty("participants") List<OnePartyTeam> participants,
+			@JsonProperty("participants") List<TeamInfo> participants,
 			@JsonProperty("deadline") Deadline deadline) {
 		this.participants = participants;
@@ -57,5 +56,5 @@
 
 	@Override
-	public List<TeamOfPartiesAndProfiles> getTeams() {
+	public List<TeamInfo> getTeams() {
 		return Collections.unmodifiableList(participants);
 	}
@@ -114,13 +113,18 @@
 
 	@Override
-	public SessionSettings with(TeamOfPartiesAndProfiles party) {
-		if (!(party instanceof OnePartyTeam))
+	public SessionSettings with(TeamInfo team) {
+		if (team.getSize() != 1)
 			throw new IllegalArgumentException(
-					"Added party must be AmopPartyWithProfile but got "
-							+ party);
-		List<OnePartyTeam> newparts = new LinkedList<>(participants);
-		newparts.add((OnePartyTeam) party);
+					"Added team must be just 1 party but got " + team);
+		List<TeamInfo> newparts = new LinkedList<>(
+				participants);
+		newparts.add(team);
 		return new AMOPSettings(newparts, deadline);
 	}
 
+	@Override
+	public Integer getTeamSize() {
+		return 1;
+	}
+
 }
Index: protocol/src/main/java/geniusweb/protocol/session/mopac/MOPACSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac/MOPACSettings.java	(revision 29)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac/MOPACSettings.java	(revision 30)
@@ -10,7 +10,6 @@
 
 import geniusweb.deadline.Deadline;
-import geniusweb.protocol.session.OnePartyTeam;
 import geniusweb.protocol.session.SessionSettings;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.PartyWithProfile;
 import geniusweb.voting.VotingEvaluator;
@@ -22,5 +21,5 @@
  */
 public class MOPACSettings implements SessionSettings {
-	private final List<OnePartyTeam> participants;
+	private final List<TeamInfo> participants;
 	private final Deadline deadline;
 	private final VotingEvaluator votingevaluator;
@@ -38,5 +37,5 @@
 	@JsonCreator
 	public MOPACSettings(
-			@JsonProperty("participants") List<OnePartyTeam> participants,
+			@JsonProperty("participants") List<TeamInfo> participants,
 			@JsonProperty("deadline") Deadline deadline,
 			@JsonProperty("votingevaluator") VotingEvaluator votingeval) {
@@ -61,5 +60,5 @@
 
 	@Override
-	public List<TeamOfPartiesAndProfiles> getTeams() {
+	public List<TeamInfo> getTeams() {
 		return Collections.unmodifiableList(participants);
 	}
@@ -88,10 +87,10 @@
 
 	@Override
-	public MOPACSettings with(TeamOfPartiesAndProfiles party) {
-		if (!(party instanceof OnePartyTeam))
+	public MOPACSettings with(TeamInfo team) {
+		if (team.getSize() != 1)
 			throw new IllegalArgumentException(
-					"Added party must be OnePartyTeam but got " + party);
-		List<OnePartyTeam> newparts = new LinkedList<>(participants);
-		newparts.add((OnePartyTeam) party);
+					"Added party must have one party but got " + team);
+		List<TeamInfo> newparts = new LinkedList<>(participants);
+		newparts.add(team);
 		return new MOPACSettings(newparts, deadline, votingevaluator);
 	}
@@ -101,4 +100,9 @@
 		return "MOPACSettings[" + participants + "," + deadline + ","
 				+ votingevaluator.getClass().getSimpleName() + "]";
+	}
+
+	@Override
+	public Integer getTeamSize() {
+		return 1;
 	}
 
@@ -147,5 +151,9 @@
 	 */
 	private void checkTeams() {
-		for (OnePartyTeam party : participants) {
+		for (TeamInfo team : participants) {
+			if (team.getSize() != 1)
+				throw new IllegalArgumentException(
+						"All teams must be size 1 but found " + team);
+			PartyWithProfile party = team.getAllParties().get(0);
 			Object power = party.getParty().getParameters().get("power");
 			if (power != null) {
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java	(revision 30)
@@ -0,0 +1,382 @@
+package geniusweb.protocol.session.mopac2;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.actions.Votes;
+import geniusweb.deadline.Deadline;
+import geniusweb.events.ProtocolEvent;
+import geniusweb.inform.Agreements;
+import geniusweb.inform.Finished;
+import geniusweb.inform.Inform;
+import geniusweb.inform.Settings;
+import geniusweb.inform.Voting;
+import geniusweb.inform.YourTurn;
+import geniusweb.issuevalue.Bid;
+import geniusweb.progress.ProgressFactory;
+import geniusweb.protocol.CurrentNegoState;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.mopac.phase.Phase;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import geniusweb.references.ProtocolRef;
+import geniusweb.references.Reference;
+import tudelft.utilities.listener.DefaultListenable;
+import tudelft.utilities.logging.Reporter;
+import tudelft.utilities.repository.NoResourcesNowException;
+
+/**
+ * 
+ * All parties are first sent the {@link SessionSettings}. Only parties
+ * specified initially in the settings do participate (no late joining in)
+ * 
+ * <h1>parameter</h1> MOPAC parties can receive a parameter <code>power</code>
+ * containing a Integer. This parameter is picked up by this protocol, for
+ * handling the agreement extraction from the offers. Power=1 for parties that
+ * do not have the power parameter.
+ * <h1>Protocol steps</h1>
+ * 
+ * <ol>
+ * <li>The protocol tries to start all parties. If not all parties start, the
+ * parties are freed up and another attempt is done to start all parties some
+ * time later.
+ * <li>The session deadline clock now starts ticking.
+ * <li>remainingparties are sent their settings.
+ * <li>Loop until {@link Deadline} is reached or |remainingparties| &le; 2:
+ * <ol>
+ * <li>protocol sends {@link YourTurn} to all remainingparties. Each party now
+ * must submit an {@link Offer} within {@link Phase#PHASE_MAXTIME} seconds. If a
+ * party fails to submit it is removed from from remainingparties.
+ * <li>protocol sends a {@link Voting} containing a List of Bid containing all
+ * received {@link Bid}s. Each party must place his {@link Votes} within the
+ * provided deadline. If a party does not submit, it is terminated and removed
+ * from remainingparties. Previous votes for the same bid do not count. But see
+ * {@link Agreements}. A party that misbehaves after submitting its vote is
+ * removed but it Votes remain standing.
+ * <li>protocol sends to all remainingparties a OptIn containing all received
+ * votes. Each party now must submit again a {@link Votes} object within the
+ * deadline. This new Votes must equal or extend the previous Votes of the
+ * party.
+ * <li>The protocol uses the {@link Phase#getEvaluator()} to determine the
+ * agreements from the votes. If there are agreements, the parties that placed
+ * these votes reached an agreement. They are moved to the agreement set and
+ * removed from the remainingparties.
+ * </ol>
+ * <li>Any remaining parties are sent a {@link Finished} object without
+ * agreement bid and are terminated.
+ * </ol>
+ * <p>
+ * A party can send {@link EndNegotiation} at any time to remove itself from the
+ * negotiation. The other parties may continue anyawy without him.
+ * <p>
+ * Parties receive the {@link Finished} information at the very end when all
+ * agreements have been collected.
+ * <p>
+ * This logs to "Protocol" logger if there are issues
+ * <p>
+ * This object is mutable: the internal state changes as parties interact with
+ * the protocol.
+ * <p>
+ * Threadsafe: all entrypoints are synhronized.
+ */
+public class MOPAC2 extends DefaultListenable<ProtocolEvent>
+		implements SessionProtocol {
+	/**
+	 * Tech notes. 0. Everything here is phase driven and runs due to callback
+	 * from parties. 1. Connect all parties. 2. If any party fails at any time,
+	 * add it to exceptions list in the state 3. Run timer to check the phase at
+	 * possible end time. The timer just checks the state, and therefore it does
+	 * no harm if it is double checked. 4. We run phases always with either
+	 * PHASE_TIME or time to the global deadline, whichever comes first.
+	 * Therefore phases naturally end at end of nego. 5. Phases must have at
+	 * least {@link #MINDURATION} otherwise it makes no sense to even start it.
+	 */
+	private static final int MINDURATION = 100;
+	private static final int MIN_SLEEP_TIME = 1000;
+	private static final int MAX_SLEEP_TIME = 60000;
+	private static final ProtocolRef MOPAC2 = new ProtocolRef("MOPAC2");
+
+	private final Reporter log;
+	private MOPAC2State state = null; // mutable!
+	/**
+	 * the existing party connections. we assume ownership of this so it should
+	 * not be modified although connections may of course break. mutable!
+	 */
+	private ProtocolToPartyConnections connections = new ProtocolToPartyConnections(
+			Collections.emptyList());
+
+	/**
+	 * Set to true after all is done: we sent final outcomes. This is needed
+	 * because we can't tell from state what we did at the very end.
+	 */
+	private boolean finished = false;
+	private Timer timer = null;
+
+	/**
+	 * 
+	 * @param state  normally the initial state coming from SAOPSettings
+	 * @param logger the {@link Reporter} to use
+	 */
+	public MOPAC2(MOPAC2State state, Reporter logger) {
+		if (state == null) {
+			throw new NullPointerException("state must be not null");
+		}
+		if (state.getSettings().getDeadline().getDuration() < MINDURATION) {
+			throw new IllegalArgumentException(
+					"Duration must be at least " + MINDURATION);
+		}
+		if (logger == null) {
+			throw new NullPointerException("Logger must be not null");
+		}
+		this.log = logger;
+		this.state = state;
+	}
+
+	@Override
+	public synchronized void start(
+			ProtocolToPartyConnFactory connectionfactory) {
+		try {
+			connect(connectionfactory);
+		} catch (Throwable e) {
+			/** We can't {@link #handleError} yet. FIXME */
+			throw new RuntimeException("Failed to connect", e);
+		}
+		long now = System.currentTimeMillis();
+		state = state.initPhase(
+				ProgressFactory.create(state.getSettings().getDeadline(), now),
+				now);
+		setupParties(now);
+		startPhase(now);
+
+	}
+
+	@Override
+	public String getDescription() {
+		return "All parties get YourTurn. They now can submit their Offer within 30 seconds. "
+				+ "Next they receive a Voting. They can now submit their Votes."
+				+ "Then they receive a a OptIn, now they can widen up their Votes."
+				+ "If one of their Vote succeeds, they finish with an agreement. "
+				+ "The VotingEvaluator setting determines the exact voting behaviour "
+				+ "and if this process repeats";
+	}
+
+	@Override
+	public void addParticipant(PartyWithProfile party)
+			throws IllegalStateException {
+		throw new IllegalStateException(
+				"Dynamic joining a negotiation is not supported in AMOP");
+	}
+
+	@Override
+	public MOPAC2State getState() {
+		return state;
+	}
+
+	@Override
+	public ProtocolRef getRef() {
+		return MOPAC2;
+	}
+
+	/*******************************************************************
+	 * private functions. Some are protected only, for testing purposes
+	 ********************************************************************/
+	/**
+	 * step 1 in protocol: connect all involved parties and start the clock.
+	 * This always "succeeds" with a valid (but possibly final) state
+	 * <p>
+	 * This is 'protected' to allow junit testing, this code is not a 'public'
+	 * part of the interface.
+	 * 
+	 * @param connectionfactory the connectionfactory for making party
+	 *                          connections
+	 * 
+	 * @throws InterruptedException if the connection procedure is unterrupted
+	 * 
+	 * @throws IOException          if this fails to properly conect to the
+	 *                              parties, eg interrupted or server not
+	 *                              responding..
+	 */
+	protected synchronized void connect(
+			ProtocolToPartyConnFactory connectionfactory)
+			throws InterruptedException, IOException {
+		List<PartyWithProfile> participants = state.getSettings()
+				.getAllParties();
+		List<Reference> parties = participants.stream()
+				.map(parti -> (parti.getParty().getPartyRef()))
+				.collect(Collectors.toList());
+		log.log(Level.INFO, "MOPAC connect " + parties);
+		List<ProtocolToPartyConn> newconnections = null;
+		while (newconnections == null) {
+			try {
+				newconnections = connectionfactory.connect(parties);
+			} catch (NoResourcesNowException e) {
+				long waitms = e.getLater().getTime()
+						- System.currentTimeMillis();
+				log.log(Level.INFO,
+						"No resources available to run session, waiting"
+								+ waitms);
+				Thread.sleep(Math.min(MAX_SLEEP_TIME,
+						Math.max(MIN_SLEEP_TIME, waitms)));
+			}
+		}
+		for (int i = 0; i < participants.size(); i++) {
+			ProtocolToPartyConn conn = newconnections.get(i);
+			connections = connections.with(conn);
+			state = state.with(conn.getParty(), participants.get(i));
+		}
+	}
+
+	/**
+	 * step 2 in protocol: listen to connections and send settings to the
+	 * parties.
+	 * <p>
+	 * This is 'protected' to allow junit testing, this code is not a 'public'
+	 * part of the interface.
+	 * 
+	 * @param now the current time.
+	 */
+	protected synchronized void setupParties(long now) {
+		for (ProtocolToPartyConn conn : connections) {
+			conn.addListener(action -> actionRequest(conn, action, now));
+		}
+		for (ProtocolToPartyConn connection : connections) {
+			try {
+				sendSettings(connection);
+			} catch (IOException e) {
+				state = state.with(new ProtocolException("Failed to initialize",
+						connection.getParty()));
+			}
+		}
+	}
+
+	/**
+	 * Send the {@link Inform} for the current phase to the remaining parties
+	 * and start the time-out checker
+	 */
+	private void startPhase(long now) {
+		Inform info = state.getPhase().getInform();
+		for (PartyId pid : state.getPhase().getPartyStates().getNotYetActed()) {
+			try {
+				connections.get(pid).send(info);
+			} catch (IOException e) {
+				state = state.with(new ProtocolException(
+						"Party seems to have disconnected", pid, e));
+			}
+		}
+		startTimer(1 + state.getPhase().getDeadline() - now);
+	}
+
+	/**
+	 * Check at given deadline if we already ended the phase.
+	 * 
+	 * @param deadn the deadline
+	 */
+	private void startTimer(long deadln) {
+		if (timer != null)
+			throw new IllegalStateException("Timer is still running!");
+		timer = new Timer("deadlinetimer");
+		timer.schedule(new TimerTask() {
+			@Override
+			public void run() {
+				checkEndPhase(System.currentTimeMillis());
+			}
+		}, deadln);
+
+	}
+
+	/**
+	 * Inform a party about its settings
+	 * 
+	 * @param connection
+	 * @throws IOException if party got disconnected
+	 */
+	private synchronized void sendSettings(ProtocolToPartyConn connection)
+			throws IOException {
+		PartyId partyid = connection.getParty();
+		ProfileRef profile = state.getPartyProfiles().get(partyid).getProfile();
+		Parameters params = state.getPartyProfiles().get(partyid).getParty()
+				.getParameters();
+		connection.send(new Settings(connection.getParty(), profile, getRef(),
+				state.getProgress(), params));
+	}
+
+	/**
+	 * This is called when one of the {@link ProtocolToPartyConn}s does an
+	 * action. Synchronized so that we always handle only 1 action at a time.
+	 * 
+	 * @param partyconn the connection on which the action came in
+	 * @param action    the {@link Action} taken by some party
+	 * @param now       current time
+	 */
+	protected synchronized void actionRequest(
+			final ProtocolToPartyConn partyconn, final Action action,
+			long now) {
+		if (finished)
+			return;
+		state = state.with(partyconn.getParty(), action, now);
+		checkEndPhase(System.currentTimeMillis());
+	}
+
+	/**
+	 * The current phase may be completed. We check, because it may already been
+	 * handled. Proceed to next phase as needed. Reset the deadline timers and
+	 * inform parties. Increase progress if necessary. Must only be called
+	 * through {@link #endPhase} to ensure this is called only once.
+	 * 
+	 */
+	private synchronized void checkEndPhase(long now) {
+		if (!state.getPhase().isFinal(now))
+			return;
+		// phase indeed ended. Check what's up.
+		if (timer != null) {
+			timer.cancel();
+			timer = null;
+		}
+		state = state.finishPhase();
+		if (state.isFinal(now)) {
+			endNegotiation();
+			return;
+		}
+
+		state = state.nextPhase(now);
+
+		startPhase(now);
+	}
+
+	/**
+	 * To be called when we reach final state. Must only be called if
+	 * {@link MOPAC2State#isFinal(long)}. Send finished info to all parties.
+	 * Double calls are automatically ignored using the global finished flag.
+	 */
+	private synchronized void endNegotiation() {
+		if (finished)
+			return;
+		finished = true;
+		Finished info = new Finished(state.getAgreements());
+		for (ProtocolToPartyConn conn : connections) {
+			try {
+				conn.send(info);
+				conn.close();
+			} catch (Exception e) {
+				log.log(Level.INFO, "Failed to send Finished to " + conn, e);
+			}
+		}
+		notifyListeners(new CurrentNegoState(state));
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2Settings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2Settings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2Settings.java	(revision 30)
@@ -0,0 +1,175 @@
+package geniusweb.protocol.session.mopac2;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import geniusweb.deadline.Deadline;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.voting.VotingEvaluator;
+import geniusweb.voting.VotingEvaluatorWithValue;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * Settings for MOPAC negotiation. in MOPAC, each party may get a "power"
+ * parameter containing an natural number &le;1.
+ */
+public class MOPAC2Settings implements SessionSettings {
+	private final List<TeamInfo> participants;
+	private final Deadline deadline;
+	private final VotingEvaluatorWithValue votingevaluator;
+
+	/**
+	 * 
+	 * @param participants the list of {@link PartyWithProfile} in clockwise
+	 *                     order. There must be at least 2 to run the MOPAC
+	 *                     protocol. This is not tested in the constructor
+	 *                     because this can be initialized with less, for use in
+	 *                     TournamentSettings.
+	 * @param deadline     the {@link Deadline} for the negotiation
+	 * @param votingeval   the {@link VotingEvaluator} to use.
+	 */
+	@JsonCreator
+	public MOPAC2Settings(
+			@JsonProperty("participants") List<TeamInfo> participants,
+			@JsonProperty("deadline") Deadline deadline,
+			@JsonProperty("votingevaluator") VotingEvaluatorWithValue votingeval) {
+		this.participants = participants;
+		this.deadline = deadline;
+		if (participants == null || deadline == null || votingeval == null)
+			throw new IllegalArgumentException(
+					"participants, deadline and votingeval must be not null");
+		this.votingevaluator = votingeval;
+		checkTeams();
+	}
+
+	@Override
+	public Double getMaxRunTime() {
+		return deadline.getDuration() / 1000d;
+	}
+
+	@Override
+	public MOPAC2 getProtocol(Reporter logger) {
+		return new MOPAC2(new MOPAC2State(this), logger);
+	}
+
+	@Override
+	public List<TeamInfo> getTeams() {
+		return Collections.unmodifiableList(participants);
+	}
+
+	/**
+	 * @return the deadline for this negotiation
+	 */
+	public Deadline getDeadline() {
+		return deadline;
+	}
+
+	@Override
+	public List<PartyWithProfile> getAllParties() {
+		return participants.stream()
+				.map(particip -> particip.getAllParties().get(0))
+				.collect(Collectors.toList());
+	}
+
+	/**
+	 * @return a class that allows us to evaluate the voting results in
+	 *         different ways, selectable by the user.
+	 */
+	public VotingEvaluatorWithValue getVotingEvaluation() {
+		return votingevaluator;
+	}
+
+	@Override
+	public MOPAC2Settings with(TeamInfo team) {
+		if (team.getSize() != 1)
+			throw new IllegalArgumentException(
+					"Added party must be OnePartyTeam but got " + team);
+		List<TeamInfo> newparts = new LinkedList<>(
+				participants);
+		newparts.add(team);
+		return new MOPAC2Settings(newparts, deadline, votingevaluator);
+	}
+
+	@Override
+	public Integer getTeamSize() {
+		return 1;
+	}
+
+	@Override
+	public String toString() {
+		return "MOPAC2Settings[" + participants + "," + deadline + ","
+				+ votingevaluator.getClass().getSimpleName() + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((deadline == null) ? 0 : deadline.hashCode());
+		result = prime * result
+				+ ((participants == null) ? 0 : participants.hashCode());
+		result = prime * result
+				+ ((votingevaluator == null) ? 0 : votingevaluator.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;
+		MOPAC2Settings other = (MOPAC2Settings) obj;
+		if (deadline == null) {
+			if (other.deadline != null)
+				return false;
+		} else if (!deadline.equals(other.deadline))
+			return false;
+		if (participants == null) {
+			if (other.participants != null)
+				return false;
+		} else if (!participants.equals(other.participants))
+			return false;
+		if (votingevaluator == null) {
+			if (other.votingevaluator != null)
+				return false;
+		} else if (!votingevaluator.equals(other.votingevaluator))
+			return false;
+		return true;
+	}
+
+	/**
+	 * @throws IllegalArgumentException if teams have improper power settings.
+	 */
+	private void checkTeams() {
+		for (TeamInfo team : participants) {
+			if (team.getSize() != 1)
+				throw new IllegalArgumentException(
+						"All teams must be size 1 but found " + team);
+			PartyWithProfile party = team.getAllParties().get(0);
+			Object power = party.getParty().getParameters().get("power");
+			if (power != null) {
+				if (!(power instanceof Integer))
+					throw new IllegalArgumentException(
+							"parameter 'power' for party" + party
+									+ " must be integer but found " + power);
+				if ((Integer) power < 1)
+					throw new IllegalArgumentException(
+							"parameter 'power'  for party" + party
+									+ " must be >=1 but found " + power);
+
+			}
+		}
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2State.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2State.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2State.java	(revision 30)
@@ -0,0 +1,358 @@
+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.
+ * <p>
+ * This object is a bit tricky. It has two states
+ * <ol>
+ * <li>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
+ * <li>The working state, where all parties are connected and all problems can
+ * be connected to an offending party.
+ * </ol>
+ */
+public class MOPAC2State implements SessionState {
+
+	private final Phase phase; // maybe null while initializing
+	private final MOPAC2Settings settings;
+	private final Map<PartyId, PartyWithProfile> partyprofiles;
+	private final List<Action> 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<Action> actions,
+			@JsonProperty("progress") Progress progress,
+			@JsonProperty("settings") MOPAC2Settings settings,
+			@JsonProperty("partyprofiles") Map<PartyId, PartyWithProfile> 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<Action> 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<PartyId, PartyWithProfile> getPartyProfiles() {
+		return Collections.unmodifiableMap(partyprofiles);
+	}
+
+	@Override
+	public boolean isFinal(long now) {
+		return phase != null && phase.isFinal(now) && !isNewPhasePossible(now);
+	}
+
+	@Override
+	public SessionResult getResult() {
+		return 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<PartyId, PartyWithProfile> 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 &gt;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<PartyId, Integer> getPowers() {
+		Map<PartyId, Integer> 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<Action> newactions = new LinkedList<Action>(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;
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/PartyStates.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/PartyStates.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/PartyStates.java	(revision 30)
@@ -0,0 +1,306 @@
+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}.
+ * <p>
+ * 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<PartyId> notYetActed;
+	private final List<Action> actions; // chrono order
+	private final Agreements agreements;
+	private final List<PartyId> walkedAway;
+	private final Map<PartyId, ProtocolException> exceptions;
+	private final Map<PartyId, Integer> powers;
+
+	@JsonCreator
+	protected PartyStates(@JsonProperty("notYetActed") Set<PartyId> notYetActed,
+			@JsonProperty("actions") List<Action> actions,
+			@JsonProperty("agreements") Agreements agreements,
+			@JsonProperty("walkedAway") List<PartyId> walkedAway,
+			@JsonProperty("exceptions") Map<PartyId, ProtocolException> exceptions,
+			@JsonProperty("powers") Map<PartyId, Integer> 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<PartyId, Integer> 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<Action> newActions = new LinkedList<Action>(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<PartyId> parties = newAgree.getMap().keySet();
+		List<Action> 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<PartyId> 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<PartyId, ProtocolException> 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<PartyId> 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<PartyId> getNegotiatingParties() {
+		Set<PartyId> 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<PartyId> removeParty(PartyId party) {
+		Set<PartyId> 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<PartyId, Integer> getPowers() {
+		return Collections.unmodifiableMap(powers);
+	}
+
+	public Map<PartyId, ProtocolException> 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<Action> getActions() {
+		return Collections.unmodifiableList(actions);
+	}
+
+	/**
+	 * 
+	 * @return all parties that walked away
+	 */
+	public List<PartyId> getWalkedAway() {
+		return walkedAway;
+	}
+
+	/**
+	 * @param <T>  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 <T extends Action> List<T> getActions(Class<T> 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;
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/DefaultPhase.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/DefaultPhase.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/DefaultPhase.java	(revision 30)
@@ -0,0 +1,146 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.PartyId;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.VotingEvaluator;
+import geniusweb.voting.VotingEvaluatorWithValue;
+
+public abstract class DefaultPhase implements Phase {
+	// deadline for this phase, ms since 1970
+	protected final Long deadline;
+	protected final PartyStates partyStates;
+	protected final VotingEvaluatorWithValue evaluator;
+
+	/**
+	 * @param partyStates the {@link PartyStates}
+	 * @param deadline    deadline for this phase, ms since 1970
+	 * @param evaluator   the {@link VotingEvaluator} to be used
+	 */
+	public DefaultPhase(PartyStates partyStates, Long deadline,
+			VotingEvaluatorWithValue evaluator) {
+		this.partyStates = partyStates;
+		this.deadline = deadline;
+		this.evaluator = evaluator;
+	}
+
+	@Override
+	public boolean isFinal(long now) {
+		return now >= deadline || partyStates.getNotYetActed().isEmpty();
+	}
+
+	@Override
+	public long getDeadline() {
+		return deadline;
+	}
+
+	/**
+	 * Check if actor can do given action. Basic checks:
+	 * <ul>
+	 * <li>real actor does not match action's actor
+	 * <li>deadline for this phase has passed
+	 * <li>action is not allowed in this phase
+	 * <li>actor already acted in this phase
+	 * </ul>
+	 * 
+	 * @param actor  the actor that really acted
+	 * @param action the action that was done
+	 * @param now    current time
+	 * @throws ProtocolException if the action violates the protocol
+	 */
+	protected void checkAction(PartyId actor, Action action, long now)
+			throws ProtocolException {
+		if (action == null)
+			throw new ProtocolException("Action=null", actor);
+		if (!(actor.equals(action.getActor())))
+			throw new ProtocolException(
+					"Incorrect actor info in action:" + action.getActor(),
+					actor);
+		if (isFinal(now))
+			throw new ProtocolException("passed deadline", actor);
+		if (!(getAllowedActions().contains(action.getClass())))
+			throw new ProtocolException("Action not allowed in "
+					+ this.getClass().getSimpleName() + ": " + action, actor);
+		if (!partyStates.getNotYetActed().contains(actor))
+			throw new ProtocolException("Actor can not act anymore", actor);
+		return;
+	}
+
+	/**
+	 * @return current PartyStates
+	 */
+	@Override
+	public PartyStates getPartyStates() {
+		return partyStates;
+	}
+
+	@Override
+	public VotingEvaluatorWithValue getEvaluator() {
+		return evaluator;
+	}
+
+	@Override
+	public Phase next(long now, long duration) {
+		if (duration < PHASE_MINTIME || duration > PHASE_MAXTIME)
+			throw new IllegalArgumentException("Bug, illegal duration");
+		if (!isFinal(now))
+			throw new IllegalStateException("phase is not final");
+		return checkedNext(now + duration);
+	}
+
+	/**
+	 * As {@link #next(long, long)} but DefaultPhase already checked that 1.
+	 * there is enough time for a next phase 2. current state is final 3.
+	 * 
+	 * @param dl the deadline for the next phase (ms since 1970).
+	 * @return the next phase
+	 */
+	protected abstract Phase checkedNext(long dl);
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + "[" + partyStates + "," + deadline
+				+ "," + evaluator + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((deadline == null) ? 0 : deadline.hashCode());
+		result = prime * result
+				+ ((evaluator == null) ? 0 : evaluator.hashCode());
+		result = prime * result
+				+ ((partyStates == null) ? 0 : partyStates.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;
+		DefaultPhase other = (DefaultPhase) obj;
+		if (deadline == null) {
+			if (other.deadline != null)
+				return false;
+		} else if (!deadline.equals(other.deadline))
+			return false;
+		if (evaluator == null) {
+			if (other.evaluator != null)
+				return false;
+		} else if (!evaluator.equals(other.evaluator))
+			return false;
+		if (partyStates == null) {
+			if (other.partyStates != null)
+				return false;
+		} else if (!partyStates.equals(other.partyStates))
+			return false;
+		return true;
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/OfferPhase.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/OfferPhase.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/OfferPhase.java	(revision 30)
@@ -0,0 +1,71 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.inform.Inform;
+import geniusweb.inform.YourTurn;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.VotingEvaluatorWithValue;
+
+public class OfferPhase extends DefaultPhase {
+
+	@JsonCreator
+	public OfferPhase(@JsonProperty("partyStates") PartyStates partyStates,
+			@JsonProperty("deadlinems") Long deadlinems,
+			@JsonProperty("evaluator") VotingEvaluatorWithValue evaluator) {
+		super(partyStates, deadlinems, evaluator);
+	}
+
+	@Override
+	public OfferPhase with(PartyId actor, Action action, long timems) {
+		try {
+			checkAction(actor, action, timems);
+		} catch (ProtocolException ex) {
+			return this.with(ex);
+		}
+		return new OfferPhase(partyStates.with(action), deadline, evaluator);
+	}
+
+	@Override
+	public Inform getInform() {
+		return new YourTurn();
+	}
+
+	@Override
+	public OfferPhase with(ProtocolException e) {
+		System.out.println("Party kicked because of protocol exception:" + e);
+		return new OfferPhase(partyStates.with(e), deadline, evaluator);
+	}
+
+	@Override
+	public Phase finish() {
+		return new OfferPhase(partyStates.finish(), deadline, evaluator);
+	}
+
+	@Override
+	public VotingPhase checkedNext(long deadln) {
+		return new VotingPhase(getOffers(), partyStates.flush(), deadln,
+				evaluator);
+	}
+
+	protected List<Offer> getOffers() {
+		return partyStates.getActions(Offer.class).stream()
+				.collect(Collectors.toList());
+	}
+
+	@Override
+	public List<Class<? extends Action>> getAllowedActions() {
+		return Arrays.asList(Offer.class, EndNegotiation.class);
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/OptInPhase.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/OptInPhase.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/OptInPhase.java	(revision 30)
@@ -0,0 +1,110 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+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.actions.Votes;
+import geniusweb.actions.VotesWithValue;
+import geniusweb.inform.Agreements;
+import geniusweb.inform.Inform;
+import geniusweb.inform.OptInWithValue;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.CollectedVotesWithValue;
+import geniusweb.voting.VotingEvaluatorWithValue;
+
+public class OptInPhase extends DefaultPhase {
+
+	/**
+	 * The votes received in the {@link VotingPhase}
+	 */
+	private final List<VotesWithValue> votes;
+
+	@JsonCreator
+	protected OptInPhase(@JsonProperty("votes") List<VotesWithValue> votes,
+			@JsonProperty("partyStates") PartyStates partyStates,
+			@JsonProperty("deadlinems") Long deadlinems,
+			@JsonProperty("evaluator") VotingEvaluatorWithValue evaluator) {
+		super(partyStates, deadlinems, evaluator);
+		this.votes = votes;
+	}
+
+	@Override
+	public Inform getInform() {
+		return new OptInWithValue(votes);
+	}
+
+	@Override
+	public Phase with(PartyId actor, Action action, long now) {
+		try {
+			checkAction(actor, action, now);
+			if (action instanceof VotesWithValue)
+				checkExtends((VotesWithValue) action);
+
+			return new OptInPhase(votes, partyStates.with(action), deadline,
+					evaluator);
+
+		} catch (ProtocolException e) {
+			return this.with(e);
+		}
+	}
+
+	/**
+	 * Check that this action extends previous action.
+	 * 
+	 * @param newvotes new {@link Votes} just received
+	 * @throws ProtocolException if this action does not correctly extend
+	 *                           previous vote.
+	 */
+	private void checkExtends(VotesWithValue newvotes)
+			throws ProtocolException {
+		PartyId actor = newvotes.getActor();
+		// this actor is active so he must have voted in previous round.
+		VotesWithValue prevVotes = votes.stream()
+				.filter(v -> v.getActor().equals(actor)).findFirst().get();
+		if (!(newvotes.isExtending(prevVotes)))
+			throw new ProtocolException("New votes " + newvotes
+					+ " does not extend previous vote " + prevVotes, actor);
+	}
+
+	@Override
+	public OptInPhase with(ProtocolException e) {
+		return new OptInPhase(votes, partyStates.with(e), deadline, evaluator);
+	}
+
+	@Override
+	public OptInPhase finish() {
+		PartyStates states = partyStates.finish();
+		Map<PartyId, VotesWithValue> votesmap = states
+				.getActions(VotesWithValue.class).stream()
+				.collect(Collectors.toMap(vts -> vts.getActor(), v -> v));
+		CollectedVotesWithValue allvotes = new CollectedVotesWithValue(votesmap,
+				states.getPowers());
+		Agreements newAgree = evaluator.create(allvotes).getAgreements();
+		if (!newAgree.getMap().isEmpty()) {
+			System.out.println("detected new agreements");
+		}
+		PartyStates finalStates = states.with(newAgree);
+
+		return new OptInPhase(votes, finalStates, deadline, evaluator);
+	}
+
+	@Override
+	public OfferPhase checkedNext(long newdeadline) {
+		return new OfferPhase(partyStates.flush(), newdeadline, evaluator);
+	}
+
+	@Override
+	public List<Class<? extends Action>> getAllowedActions() {
+		return Arrays.asList(VotesWithValue.class, EndNegotiation.class);
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/Phase.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/Phase.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/Phase.java	(revision 30)
@@ -0,0 +1,121 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.PartyId;
+import geniusweb.inform.Inform;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.VotingEvaluatorWithValue;
+
+/**
+ * A Phase is a part of the round structure. In each round parties have to take
+ * multiple actions. Each action is part of a phase.
+ * 
+ * Invariant: A phase object handles negotiation events, ensuring that the
+ * events are handled according to the possible actions in this phase. A phase
+ * object must always remain in a consistent state. It does so by modifying the
+ * contained {@link PartyStates} as needed.
+ * <p>
+ * The standard exception handling is assumed: unchecked exceptions are thrown
+ * only if there is a bug in the protocol. Because none of the functions in this
+ * interface throws, any error must be either ignored or the party must be
+ * kicked.
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
+@JsonSubTypes({ @JsonSubTypes.Type(value = OfferPhase.class),
+		@JsonSubTypes.Type(value = OptInPhase.class),
+		@JsonSubTypes.Type(value = VotingPhase.class) })
+public interface Phase {
+	public static final Long PHASE_MAXTIME = 30000l; // 30sec
+	public static final Long PHASE_MINTIME = 100l; // 100 millisec
+
+	/**
+	 * Handle an actor's action. If a {@link ProtocolException} occurs, this is
+	 * handled by updating the PartyStates
+	 * 
+	 * @param actor  the real actor (may differ from the actor contained in the
+	 *               action)
+	 * @param action the action submitted by actor, which this phase can really
+	 *               handle.
+	 * @param now    the current time
+	 * @return new VotingPhase
+	 */
+	public Phase with(PartyId actor, Action action, long now);
+
+	/**
+	 * 
+	 * @param e a {@link ProtocolException}
+	 * @return new Phase with a party disabled because it violated a protocol eg
+	 *         breaking a websocket link.
+	 */
+	public Phase with(ProtocolException e);
+
+	/**
+	 * 
+	 * @return the inform object to send to all parties at start of phase.
+	 */
+	public Inform getInform();
+
+	/**
+	 * 
+	 * @param now the current time
+	 * @return true iff past deadline or no more actions are allowed anyway.
+	 *         (which usually means, all parties have done exactly one act).
+	 *         Notice, this is the main reason that it is desirable to require
+	 *         all parties to act exactly once in each phase. Without such a
+	 *         rule, the protocol will always have to wait till the deadline.
+	 */
+	public boolean isFinal(long now);
+
+	/**
+	 * 
+	 * @return finished state. That means, all parties that did not act have
+	 *         been kicked, new agreements have been computed, etc.
+	 */
+	public Phase finish();
+
+	/**
+	 * Determines the next phase. Resets the actions field. Can only be called
+	 * after {@link #finish()}
+	 * 
+	 * @param now      the current time
+	 * @param duration the max duration (ms) of the next phase. Must be between
+	 *                 {@link #PHASE_MINTIME} and {@link #PHASE_MAXTIME}. Also
+	 *                 make sure that now+duration is at most at the total
+	 *                 negotiation deadline.
+	 * @return the next phase, or null if negotiation is complete.
+	 */
+	public Phase next(long now, long duration);
+
+	/**
+	 * @return the voting evaluator
+	 */
+	public VotingEvaluatorWithValue getEvaluator();
+
+	/**
+	 * 
+	 * @return time (ms since 1970) at which phase ends. The phase may end
+	 *         before, but never after this.
+	 */
+	public long getDeadline();
+
+	/**
+	 * @return the party states. Notice that someone must call
+	 *         {@link PartyStates#finish()} if passed the deadline.
+	 */
+	public PartyStates getPartyStates();
+
+	/**
+	 * @return the allowed actinos in this phase
+	 */
+	public abstract List<Class<? extends Action>> getAllowedActions();
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/VotingPhase.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/VotingPhase.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/phase/VotingPhase.java	(revision 30)
@@ -0,0 +1,85 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.actions.VotesWithValue;
+import geniusweb.inform.Inform;
+import geniusweb.inform.Voting;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.VotingEvaluatorWithValue;
+
+public class VotingPhase extends DefaultPhase {
+
+	/**
+	 * the offers received in the {@link OfferPhase}
+	 */
+	private final List<Offer> offers;
+
+	@JsonCreator
+	protected VotingPhase(@JsonProperty("offers") List<Offer> offers,
+			@JsonProperty("partyStates") PartyStates partyStates,
+			@JsonProperty("deadlinems") Long deadlinems,
+			@JsonProperty("evaluator") VotingEvaluatorWithValue evaluator) {
+		super(partyStates, deadlinems, evaluator);
+		this.offers = offers;
+	}
+
+	@Override
+	public VotingPhase with(PartyId actor, Action action, long now) {
+		try {
+			checkAction(actor, action, now);
+		} catch (ProtocolException ex) {
+			return this.with(ex);
+		}
+		return new VotingPhase(offers, partyStates.with(action), deadline,
+				evaluator);
+	}
+
+	@Override
+	public Inform getInform() {
+		return new Voting(offers, partyStates.getPowers());
+	}
+
+	@Override
+	public VotingPhase with(ProtocolException e) {
+		return new VotingPhase(offers, partyStates.with(e), deadline,
+				evaluator);
+	}
+
+	@Override
+	public VotingPhase finish() {
+		return new VotingPhase(offers, partyStates.finish(), deadline,
+				evaluator);
+	}
+
+	@Override
+	protected Phase checkedNext(long deadln) {
+		return new OptInPhase(getVotes(), partyStates.flush(), deadln,
+				evaluator);
+	}
+
+	@Override
+	public List<Class<? extends Action>> getAllowedActions() {
+		return Arrays.asList(VotesWithValue.class, EndNegotiation.class);
+	}
+
+	/**
+	 * @return all votes done in this phase.
+	 */
+	public List<VotesWithValue> getVotes() {
+		return partyStates.getActions().stream()
+				.filter(act -> act instanceof VotesWithValue)
+				.map(act -> (VotesWithValue) act).collect(Collectors.toList());
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 29)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 30)
@@ -10,13 +10,12 @@
 
 import geniusweb.deadline.Deadline;
-import geniusweb.protocol.session.OnePartyTeam;
 import geniusweb.protocol.session.SessionProtocol;
 import geniusweb.protocol.session.SessionSettings;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
 
 public class SAOPSettings implements SessionSettings {
-	private final List<OnePartyTeam> participants;
+	private final List<TeamInfo> participants;
 	private final Deadline deadline;
 
@@ -31,5 +30,5 @@
 	@JsonCreator
 	public SAOPSettings(
-			@JsonProperty("participants") List<OnePartyTeam> participants,
+			@JsonProperty("participants") List<TeamInfo> participants,
 			@JsonProperty("deadline") Deadline deadline) {
 		this.participants = participants;
@@ -54,5 +53,5 @@
 
 	@Override
-	public List<TeamOfPartiesAndProfiles> getTeams() {
+	public List<TeamInfo> getTeams() {
 		return Collections.unmodifiableList(participants);
 	}
@@ -70,4 +69,9 @@
 				.map(particip -> particip.getAllParties().get(0))
 				.collect(Collectors.toList());
+	}
+
+	@Override
+	public Integer getTeamSize() {
+		return 1;
 	}
 
@@ -111,10 +115,11 @@
 
 	@Override
-	public SessionSettings with(TeamOfPartiesAndProfiles party) {
-		if (!(party instanceof OnePartyTeam))
+	public SessionSettings with(TeamInfo team) {
+		if (team.getSize() != 1)
 			throw new IllegalArgumentException(
-					"Added party must be OnePartyTeam but got " + party);
-		List<OnePartyTeam> newparts = new LinkedList<>(participants);
-		newparts.add((OnePartyTeam) party);
+					"Team must be size 1 but found " + team);
+		List<TeamInfo> newparts = new LinkedList<>(
+				participants);
+		newparts.add(team);
 		return new SAOPSettings(newparts, deadline);
 	}
Index: protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOP.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOP.java	(revision 29)
+++ protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOP.java	(revision 30)
@@ -33,4 +33,5 @@
 import geniusweb.protocol.session.SessionProtocol;
 import geniusweb.protocol.session.SessionState;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.Parameters;
 import geniusweb.references.PartyWithProfile;
@@ -59,5 +60,5 @@
  * <p>
  * The Comparison/ComparisonTest actions are private between the
- * {@link ShaopTeam}. A SHAOP party can do a {@link ElicitComparison} action at
+ * {@link TeamInfo}. A SHAOP party can do a {@link ElicitComparison} action at
  * any time. These actions are completely transparent to the other parties and
  * are not dependent or influencing the normal turn taking.
@@ -80,5 +81,5 @@
 			false);
 	private volatile Timer deadlinetimer = null;
-	private List<Class> ALLOWED_ACTIONS = Arrays.asList(Offer.class,
+	private List ALLOWED_ACTIONS = Arrays.asList(Offer.class,
 			ElicitComparison.class, Accept.class, EndNegotiation.class);
 
Index: protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPSettings.java	(revision 29)
+++ protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPSettings.java	(revision 30)
@@ -13,22 +13,25 @@
 import geniusweb.protocol.session.SessionProtocol;
 import geniusweb.protocol.session.SessionSettings;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
 
 public class SHAOPSettings implements SessionSettings {
-	private final List<ShaopTeam> participants;
+	private static final int SHAOP = 0;
+	private static final int COB = 1;
+	private final List<TeamInfo> participants;
 	private final Deadline deadline;
 	private final transient List<PartyWithProfile> allParties = new LinkedList<>();
 
 	/**
-	 * @param teams    the list of {@link ShaopTeam}s containing the shaop
-	 *                 parties and their partner cob There must be at least 2 to
-	 *                 run the SHAOP protocol. But SHAOP can be initialized with
-	 *                 less, for use in TournamentSettings.
+	 * @param teams    the {@link TeamInfo}s each containing the shaop parties
+	 *                 and their partner cob, in that order. There must be at
+	 *                 least 2 {@link TeamInfo}s to run the SHAOP protocol. But
+	 *                 SHAOP can be initialized with less, for use in
+	 *                 TournamentSettings.
 	 * @param deadline the deadline settings for the session
 	 */
 	@JsonCreator
-	public SHAOPSettings(@JsonProperty("participants") List<ShaopTeam> teams,
+	public SHAOPSettings(@JsonProperty("participants") List<TeamInfo> teams,
 			@JsonProperty("deadline") Deadline deadline) {
 		if (teams == null)
@@ -39,7 +42,10 @@
 		this.deadline = deadline;
 		// notice, order here is crucial because of way #isShaopParty works.
-		for (ShaopTeam tuple : teams) {
-			allParties.add(tuple.getShaop());
-			allParties.add(tuple.getCob());
+		for (TeamInfo team : teams) {
+			if (team.getSize() != 2)
+				throw new IllegalArgumentException(
+						"Team must be size 2 but found " + team);
+			allParties.add(team.getAllParties().get(SHAOP));
+			allParties.add(team.getAllParties().get(COB));
 		}
 	}
@@ -56,7 +62,7 @@
 
 	@Override
-	public List<TeamOfPartiesAndProfiles> getTeams() {
+	public List<TeamInfo> getTeams() {
 		// cast needed...
-		return new ArrayList<TeamOfPartiesAndProfiles>(participants);
+		return new ArrayList<TeamInfo>(participants);
 	}
 
@@ -81,11 +87,13 @@
 
 	@Override
-	public SessionSettings with(TeamOfPartiesAndProfiles party) {
-		if (!(party instanceof ShaopTeam))
-			throw new IllegalArgumentException(
-					"Added party must be ShaopTeam but got " + party);
-		List<ShaopTeam> newparts = new LinkedList<>(participants);
-		newparts.add((ShaopTeam) party);
+	public SessionSettings with(TeamInfo team) {
+		List<TeamInfo> newparts = new LinkedList<>(participants);
+		newparts.add(team);
 		return new SHAOPSettings(newparts, deadline);
+	}
+
+	@Override
+	public Integer getTeamSize() {
+		return 2;
 	}
 
Index: protocol/src/main/java/geniusweb/protocol/session/shaop/ShaopTeam.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/ShaopTeam.java	(revision 29)
+++ 	(revision )
@@ -1,93 +1,0 @@
-package geniusweb.protocol.session.shaop;
-
-import java.util.Arrays;
-import java.util.List;
-
-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.Comparison;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
-import geniusweb.references.PartyWithProfile;
-
-/**
- * A tuple of a SHOAP party and a COB party. Part of the settings.
- *
- */
-@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
-public class ShaopTeam implements TeamOfPartiesAndProfiles {
-	private final PartyWithProfile shaop;
-	private final PartyWithProfile cob;
-
-	/**
-	 * @param shaop {@link PartyWithProfile} participants supporting SHAOP
-	 *              behaviour. SHAOP behaviour has parameters:
-	 *              <ul>
-	 *              <li>elicitationcost: the value must be a double number
-	 *              indicating the costs of each RequestComparison action.
-	 *              <li>credit: the credit the party has to start with. The sum
-	 *              of the elicitationcost should stay below this.
-	 *              </ul>
-	 * @param cob   {@link PartyWithProfile} party, must support the COB
-	 *              behaviour. This is the cob partie that the shaop requests
-	 *              for {@link Comparison} will be forwarded to
-	 */
-	@JsonCreator
-	public ShaopTeam(@JsonProperty("shoap") PartyWithProfile shaop,
-			@JsonProperty("cob") PartyWithProfile cob) {
-		this.shaop = shaop;
-		this.cob = cob;
-	}
-
-	@Override
-	public List<PartyWithProfile> getAllParties() {
-		return Arrays.asList(shaop, cob);
-	}
-
-	@Override
-	public String toString() {
-		return "Parties[" + shaop + "," + cob + "]";
-	}
-
-	public PartyWithProfile getShaop() {
-		return shaop;
-	}
-
-	public PartyWithProfile getCob() {
-		return cob;
-	}
-
-	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + ((cob == null) ? 0 : cob.hashCode());
-		result = prime * result + ((shaop == null) ? 0 : shaop.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;
-		ShaopTeam other = (ShaopTeam) obj;
-		if (cob == null) {
-			if (other.cob != null)
-				return false;
-		} else if (!cob.equals(other.cob))
-			return false;
-		if (shaop == null) {
-			if (other.shaop != null)
-				return false;
-		} else if (!shaop.equals(other.shaop))
-			return false;
-		return true;
-	}
-
-}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 29)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 30)
@@ -3,4 +3,5 @@
 import java.math.BigInteger;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.function.BiFunction;
@@ -9,15 +10,13 @@
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-import geniusweb.protocol.session.OnePartyTeam;
 import geniusweb.protocol.session.SessionSettings;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
-import geniusweb.protocol.session.amop.AMOPSettings;
-import geniusweb.protocol.session.mopac.MOPACSettings;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.protocol.session.saop.SAOPSettings;
 import geniusweb.protocol.session.shaop.SHAOPSettings;
-import geniusweb.protocol.session.shaop.ShaopTeam;
 import geniusweb.protocol.tournament.TournamentProtocol;
 import geniusweb.protocol.tournament.TournamentSettings;
+import geniusweb.references.PartyWithParameters;
 import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
 import tudelft.utilities.immutablelist.FixedList;
 import tudelft.utilities.immutablelist.ImmutableList;
@@ -82,7 +81,7 @@
 	 * @param teamsPerSession number of parties per session, must be at least 2.
 	 * @param sesettings      The generic {@link SessionSettings}.
-	 *                        {@link SessionSettings#with(TeamOfPartiesAndProfiles)}
+	 *                        {@link SessionSettings#with(TeamInfo)}
 	 *                        will be used to add the required
-	 *                        {@link TeamOfPartiesAndProfiles}s
+	 *                        {@link TeamInfo}s
 	 * @param nTournaments    the number of times the tournament should be run.
 	 */
@@ -105,14 +104,5 @@
 		if (nTournaments <= 0)
 			throw new IllegalArgumentException("nTournaments must be >0");
-		int teamsize;
-		if (sesettings instanceof SAOPSettings
-				|| sesettings instanceof MOPACSettings
-				|| sesettings instanceof AMOPSettings)
-			teamsize = 1;
-		else if (sesettings instanceof SHAOPSettings)
-			teamsize = 2;
-		else
-			throw new IllegalArgumentException(
-					"Unsupported protocol " + sesettings.getClass());
+		int teamsize = sesettings.getTeamSize();
 
 		for (Team team : teams) {
@@ -156,5 +146,5 @@
 	 */
 	public ImmutableList<SessionSettings> permutations() {
-		ImmutableList<ImmutableList<TeamOfPartiesAndProfiles>> partylistlist;
+		ImmutableList<ImmutableList<TeamInfo>> partylistlist;
 		ImmutableList<Team> partieslist = new FixedList<Team>(teams);
 		ImmutableList<ProfileList> profileslist = new FixedList<ProfileList>(
@@ -227,7 +217,7 @@
 	 */
 	private SessionSettings createSetting(
-			ImmutableList<TeamOfPartiesAndProfiles> partyproflist) {
+			ImmutableList<TeamInfo> partyproflist) {
 		SessionSettings settings = sessionsettings;
-		for (TeamOfPartiesAndProfiles partyprof : partyproflist) {
+		for (TeamInfo partyprof : partyproflist) {
 			settings = settings.with(partyprof);
 		}
@@ -242,9 +232,9 @@
 	 * @param drawPartyWithPutback if parties can be drawn multiple times
 	 * @return list of sessionsets where each sessionset contains settings for a
-	 *         session: a list with {@link TeamOfPartiesAndProfiles}s. The
+	 *         session: a list with {@link TeamInfo}s. The
 	 *         sessionsets are made by making all combinations of parties and
 	 *         profiles. Profiles are drawn with replace.
 	 */
-	private ImmutableList<ImmutableList<TeamOfPartiesAndProfiles>> getParticipants(
+	private ImmutableList<ImmutableList<TeamInfo>> getParticipants(
 			ImmutableList<Team> parties, ImmutableList<ProfileList> profiles,
 			int n, boolean drawPartyWithPutback) {
@@ -265,7 +255,7 @@
 				partiesPermutations, profilesPermutations);
 
-		MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamOfPartiesAndProfiles>> tournamentsettings = new MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamOfPartiesAndProfiles>>(
+		MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamInfo>> tournamentsettings = new MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamInfo>>(
 				tuple -> teamlist(tuple), tuples);
-		return new Repeat<ImmutableList<TeamOfPartiesAndProfiles>>(
+		return new Repeat<ImmutableList<TeamInfo>>(
 				tournamentsettings, BigInteger.valueOf(numberTournaments),
 				true);
@@ -279,29 +269,27 @@
 	 *         in order.
 	 */
-	private ImmutableList<TeamOfPartiesAndProfiles> teamlist(
+	private ImmutableList<TeamInfo> teamlist(
 			Tuple<ImmutableList<Team>, ImmutableList<ProfileList>> tuple) {
-		BiFunction<Team, ProfileList, TeamOfPartiesAndProfiles> makeparty = new BiFunction<Team, ProfileList, TeamOfPartiesAndProfiles>() {
+		BiFunction<Team, ProfileList, TeamInfo> makeparty = new BiFunction<Team, ProfileList, TeamInfo>() {
 			@Override
-			public TeamOfPartiesAndProfiles apply(Team team,
+			public TeamInfo apply(Team team,
 					ProfileList profilelist) {
-				if (sessionsettings instanceof SAOPSettings
-						|| sessionsettings instanceof AMOPSettings
-						|| sessionsettings instanceof MOPACSettings)
-					return new OnePartyTeam(team.getParties().get(0),
-							profilelist.getProfiles().get(0));
-				else if (sessionsettings instanceof SHAOPSettings) {
-					return new ShaopTeam(
-							new PartyWithProfile(team.getParties().get(0),
-									profilelist.getProfiles().get(0)),
-							new PartyWithProfile(team.getParties().get(1),
-									profilelist.getProfiles().get(1)));
-				} else
+				List<PartyWithParameters> parties = team.getParties();
+				List<ProfileRef> profiles = profilelist.getProfiles();
+				if (parties.size() != profiles.size())
 					throw new IllegalArgumentException(
-							"Unknown/unsupported protocol "
-									+ sessionsettings.getClass());
+							"Parties and profiles list must match, but found "
+									+ team + " and " + profilelist);
+
+				List<PartyWithProfile> theteams = new LinkedList<>();
+				for (int n = 0; n < parties.size(); n++) {
+					theteams.add(new PartyWithProfile(parties.get(n),
+							profiles.get(n)));
+				}
+				return new TeamInfo(theteams);
 			}
 
 		};
-		return new MapThreadList<TeamOfPartiesAndProfiles, Team, ProfileList>(
+		return new MapThreadList<TeamInfo, Team, ProfileList>(
 				makeparty, tuple.get1(), tuple.get2());
 	}
Index: protocol/src/test/java/geniusweb/protocol/session/DefaultSessionStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/DefaultSessionStateTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/DefaultSessionStateTest.java	(revision 30)
@@ -41,5 +41,5 @@
 	private DefaultSessionState1 state1, state1a, stateA, stateE, stateF;
 	// The 1 is because we are testing an instance, not the abstract class
-	private String state1string = "{\"DefaultSessionState1\":{\"actions\":[],\"progress\":{\"time\":{\"duration\":1000,\"start\":1000}},\"settings\":{\"SAOPSettings\":{\"participants\":[{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"},{\"party\":{\"partyref\":\"party2\",\"parameters\":{}},\"profile\":\"profile2\"}],\"deadline\":{\"deadlinetime\":{\"durationms\":1000}}}},\"error\":null,\"partyprofiles\":{\"party1\":{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}}}}";
+	private String state1string = "{\"DefaultSessionState1\":{\"actions\":[],\"progress\":{\"time\":{\"duration\":1000,\"start\":1000}},\"settings\":{\"SAOPSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party2\",\"parameters\":{}},\"profile\":\"profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":1000}}}},\"error\":null,\"partyprofiles\":{\"party1\":{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}}}}";
 	private ProgressTime progr;
 	private final Date start = new Date(1000l);
@@ -60,7 +60,7 @@
 				new PartyRef("party2"), new Parameters());
 		ProfileRef profile2 = new ProfileRef("profile2");
-		List<OnePartyTeam> participants = Arrays.asList(
-				new OnePartyTeam(party1ref, profile1),
-				new OnePartyTeam(party2ref, profile2));
+		List<TeamInfo> participants = Arrays.asList(
+				new TeamInfo(party1ref, profile1),
+				new TeamInfo(party2ref, profile2));
 		SessionSettings settings = new SAOPSettings(participants, deadline);
 		ProtocolException e = new ProtocolException("test protocol error",
Index: protocol/src/test/java/geniusweb/protocol/session/TeamInfoTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/TeamInfoTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/TeamInfoTest.java	(revision 30)
@@ -0,0 +1,73 @@
+package geniusweb.protocol.session;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyRef;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.ProfileRef;
+import tudelft.utilities.junit.GeneralTests;
+
+public class TeamInfoTest extends GeneralTests<TeamInfo> {
+
+	private TeamInfo team1 = new TeamInfo(Collections.emptyList());
+	private TeamInfo team1a = new TeamInfo(Collections.emptyList());
+	private PartyRef partyref;
+	private Parameters parameters = new Parameters();;
+	private ProfileRef profileref;
+	private TeamInfo team2, team2a;
+	private String asString1 = "{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}]}}";
+
+	@Before
+	public void before() throws URISyntaxException {
+		partyref = new PartyRef("party1");
+		profileref = new ProfileRef("profile1");
+		team2 = new TeamInfo(new PartyWithParameters(partyref, parameters),
+				profileref);
+		team2a = new TeamInfo(new PartyWithParameters(partyref, parameters),
+				profileref);
+
+	}
+
+	@Override
+	public List<List<TeamInfo>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(team1, team1a),
+				Arrays.asList(team2, team2a));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList("TeamInfo.\\[\\].",
+				"TeamInfo.\\[PartyWithProfile.*party1.*profile1.*\\].*");
+	}
+
+	@Test
+	public void testSerialize() throws JsonProcessingException {
+
+		ObjectMapper jackson = new ObjectMapper();
+		System.out.println(jackson.writeValueAsString(team2));
+		assertEquals(asString1, jackson.writeValueAsString(team2));
+
+	}
+
+	@Test
+	public void testDeserialize() throws IOException {
+
+		ObjectMapper jackson = new ObjectMapper();
+		TeamInfo read = jackson.readValue(asString1, TeamInfo.class);
+		assertEquals(team2, read);
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/amop/AMOPSettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/amop/AMOPSettingsTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/amop/AMOPSettingsTest.java	(revision 30)
@@ -19,9 +19,10 @@
 import geniusweb.deadline.DeadlineRounds;
 import geniusweb.deadline.DeadlineTime;
-import geniusweb.protocol.session.OnePartyTeam;
 import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.Parameters;
 import geniusweb.references.PartyRef;
 import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
 import geniusweb.references.ProfileRef;
 import tudelft.utilities.junit.GeneralTests;
@@ -30,11 +31,11 @@
 public class AMOPSettingsTest extends GeneralTests<AMOPSettings> {
 
-	private OnePartyTeam partyprof1 = mock(OnePartyTeam.class);
-	private OnePartyTeam partyprof2 = mock(OnePartyTeam.class);
-	private OnePartyTeam partyprof3 = mock(OnePartyTeam.class);
-	private List<OnePartyTeam> participants2 = Arrays.asList(partyprof1,
+	private TeamInfo partyprof1 = mock(TeamInfo.class);
+	private TeamInfo partyprof2 = mock(TeamInfo.class);
+	private TeamInfo partyprof3 = mock(TeamInfo.class);
+	private List<TeamInfo> participants2 = Arrays.asList(partyprof1,
 			partyprof2);
-	private List<OnePartyTeam> participants3 = Arrays.asList(partyprof1,
-			partyprof2, partyprof3);
+	private List<TeamInfo> participants3 = Arrays.asList(partyprof1, partyprof2,
+			partyprof3);
 
 	private DeadlineTime deadline = mock(DeadlineTime.class);
@@ -47,6 +48,7 @@
 
 	private AMOPSettings sersettings;
-	private final String serialized = "{\"AMOPSettings\":{\"participants\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"},{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}}}}";
-	private List<OnePartyTeam> participants;
+	private final String serialized = "{\"AMOPSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}}}}";
+	private List<TeamInfo> participants;
+	private PartyWithProfile partywithprof1, partywithprof2;
 
 	@Before
@@ -60,4 +62,6 @@
 		when(partyprof3.toString()).thenReturn("party and profile 3");
 
+		when(partyprof3.getSize()).thenReturn(1);
+
 		// SERIALIZABLE version with REAL objects. Still no workaround for
 		// this...
@@ -68,7 +72,9 @@
 				new PartyRef("http://party2"), new Parameters());
 		ProfileRef profile2 = new ProfileRef("http://profile2");
-		OnePartyTeam partywithprof1 = new OnePartyTeam(party1, profile1);
-		OnePartyTeam partywithprof2 = new OnePartyTeam(party2, profile2);
-		participants = Arrays.asList(partywithprof1, partywithprof2);
+		partywithprof1 = new PartyWithProfile(party1, profile1);
+		partywithprof2 = new PartyWithProfile(party2, profile2);
+		participants = Arrays.asList(
+				new TeamInfo(Arrays.asList(partywithprof1)),
+				new TeamInfo(Arrays.asList(partywithprof2)));
 
 		Deadline deadlinetime = new DeadlineTime(100);
@@ -145,5 +151,6 @@
 	@Test
 	public void testGetAllParties() {
-		assertEquals(participants, sersettings.getAllParties());
+		assertEquals(Arrays.asList(partywithprof1, partywithprof2),
+				sersettings.getAllParties());
 	}
 
Index: protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACSettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACSettingsTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACSettingsTest.java	(revision 30)
@@ -19,9 +19,10 @@
 import geniusweb.deadline.DeadlineRounds;
 import geniusweb.deadline.DeadlineTime;
-import geniusweb.protocol.session.OnePartyTeam;
 import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.Parameters;
 import geniusweb.references.PartyRef;
 import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
 import geniusweb.references.ProfileRef;
 import geniusweb.voting.VotingEvaluator;
@@ -34,11 +35,11 @@
 	private final ObjectMapper jackson = new ObjectMapper();
 	private final Parameters noparams = new Parameters();
-	private OnePartyTeam partyprof1 = mockParty("party1", noparams);
-	private OnePartyTeam partyprof2 = mockParty("party2", noparams);
-	private OnePartyTeam partyprof3 = mockParty("party3", noparams);
-	private List<OnePartyTeam> participants2 = Arrays.asList(partyprof1,
+	private TeamInfo partyprof1 = mockParty("party1", noparams);
+	private TeamInfo partyprof2 = mockParty("party2", noparams);
+	private TeamInfo partyprof3 = mockParty("party3", noparams);
+	private List<TeamInfo> participants2 = Arrays.asList(partyprof1,
 			partyprof2);
-	private List<OnePartyTeam> participants3 = Arrays.asList(partyprof1,
-			partyprof2, partyprof3);
+	private List<TeamInfo> participants3 = Arrays.asList(partyprof1, partyprof2,
+			partyprof3);
 
 	private final VotingEvaluator ve = new LargestAgreement();
@@ -56,11 +57,15 @@
 
 	private MOPACSettings sersettings;
-	private final String serialized = "{\"MOPACSettings\":{\"participants\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"},{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreement\":{}}}}";
-	private List<OnePartyTeam> participants;
+	private final String serialized = "{\"MOPACSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreement\":{}}}}";
+	private List<TeamInfo> participants;
+	private PartyWithProfile partywithprof1;
+	private PartyWithProfile partywithprof2;
 
-	private OnePartyTeam mockParty(String partyname, Parameters params) {
-		OnePartyTeam team = mock(OnePartyTeam.class);
+	private TeamInfo mockParty(String partyname, Parameters params) {
+		TeamInfo team = mock(TeamInfo.class);
+		when(team.getSize()).thenReturn(1);
 		PartyWithParameters pwithp = mock(PartyWithParameters.class);
-		when(team.getParty()).thenReturn(pwithp);
+		when(team.getAllParties()).thenReturn(Arrays
+				.asList(new PartyWithProfile(pwithp, mock(ProfileRef.class))));
 		when(team.toString()).thenReturn(partyname);
 		when(pwithp.toString()).thenReturn(partyname);
@@ -84,7 +89,9 @@
 				new PartyRef("http://party2"), new Parameters());
 		ProfileRef profile2 = new ProfileRef("http://profile2");
-		OnePartyTeam partywithprof1 = new OnePartyTeam(party1, profile1);
-		OnePartyTeam partywithprof2 = new OnePartyTeam(party2, profile2);
-		participants = Arrays.asList(partywithprof1, partywithprof2);
+		partywithprof1 = new PartyWithProfile(party1, profile1);
+		partywithprof2 = new PartyWithProfile(party2, profile2);
+		participants = Arrays.asList(
+				new TeamInfo(Arrays.asList(partywithprof1)),
+				new TeamInfo(Arrays.asList(partywithprof2)));
 
 		Deadline deadlinetime = new DeadlineTime(100);
@@ -160,5 +167,6 @@
 	@Test
 	public void testGetAllParties() {
-		assertEquals(participants, sersettings.getAllParties());
+		assertEquals(Arrays.asList(partywithprof1, partywithprof2),
+				sersettings.getAllParties());
 	}
 
Index: protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACStateTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACStateTest.java	(revision 30)
@@ -31,5 +31,5 @@
 import geniusweb.progress.ProgressTime;
 import geniusweb.protocol.ProtocolException;
-import geniusweb.protocol.session.OnePartyTeam;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.protocol.session.mopac.phase.OfferPhase;
 import geniusweb.protocol.session.mopac.phase.Phase;
@@ -60,5 +60,7 @@
 			mock(ProfileRef.class));
 
-	private final String mopacsettingstr = "{\"MOPACSettings\":{\"participants\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"},{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreement\":{}}}}";
+	// copy from MopacSettingsTest. We need settings here but don't want to mock
+	// this large structure.
+	private final String mopacsettingstr = "{\"MOPACSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreement\":{}}}}";
 	// create real objects for the GeneralTests and for serialization tests
 	private PartyStates partyStates = new PartyStates(Collections.emptyMap());
@@ -72,5 +74,5 @@
 	private MOPACState state1, state1a, state2, state3, state4, state5, state6;
 
-	private String serialized = "{\"MOPACState\":{\"phase\":{\"OfferPhase\":{\"partyStates\":{\"notYetActed\":[],\"actions\":[],\"agreements\":{},\"walkedAway\":[],\"exceptions\":{},\"powers\":{}},\"evaluator\":{\"LargestAgreement\":{}},\"deadline\":100}},\"actions\":[],\"progress\":{\"time\":{\"duration\":100,\"start\":1001}},\"settings\":{\"MOPACSettings\":{\"participants\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"},{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreement\":{}}}},\"partyprofiles\":{}}}";
+	private String serialized = "{\"MOPACState\":{\"phase\":{\"OfferPhase\":{\"partyStates\":{\"notYetActed\":[],\"actions\":[],\"agreements\":{},\"walkedAway\":[],\"exceptions\":{},\"powers\":{}},\"evaluator\":{\"LargestAgreement\":{}},\"deadline\":100}},\"actions\":[],\"progress\":{\"time\":{\"duration\":100,\"start\":1001}},\"settings\":{\"MOPACSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreement\":{}}}},\"partyprofiles\":{}}}";
 
 	@Before
@@ -82,8 +84,10 @@
 		MOPACSettings realsettings = jackson.readValue(mopacsettingstr,
 				MOPACSettings.class);
-		OnePartyTeam party = mock(OnePartyTeam.class);
+		TeamInfo party = mock(TeamInfo.class);
 		PartyWithParameters pwithp = mock(PartyWithParameters.class);
 		when(pwithp.getParameters()).thenReturn(new Parameters());
-		when(party.getParty()).thenReturn(pwithp);
+		when(party.getAllParties()).thenReturn(Arrays
+				.asList(new PartyWithProfile(pwithp, mock(ProfileRef.class))));
+		when(party.getSize()).thenReturn(1);
 		MOPACSettings realsettings2 = realsettings.with(party);
 
Index: protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACTest.java	(revision 30)
@@ -47,7 +47,7 @@
 	// makes the test unreadable.
 	private final String setstr = "{\"MOPACSettings\":{\"participants\":["
-			+ "{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"},"
-			+ "{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"},"
-			+ "{\"party\":{\"partyref\":\"http://party3\",\"parameters\":{}},\"profile\":\"http://profile3\"}],"
+			+ "{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},"
+			+ "{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}},"
+			+ "{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party3\",\"parameters\":{}},\"profile\":\"http://profile3\"}]}}],"
 			+ "\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreement\":{}}}}";
 
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2SettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2SettingsTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2SettingsTest.java	(revision 30)
@@ -0,0 +1,173 @@
+package geniusweb.protocol.session.mopac2;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.deadline.Deadline;
+import geniusweb.deadline.DeadlineRounds;
+import geniusweb.deadline.DeadlineTime;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyRef;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import geniusweb.voting.VotingEvaluatorWithValue;
+import geniusweb.voting.evaluatorwithvalue.LargestAgreementWithValue;
+import tudelft.utilities.junit.GeneralTests;
+import tudelft.utilities.logging.ReportToLogger;
+
+public class MOPAC2SettingsTest extends GeneralTests<MOPAC2Settings> {
+
+	private final ObjectMapper jackson = new ObjectMapper();
+	private final Parameters noparams = new Parameters();
+	private TeamInfo partyprof1 = mockParty("party1", noparams);
+	private TeamInfo partyprof2 = mockParty("party2", noparams);
+	private TeamInfo partyprof3 = mockParty("party3", noparams);
+	private List<TeamInfo> participants2 = Arrays.asList(partyprof1,
+			partyprof2);
+	private List<TeamInfo> participants3 = Arrays.asList(partyprof1, partyprof2,
+			partyprof3);
+
+	private final VotingEvaluatorWithValue ve = new LargestAgreementWithValue();
+
+	private DeadlineTime deadline = mock(DeadlineTime.class);
+	private Deadline deadline2 = mock(Deadline.class);
+	private MOPAC2Settings settings1 = new MOPAC2Settings(participants2,
+			deadline, ve);
+	private MOPAC2Settings settings1a = new MOPAC2Settings(participants2,
+			deadline, ve);
+	private MOPAC2Settings settings2 = new MOPAC2Settings(participants3,
+			deadline, ve);
+	private MOPAC2Settings settings3 = new MOPAC2Settings(participants2,
+			deadline2, ve);
+
+	private MOPAC2Settings sersettings;
+	private final String serialized = "{\"MOPAC2Settings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreementWithValue\":{}}}}";
+	private List<TeamInfo> participants;
+	private PartyWithProfile partywithprof1, partywithprof2;
+
+	private TeamInfo mockParty(String partyname, Parameters params) {
+		TeamInfo team = mock(TeamInfo.class);
+		when(team.getSize()).thenReturn(1);
+		PartyWithParameters pwithp = mock(PartyWithParameters.class);
+		when(team.getAllParties()).thenReturn(Arrays
+				.asList(new PartyWithProfile(pwithp, mock(ProfileRef.class))));
+		when(team.toString()).thenReturn(partyname);
+		when(pwithp.toString()).thenReturn(partyname);
+		when(pwithp.getParameters()).thenReturn(params);
+		return team;
+	}
+
+	@Before
+	public void before() throws URISyntaxException {
+
+		when(deadline.getDuration()).thenReturn(1000l);
+		when(deadline.toString()).thenReturn("deadline");
+		when(deadline2.toString()).thenReturn("deadline2");
+
+		// SERIALIZABLE version with REAL objects. Still no workaround for
+		// this...
+		PartyWithParameters party1 = new PartyWithParameters(
+				new PartyRef("http://party1"), new Parameters());
+		ProfileRef profile1 = new ProfileRef("http://profile1");
+		PartyWithParameters party2 = new PartyWithParameters(
+				new PartyRef("http://party2"), new Parameters());
+		ProfileRef profile2 = new ProfileRef("http://profile2");
+		partywithprof1 = new PartyWithProfile(party1, profile1);
+		partywithprof2 = new PartyWithProfile(party2, profile2);
+		participants = Arrays.asList(
+				new TeamInfo(Arrays.asList(partywithprof1)),
+				new TeamInfo(Arrays.asList(partywithprof2)));
+
+		Deadline deadlinetime = new DeadlineTime(100);
+		sersettings = new MOPAC2Settings(participants, deadlinetime, ve);
+
+	}
+
+	@Override
+	public List<List<MOPAC2Settings>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(settings1, settings1a),
+				Arrays.asList(settings2), Arrays.asList(settings3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"MOPAC2Settings.*party1.*party2.*deadline.*LargestAgreement.*",
+				"MOPAC2Settings.*party1.*party2.*deadline.*LargestAgreement.*",
+				"MOPAC2Settings.*party1.*party2.*deadline2.*LargestAgreement.*");
+	}
+
+	@Test
+	public void getProtocolTest() {
+		assertEquals("MOPAC2", settings1.getProtocol(new ReportToLogger("test"))
+				.getRef().getURI().toString());
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorNoDeadlineTest() {
+		new MOPAC2Settings(participants2, null, ve);
+	}
+
+	@Test
+	public void testMaxRuntime() {
+		when(deadline.getDuration()).thenReturn(234l);
+		assertEquals((Double) .234d, settings1.getMaxRunTime());
+	}
+
+	@Test
+	public void testMaxRuntimeRounds() {
+		deadline = mock(DeadlineRounds.class);
+		when(deadline.getDuration()).thenReturn(12000l);
+
+		MOPAC2Settings settings = new MOPAC2Settings(participants2, deadline,
+				ve);
+
+		assertEquals((Double) 12d, settings.getMaxRunTime());
+	}
+
+	@Test
+	public void testDeserialize() throws IOException {
+		SessionSettings obj = jackson.readValue(serialized,
+				SessionSettings.class);
+		System.out.println(obj);
+		assertEquals(sersettings, obj);
+	}
+
+	@Test
+	public void testSerialize()
+			throws JsonProcessingException, URISyntaxException {
+
+		String string = jackson.writeValueAsString(sersettings);
+		System.out.println(string);
+		assertEquals(serialized, string);
+	}
+
+	@Test
+	public void testWith() {
+		MOPAC2Settings saop = new MOPAC2Settings(participants2, deadline, ve);
+		SessionSettings saop2 = saop.with(partyprof3);
+		assertEquals(3, saop2.getTeams().size());
+	}
+
+	@Test
+	public void testGetAllParties() {
+		assertEquals(Arrays.asList(partywithprof1, partywithprof2),
+				sersettings.getAllParties());
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2StateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2StateTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2StateTest.java	(revision 30)
@@ -0,0 +1,284 @@
+package geniusweb.protocol.session.mopac2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.PartyId;
+import geniusweb.progress.Progress;
+import geniusweb.progress.ProgressTime;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.protocol.session.mopac2.phase.OfferPhase;
+import geniusweb.protocol.session.mopac2.phase.Phase;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import geniusweb.voting.evaluatorwithvalue.LargestAgreementWithValue;
+import tudelft.utilities.junit.GeneralTests;
+
+public class MOPAC2StateTest extends GeneralTests<MOPAC2State> {
+	private static final Date NOW = new Date(1001);
+
+	private final ObjectMapper jackson = new ObjectMapper();
+
+	private final MOPAC2Settings settings = mock(MOPAC2Settings.class);
+
+	private final MOPAC2State state = new MOPAC2State(settings);
+
+	private PartyId PARTY1 = new PartyId("party1");
+	private PartyId PARTY2 = new PartyId("party2");
+	private PartyId PARTY3 = new PartyId("party3");
+
+	private PartyWithParameters partyparams1 = mock(PartyWithParameters.class);
+	private Parameters params1 = new Parameters();
+
+	private PartyWithProfile profile1 = new PartyWithProfile(partyparams1,
+			mock(ProfileRef.class));
+
+	// settings is copy from the MOPAC2Settings string. Shortcut to creating
+	// this all manually.
+	private final String mopacsettingstr = "{\"MOPAC2Settings\":"
+			+ "{\"participants\":["
+			+ "{\"TeamInfo\":{\"parties\":[ {\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"} ]}},"
+			+ "{\"TeamInfo\":{\"parties\":[ {\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"} ]}}  ],"
+			+ "\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreementWithValue\":{}}}}";;
+	// create real objects for the GeneralTests and for serialization tests
+	private PartyStates partyStates = new PartyStates(Collections.emptyMap());
+	private Phase phase1 = new OfferPhase(partyStates, 100l,
+			new LargestAgreementWithValue());
+	private Phase phase2 = new OfferPhase(partyStates, 200l,
+			new LargestAgreementWithValue());
+	private Progress progress1 = new ProgressTime(100l, NOW);
+	private Progress progress2 = new ProgressTime(200l, NOW);
+
+	private MOPAC2State state1, state1a, state2, state3, state4, state5, state6;
+
+	private String serialized = "{\"MOPAC2State\":{\"phase\":{\"OfferPhase\":{\"partyStates\":{\"notYetActed\":[],\"actions\":[],\"agreements\":{},\"walkedAway\":[],\"exceptions\":{},\"powers\":{}},\"evaluator\":{\"LargestAgreementWithValue\":{}},\"deadline\":100}},\"actions\":[],\"progress\":{\"time\":{\"duration\":100,\"start\":1001}},\"settings\":{\"MOPAC2Settings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreementWithValue\":{}}}},\"partyprofiles\":{}}}";
+
+	@Before
+	public void before()
+			throws JsonParseException, JsonMappingException, IOException {
+		params1.put("power", 2);
+		when(partyparams1.getParameters()).thenReturn(params1);
+
+		MOPAC2Settings realsettings = jackson.readValue(mopacsettingstr,
+				MOPAC2Settings.class);
+		TeamInfo party = mock(TeamInfo.class);
+		PartyWithParameters pwithp = mock(PartyWithParameters.class);
+		when(pwithp.getParameters()).thenReturn(new Parameters());
+		when(party.getAllParties()).thenReturn(Arrays
+				.asList(new PartyWithProfile(pwithp, mock(ProfileRef.class))));
+		when(party.getSize()).thenReturn(1);
+		MOPAC2Settings realsettings2 = realsettings.with(party);
+
+		state1 = new MOPAC2State(phase1, Collections.emptyList(), progress1,
+				realsettings, Collections.emptyMap());
+		state1a = new MOPAC2State(phase1, Collections.emptyList(), progress1,
+				realsettings, Collections.emptyMap());
+		state2 = new MOPAC2State(phase2, Collections.emptyList(), progress1,
+				realsettings, Collections.emptyMap());
+		state3 = new MOPAC2State(phase1, Arrays.asList(mock(Action.class)),
+				progress1, realsettings, Collections.emptyMap());
+		state4 = new MOPAC2State(phase1, Collections.emptyList(), progress2,
+				realsettings, Collections.emptyMap());
+		state5 = new MOPAC2State(phase1, Collections.emptyList(), progress1,
+				realsettings2, Collections.emptyMap());
+		state6 = new MOPAC2State(phase1, Collections.emptyList(), progress1,
+				realsettings,
+				Collections.singletonMap(PARTY1, mock(PartyWithProfile.class)));
+
+	}
+
+	@Override
+	public List<List<MOPAC2State>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(state1, state1a),
+				Arrays.asList(state2), Arrays.asList(state3),
+				Arrays.asList(state4), Arrays.asList(state5),
+				Arrays.asList(state6));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"MOPAC2State.*OfferPhase.*PartyStates.*,MOPAC2Settings.*PartyWithProfile.*ProgressTime.*",
+				"MOPAC2State.*OfferPhase.*PartyStates.*,MOPAC2Settings.*PartyWithProfile.*ProgressTime.*",
+				"MOPAC2State.*OfferPhase.*PartyStates.*,MOPAC2Settings.*PartyWithProfile.*ProgressTime.*",
+				"MOPAC2State.*OfferPhase.*PartyStates.*,MOPAC2Settings.*PartyWithProfile.*ProgressTime.*",
+				"MOPAC2State.*OfferPhase.*PartyStates.*,MOPAC2Settings.*PartyWithProfile.*ProgressTime.*",
+				"MOPAC2State.*OfferPhase.*PartyStates.*,MOPAC2Settings.*PartyWithProfile.*ProgressTime.*");
+	}
+
+	@Test
+	public void smokeTest() {
+	}
+
+	@Test
+	public void addProfilesBeforeInit() {
+		MOPAC2State st = state.with(PARTY1, profile1);
+		assertTrue(st.getPartyProfiles().containsKey(PARTY1));
+	}
+
+	@Test
+	public void testInit() {
+		// no parties were added, should throw? Go to final mode?
+		// Notice that it's possible to create settings with 0 participants,
+		// because this is needed for Tournament mechanism.
+		MOPAC2State state1 = state
+				.initPhase(new ProgressTime(100l, new Date(0)), 1);
+		assertTrue(state1.getActions().isEmpty());
+		assertTrue(state1.getAgreements().getMap().isEmpty());
+		assertTrue(state1.getPartyProfiles().isEmpty());
+		Phase phase1 = state1.getPhase();
+		assertTrue(phase1 instanceof OfferPhase);
+		OfferPhase offer1 = (OfferPhase) phase1;
+		assertEquals(new PartyStates(Collections.emptyMap()),
+				offer1.getPartyStates());
+	}
+
+	@Test
+	public void getAgreementsTest() {
+		MOPAC2State st = state.initPhase(new ProgressTime(100l, new Date(0)),
+				1);
+		assertTrue(st.getAgreements().getMap().isEmpty());
+	}
+
+	@Test
+	public void testDeserialize() throws IOException {
+		MOPAC2State obj = jackson.readValue(serialized, MOPAC2State.class);
+		System.out.println(obj);
+		assertEquals(state1, obj);
+	}
+
+	@Test
+	public void testSerialize()
+			throws JsonProcessingException, URISyntaxException {
+
+		String string = jackson.writeValueAsString(state1);
+		System.out.println(string);
+		assertEquals(serialized, string);
+	}
+
+	@Test
+	public void testNewPhasePossible() {
+
+		Phase phase = mock(Phase.class);
+		List<Action> actions = Collections.emptyList();
+		Progress progress = mock(Progress.class);
+		when(progress.getTerminationTime())
+				.thenReturn(new Date(Phase.PHASE_MAXTIME)); // plenty for new
+															// phase
+		Map<PartyId, PartyWithProfile> partyprofiles = mock(Map.class);
+		MOPAC2State s1 = new MOPAC2State(phase, actions, progress, settings,
+				partyprofiles);
+
+		PartyStates states = mock(PartyStates.class);
+		when(phase.getPartyStates()).thenReturn(states);
+		when(states.getNegotiatingParties())
+				.thenReturn(new HashSet<>(Arrays.asList(PARTY1, PARTY2)));
+		assertTrue(s1.isNewPhasePossible(10));
+	}
+
+	@Test
+	public void testNewPhasePossibleOnly1Party() {
+
+		Phase phase = mock(Phase.class);
+		List<Action> actions = Collections.emptyList();
+		Progress progress = mock(Progress.class);
+		when(progress.getTerminationTime())
+				.thenReturn(new Date(Phase.PHASE_MAXTIME)); // plenty for new
+															// phase
+		Map<PartyId, PartyWithProfile> partyprofiles = mock(Map.class);
+		MOPAC2State s1 = new MOPAC2State(phase, actions, progress, settings,
+				partyprofiles);
+
+		PartyStates states = mock(PartyStates.class);
+		when(phase.getPartyStates()).thenReturn(states);
+		when(states.getNegotiatingParties())
+				.thenReturn(new HashSet<>(Arrays.asList(PARTY1)));
+		assertFalse(s1.isNewPhasePossible(10));
+	}
+
+	@Test
+	public void testNewPhasePossibleNotEnoughTime() {
+
+		Phase phase = mock(Phase.class);
+		List<Action> actions = Collections.emptyList();
+		Progress progress = mock(Progress.class);
+		when(progress.getTerminationTime())
+				.thenReturn(new Date(Phase.PHASE_MINTIME - 10));
+		Map<PartyId, PartyWithProfile> partyprofiles = mock(Map.class);
+		MOPAC2State s1 = new MOPAC2State(phase, actions, progress, settings,
+				partyprofiles);
+
+		PartyStates states = mock(PartyStates.class);
+		when(phase.getPartyStates()).thenReturn(states);
+		when(states.getNegotiatingParties())
+				.thenReturn(new HashSet<>(Arrays.asList(PARTY1)));
+		assertFalse(s1.isNewPhasePossible(10));
+	}
+
+	@Test
+	public void nextPhaseTest() {
+		long NOW = 102;
+		Phase phase = mock(Phase.class);
+		List<Action> actions = Collections.emptyList();
+		Progress progress = mock(Progress.class);
+		when(progress.getTerminationTime())
+				.thenReturn(new Date(Phase.PHASE_MAXTIME));
+		Map<PartyId, PartyWithProfile> partyprofiles = mock(Map.class);
+		MOPAC2State s1 = new MOPAC2State(phase, actions, progress, settings,
+				partyprofiles);
+
+		// PhaseB is the next phase, with remaining time
+		Phase phaseB = mock(Phase.class);
+		when(phase.next(eq(NOW), eq(Phase.PHASE_MAXTIME - NOW)))
+				.thenReturn(phaseB);
+
+		MOPAC2State s2 = s1.nextPhase(NOW);
+		assertEquals(phaseB, s2.getPhase());
+	}
+
+	@Test
+	public void exceptionTest() {
+		Phase phase = mock(Phase.class);
+		List<Action> actions = Collections.emptyList();
+		Progress progress = mock(Progress.class);
+		Map<PartyId, PartyWithProfile> partyprofiles = mock(Map.class);
+		MOPAC2State s1 = new MOPAC2State(phase, actions, progress, settings,
+				partyprofiles);
+
+		Phase exphase = mock(Phase.class); // supposed phase that contains the
+											// exception
+		when(phase.with(any(ProtocolException.class))).thenReturn(exphase);
+
+		MOPAC2State errstate = s1.with(new ProtocolException("test", PARTY1));
+		assertEquals(exphase, errstate.getPhase());
+		assertEquals(progress, errstate.getProgress());
+
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2Test.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2Test.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2Test.java	(revision 30)
@@ -0,0 +1,218 @@
+package geniusweb.protocol.session.mopac2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.inform.Inform;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.session.mopac2.phase.OfferPhase;
+import geniusweb.protocol.session.mopac2.phase.Phase;
+import geniusweb.protocol.session.mopac2.phase.VotingPhase;
+import tudelft.utilities.logging.Reporter;
+import tudelft.utilities.repository.NoResourcesNowException;
+
+public class MOPAC2Test {
+
+	private final ObjectMapper jackson = new ObjectMapper();
+
+	private static final PartyId PARTY1ID = new PartyId("party1");
+	private static final PartyId PARTY2ID = new PartyId("party2");
+	private static final PartyId PARTY3ID = new PartyId("party3");
+
+	// don't try to mock settings, all parts are needed and mocking
+	// makes the test unreadable.
+	// notice, 3 parties, this differs from MopacSettingsTest output.
+	private final String setstr = "{\"MOPAC2Settings\":{\"participants\":["
+			+ "{\"TeamInfo\":{\"parties\":[  {\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"} ]}},"
+			+ "{\"TeamInfo\":{\"parties\":[ {\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}},"
+			+ "{\"TeamInfo\":{\"parties\":[ {\"party\":{\"partyref\":\"http://party3\",\"parameters\":{}},\"profile\":\"http://profile3\"} ]}}  ],"
+			+ "\"deadline\":{\"deadlinetime\":{\"durationms\":100}},\"votingevaluator\":{\"LargestAgreementWithValue\":{}}}}";
+
+	// {"MOPAC2Settings":{"participants":[{"TeamInfo":{"parties":[{"party":{"partyref":"http://party1","parameters":{}},"profile":"http://profile1"}]}},{"TeamInfo":{"parties":[{"party":{"partyref":"http://party2","parameters":{}},"profile":"http://profile2"}]}}],"deadline":{"deadlinetime":{"durationms":100}},"votingevaluator":{"LargestAgreementWithValue":{}}}}
+
+	private MOPAC2Settings settings;
+	private Reporter logger = mock(Reporter.class);
+	private MOPAC2 mopac;
+
+	private final ProtocolToPartyConnFactory factory = mock(
+			ProtocolToPartyConnFactory.class);
+	private final ProtocolToPartyConn conn1 = mock(ProtocolToPartyConn.class),
+			conn2 = mock(ProtocolToPartyConn.class),
+			conn3 = mock(ProtocolToPartyConn.class);
+
+	private List<ProtocolToPartyConn> connections;
+
+	@Before
+	public void before() throws JsonParseException, JsonMappingException,
+			IOException, NoResourcesNowException {
+		settings = jackson.readValue(setstr, MOPAC2Settings.class);
+		mopac = settings.getProtocol(logger);
+
+		connections = Arrays.asList(conn1, conn2, conn3);
+		when(factory.connect(any(List.class))).thenReturn(connections);
+
+		// hack the state.with(connection) stuff simplistically
+
+		when(conn1.getParty()).thenReturn(PARTY1ID);
+		when(conn2.getParty()).thenReturn(PARTY2ID);
+		when(conn3.getParty()).thenReturn(PARTY3ID);
+	}
+
+	@Test
+	public void testSmoke() {
+	}
+
+	@Test
+	public void testGetDescr() {
+		assertNotNull(mopac.getDescription());
+	}
+
+	@Test
+	public void testInitialState() {
+		MOPAC2State state = mopac.getState();
+		assertEquals(settings, state.getSettings());
+		assertTrue(state.getActions().isEmpty());
+	}
+
+	@Test
+	public void testProtocol() {
+		assertEquals("MOPAC2", mopac.getRef().getURI().getPath());
+	}
+
+	@Test
+	public void testWorkingCheckDeadline()
+			throws IOException, NoResourcesNowException, InterruptedException {
+
+		mopac.start(factory);
+
+		assertFalse(mopac.getState().isFinal(System.currentTimeMillis()));
+		// we have deadline in 100ms so check
+		Thread.sleep(200);
+		assertTrue(mopac.getState().isFinal(System.currentTimeMillis()));
+	}
+
+	@Test
+	public void testSubscribedToConnections() {
+		mopac.start(factory);
+		verify(conn1, times(1)).addListener(any());
+		verify(conn2, times(1)).addListener(any());
+		verify(conn3, times(1)).addListener(any());
+	}
+
+	@Test
+	public void testActionIsBroadcast()
+			throws IOException, NoResourcesNowException, InterruptedException {
+
+		mopac.start(factory);
+		Action offer = mock(Offer.class);
+		mopac.actionRequest(conn1, offer, 1l);
+
+		// 1 time for YourTurn, one time for ActionDone.
+		// but filtering ActionDone here (instead of Action) does not work,
+		// maybe bug in Mockito?
+		verify(conn1, times(2)).send(any(Inform.class));
+	}
+
+	/**
+	 * Fatal error occurs, run should fail
+	 */
+	@Test(expected = RuntimeException.class)
+	public void testPartyFailsConnect()
+			throws IOException, NoResourcesNowException {
+		ProtocolToPartyConnFactory factory1 = mock(
+				ProtocolToPartyConnFactory.class);
+		// irrecoverable fail test
+		when(factory1.connect(any(List.class)))
+				.thenThrow(new IOException("Failed to connect to parties"));
+
+		mopac.start(factory1);
+
+	}
+
+	/**
+	 * Nonfatal error occurs, protocol should retry till success
+	 */
+	@Test
+	public void testPartyFailsConnect2ndtimeOK()
+			throws IOException, NoResourcesNowException {
+		long now = System.currentTimeMillis();
+		ProtocolToPartyConnFactory factory1 = mock(
+				ProtocolToPartyConnFactory.class);
+		// recoverable fail, and then success.
+		when(factory1.connect(any(List.class)))
+				.thenThrow(new NoResourcesNowException("try again later",
+						new Date(now + 100)))
+				.thenReturn(connections);
+
+		mopac.start(factory1);
+
+	}
+
+	/**
+	 * Test that in Offer phase, and party does action, the next phase is
+	 * entered.
+	 */
+	@Test
+	public void testProceedsNextPhase() {
+		// the state before the last party makes the offer
+		MOPAC2State offerstate = mock(MOPAC2State.class);
+		when(offerstate.getSettings()).thenReturn(settings);
+		OfferPhase offerphase = mock(OfferPhase.class);
+		when(offerphase.isFinal(anyLong())).thenReturn(false);
+		when(offerstate.getPhase()).thenReturn(offerphase);
+
+		// the phase where all parties placed an offer
+		OfferPhase finalofferphase = mock(OfferPhase.class);
+		when(finalofferphase.isFinal(anyLong())).thenReturn(true);
+		MOPAC2State finalofferstate = mock(MOPAC2State.class);
+		when(finalofferstate.getPhase()).thenReturn(finalofferphase);
+		when(finalofferstate.finishPhase()).thenReturn(finalofferstate);
+
+		when(offerstate.with(any(PartyId.class), any(Action.class), anyLong()))
+				.thenReturn(finalofferstate);
+
+		// the phase where all parties can start voting.
+		// this is the state to be reached.
+		long now = System.currentTimeMillis();
+		MOPAC2State votingstate = mock(MOPAC2State.class);
+		Phase votingphase = mock(VotingPhase.class);
+		PartyStates votingpartystates = mock(PartyStates.class);
+		when(votingphase.getDeadline()).thenReturn(now + 100);
+		when(votingphase.getPartyStates()).thenReturn(votingpartystates);
+		when(votingstate.getPhase()).thenReturn(votingphase);
+
+		when(finalofferstate.nextPhase(anyLong())).thenReturn(votingstate);
+
+		mopac = new MOPAC2(offerstate, logger);
+		Offer action = mock(Offer.class);
+		mopac.actionRequest(conn3, action, now);
+
+		// check that the action result in reaching the voting phase
+		assertEquals(votingstate, mopac.getState());
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/PartyStatesTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/PartyStatesTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/PartyStatesTest.java	(revision 30)
@@ -0,0 +1,112 @@
+package geniusweb.protocol.session.mopac2;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.PartyId;
+import geniusweb.inform.Agreements;
+import geniusweb.protocol.ProtocolException;
+import tudelft.utilities.junit.GeneralTests;
+
+public class PartyStatesTest extends GeneralTests<PartyStates> {
+	private final ObjectMapper jackson = new ObjectMapper();
+
+	private final PartyId party1 = new PartyId("party1");
+	private final PartyId party2 = new PartyId("party2");
+	private final PartyId party3 = new PartyId("party3");
+
+	private final Map<PartyId, Integer> powers = new HashMap<>();
+	private final PartyStates states1, states1a, states2;
+	private final String serialized = "{\"notYetActed\":[\"party2\",\"party1\",\"party3\"],\"actions\":[],\"agreements\":{},\"walkedAway\":[],\"exceptions\":{},\"powers\":{\"party2\":3,\"party1\":2,\"party3\":3}}";
+
+	public PartyStatesTest() {
+
+		// FIXME use the other constructor to really test this.
+//		PartyStates(Set<PartyId> notYetActed, List<Action> actions,
+//				Agreements agreements, List<PartyId> walkedAway,
+//				Map<PartyId, ProtocolException> exceptions,
+//				Map<PartyId, Integer> powers)
+//		
+		powers.put(party1, 2);
+		powers.put(party2, 3);
+		states2 = new PartyStates(powers);
+		powers.put(party3, 3);
+		states1 = new PartyStates(powers);
+		states1a = new PartyStates(powers);
+	}
+
+	@Override
+	public List<List<PartyStates>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(states1, states1a),
+				Arrays.asList(states2));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"PartyStates.*\\[party2, party1, party3\\],\\[\\],Agreements\\{\\},\\[\\],\\{\\}.*",
+				"PartyStates.*\\[party2, party1\\],\\[\\],Agreements\\{\\},\\[\\],\\{\\}.*");
+	}
+
+	@Test
+	public void testBasics() {
+		assertEquals(powers.keySet(), states1.getNegotiatingParties());
+		assertEquals(powers.keySet(), states1.getNotYetActed());
+		assertEquals(0, states1.getExceptions().size());
+	}
+
+	@Test
+	public void testException() {
+		PartyStates newstates = states1
+				.with(new ProtocolException("bla", party1));
+		assertEquals(new Agreements(), newstates.getAgreements());
+		assertEquals(powers.keySet(), states1.getNegotiatingParties());
+		assertEquals(new HashSet<>(Arrays.asList(party2, party3)),
+				newstates.getNotYetActed());
+	}
+
+	@Test
+	public void testFinish() {
+		PartyStates newstates = states1.finish();
+		assertEquals(new Agreements(), newstates.getAgreements());
+		assertEquals(powers.keySet(), states1.getNegotiatingParties());
+		assertEquals(Collections.emptySet(), newstates.getNotYetActed());
+	}
+
+	@Test
+	public void testDeserialize() throws IOException {
+		PartyStates obj = jackson.readValue(serialized, PartyStates.class);
+		System.out.println(obj);
+		assertEquals(states1, obj);
+	}
+
+	@Test
+	public void testSerialize()
+			throws JsonProcessingException, URISyntaxException {
+
+		String string = jackson.writeValueAsString(states1);
+		System.out.println(string);
+		assertEquals(serialized, string);
+	}
+
+	@Test
+	public void testWalkAway() {
+		PartyStates walkawaystate = states1.withWalkAway(party2);
+		assertEquals(new HashSet<>(Arrays.asList(party1, party3)),
+				walkawaystate.getNotYetActed());
+		assertEquals(Arrays.asList(party2), walkawaystate.getWalkedAway());
+	}
+}
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/OfferPhaseTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/OfferPhaseTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/OfferPhaseTest.java	(revision 30)
@@ -0,0 +1,272 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.Accept;
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.actions.Votes;
+import geniusweb.inform.YourTurn;
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.DiscreteValue;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.VotingEvaluatorWithValue;
+import geniusweb.voting.evaluatorwithvalue.LargestAgreementWithValue;
+import tudelft.utilities.junit.GeneralTests;
+
+/**
+ * We also test defaultPhase here.
+ */
+public class OfferPhaseTest extends GeneralTests<OfferPhase> {
+	private final ObjectMapper jackson = new ObjectMapper();
+
+	private static final long DEADLINE = 10l;
+	private final PartyId party1 = new PartyId("party1");
+	private final PartyId party2 = new PartyId("party2");
+	private final PartyId party3 = new PartyId("party3");
+
+	private final Map<PartyId, Integer> powers = new HashMap<>();
+	private final PartyStates states, states2;
+	private final VotingEvaluatorWithValue evaluator = new LargestAgreementWithValue();
+	private final OfferPhase phase, phase1, phase1a, phase2, phase3;
+
+	// just a test bid
+	private final Bid bid = new Bid("issue", new DiscreteValue("yes"));
+	private final Offer offer = new Offer(party1, bid);
+
+	private final String serialized = "{\"OfferPhase\":{\"partyStates\":{\"notYetActed\":[\"party2\",\"party1\",\"party3\"],\"actions\":[],\"agreements\":{},\"walkedAway\":[],\"exceptions\":{},\"powers\":{\"party2\":3,\"party1\":2,\"party3\":3}},\"evaluator\":{\"LargestAgreementWithValue\":{}},\"deadline\":10}}";
+
+	public OfferPhaseTest() {
+		powers.put(party1, 2);
+		powers.put(party2, 3);
+		states2 = new PartyStates(powers);
+		powers.put(party3, 3);
+		states = new PartyStates(powers);
+		phase = new OfferPhase(states, DEADLINE, evaluator);
+
+		phase1 = new OfferPhase(states, 10l, evaluator);
+		phase1a = new OfferPhase(states, 10l, evaluator);
+		phase2 = new OfferPhase(states2, 10l, evaluator);
+		phase3 = new OfferPhase(states, 20l, evaluator);
+
+	}
+
+	@Override
+	public List<List<OfferPhase>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(phase1, phase1a),
+				Arrays.asList(phase2), Arrays.asList(phase3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"OfferPhase.*PartyStates.*party2, party1, party3.*\\[\\],Agreements.*\\[\\],\\{\\}.*],10,LargestAgreement.*",
+				"OfferPhase.*PartyStates.*party2, party1.*\\[\\],Agreements.*\\[\\],\\{\\}.*],10,LargestAgreement.*",
+				"OfferPhase.*PartyStates.*party2, party1, party3.*\\[\\],Agreements.*\\[\\],\\{\\}.*],20,LargestAgreement.*");
+	}
+
+	@Test
+	public void smokeTest() {
+	}
+
+	@Test
+	public void testInitState() {
+		assertEquals(3, phase.getPartyStates().getNotYetActed().size());
+	}
+
+	@Test
+	public void testInform() {
+		assertEquals(new YourTurn(), phase.getInform());
+	}
+
+	@Test
+	public void isFinalTest() {
+		assertFalse(phase.isFinal(1l));
+		assertTrue(phase.isFinal(10l));
+	}
+
+	@Test
+	public void isFinalTestActorNotYetActed() {
+		PartyStates actedstate = mock(PartyStates.class);
+		when(actedstate.getNotYetActed())
+				.thenReturn(Collections.singleton(party1));
+		OfferPhase testphase = new OfferPhase(actedstate, 10l, evaluator);
+		assertFalse(testphase.isFinal(1l));
+	}
+
+	@Test
+	public void isFinalTestAllActorsActed() {
+		PartyStates actedstate = mock(PartyStates.class);
+		when(actedstate.getNotYetActed()).thenReturn(Collections.emptySet());
+		OfferPhase testphase = new OfferPhase(actedstate, 10l, evaluator);
+		assertTrue(testphase.isFinal(1l));
+	}
+
+	@Test(expected = ProtocolException.class)
+	public void checkIncorrectActorTest() throws ProtocolException {
+		phase.checkAction(party1, new EndNegotiation(party2), 1);
+	}
+
+	@Test(expected = ProtocolException.class)
+	public void checkNotAllowedActionTest() throws ProtocolException {
+		phase.checkAction(party1, new Votes(party2, Collections.emptySet()), 1);
+	}
+
+	@Test
+	public void testException() {
+		OfferPhase newphase = phase.with(new ProtocolException("bla", party1));
+		assertEquals(10l, newphase.getDeadline());
+		assertEquals(0l, phase.getPartyStates().getActions().size());
+	}
+
+	@Test
+	public void testFinish() {
+		Phase ph = phase.finish();
+		assertTrue(ph.getPartyStates().getNotYetActed().isEmpty());
+		assertEquals(3, ph.getPartyStates().getExceptions().size());
+		assertTrue(phase.getPartyStates().getAgreements().getMap().isEmpty());
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNextWrong() {
+		// state is not final, should not work
+		phase.next(1, 1000);
+	}
+
+	@Test
+	public void testNextNoParties() {
+		Phase ph = phase.finish();
+		// no parties are left now, all failed
+		// but this is not part of the next() check.
+		Phase next = ph.next(1, 1000);
+		assertTrue(next instanceof VotingPhase);
+		// no remaining parties, since finish kicked all
+		assertTrue(next.getPartyStates().getNotYetActed().isEmpty());
+	}
+
+	@Test
+	public void testAllowed() {
+		phase.with(party1, offer, 1);
+	}
+
+	@Test
+	public void testAllowedWrongClass() {
+		try {
+			phase.checkAction(party1, new Accept(party1, bid), 1);
+		} catch (ProtocolException e) {
+			assertTrue(e.getMessage().contains("not allowed in OfferPhase"));
+			return;
+		}
+		fail("checkAction did not throw as expected");
+	}
+
+	@Test
+	public void testAllowedWrongActor() {
+		try {
+			phase.checkAction(party1, new Accept(party2, bid), 1);
+		} catch (ProtocolException e) {
+			assertTrue(
+					e.getMessage().contains("Incorrect actor info in action"));
+			return;
+		}
+		fail("checkAction did not throw as expected");
+	}
+
+	@Test
+	public void testAllowedActorAlreadyActed() {
+		OfferPhase testphase = phase.with(party1, offer, 1);
+
+		try {
+			testphase.checkAction(party1, offer, 2);
+		} catch (ProtocolException e) {
+			assertTrue(e.getMessage().contains("can not act anymore"));
+			return;
+		}
+		fail("checkAction did not throw as expected");
+	}
+
+	@Test
+	public void testAllowedActorAlreadyActed1() {
+		// as theoprevious test, but using with() instead of checkAction
+		OfferPhase testphase = phase.with(party1, offer, 1);
+
+		OfferPhase newphase = testphase.with(party1, offer, 2);
+		// the party must remain as acted, because we can't retract his
+		// action...
+		assertEquals(1, newphase.getPartyStates().getActions().size());
+		assertEquals(offer, newphase.getPartyStates().getActions().get(0));
+		assertFalse(
+				newphase.getPartyStates().getExceptions().containsKey(party1));
+	}
+
+	@Test
+	public void testAllowedActingTooLate() {
+
+		try {
+			phase.checkAction(party1, offer, DEADLINE + 1);
+		} catch (ProtocolException e) {
+			assertTrue(e.getMessage().contains("passed deadline"));
+			return;
+		}
+		fail("checkAction did not throw as expected");
+	}
+
+	@Test
+	public void getOffersTest() {
+		assertEquals(Collections.emptyList(), phase.getOffers());
+		List<Offer> offs = phase.with(party1, offer, 1).getOffers();
+		assertEquals(1, offs.size());
+		assertEquals(bid, offs.get(0).getBid());
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testNextTooShortDuration() {
+		phase.next(1, Phase.PHASE_MINTIME - 1);
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNextNotFinished() {
+		phase.next(1, Phase.PHASE_MINTIME + 1);
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNext() {
+		phase.next(11, Phase.PHASE_MINTIME + 1);
+	}
+
+	@Test
+	public void testDeserialize() throws IOException {
+		Phase obj = jackson.readValue(serialized, Phase.class);
+		System.out.println(obj);
+		assertEquals(phase1, obj);
+	}
+
+	@Test
+	public void testSerialize()
+			throws JsonProcessingException, URISyntaxException {
+
+		String string = jackson.writeValueAsString(phase1);
+		System.out.println(string);
+		assertEquals(serialized, string);
+	}
+}
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/OptInPhaseTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/OptInPhaseTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/OptInPhaseTest.java	(revision 30)
@@ -0,0 +1,202 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.PartyId;
+import geniusweb.actions.Vote;
+import geniusweb.actions.VoteWithValue;
+import geniusweb.actions.Votes;
+import geniusweb.actions.VotesWithValue;
+import geniusweb.inform.OptInWithValue;
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.DiscreteValue;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.VotingEvaluatorWithValue;
+import geniusweb.voting.evaluatorwithvalue.LargestAgreementWithValue;
+import tudelft.utilities.junit.GeneralTests;
+
+/**
+ * We also test defaultPhase here.
+ */
+public class OptInPhaseTest extends GeneralTests<OptInPhase> {
+
+	private static final long DEADLINE = 10l;
+	private final PartyId party1 = new PartyId("party1");
+	private final PartyId party2 = new PartyId("party2");
+	private final PartyId party3 = new PartyId("party3");
+
+	// private final VotingPhase prevPhase = mock(VotingPhase.class);
+
+	private final Map<PartyId, Integer> powers = new HashMap<>();
+	private final PartyStates states, states2;
+	private final VotingEvaluatorWithValue evaluator = new LargestAgreementWithValue();
+	private final OptInPhase phase, phase1, phase1a, phase2, phase3;
+
+	// just a test bid
+	private final Bid bid = new Bid("issue", new DiscreteValue("yes"));
+	private final VoteWithValue vote = new VoteWithValue(party1, bid, 2, 3,
+			100);
+	private final VotesWithValue votes = new VotesWithValue(party1,
+			Collections.singleton(vote));
+
+	private final List<VotesWithValue> prevVotes = Arrays.asList(votes);
+
+	public OptInPhaseTest() {
+		powers.put(party1, 2);
+		powers.put(party2, 3);
+		states2 = new PartyStates(powers);
+		powers.put(party3, 3);
+		states = new PartyStates(powers);
+
+		phase = new OptInPhase(prevVotes, states, DEADLINE, evaluator);
+
+		// in these phases we just don't set prevPhase.
+		// this to avoid serialization troubles.
+		phase1 = new OptInPhase(null, states, 10l, evaluator);
+		phase1a = new OptInPhase(null, states, 10l, evaluator);
+		phase2 = new OptInPhase(null, states2, 10l, evaluator);
+		phase3 = new OptInPhase(null, states, 20l, evaluator);
+
+	}
+
+	@Override
+	public List<List<OptInPhase>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(phase1, phase1a),
+				Arrays.asList(phase2), Arrays.asList(phase3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"OptInPhase.*PartyStates.*party2, party1, party3.*\\[\\],Agreements.*\\[\\],\\{\\}.*],10,LargestAgreement.*",
+				"OptInPhase.*PartyStates.*party2, party1.*\\[\\],Agreements.*\\[\\],\\{\\}.*],10,LargestAgreement.*",
+				"OptInPhase.*PartyStates.*party2, party1, party3.*\\[\\],Agreements.*\\[\\],\\{\\}.*],20,LargestAgreement.*");
+	}
+
+	@Test
+	public void smokeTest() {
+	}
+
+	@Test
+	public void testInitState() {
+		assertEquals(3, phase.getPartyStates().getNotYetActed().size());
+	}
+
+	@Test
+	public void testInform() {
+		// when(prevPhase.getVotes()).thenReturn(Arrays.asList(votes));
+		assertEquals(new OptInWithValue(Arrays.asList(votes)),
+				phase.getInform());
+
+	}
+
+	@Test
+	public void isFinalTest() {
+		assertFalse(phase.isFinal(1l));
+		assertTrue(phase.isFinal(10l));
+	}
+
+	@Test
+	public void isFinalTestActorNotYetActed() {
+		PartyStates actedstate = mock(PartyStates.class);
+		when(actedstate.getNotYetActed())
+				.thenReturn(Collections.singleton(party1));
+		OfferPhase testphase = new OfferPhase(actedstate, 10l, evaluator);
+		assertFalse(testphase.isFinal(1l));
+	}
+
+	@Test
+	public void isFinalTestAllActorsActed() {
+		PartyStates actedstate = mock(PartyStates.class);
+		when(actedstate.getNotYetActed()).thenReturn(Collections.emptySet());
+		OfferPhase testphase = new OfferPhase(actedstate, 10l, evaluator);
+		assertTrue(testphase.isFinal(1l));
+	}
+
+	@Test(expected = ProtocolException.class)
+	public void checkIncorrectActorTest() throws ProtocolException {
+		phase.checkAction(party1, new EndNegotiation(party2), 1);
+	}
+
+	@Test(expected = ProtocolException.class)
+	public void checkNotAllowedActionTest() throws ProtocolException {
+		phase.checkAction(party1, new Votes(party2, Collections.emptySet()), 1);
+	}
+
+	@Test
+	public void testFinish() {
+		Phase ph = phase.finish();
+		assertTrue(ph.getPartyStates().getNotYetActed().isEmpty());
+		assertEquals(3, ph.getPartyStates().getExceptions().size());
+		assertTrue(phase.getPartyStates().getAgreements().getMap().isEmpty());
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNextWrong() {
+		// state is not final, should not work
+		phase.next(1, 1000);
+	}
+
+	@Test
+	public void testNextNoParties() {
+		Phase ph = phase.finish();
+		// no parties are left now, all failed
+		// but this is not part of the next() check.
+		Phase next = ph.next(1, 1000);
+		assertTrue(next instanceof OfferPhase);
+		// no remaining parties, since finish kicked all
+		assertTrue(next.getPartyStates().getNotYetActed().isEmpty());
+	}
+
+	@Test
+	public void testAllowed() {
+		// when(prevPhase.getVotes()).thenReturn(Arrays.asList(votes));
+		phase.with(party1, votes, 1l);
+	}
+
+	@Test
+	public void testAllowedNarrowingVote() {
+		// the previous vote was maxPower=3,
+		// so this is an illegal narrowing of the previous vote
+		Vote newVote = new Vote(party1, bid, 2, 2);
+		Votes newVotes = new Votes(party1, Collections.singleton(newVote));
+
+		// when(prevPhase.getVotes()).thenReturn(Arrays.asList(votes));
+
+		// check that party1 was kicked
+		Phase newphase = phase.with(party1, newVotes, 1l);
+		assertTrue(
+				newphase.getPartyStates().getExceptions().containsKey(party1));
+
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testNextTooShortDuration() {
+		phase.next(1, Phase.PHASE_MINTIME - 1);
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNextNotFinished() {
+		phase.next(1, Phase.PHASE_MINTIME + 1);
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNext() {
+		phase.next(11, Phase.PHASE_MINTIME + 1);
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/VotingPhaseTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/VotingPhaseTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/phase/VotingPhaseTest.java	(revision 30)
@@ -0,0 +1,192 @@
+package geniusweb.protocol.session.mopac2.phase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.actions.VoteWithValue;
+import geniusweb.actions.VotesWithValue;
+import geniusweb.inform.Voting;
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.DiscreteValue;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.mopac2.PartyStates;
+import geniusweb.voting.VotingEvaluatorWithValue;
+import geniusweb.voting.evaluatorwithvalue.LargestAgreementWithValue;
+import tudelft.utilities.junit.GeneralTests;
+
+/**
+ * We also test defaultPhase here.
+ */
+public class VotingPhaseTest extends GeneralTests<VotingPhase> {
+
+	private static final long DEADLINE = 10l;
+	private final PartyId party1 = new PartyId("party1");
+	private final PartyId party2 = new PartyId("party2");
+	private final PartyId party3 = new PartyId("party3");
+
+	// private final OfferPhase prevPhase = mock(OfferPhase.class);
+
+	private final Map<PartyId, Integer> powers = new HashMap<>();
+	private final PartyStates states, states2;
+	private final VotingEvaluatorWithValue evaluator = new LargestAgreementWithValue();
+	private final VotingPhase phase, phase1, phase1a, phase2, phase3;
+
+	// just a test bid
+	private final Bid bid = new Bid("issue", new DiscreteValue("yes"));
+	private final VoteWithValue vote = new VoteWithValue(party1, bid, 2, 3,
+			100);
+	private final VotesWithValue votes = new VotesWithValue(party1,
+			Collections.singleton(vote));
+
+	private final List<Offer> prevPhase = Arrays.asList(new Offer(party1, bid));
+
+	public VotingPhaseTest() {
+		powers.put(party1, 2);
+		powers.put(party2, 3);
+		states2 = new PartyStates(powers);
+		powers.put(party3, 3);
+		states = new PartyStates(powers);
+
+		phase = new VotingPhase(prevPhase, states, DEADLINE, evaluator);
+
+		// in these phases we just don't set prevPhase.
+		// this to avoid serialization troubles.
+		phase1 = new VotingPhase(null, states, 10l, evaluator);
+		phase1a = new VotingPhase(null, states, 10l, evaluator);
+		phase2 = new VotingPhase(null, states2, 10l, evaluator);
+		phase3 = new VotingPhase(null, states, 20l, evaluator);
+
+	}
+
+	@Override
+	public List<List<VotingPhase>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(phase1, phase1a),
+				Arrays.asList(phase2), Arrays.asList(phase3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"VotingPhase.*PartyStates.*party2, party1, party3.*\\[\\],Agreements.*\\[\\],\\{\\}.*],10,LargestAgreement.*",
+				"VotingPhase.*PartyStates.*party2, party1.*\\[\\],Agreements.*\\[\\],\\{\\}.*],10,LargestAgreement.*",
+				"VotingPhase.*PartyStates.*party2, party1, party3.*\\[\\],Agreements.*\\[\\],\\{\\}.*],20,LargestAgreement.*");
+	}
+
+	@Test
+	public void smokeTest() {
+	}
+
+	@Test
+	public void testInitState() {
+		assertEquals(3, phase.getPartyStates().getNotYetActed().size());
+	}
+
+	@Test
+	public void testInform() {
+		// when(prevPhase.getOffers()).thenReturn(Arrays.asList(bid));
+		assertEquals(new Voting(Arrays.asList(new Offer(party1, bid)), powers),
+				phase.getInform());
+
+	}
+
+	@Test
+	public void isFinalTest() {
+		assertFalse(phase.isFinal(1l));
+		assertTrue(phase.isFinal(10l));
+	}
+
+	@Test
+	public void isFinalTestActorNotYetActed() {
+		PartyStates actedstate = mock(PartyStates.class);
+		when(actedstate.getNotYetActed())
+				.thenReturn(Collections.singleton(party1));
+		OfferPhase testphase = new OfferPhase(actedstate, 10l, evaluator);
+		assertFalse(testphase.isFinal(1l));
+	}
+
+	@Test
+	public void isFinalTestAllActorsActed() {
+		PartyStates actedstate = mock(PartyStates.class);
+		when(actedstate.getNotYetActed()).thenReturn(Collections.emptySet());
+		OfferPhase testphase = new OfferPhase(actedstate, 10l, evaluator);
+		assertTrue(testphase.isFinal(1l));
+	}
+
+	@Test(expected = ProtocolException.class)
+	public void checkIncorrectActorTest() throws ProtocolException {
+		phase.checkAction(party1, new EndNegotiation(party2), 1);
+	}
+
+	@Test(expected = ProtocolException.class)
+	public void checkNotAllowedActionTest() throws ProtocolException {
+		phase.checkAction(party2,
+				new VotesWithValue(party1, Collections.singleton(vote)), 1);
+	}
+
+	@Test
+	public void testFinish() {
+		Phase ph = phase.finish();
+		assertTrue(ph.getPartyStates().getNotYetActed().isEmpty());
+		assertEquals(3, ph.getPartyStates().getExceptions().size());
+		assertTrue(phase.getPartyStates().getAgreements().getMap().isEmpty());
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNextWrong() {
+		// state is not final, should not work
+		phase.next(1, 1000);
+	}
+
+	@Test
+	public void testNextNoParties() {
+		Phase ph = phase.finish();
+		// no parties are left now, all failed
+		// but this is not part of the next() check.
+		Phase next = ph.next(1, 1000);
+		assertTrue(next instanceof OptInPhase);
+		// no remaining parties, since finish kicked all
+		assertTrue(next.getPartyStates().getNotYetActed().isEmpty());
+	}
+
+	@Test
+	public void testAllowed() {
+		phase.with(party1, votes, 1);
+	}
+
+	@Test
+	public void getVotesTest() {
+		assertEquals(Collections.emptyList(), phase.getVotes());
+		List<VotesWithValue> vts = phase.with(party1, votes, 1).getVotes();
+		assertEquals(Arrays.asList(votes), vts);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testNextTooShortDuration() {
+		phase.next(1, Phase.PHASE_MINTIME - 1);
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNextNotFinished() {
+		phase.next(1, Phase.PHASE_MINTIME + 1);
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testNext() {
+		phase.next(11, Phase.PHASE_MINTIME + 1);
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/saop/SAOPSettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/saop/SAOPSettingsTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/saop/SAOPSettingsTest.java	(revision 30)
@@ -19,6 +19,6 @@
 import geniusweb.deadline.DeadlineRounds;
 import geniusweb.deadline.DeadlineTime;
-import geniusweb.protocol.session.OnePartyTeam;
 import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.Parameters;
 import geniusweb.references.PartyRef;
@@ -30,11 +30,11 @@
 public class SAOPSettingsTest extends GeneralTests<SAOPSettings> {
 
-	private OnePartyTeam partyprof1 = mock(OnePartyTeam.class);
-	private OnePartyTeam partyprof2 = mock(OnePartyTeam.class);
-	private OnePartyTeam partyprof3 = mock(OnePartyTeam.class);
-	private List<OnePartyTeam> participants2 = Arrays.asList(partyprof1,
+	private TeamInfo partyprof1 = mock(TeamInfo.class);
+	private TeamInfo partyprof2 = mock(TeamInfo.class);
+	private TeamInfo partyprof3 = mock(TeamInfo.class);
+	private List<TeamInfo> participants2 = Arrays.asList(partyprof1,
 			partyprof2);
-	private List<OnePartyTeam> participants3 = Arrays.asList(partyprof1,
-			partyprof2, partyprof3);
+	private List<TeamInfo> participants3 = Arrays.asList(partyprof1, partyprof2,
+			partyprof3);
 
 	private DeadlineTime deadline = mock(DeadlineTime.class);
@@ -47,5 +47,5 @@
 
 	private SAOPSettings sersettings;
-	private final String serialized = "{\"SAOPSettings\":{\"participants\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"},{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}}}}";
+	private final String serialized = "{\"SAOPSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party1\",\"parameters\":{}},\"profile\":\"http://profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"http://party2\",\"parameters\":{}},\"profile\":\"http://profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":100}}}}";
 
 	@Before
@@ -54,4 +54,8 @@
 		when(deadline.toString()).thenReturn("deadline");
 		when(deadline2.toString()).thenReturn("deadline2");
+
+		when(partyprof1.getSize()).thenReturn(1);
+		when(partyprof2.getSize()).thenReturn(1);
+		when(partyprof3.getSize()).thenReturn(1);
 
 		when(partyprof1.toString()).thenReturn("party and profile 1");
@@ -67,7 +71,7 @@
 				new PartyRef("http://party2"), new Parameters());
 		ProfileRef profile2 = new ProfileRef("http://profile2");
-		OnePartyTeam partywithprof1 = new OnePartyTeam(party1, profile1);
-		OnePartyTeam partywithprof2 = new OnePartyTeam(party2, profile2);
-		List<OnePartyTeam> participants = Arrays.asList(partywithprof1,
+		TeamInfo partywithprof1 = new TeamInfo(party1, profile1);
+		TeamInfo partywithprof2 = new TeamInfo(party2, profile2);
+		List<TeamInfo> participants = Arrays.asList(partywithprof1,
 				partywithprof2);
 
Index: protocol/src/test/java/geniusweb/protocol/session/saop/SAOPTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/saop/SAOPTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/saop/SAOPTest.java	(revision 30)
@@ -20,6 +20,6 @@
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
-
+
+import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -44,6 +44,5 @@
 import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
 import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
-import geniusweb.protocol.session.OnePartyTeam;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.Parameters;
 import geniusweb.references.PartyRef;
@@ -71,5 +70,5 @@
 			finalstate = mock(SAOPState.class);
 
-	private final ProtocolRef SAOP;
+	private ProtocolRef SAOP;
 	private static final PartyId PARTY2ID = new PartyId("party2");
 	private static final PartyId PARTY1ID = new PartyId("party1");
@@ -77,6 +76,6 @@
 	private final PartyRef party2ref = mock(PartyRef.class);
 	private final SAOPSettings settings = mock(SAOPSettings.class);
-	private final OnePartyTeam team1 = mock(OnePartyTeam.class);
-	private final OnePartyTeam team2 = mock(OnePartyTeam.class);
+	private final TeamInfo team1 = mock(TeamInfo.class);
+	private final TeamInfo team2 = mock(TeamInfo.class);
 	private SAOP saop;
 	private ProtocolToPartyConnFactory factory;
@@ -84,5 +83,5 @@
 	private ProtocolToPartyConn conn1 = mock(ProtocolToPartyConn.class),
 			conn2 = mock(ProtocolToPartyConn.class);
-	private Map<PartyId, OnePartyTeam> partyprofiles;
+	private Map<PartyId, TeamInfo> partyprofiles;
 	private Progress progress = mock(Progress.class);
 	private ProfileRef profile1;
@@ -102,11 +101,16 @@
 	private final Deadline deadlinetime = mock(DeadlineTime.class);
 
-	public SAOPTest()
+	@Before
+	public void before()
 			throws URISyntaxException, IOException, NoResourcesNowException {
 		SAOP = new ProtocolRef("SAOP");
-		when(team1.getParty()).thenReturn(partywithparam1);
-		when(team2.getParty()).thenReturn(partywithparam2);
-		when(team1.getAllParties()).thenReturn(Arrays.asList(team1));
-		when(team2.getAllParties()).thenReturn(Arrays.asList(team2));
+
+		profile1 = mock(ProfileRef.class);
+		profile2 = mock(ProfileRef.class);
+
+		PartyWithProfile pwp1 = new PartyWithProfile(partywithparam1, profile1);
+		PartyWithProfile pwp2 = new PartyWithProfile(partywithparam2, profile2);
+		when(team1.getAllParties()).thenReturn(Arrays.asList(pwp1));
+		when(team2.getAllParties()).thenReturn(Arrays.asList(pwp2));
 
 		partyprofiles = new HashMap<>();
@@ -116,9 +120,9 @@
 		when(deadlinetime.getDuration()).thenReturn(1000l);
 
-		List<TeamOfPartiesAndProfiles> teams = new ArrayList<>();
+		List<TeamInfo> teams = new ArrayList<>();
 		teams.add(team1);
 		teams.add(team2);
 		when(settings.getTeams()).thenReturn(teams);
-		when(settings.getAllParties()).thenReturn(Arrays.asList(team1, team2));
+		when(settings.getAllParties()).thenReturn(Arrays.asList(pwp1, pwp2));
 		when(settings.getDeadline()).thenReturn(deadlinetime);
 
@@ -131,9 +135,4 @@
 		when(conn1.getParty()).thenReturn(PARTY1ID);
 		when(conn2.getParty()).thenReturn(PARTY2ID);
-
-		profile1 = mock(ProfileRef.class);
-		when(team1.getProfile()).thenReturn(profile1);
-		profile2 = mock(ProfileRef.class);
-		when(team2.getProfile()).thenReturn(profile2);
 
 		mockState(connectedstate, "connected state");
@@ -153,4 +152,10 @@
 		saop = new SAOP(state, new ReportToLogger("test"));
 		saop.addListener(testlistener);
+
+		Map<PartyId, PartyWithProfile> profilesmap = new HashMap<>();
+		profilesmap.put(PARTY1ID, pwp1);
+		profilesmap.put(PARTY2ID, pwp2);
+		when(state.getPartyProfiles()).thenReturn(profilesmap);
+
 	}
 
@@ -164,7 +169,5 @@
 		when(state.getSettings()).thenReturn(settings);
 		when(state.getConnections()).thenReturn(connectionswithparties);
-		when(state.getPartyProfiles())
-				.thenReturn(partyprofiles.entrySet().stream().collect(
-						Collectors.toMap(e -> e.getKey(), e -> e.getValue())));
+
 		when(state.getProgress()).thenReturn(progress);
 		when(state.with(any(ProtocolToPartyConn.class),
Index: protocol/src/test/java/geniusweb/protocol/session/saop/SaopPartyWithProfileTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/saop/SaopPartyWithProfileTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/saop/SaopPartyWithProfileTest.java	(revision 30)
@@ -17,6 +17,5 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-import geniusweb.protocol.session.OnePartyTeam;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.Parameters;
 import geniusweb.references.PartyRef;
@@ -25,11 +24,11 @@
 import tudelft.utilities.junit.GeneralTests;
 
-public class SaopPartyWithProfileTest extends GeneralTests<OnePartyTeam> {
+public class SaopPartyWithProfileTest extends GeneralTests<TeamInfo> {
 
 	private PartyRef party1, party1a, party2, party3;
 	private ProfileRef profile1, profile2;
-	private OnePartyTeam partyprof1, partyprof1a, partyprof2, partyprof3,
+	private TeamInfo partyprof1, partyprof1a, partyprof2, partyprof3,
 			partyprof4;
-	private final String serialized = "{\"party\":{\"partyref\":\"ws:party1\",\"parameters\":{}},\"profile\":\"ws:profile1\"}";
+	private final String serialized = "{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"ws:party1\",\"parameters\":{}},\"profile\":\"ws:profile1\"}]}}";
 
 	@Before
@@ -54,14 +53,14 @@
 				settings1);
 
-		partyprof1 = new OnePartyTeam(party1withparams1, profile1);
-		partyprof1a = new OnePartyTeam(party1withparams1, profile1);
-		partyprof2 = new OnePartyTeam(party2withparams1, profile1);
-		partyprof3 = new OnePartyTeam(party1withparams1, profile2);
-		partyprof4 = new OnePartyTeam(party1withparams2, profile1);
+		partyprof1 = new TeamInfo(party1withparams1, profile1);
+		partyprof1a = new TeamInfo(party1withparams1, profile1);
+		partyprof2 = new TeamInfo(party2withparams1, profile1);
+		partyprof3 = new TeamInfo(party1withparams1, profile2);
+		partyprof4 = new TeamInfo(party1withparams2, profile1);
 
 	}
 
 	@Override
-	public List<List<OnePartyTeam>> getGeneralTestData() {
+	public List<List<TeamInfo>> getGeneralTestData() {
 		return Arrays.asList(Arrays.asList(partyprof1, partyprof1a),
 				Arrays.asList(partyprof2), Arrays.asList(partyprof3),
@@ -72,8 +71,8 @@
 	public List<String> getGeneralTestStrings() {
 		return Arrays.asList(
-				"PartyWithProfile.PartyRef.ws:party1.,ProfileRef.ws:profile1.*",
-				"PartyWithProfile.PartyRef.ws:party2.,ProfileRef.ws:profile1.*",
-				"PartyWithProfile.PartyRef.ws:party1.,ProfileRef.ws:profile2.*",
-				"PartyWithProfile.PartyRef.ws:party1.\\{a=1\\},ProfileRef.ws:profile1.*");
+				"TeamInfo.*PartyWithProfile.PartyRef.ws:party1.,ProfileRef.ws:profile1.*",
+				"TeamInfo.*PartyWithProfile.PartyRef.ws:party2.,ProfileRef.ws:profile1.*",
+				"TeamInfo.*PartyWithProfile.PartyRef.ws:party1.,ProfileRef.ws:profile2.*",
+				"TeamInfo.*PartyWithProfile.PartyRef.ws:party1.\\{a=1\\},ProfileRef.ws:profile1.*");
 	}
 
@@ -84,5 +83,5 @@
 	@Test(expected = IllegalArgumentException.class)
 	public void nullTest() throws URISyntaxException {
-		new OnePartyTeam((PartyWithParameters) null, (ProfileRef) null);
+		new TeamInfo((PartyWithParameters) null, (ProfileRef) null);
 	}
 
@@ -102,6 +101,5 @@
 		// always used as part of bigger class, do not include class name
 		// and therfore can not serialize as TeamOfPartiesAndProfiles
-		TeamOfPartiesAndProfiles p = jackson.readValue(serialized,
-				OnePartyTeam.class);
+		TeamInfo p = jackson.readValue(serialized, TeamInfo.class);
 		System.out.println(p);
 		assertEquals(partyprof1, p);
Index: protocol/src/test/java/geniusweb/protocol/session/shaop/SHAOPSettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/shaop/SHAOPSettingsTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/shaop/SHAOPSettingsTest.java	(revision 30)
@@ -16,4 +16,5 @@
 import geniusweb.deadline.DeadlineTime;
 import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
 import geniusweb.references.Parameters;
 import geniusweb.references.PartyRef;
@@ -28,7 +29,7 @@
 	private final ObjectMapper jackson = new ObjectMapper();
 	private Parameters params = new Parameters();
-	private final String serialized = "{\"SHAOPSettings\":{\"participants\":[{\"cob\":{\"party\":{\"partyref\":\"party2\",\"parameters\":{}},\"profile\":\"profile1\"},\"shaop\":{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}},{\"cob\":{\"party\":{\"partyref\":\"party4\",\"parameters\":{}},\"profile\":\"profile1\"},\"shaop\":{\"party\":{\"partyref\":\"party3\",\"parameters\":{}},\"profile\":\"profile1\"}}],\"deadline\":{\"deadlinetime\":{\"durationms\":1000}}}}";
+	private final String serialized = "{\"SHAOPSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"},{\"party\":{\"partyref\":\"party2\",\"parameters\":{}},\"profile\":\"profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party3\",\"parameters\":{}},\"profile\":\"profile1\"},{\"party\":{\"partyref\":\"party4\",\"parameters\":{}},\"profile\":\"profile1\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":1000}}}}";
 	private SHAOPSettings settings1, settings1a, settings2, settings3;
-	private ShaopTeam pair1, pair2, pair3;
+	private TeamInfo pair1, pair2, pair3;
 
 	@Before
@@ -57,7 +58,7 @@
 		DeadlineTime deadline = new DeadlineTime(1000);
 		DeadlineTime deadline2 = new DeadlineTime(2000);
-		pair1 = new ShaopTeam(partyprof1, partyprof2);
-		pair2 = new ShaopTeam(partyprof3, partyprof4);
-		pair3 = new ShaopTeam(partyprof5, partyprof6);
+		pair1 = new TeamInfo(Arrays.asList(partyprof1, partyprof2));
+		pair2 = new TeamInfo(Arrays.asList(partyprof3, partyprof4));
+		pair3 = new TeamInfo(Arrays.asList(partyprof5, partyprof6));
 
 		settings1 = new SHAOPSettings(Arrays.asList(pair1, pair2), deadline);
@@ -76,7 +77,7 @@
 	public List<String> getGeneralTestStrings() {
 		return Arrays.asList(
-				"SHAOPSettings.*Parties.*PartyWithProfile.*party1.*party2.*Parties.*party3.*party4.*DeadlineTime.*1000.*",
-				"SHAOPSettings.*Parties.*PartyWithProfile.*party1.*party2.*Parties.*party5.*party6.*DeadlineTime.*1000.*",
-				"SHAOPSettings.*Parties.*PartyWithProfile.*party1.*party2.*Parties.*party3.*party4.*DeadlineTime.*2000.*");
+				"SHAOPSettings.*TeamInfo.*PartyWithProfile.*party1.*party2.*TeamInfo.*party3.*party4.*DeadlineTime.*1000.*",
+				"SHAOPSettings.*TeamInfo.*PartyWithProfile.*party1.*party2.*TeamInfo.*party5.*party6.*DeadlineTime.*1000.*",
+				"SHAOPSettings.*TeamInfo.*PartyWithProfile.*party1.*party2.*TeamInfo.*party3.*party4.*DeadlineTime.*2000.*");
 	}
 
Index: protocol/src/test/java/geniusweb/protocol/session/shaop/SHAOPStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/shaop/SHAOPStateTest.java	(revision 29)
+++ protocol/src/test/java/geniusweb/protocol/session/shaop/SHAOPStateTest.java	(revision 30)
@@ -32,5 +32,5 @@
 import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
 import geniusweb.protocol.session.SessionResult;
-import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.TeamInfo;
 import tudelft.utilities.junit.GeneralTests;
 
@@ -75,8 +75,8 @@
 	private Map<PartyId, Integer> partynrs = new HashMap<>();
 
-	private TeamOfPartiesAndProfiles team1 = mock(
-			TeamOfPartiesAndProfiles.class);
-	private TeamOfPartiesAndProfiles team2 = mock(
-			TeamOfPartiesAndProfiles.class);
+	private TeamInfo team1 = mock(
+			TeamInfo.class);
+	private TeamInfo team2 = mock(
+			TeamInfo.class);
 
 	@Before
