Index: /bidspace/pom.xml
===================================================================
--- /bidspace/pom.xml	(revision 3)
+++ /bidspace/pom.xml	(revision 4)
@@ -140,4 +140,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /bidspace/src/main/java/geniusweb/bidspace/BidsWithUtility.java
===================================================================
--- /bidspace/src/main/java/geniusweb/bidspace/BidsWithUtility.java	(revision 3)
+++ /bidspace/src/main/java/geniusweb/bidspace/BidsWithUtility.java	(revision 4)
@@ -3,57 +3,226 @@
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.LinkedList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.Domain;
+import geniusweb.issuevalue.Value;
 import geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace;
 import tudelft.utilities.immutablelist.AbstractImmutableList;
+import tudelft.utilities.immutablelist.FixedList;
+import tudelft.utilities.immutablelist.ImmutableList;
+import tudelft.utilities.immutablelist.JoinedList;
+import tudelft.utilities.immutablelist.MapList;
+import tudelft.utilities.immutablelist.Tuple;
 
 /**
- * Object containing a subset of all bids, that have utility within given range.
+ * Tool class containing functions dealing with utilities of all bids in a given
+ * {@link LinearAdditiveUtilitySpace}. This class caches previously computed
+ * values to accelerate the calls and subsequent calls. Re-use the object to
+ * keep/reuse the cache.
  */
-public class BidsWithUtility extends AbstractImmutableList<Bid> {
-
-	private final List<Bid> collectedbids;
-
-	/**
-	 * Create Bids with utility within given bounds.
-	 * 
-	 * @param space the {@link LinearAdditiveUtilitySpace} to search
-	 * @param min   the minimum required utility
-	 * @param max   the maximum required utility
-	 */
-	public BidsWithUtility(LinearAdditiveUtilitySpace space, BigDecimal min, BigDecimal max) {
-		this.collectedbids = new ArrayList<Bid>(getBids(space, min, max));
-	}
-
-	/**
-	 * Brute force hack for this time. Maybe we can construct this in a lazy way.
-	 * 
-	 * @param space
-	 * @param min
-	 * @param max
-	 * @return
-	 */
-	private List<Bid> getBids(LinearAdditiveUtilitySpace space, BigDecimal min, BigDecimal max) {
-		List<Bid> goodbids = new LinkedList<Bid>();
-		for (Bid bid : new AllBidsList(space.getDomain())) {
-			BigDecimal util = space.getUtility(bid);
-			if (util.compareTo(min) >= 0 && util.compareTo(max) <= 0) {
-				goodbids.add(bid);
-			}
-		}
-		return goodbids;
+public class BidsWithUtility {
+
+	private final List<IssueInfo> issueInfo;
+	private final int precision; // #digits used for Intervals
+
+	/**
+	 * cache. Key = call arguments for {@link #get(int, Interval)}. Value=return
+	 * value of that call.
+	 */
+	private final Map<Tuple<Integer, Interval>, ImmutableList<Bid>> cache = new HashMap<>();
+
+	/**
+	 * Default constructor, uses default precision 6. This value seems practical
+	 * for the common range of issues, utilities and weights. See
+	 * {@link #BidsWithUtil(LinearAdditiveUtilitySpace, int)} for more details
+	 * on the precision.
+	 * 
+	 * @param space the {@link LinearAdditiveUtilitySpace} to analyze
+	 */
+	public BidsWithUtility(LinearAdditiveUtilitySpace space) {
+		this(space, 6);
+	}
+
+	/**
+	 * 
+	 * @param space     the {@link LinearAdditiveUtilitySpace} to analyze
+	 * @param precision the number of digits to use for computations. This value
+	 *                  should match the max number of (digits used in the
+	 *                  weight of an issue + number of digits used in the issue
+	 *                  utility). Usually you also look at the step size, and
+	 *                  the range of interest. For instance if your utility
+	 *                  function has values 1/3 and 2/3, then these have an
+	 *                  'infinite' number of relevant digits. But if we want to
+	 *                  search bids between utility 0.1 and 0.2, then computing
+	 *                  in 2 digits might already be sufficient. This algorithm
+	 *                  has memory and space complexity O( |nissues|
+	 *                  10^precision ). For spaces up to 7 issues, 7 digits
+	 *                  should be feasible; for 9 issues, 6 digits may be the
+	 *                  maximum.
+	 */
+	public BidsWithUtility(LinearAdditiveUtilitySpace space, int precision) {
+		this(getInfo(space, precision), precision);
+	}
+
+	/**
+	 * 
+	 * @param issuesInfo List of the relevant issues (in order of relevance) and
+	 *                   all info of each issue.
+	 * @param precision  the number of digits used in Intervals.
+	 */
+	public BidsWithUtility(List<IssueInfo> issuesInfo, int precision) {
+		if (issuesInfo == null || issuesInfo.isEmpty()) {
+			throw new IllegalArgumentException(
+					"sortedissues list must contain at least 1 element");
+		}
+
+		this.issueInfo = issuesInfo;
+		this.precision = precision;
+
+	}
+
+	/**
+	 * @return the utility {@link Interval} of this space: minimum and maximum
+	 *         achievable utility.
+	 */
+	public Interval getRange() {
+		return getRange(issueInfo.size() - 1);
+	}
+
+	/**
+	 * 
+	 * @param range the minimum and maximum utility required of the bids. to be
+	 *              included (both ends inclusive).
+	 * @return a list with bids that have utility inside range. possibly empty.
+	 *         With decreasing precision, more bids may drop out due to rounding
+	 *         errors.
+	 */
+	public ImmutableList<Bid> getBids(Interval range) {
+		return get(issueInfo.size() - 1, range.round(precision));
+	}
+
+	public List<IssueInfo> getInfo() {
+		return Collections.unmodifiableList(issueInfo);
+	}
+
+	/**
+	 * Create partial BidsWithUtil list considering only issues 0..n, with
+	 * utilities in given range.
+	 * 
+	 * @param n    the number of {@link #issueRanges} to consider, we consider
+	 *             0..n here. The recursion decreases n until n=0
+	 * @param goal the minimum and maximum utility required of the bids. to be
+	 *             included (both ends inclusive)
+	 * @return BidsWithUtil list, possibly empty.
+	 */
+	protected ImmutableList<Bid> get(int n, Interval goal) {
+		if (goal == null) {
+			throw new NullPointerException("Interval=null");
+		}
+
+		// clamp goal into what is reachable. Avoid caching empty
+		goal = goal.intersect(getRange(n));
+		if (goal.isEmpty()) {
+			return new FixedList<>();
+		}
+
+		Tuple<Integer, Interval> cachetuple = new Tuple<>(n, goal);
+		ImmutableList<Bid> cached = cache.get(cachetuple);
+		if (cached != null) {
+			// hits++;
+			return cached;
+		}
+
+		ImmutableList<Bid> result = checkedGet(n, goal);
+		cache.put(cachetuple, result);
+		return result;
+	}
+
+	private static List<IssueInfo> getInfo(LinearAdditiveUtilitySpace space2,
+			int precision) {
+		Domain dom = space2.getDomain();
+		return space2.getDomain().getIssues().stream()
+				.map(issue -> new IssueInfo(issue, dom.getValues(issue),
+						space2.getUtilities().get(issue),
+						space2.getWeight(issue), precision))
+				.collect(Collectors.toList());
+	}
+
+	private ImmutableList<Bid> checkedGet(int n, Interval goal) {
+		IssueInfo info = issueInfo.get(n);
+		// issue is the first issuesWithRange.
+		String issue = issueInfo.get(n).getName();
+
+		if (n == 0)
+			return new OneIssueSubset(info, goal);
+
+		// make new list, joining all sub-lists
+		ImmutableList<Bid> fulllist = new FixedList<>();
+		for (Value val : info.getValues()) {
+			BigDecimal weightedutil = info.getWeightedUtil(val);
+			Interval subgoal = goal.subtract(weightedutil);
+			// recurse: get list of bids for the subspace
+			ImmutableList<Bid> partialbids = get(n - 1, subgoal);
+
+			Bid bid = new Bid(issue, val);
+			ImmutableList<Bid> fullbids = new MapList<Bid, Bid>(
+					pbid -> pbid.merge(bid), partialbids);
+			if (!fullbids.size().equals(BigInteger.ZERO))
+				fulllist = new JoinedList<Bid>(fullbids, fulllist);
+		}
+		return fulllist;
+	}
+
+	/**
+	 * @param n the maximum issuevalue utility to include. Use n=index of last
+	 *          issue s= (#issues in the domain - 1) for the full range of this
+	 *          domain.
+	 * @return Interval (min, max) of the total weighted utility Interval of
+	 *         issues 0..n. All weighted utilities have been rounded to the set
+	 *         {@link #precision}
+	 */
+	private Interval getRange(int n) {
+		Interval value = Interval.ZERO;
+		for (int i = 0; i <= n; i++) {
+			value = value.add(issueInfo.get(i).getInterval());
+		}
+		return value;
+	}
+
+}
+
+/**
+ * List of all one-issue bids that have utility inside given interval.
+ */
+class OneIssueSubset extends AbstractImmutableList<Bid> {
+	private final IssueInfo info;
+	private final Interval interval;
+	private BigInteger size;
+
+	/**
+	 * 
+	 * @param info     the {@link IssueInfo}
+	 * @param interval a utility interval (weighted)
+	 */
+	OneIssueSubset(IssueInfo info, Interval interval) {
+		this.info = info;
+		this.interval = interval;
+		this.size = BigInteger.valueOf(info.subsetSize(interval));
 	}
 
 	@Override
 	public Bid get(BigInteger index) {
-		return collectedbids.get(index.intValue());
+		return new Bid(info.getName(),
+				info.subset(interval).get(index.intValue()));
 	}
 
 	@Override
 	public BigInteger size() {
-		return BigInteger.valueOf(collectedbids.size());
+		return size;
 	}
 
Index: /bidspace/src/main/java/geniusweb/bidspace/Interval.java
===================================================================
--- /bidspace/src/main/java/geniusweb/bidspace/Interval.java	(revision 4)
+++ /bidspace/src/main/java/geniusweb/bidspace/Interval.java	(revision 4)
@@ -0,0 +1,149 @@
+package geniusweb.bidspace;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * An interval [min, max].
+ *
+ */
+public class Interval {
+	public static Interval ZERO = new Interval(BigDecimal.ZERO,
+			BigDecimal.ZERO);
+
+	private final BigDecimal min;
+	private final BigDecimal max;
+
+	/**
+	 * AN interval [min,max]. If min>max, then there are no values in this
+	 * interval.
+	 * 
+	 * @param min
+	 * @param max
+	 */
+	public Interval(BigDecimal min, BigDecimal max) {
+		if (min == null || max == null) {
+			throw new NullPointerException("min and max must contain not null");
+		}
+		this.min = min;
+		this.max = max;
+	}
+
+	public BigDecimal getMin() {
+		return min;
+	}
+
+	public BigDecimal getMax() {
+		return max;
+	}
+
+	/**
+	 * 
+	 * @return true iff this range does not contain any element.
+	 */
+	public boolean isEmpty() {
+		return min.compareTo(max) > 0;
+	}
+
+	/**
+	 * 
+	 * @param value the value to test
+	 * @return true iff min<=value<=max
+	 */
+	public boolean contains(BigDecimal value) {
+		return min.compareTo(value) <= 0 && max.compareTo(value) >= 0;
+	}
+
+	/**
+	 * 
+	 * @param other {@link Interval} to be added to this
+	 * @return new interval [ this.min + other.min , this.max + other.max ]
+	 */
+	public Interval add(Interval other) {
+		return new Interval(min.add(other.min), max.add(other.max));
+	}
+
+	/**
+	 * 
+	 * @param minMax another minMax to intersect with
+	 * @return intersection with other. returns null if intersection is empty.
+	 */
+	public Interval intersect(Interval other) {
+		return new Interval(min.max(other.min), max.min(other.max));
+	}
+
+	/**
+	 * 
+	 * @param other the other minmax to deal with
+	 * @return the range of values that, when added to a value from other, will
+	 *         possibly get in our range. effectively, [min-other.max,
+	 *         max-other.min]. Returns null if the resulting range is empty.
+	 */
+	public Interval invert(Interval other) {
+		BigDecimal newmin = min.subtract(other.max);
+		BigDecimal newmax = max.subtract(other.min);
+		if (newmin.compareTo(newmax) > 0)
+			return null;
+		return new Interval(newmin, newmax);
+	}
+
+	/**
+	 * 
+	 * @param value the value to subtract
+	 * @return Interval with both min and max reduced by value.
+	 */
+	public Interval subtract(BigDecimal value) {
+		return new Interval(min.subtract(value), max.subtract(value));
+	}
+
+	public Interval multiply(BigDecimal weight) {
+		return new Interval(min.multiply(weight), max.multiply(weight));
+	}
+
+	@Override
+	public String toString() {
+		return "Interval[" + min + "," + max + "]";
+	}
+
+	/**
+	 * @param precision number of digits required
+	 * @return this but with modified precision. The interval is rounded so that
+	 *         the new interval is inside the old one.
+	 */
+	public Interval round(int precision) {
+		return new Interval(min.setScale(precision, RoundingMode.UP),
+				max.setScale(precision, RoundingMode.DOWN));
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((max == null) ? 0 : max.hashCode());
+		result = prime * result + ((min == null) ? 0 : min.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;
+		Interval other = (Interval) obj;
+		if (max == null) {
+			if (other.max != null)
+				return false;
+		} else if (!max.equals(other.max))
+			return false;
+		if (min == null) {
+			if (other.min != null)
+				return false;
+		} else if (!min.equals(other.min))
+			return false;
+		return true;
+	}
+
+}
Index: /bidspace/src/main/java/geniusweb/bidspace/IssueInfo.java
===================================================================
--- /bidspace/src/main/java/geniusweb/bidspace/IssueInfo.java	(revision 4)
+++ /bidspace/src/main/java/geniusweb/bidspace/IssueInfo.java	(revision 4)
@@ -0,0 +1,122 @@
+package geniusweb.bidspace;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import geniusweb.issuevalue.Value;
+import geniusweb.issuevalue.ValueSet;
+import geniusweb.profile.utilityspace.ValueSetUtilities;
+
+/**
+ * Tool class to collect all relevant info about one issue (from LinearAdditive)
+ * in one class. Used for internally grouping data for more efficient
+ * processing. This class may change in the future, not recommended for direct
+ * use.
+ * <p>
+ * immutable
+ */
+public class IssueInfo {
+	private final String name;
+	private final ValueSet values;
+	private final Interval interval;
+	private final Map<Value, BigDecimal> weightedUtils;
+
+	public IssueInfo(String name, ValueSet values, ValueSetUtilities utils,
+			BigDecimal weight, int precision) {
+		this.name = name;
+		this.values = values;
+		this.weightedUtils = computeWeightedUtils(utils, weight, precision);
+		this.interval = getRange();
+	}
+
+	public ValueSet getValues() {
+		return values;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * 
+	 * @return weighted minimum and maximum utility achievable with this issue,
+	 *         rounded to the {@link #precision}.
+	 */
+	public Interval getInterval() {
+		return interval;
+	}
+
+	/**
+	 * @param val the issue value to be evaluated
+	 * @return weighted utility of given value, rounded to nearest value with
+	 *         {@link #precision} digits.
+	 */
+	public BigDecimal getWeightedUtil(Value val) {
+		return weightedUtils.get(val);
+	}
+
+	/**
+	 * 
+	 * @param interval an {@link Interval} of utility values.
+	 * @return all values that are inside the interval.
+	 */
+	protected List<Value> subset(Interval interval) {
+
+		List<Value> selection = new ArrayList<>();
+		for (Value value : values) {
+			if (interval.contains(getWeightedUtil(value)))
+				selection.add(value);
+		}
+		return selection;
+	}
+
+	/**
+	 * Faster way to determine subset size, it does not create a list
+	 * 
+	 * @param interval an {@link Interval} of utility values.
+	 * @return size of the subset that you will get from calling subset
+	 */
+	protected int subsetSize(Interval interval) {
+		int n = 0;
+		for (Value value : values)
+			if (interval.contains(getWeightedUtil(value)))
+				n++;
+		return n;
+	}
+
+	/**
+	 * @return the {@link Interval} (minimum and maximum) of the utility of the
+	 *         weighted utility of this issue, properly rounded to the
+	 *         {@link #precision}/
+	 * 
+	 */
+	private Interval getRange() {
+		BigDecimal min = BigDecimal.ONE;
+		BigDecimal max = BigDecimal.ZERO;
+		for (Value value : values) {
+			BigDecimal util = getWeightedUtil(value);
+			if (util.compareTo(min) < 0)
+				min = util;
+			if (util.compareTo(max) > 0)
+				max = util;
+		}
+		return new Interval(min, max);
+
+	}
+
+	private Map<Value, BigDecimal> computeWeightedUtils(
+			ValueSetUtilities utilities, BigDecimal w, int prec) {
+		Map<Value, BigDecimal> map = new HashMap<>();
+
+		for (Value val : values) {
+			map.put(val, utilities.getUtility(val).multiply(w).setScale(prec,
+					RoundingMode.HALF_UP));
+		}
+		return map;
+	}
+
+}
Index: /bidspace/src/test/java/geniusweb/bidspace/BidsWithUtilityTest.java
===================================================================
--- /bidspace/src/test/java/geniusweb/bidspace/BidsWithUtilityTest.java	(revision 3)
+++ /bidspace/src/test/java/geniusweb/bidspace/BidsWithUtilityTest.java	(revision 4)
@@ -1,99 +1,112 @@
 package geniusweb.bidspace;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.math.RoundingMode;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
+import java.util.Random;
 
-import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import geniusweb.issuevalue.Bid;
-import geniusweb.issuevalue.DiscreteValue;
-import geniusweb.issuevalue.DiscreteValueSet;
-import geniusweb.issuevalue.Domain;
-import geniusweb.issuevalue.NumberValue;
-import geniusweb.issuevalue.NumberValueSet;
-import geniusweb.issuevalue.ValueSet;
 import geniusweb.profile.Profile;
 import geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace;
+import tudelft.utilities.immutablelist.ImmutableList;
 
+@RunWith(Parameterized.class)
 public class BidsWithUtilityTest {
-	private static final DiscreteValue I1V2 = new DiscreteValue("i1v2");
-	private static final DiscreteValue I1V1 = new DiscreteValue("i1v1");
-	private static final NumberValue I2V1 = new NumberValue("2.00");
-	private static final NumberValue I2V2 = new NumberValue("2.45");
-	private static final NumberValue I2V3 = new NumberValue("2.90");
-	private static final String DOMAINNAME = "testdomain";
-	private static final String ISSUE1 = "issue1";
-	private static final String ISSUE2 = "issue2";
-	private static final Map<String, ValueSet> issues = new HashMap<>();
+	private final static ObjectMapper jackson = new ObjectMapper();
+	private final Interval utilityGoal = new Interval(new BigDecimal("0.50"),
+			new BigDecimal("0.51"));
+	private LinearAdditiveUtilitySpace profile;
+	private long expectedSize;
+	private int accuracy = 5;
 
-	private static ValueSet values1;
-	private static ValueSet values2;
-	private static final BigDecimal TWO = new BigDecimal("2");
-	private static final BigDecimal THREE = new BigDecimal("3");
-	private final static BigDecimal ZERO_1 = new BigDecimal("0.1"), ZERO_2 = new BigDecimal("0.2"),
-			ZERO_3 = new BigDecimal("0.3");
+	@Parameters
+	public static Collection<Object[]> data() {
+		return Arrays.asList(new Object[][] {
+				{ "src/test/resources/jobs/jobs1.json", 11 },
+				{ "src/test/resources/7issues/7issues1.json", 260000 },
+				{ "src/test/resources/9issues/9issues1.json", 25000000 } });
+	}
 
-	private static Domain domain;
-	private final static ObjectMapper jackson = new ObjectMapper();
+	public BidsWithUtilityTest(String filename, long expectedsize)
+			throws IOException {
+		String file = new String(Files.readAllBytes(Paths.get(filename)));
+		profile = (LinearAdditiveUtilitySpace) jackson.readValue(file,
+				Profile.class);
+		this.expectedSize = expectedsize;
+	}
 
-	@Before
-	public void before() {
-		Collection<DiscreteValue> discretevalues1 = new LinkedList<>();
-		discretevalues1.add(I1V1);
-		discretevalues1.add(I1V2);
-		values1 = new DiscreteValueSet(discretevalues1);
-		issues.put(ISSUE1, values1);
-
-		values2 = new NumberValueSet(TWO, THREE, new BigDecimal("0.45"));
-		issues.put(ISSUE2, values2);
-
-		domain = new Domain(DOMAINNAME, issues);
+	/**
+	 * Test if the values are within acceptable range from the goal.
+	 */
+	@Test
+	public void test() {
+		ImmutableList<Bid> list = new BidsWithUtility(profile, accuracy)
+				.getBids(utilityGoal);
+		Random rand = new Random();
+		// check not all but only 10000 random bids as list may be way too large
+		// to test them all. Testing 1 billion bids may take 15 minutes or so on
+		// quad core i7 @2.4GHz.....
+		// also notice that we may be checking only part of the list if
+		// the size of the list would become bigger than maxint.
+		for (int n = 0; n < 10000; n++) {
+			Bid bid = list.get(rand.nextInt(list.size().intValue()));
+			assertTrue(utilityGoal.contains(profile.getUtility(bid)
+					.setScale(accuracy - 1, RoundingMode.HALF_UP)));
+		}
+		assertTrue(
+				list.size().compareTo(BigInteger.valueOf(expectedSize)) >= 0);
 
 	}
 
 	@Test
-	public void smokeTest() {
-		LinearAdditiveUtilitySpace space = mock(LinearAdditiveUtilitySpace.class);
-		when(space.getDomain()).thenReturn(domain);
-		when(space.getUtility(any(Bid.class))).thenReturn(ZERO_2);
-		new BidsWithUtility(space, new BigDecimal("0.1"), new BigDecimal("0.3"));
+	public void testMaxUtil() {
+		BidsWithUtility bidswithutil = new BidsWithUtility(profile);
+		// notice, this is the *rounded* max util
+		BigDecimal maxutil = bidswithutil.getRange().getMax();
+		Interval goal = new Interval(
+				maxutil.subtract(new BigDecimal("0.00001")), maxutil);
+
+		ImmutableList<Bid> bidsnearmax = bidswithutil.getBids(goal);
+		assertTrue(bidsnearmax.size() != BigInteger.ZERO);
+
+		BigDecimal foundmax = BigDecimal.ZERO;
+		for (Bid bid : bidsnearmax) {
+			BigDecimal util = profile.getUtility(bid);
+			if (util.compareTo(foundmax) > 0) {
+				foundmax = util;
+			}
+		}
+		// found maximum may be slightly lower or higher because we rounded
+		// all weighted utilities.
+		assertTrue(Math
+				.abs(foundmax.doubleValue() - maxutil.doubleValue()) < 0.0001);
 	}
 
 	@Test
-	public void testSelection() {
-		LinearAdditiveUtilitySpace space = mock(LinearAdditiveUtilitySpace.class);
+	public void benchmark() {
+		System.out.println("\nBenchmarking " + profile.getName());
+		BigInteger domainsize = new AllBidsList(profile.getDomain()).size();
 
-		when(space.getDomain()).thenReturn(domain);
-		when(space.getUtility(any(Bid.class))).thenReturn(ZERO_2);
-		BidsWithUtility selected = new BidsWithUtility(space, new BigDecimal("0.1"), new BigDecimal("0.3"));
-		assertEquals(new BigInteger("6"), selected.size());
-	}
-
-	@Test
-	public void testRealSpace() throws IOException {
-		String file1 = new String(Files.readAllBytes(Paths.get("src/test/resources/jobs/jobs1.json")));
-		LinearAdditiveUtilitySpace profile = (LinearAdditiveUtilitySpace) jackson.readValue(file1, Profile.class);
-		BidsWithUtility selected = new BidsWithUtility(profile, ZERO_1, ZERO_3);
-
-		assertEquals(new BigInteger("56"), selected.size());
-		for (Bid bid : selected) {
-			assertEquals(1, profile.getUtility(bid).compareTo(ZERO_1));
-			assertEquals(-1, profile.getUtility(bid).compareTo(ZERO_3));
-		}
+		long start = System.currentTimeMillis();
+		BidsWithUtility body = new BidsWithUtility(profile, accuracy);
+		ImmutableList<Bid> list = body.getBids(utilityGoal);
+		long end = System.currentTimeMillis();
+		System.out.println("run time: " + (end - start) / 1000. + "s");
+		System.out.println("Total size of bidspace:" + domainsize);
+		System.out.println("Result size: " + list.size());
 	}
 
Index: /bidspace/src/test/java/geniusweb/bidspace/IntervalTest.java
===================================================================
--- /bidspace/src/test/java/geniusweb/bidspace/IntervalTest.java	(revision 4)
+++ /bidspace/src/test/java/geniusweb/bidspace/IntervalTest.java	(revision 4)
@@ -0,0 +1,118 @@
+package geniusweb.bidspace;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+
+import tudelft.utilities.junit.GeneralTests;
+
+public class IntervalTest extends GeneralTests<Interval> {
+
+	private static final BigDecimal ZERO = BigDecimal.ZERO;
+	private static final BigDecimal ONE = BigDecimal.ONE;
+	private static final BigDecimal TWO = BigDecimal.valueOf(2);
+	private static final BigDecimal THREE = BigDecimal.valueOf(3);
+	private static final BigDecimal TEN = BigDecimal.TEN;
+	private static final Interval int1 = new Interval(ZERO, ONE);
+	private static final Interval int1a = new Interval(ZERO, ONE);
+	private static final Interval int2 = new Interval(ONE, TEN);
+	private static final Interval int3 = new Interval(TWO, TEN);
+
+	@Override
+	public List<List<Interval>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(int1, int1a), Arrays.asList(int2),
+				Arrays.asList(int3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList("Interval\\[0,1\\]", "Interval\\[1,10\\]",
+				"Interval\\[2,10\\]");
+	}
+
+	@Test
+	public void smokeTest() {
+		new Interval(BigDecimal.ZERO, BigDecimal.ONE);
+	}
+
+	@Test
+	public void smokeTestBad() {
+		Interval i = new Interval(BigDecimal.TEN, BigDecimal.ONE);
+		assertTrue(i.isEmpty());
+	}
+
+	@Test(expected = NullPointerException.class)
+	public void smokeNull1() {
+		new Interval(null, BigDecimal.ZERO);
+	}
+
+	@Test(expected = NullPointerException.class)
+	public void smokeNull2() {
+		new Interval(BigDecimal.ZERO, null);
+	}
+
+	@Test
+	public void testMin() {
+		assertEquals(BigDecimal.ZERO, int1.getMin());
+		assertEquals(BigDecimal.ONE, int2.getMin());
+	}
+
+	@Test
+	public void testMax() {
+		assertEquals(BigDecimal.ONE, int1.getMax());
+		assertEquals(BigDecimal.TEN, int2.getMax());
+	}
+
+	@Test
+	public void containsTest() {
+		assertTrue(int1.contains(BigDecimal.valueOf(0.3)));
+		assertFalse(int1.contains(BigDecimal.valueOf(-0.3)));
+		assertFalse(int1.contains(BigDecimal.valueOf(1.3)));
+
+		assertFalse(int3.contains(ZERO));
+		assertFalse(int3.contains(ONE));
+		assertTrue(int3.contains(TWO));
+		assertTrue(int3.contains(THREE));
+		assertTrue(int3.contains(TEN));
+		assertFalse(int3.contains(BigDecimal.valueOf(10.3)));
+
+	}
+
+	@Test
+	public void testIntersect() {
+		assertEquals(int1, int1.intersect(int1));
+		assertEquals(new Interval(ONE, ONE), int1.intersect(int2));
+		assertTrue(int1.intersect(int3).isEmpty());
+		assertEquals(new Interval(ONE, TWO),
+				new Interval(ZERO, TEN).intersect(new Interval(ONE, TWO)));
+		assertEquals(new Interval(ONE, TWO),
+				new Interval(ONE, TWO).intersect(new Interval(ZERO, TEN)));
+		assertEquals(new Interval(ONE, TWO),
+				new Interval(ZERO, TWO).intersect(new Interval(ONE, TEN)));
+		assertEquals(new Interval(ONE, TWO),
+				new Interval(ONE, TEN).intersect(new Interval(ZERO, TWO)));
+	}
+
+	@Test
+	public void subtractTest() {
+		assertEquals(new Interval(BigDecimal.valueOf(-1), ZERO),
+				int1.subtract(ONE));
+	}
+
+	@Test
+	public void invertTest() {
+		assertEquals(new Interval(BigDecimal.valueOf(-10), ZERO),
+				int1.invert(int2));
+		assertEquals(new Interval(ZERO, TEN), int2.invert(int1));
+		assertEquals(
+				new Interval(BigDecimal.valueOf(-9), BigDecimal.valueOf(8)),
+				int2.invert(int3));
+	}
+
+}
Index: /bidspace/src/test/resources/9issues/9issues1.json
===================================================================
--- /bidspace/src/test/resources/9issues/9issues1.json	(revision 4)
+++ /bidspace/src/test/resources/9issues/9issues1.json	(revision 4)
@@ -0,0 +1,134 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"name": "9issues1",
+		"issueUtilities": {
+			"i1": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.11,
+					"highValue": 9,
+					"highUtility": 0.99
+
+				}
+			},
+			"i2": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.12,
+					"highValue": 9,
+					"highUtility": 0.97
+
+				}
+			},
+			"i3": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.13,
+					"highValue": 9,
+					"highUtility": 0.96
+
+				}
+			},
+			"i4": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.14,
+					"highValue": 9,
+					"highUtility": 0.94
+
+				}
+			},
+			"i5": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.15,
+					"highValue": 9,
+					"highUtility": 0.93
+
+				}
+			},
+			"i6": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.92
+
+				}
+			},
+			"i7": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.91
+
+				}
+			},
+			"i8": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.7664,
+					"highValue": 9,
+					"highUtility": 0.183
+
+				}
+			},
+
+			"i9": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.191,
+					"highValue": 9,
+					"highUtility": 0.891
+
+				}
+			}
+
+		},
+		"issueWeights": {
+			"i1": 0.07531677352656704,
+			"i2": 0.18148468268008364,
+			"i3": 0.12035193969082373,
+			"i4": 0.21649985443378972,
+			"i5": 0.15621694966873587,
+			"i6": 0.111253,
+			"i7": 0.0656768,
+			"i8": 0.0201,
+			"i9": 0.0531
+		},
+		"domain": {
+			"name": "9issues",
+			"issuesValues": {
+				"i1": {
+					"range": ["0", "9", "1"]
+				},
+				"i2": {
+					"range": ["0", "9", "1"]
+				},
+				"i3": {
+					"range": ["0", "9", "1"]
+				},
+				"i4": {
+					"range": ["0", "9", "1"]
+				},
+				"i5": {
+					"range": ["0", "9", "1"]
+				},
+				"i6": {
+					"range": ["0", "9", "1"]
+				},
+				"i7": {
+					"range": ["0", "9", "1"]
+				},
+				"i8": {
+					"range": ["0", "9", "1"]
+				},
+				"i9": {
+					"range": ["0", "9", "1"]
+				}
+
+			}
+		}
+	}
+}
Index: /events/pom.xml
===================================================================
--- /events/pom.xml	(revision 3)
+++ /events/pom.xml	(revision 4)
@@ -134,4 +134,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /exampleparties/boulware/pom.xml
===================================================================
--- /exampleparties/boulware/pom.xml	(revision 3)
+++ /exampleparties/boulware/pom.xml	(revision 4)
@@ -74,4 +74,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /exampleparties/conceder/pom.xml
===================================================================
--- /exampleparties/conceder/pom.xml	(revision 3)
+++ /exampleparties/conceder/pom.xml	(revision 4)
@@ -74,4 +74,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /exampleparties/hardliner/pom.xml
===================================================================
--- /exampleparties/hardliner/pom.xml	(revision 3)
+++ /exampleparties/hardliner/pom.xml	(revision 4)
@@ -74,4 +74,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /exampleparties/linear/pom.xml
===================================================================
--- /exampleparties/linear/pom.xml	(revision 3)
+++ /exampleparties/linear/pom.xml	(revision 4)
@@ -74,4 +74,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /exampleparties/randomparty/pom.xml
===================================================================
--- /exampleparties/randomparty/pom.xml	(revision 3)
+++ /exampleparties/randomparty/pom.xml	(revision 4)
@@ -156,4 +156,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /exampleparties/randompartypy/pom.xml
===================================================================
--- /exampleparties/randompartypy/pom.xml	(revision 3)
+++ /exampleparties/randompartypy/pom.xml	(revision 4)
@@ -147,4 +147,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /exampleparties/timedependentparty/pom.xml
===================================================================
--- /exampleparties/timedependentparty/pom.xml	(revision 3)
+++ /exampleparties/timedependentparty/pom.xml	(revision 4)
@@ -158,4 +158,16 @@
 			</plugin>
 
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
 
 			<plugin>
Index: /exampleparties/timedependentparty/src/main/java/geniusweb/exampleparties/timedependentparty/ExtendedUtilSpace.java
===================================================================
--- /exampleparties/timedependentparty/src/main/java/geniusweb/exampleparties/timedependentparty/ExtendedUtilSpace.java	(revision 4)
+++ /exampleparties/timedependentparty/src/main/java/geniusweb/exampleparties/timedependentparty/ExtendedUtilSpace.java	(revision 4)
@@ -0,0 +1,102 @@
+package geniusweb.exampleparties.timedependentparty;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.LinkedList;
+
+import geniusweb.bidspace.BidsWithUtility;
+import geniusweb.bidspace.Interval;
+import geniusweb.bidspace.IssueInfo;
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.Value;
+import geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace;
+import tudelft.utilities.immutablelist.ImmutableList;
+
+/**
+ * Inner class for TimeDependentParty, made public for testing purposes. This
+ * class may change in the future, use at your own risk.
+ *
+ */
+public class ExtendedUtilSpace {
+	private LinearAdditiveUtilitySpace utilspace;
+	private BigDecimal tolerance; // utility tolerance for a bid.
+	private BidsWithUtility bidutils;
+	// min and max achievable utility
+	private BigDecimal minUtil;
+	private BigDecimal maxUtil;
+
+	public ExtendedUtilSpace(LinearAdditiveUtilitySpace space) {
+		this.utilspace = space;
+		bidutils = new BidsWithUtility(utilspace);
+		computeMinMax();
+		this.tolerance = computeTolerance();
+
+	}
+
+	/**
+	 * Computes the fields minutil and maxUtil.
+	 * <p>
+	 * TODO this is simplistic, very expensive method and may cause us to run
+	 * out of time on large domains.
+	 * <p>
+	 * Assumes that utilspace and bidutils have been set properly.
+	 */
+	private void computeMinMax() {
+		Interval range = bidutils.getRange();
+		this.minUtil = range.getMin();
+		this.maxUtil = range.getMax();
+
+		Bid rvbid = utilspace.getReservationBid();
+		if (rvbid != null) {
+			BigDecimal rv = utilspace.getUtility(rvbid);
+			if (rv.compareTo(minUtil) > 0)
+				minUtil = rv;
+		}
+	}
+
+	/**
+	 * Tolerance is the Interval we need when searching bids. When we are close
+	 * to the maximum utility, this value has to be the distance between the
+	 * best and one-but-best utility.
+	 * 
+	 * @return the minimum tolerance required, which is the minimum difference
+	 *         between the weighted utility of the best and one-but-best issue
+	 *         value.
+	 */
+	protected BigDecimal computeTolerance() {
+		BigDecimal tolerance = BigDecimal.ONE;
+		for (IssueInfo iss : bidutils.getInfo()) {
+			if (iss.getValues().size().compareTo(BigInteger.ONE) > 0) {
+				// we have at least 2 values.
+				LinkedList<BigDecimal> values = new LinkedList<BigDecimal>();
+				for (Value val : iss.getValues()) {
+					values.add(iss.getWeightedUtil(val));
+				}
+				Collections.sort(values);
+				Collections.reverse(values);
+				tolerance = tolerance
+						.min(values.get(0).subtract(values.get(1)));
+			}
+		}
+		return tolerance;
+	}
+
+	public BigDecimal getMin() {
+		return minUtil;
+	}
+
+	public BigDecimal getMax() {
+		return maxUtil;
+	}
+
+	/**
+	 * @param utilityGoal
+	 * @return bids with utility inside [utilitygoal-tolerance, utilitygoal]
+	 */
+	public ImmutableList<Bid> getBids(BigDecimal utilityGoal) {
+		return bidutils.getBids(
+				new Interval(utilityGoal.subtract(tolerance), utilityGoal));
+	}
+
+}
Index: /exampleparties/timedependentparty/src/main/java/geniusweb/exampleparties/timedependentparty/TimeDependentParty.java
===================================================================
--- /exampleparties/timedependentparty/src/main/java/geniusweb/exampleparties/timedependentparty/TimeDependentParty.java	(revision 3)
+++ /exampleparties/timedependentparty/src/main/java/geniusweb/exampleparties/timedependentparty/TimeDependentParty.java	(revision 4)
@@ -3,4 +3,5 @@
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -14,6 +15,4 @@
 import geniusweb.actions.Offer;
 import geniusweb.actions.PartyId;
-import geniusweb.bidspace.AllBidsList;
-import geniusweb.bidspace.BidsWithUtility;
 import geniusweb.issuevalue.Bid;
 import geniusweb.party.Capabilities;
@@ -24,6 +23,6 @@
 import geniusweb.party.inform.Settings;
 import geniusweb.party.inform.YourTurn;
+import geniusweb.profile.Profile;
 import geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace;
-import geniusweb.profile.utilityspace.UtilitySpace;
 import geniusweb.profileconnection.ProfileConnectionFactory;
 import geniusweb.profileconnection.ProfileInterface;
@@ -31,4 +30,5 @@
 import geniusweb.progress.ProgressRounds;
 import geniusweb.references.ProtocolRef;
+import tudelft.utilities.immutablelist.ImmutableList;
 import tudelft.utilities.logging.Reporter;
 
@@ -41,10 +41,12 @@
 public abstract class TimeDependentParty extends DefaultParty {
 
+	private static final BigDecimal DEC0001 = new BigDecimal("0.0001");
+	private static final BigDecimal DEC100 = new BigDecimal("100");
 	private ProfileInterface profileint;
+	private LinearAdditiveUtilitySpace utilspace = null; // last received space
 	private PartyId me;
 	private Progress progress;
-	private Double minUtil = null, maxUtil = null;
 	private Bid lastReceivedBid = null;
-	private double tolerance;
+	private ExtendedUtilSpace extendedspace;
 
 	public TimeDependentParty() {
@@ -77,5 +79,4 @@
 				this.me = settings.getID();
 				this.progress = settings.getProgress();
-				computeMinMax();
 			} else if (info instanceof ActionDone) {
 				Action otheract = ((ActionDone) info).getAction();
@@ -117,13 +118,15 @@
 
 	private void myTurn() throws IOException {
-		Action myAction = null;
+		updateUtilSpace();
 		Bid bid = makeBid();
-		UtilitySpace utilspace = getUtilSpace();
 
-		if (lastReceivedBid != null && utilspace.getUtility(lastReceivedBid)
-				.compareTo(utilspace.getUtility(bid)) >= 0) {
+		Action myAction;
+		if (bid == null || lastReceivedBid != null
+				&& utilspace.getUtility(lastReceivedBid)
+						.compareTo(utilspace.getUtility(bid)) >= 0) {
+			// if bid==null we failed to suggest next bid.
 			myAction = new Accept(me, lastReceivedBid);
 		} else {
-			myAction = new Offer(me, makeBid());
+			myAction = new Offer(me, bid);
 		}
 		getConnection().send(myAction);
@@ -131,51 +134,23 @@
 	}
 
-	private UtilitySpace getUtilSpace() {
-		return (LinearAdditiveUtilitySpace) profileint.getProfile();
+	private LinearAdditiveUtilitySpace updateUtilSpace() {
+		Profile newutilspace = profileint.getProfile();
+		if (!newutilspace.equals(utilspace)) {
+			utilspace = (LinearAdditiveUtilitySpace) newutilspace;
+			extendedspace = new ExtendedUtilSpace(utilspace);
+		}
+		return utilspace;
 	}
 
 	/**
-	 * Computes the fields minutil and maxUtil if not yet computed. minutil is
-	 * the minimum utility of any bid, but at least the utility of the
-	 * reservation bid. maxUtil is the maximum util of any bid.
-	 * <p>
-	 * TODO this is simplistic, very expensive method and may cause us to run
-	 * out of time on large domains.
-	 * <p>
-	 * Can be called only after profileint has been set.
+	 * @return next possible bid with current target utility, or null if no such
+	 *         bid.
 	 */
-	private void computeMinMax() {
-		if (this.maxUtil != null && this.minUtil != null)
-			return;
-		this.minUtil = 1d;
-		this.maxUtil = 0d;
-		UtilitySpace utilspace = getUtilSpace();
-		AllBidsList allbidslist = new AllBidsList(
-				profileint.getProfile().getDomain());
-		for (Bid bid : allbidslist) {
-			double util = utilspace.getUtility(bid).doubleValue();
-			if (util < minUtil)
-				minUtil = util;
-			if (util > maxUtil)
-				maxUtil = util;
-		}
-
-		Bid rvbid = utilspace.getReservationBid();
-		if (rvbid != null) {
-			double rv = utilspace.getUtility(rvbid).doubleValue();
-			if (rv > minUtil)
-				minUtil = rv;
-		}
-		tolerance = 100 / allbidslist.size().longValue();
-		tolerance = Math.max(tolerance, 0.1);
-	}
-
 	private Bid makeBid() {
 		double time = progress.get(System.currentTimeMillis());
-		double utilityGoal = utilityGoal(time, getE());
-		BidsWithUtility options = new BidsWithUtility(
-				(LinearAdditiveUtilitySpace) profileint.getProfile(),
-				BigDecimal.valueOf(utilityGoal),
-				BigDecimal.valueOf(utilityGoal + tolerance));
+		BigDecimal utilityGoal = utilityGoal(time, getE());
+		ImmutableList<Bid> options = extendedspace.getBids(utilityGoal);
+		if (options.size() == BigInteger.ZERO)
+			return null;
 		// pick a random one.
 		return options.get(new Random().nextInt(options.size().intValue()));
@@ -190,10 +165,15 @@
 	 * @return the utility goal for this time and e value
 	 */
-	private double utilityGoal(double t, double e) {
+	private BigDecimal utilityGoal(double t, double e) {
+		BigDecimal minUtil = extendedspace.getMin();
+		BigDecimal maxUtil = extendedspace.getMax();
+
 		double ft = 0;
 		if (e != 0)
 			ft = Math.pow(t, 1 / e);
-		// we subtract 0.000001 to correct possibly small round-up errors
-		return minUtil + (maxUtil - minUtil) * (1 - ft) - 0.000001;
+		// we subtract epsilon to correct possibly small round-up errors
+		return new BigDecimal(minUtil.doubleValue()
+				+ (maxUtil.doubleValue() - minUtil.doubleValue()) * (1 - ft))
+						.min(maxUtil).max(minUtil);
 	}
 }
Index: /exampleparties/timedependentparty/src/test/java/geniusweb/exampleparties/timedependentparty/ExtendedUtilSpaceTest.java
===================================================================
--- /exampleparties/timedependentparty/src/test/java/geniusweb/exampleparties/timedependentparty/ExtendedUtilSpaceTest.java	(revision 4)
+++ /exampleparties/timedependentparty/src/test/java/geniusweb/exampleparties/timedependentparty/ExtendedUtilSpaceTest.java	(revision 4)
@@ -0,0 +1,63 @@
+package geniusweb.exampleparties.timedependentparty;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.profile.Profile;
+import geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace;
+
+@RunWith(Parameterized.class)
+public class ExtendedUtilSpaceTest {
+	private final static ObjectMapper jackson = new ObjectMapper();
+	private final static BigDecimal SMALL = new BigDecimal("0.0001");
+
+	@Parameters
+	public static Collection<Object[]> data() {
+		return Arrays.asList(new Object[][] {
+				{ "src/test/resources/jobs/jobs1.json", 0.02 },
+				{ "src/test/resources/7issues/7issues1.json", 0.0055 },
+				{ "src/test/resources/9issues/9issues1.json", 0.0013 } });
+	}
+
+	private String filename;
+	private BigDecimal expectedTolerance;
+	private ExtendedUtilSpace space;
+
+	public ExtendedUtilSpaceTest(String filename, double expectedTolerance) {
+		this.filename = filename;
+		this.expectedTolerance = BigDecimal.valueOf(expectedTolerance);
+	}
+
+	@Before
+	public void before() throws IOException {
+		String file = new String(Files.readAllBytes(Paths.get(filename)));
+		LinearAdditiveUtilitySpace profile = (LinearAdditiveUtilitySpace) jackson
+				.readValue(file, Profile.class);
+		space = new ExtendedUtilSpace(profile);
+	}
+
+	@Test
+	public void smokeTest() {
+
+	}
+
+	@Test
+	public void testTolerance() {
+		assertTrue(space.computeTolerance().subtract(expectedTolerance).abs()
+				.compareTo(SMALL) < 0);
+	}
+}
Index: /exampleparties/timedependentparty/src/test/resources/7issues/7issues1.json
===================================================================
--- /exampleparties/timedependentparty/src/test/resources/7issues/7issues1.json	(revision 4)
+++ /exampleparties/timedependentparty/src/test/resources/7issues/7issues1.json	(revision 4)
@@ -0,0 +1,105 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"name": "7issues1",
+		"issueUtilities": {
+			"i1": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.11,
+					"highValue": 9,
+					"highUtility": 0.99
+
+				}
+			},
+			"i2": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.12,
+					"highValue": 9,
+					"highUtility": 0.97
+
+				}
+			},
+			"i3": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.13,
+					"highValue": 9,
+					"highUtility": 0.96
+
+				}
+			},
+			"i4": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.14,
+					"highValue": 9,
+					"highUtility": 0.94
+
+				}
+			},
+			"i5": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.15,
+					"highValue": 9,
+					"highUtility": 0.93
+
+				}
+			},
+			"i6": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.92
+
+				}
+			},
+			"i7": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.91
+
+				}
+			}
+		},
+		"issueWeights": {
+			"i1": 0.07531677352656704,
+			"i2": 0.18148468268008364,
+			"i3": 0.13035193969082373,
+			"i4": 0.24649985443378972,
+			"i5": 0.16931694966873587,
+			"i6": 0.131353,
+			"i7": 0.0656768
+		},
+		"domain": {
+			"name": "7issues",
+			"issuesValues": {
+				"i1": {
+					"range": ["0", "9", "1"]
+				},
+				"i2": {
+					"range": ["0", "9", "1"]
+				},
+				"i3": {
+					"range": ["0", "9", "1"]
+				},
+				"i4": {
+					"range": ["0", "9", "1"]
+				},
+				"i5": {
+					"range": ["0", "9", "1"]
+				},
+				"i6": {
+					"range": ["0", "9", "1"]
+				},
+				"i7": {
+					"range": ["0", "9", "1"]
+				}
+			}
+		}
+	}
+}
Index: /exampleparties/timedependentparty/src/test/resources/9issues/9issues1.json
===================================================================
--- /exampleparties/timedependentparty/src/test/resources/9issues/9issues1.json	(revision 4)
+++ /exampleparties/timedependentparty/src/test/resources/9issues/9issues1.json	(revision 4)
@@ -0,0 +1,134 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"name": "9issues1",
+		"issueUtilities": {
+			"i1": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.11,
+					"highValue": 9,
+					"highUtility": 0.99
+
+				}
+			},
+			"i2": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.12,
+					"highValue": 9,
+					"highUtility": 0.97
+
+				}
+			},
+			"i3": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.13,
+					"highValue": 9,
+					"highUtility": 0.96
+
+				}
+			},
+			"i4": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.14,
+					"highValue": 9,
+					"highUtility": 0.94
+
+				}
+			},
+			"i5": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.15,
+					"highValue": 9,
+					"highUtility": 0.93
+
+				}
+			},
+			"i6": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.92
+
+				}
+			},
+			"i7": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.91
+
+				}
+			},
+			"i8": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.7664,
+					"highValue": 9,
+					"highUtility": 0.183
+
+				}
+			},
+
+			"i9": {
+				"numberutils": {
+					"lowValue": 0,
+					"lowUtility": 0.191,
+					"highValue": 9,
+					"highUtility": 0.891
+
+				}
+			}
+
+		},
+		"issueWeights": {
+			"i1": 0.07531677352656704,
+			"i2": 0.18148468268008364,
+			"i3": 0.12035193969082373,
+			"i4": 0.21649985443378972,
+			"i5": 0.15621694966873587,
+			"i6": 0.111253,
+			"i7": 0.0656768,
+			"i8": 0.0201,
+			"i9": 0.0531
+		},
+		"domain": {
+			"name": "9issues",
+			"issuesValues": {
+				"i1": {
+					"range": ["0", "9", "1"]
+				},
+				"i2": {
+					"range": ["0", "9", "1"]
+				},
+				"i3": {
+					"range": ["0", "9", "1"]
+				},
+				"i4": {
+					"range": ["0", "9", "1"]
+				},
+				"i5": {
+					"range": ["0", "9", "1"]
+				},
+				"i6": {
+					"range": ["0", "9", "1"]
+				},
+				"i7": {
+					"range": ["0", "9", "1"]
+				},
+				"i8": {
+					"range": ["0", "9", "1"]
+				},
+				"i9": {
+					"range": ["0", "9", "1"]
+				}
+
+			}
+		}
+	}
+}
Index: /exampleparties/timedependentparty/src/test/resources/jobs/jobs1.json
===================================================================
--- /exampleparties/timedependentparty/src/test/resources/jobs/jobs1.json	(revision 4)
+++ /exampleparties/timedependentparty/src/test/resources/jobs/jobs1.json	(revision 4)
@@ -0,0 +1,116 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"issueUtilities": {
+			"salary": {
+				"discreteutils": {
+					"valueUtilities": {
+						"2000": 0,
+						"2500": 0.25,
+						"3000": 0.3,
+						"3500": 0.75,
+						"4000": 1.0
+					}
+				}
+			},
+			"fte": {
+				"discreteutils": {
+					"valueUtilities": {
+						"0.6": 0.25,
+						"0.8": 0.5,
+						"1.0": 0.75
+					}
+				}
+			},
+			"work from home": {
+				"discreteutils": {
+					"valueUtilities": {
+						"0": 0.333333333,
+						"1": 0.5,
+						"2": 0.666666666666
+					}
+				}
+			},
+			"lease car": {
+				"discreteutils": {
+					"valueUtilities": {
+						"yes":1,
+						"no":0
+					}
+				}
+			},
+			"permanent contract": {
+				"discreteutils": {
+					"valueUtilities": {
+						"yes":1,
+						"no":0
+					}
+				}
+			},
+			"career development opportunities": {
+				"discreteutils": {
+					"valueUtilities": {
+						"low":0,
+						"medium":0.5,
+						"high":1
+					}
+				}
+			}
+		},
+		"issueWeights": {
+			"salary": 0.24,
+			"fte": 0.32,
+			"work from home": 0.18,
+			"lease car": 0.06,
+			"permanent contract": 0.16,
+			"career development opportunities": 0.04
+		},
+		"domain": {
+			"name": "jobs",
+			"issuesValues": {
+				"salary": {
+					"values": [
+						"2000",
+						"2500",
+						"3000",
+						"3500",
+						"4000"
+					]
+				},
+				"fte": {
+					"values": [
+						"0.6",
+						"0.8",
+						"1.0"
+					]
+				},
+				"work from home": {
+					"values": [
+						"0",
+						"1",
+						"2"
+					]
+				},
+				"lease car": {
+					"values": [
+						"yes",
+						"no"
+					]
+				},
+				"permanent contract": {
+					"values": [
+						"yes",
+						"no"
+					]
+				},
+				"career development opportunities": {
+					"values": [
+						"low",
+						"medium",
+						"high"
+					]
+				}
+			}
+		},
+		"name": "jobs1"
+	}
+}
Index: /issuevalue/pom.xml
===================================================================
--- /issuevalue/pom.xml	(revision 3)
+++ /issuevalue/pom.xml	(revision 4)
@@ -128,4 +128,17 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /issuevalue/src/main/java/geniusweb/issuevalue/Bid.java
===================================================================
--- /issuevalue/src/main/java/geniusweb/issuevalue/Bid.java	(revision 3)
+++ /issuevalue/src/main/java/geniusweb/issuevalue/Bid.java	(revision 4)
@@ -3,4 +3,5 @@
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -40,4 +41,19 @@
 
 	/**
+	 * Makes partial bid with just 1 issue and value.
+	 * 
+	 * @param issue the issue name
+	 * @param value the {@link Value}
+	 */
+	public Bid(String issue, Value value) {
+		if (issue == null || value == null) {
+			throw new NullPointerException("issue and value must be not =null");
+		}
+
+		issuevalues = new HashMap<>();
+		issuevalues.put(issue, value);
+	}
+
+	/**
 	 * @param issue
 	 * @return the value for the given issue, or null if there is no value for
@@ -59,4 +75,25 @@
 	public Set<String> getIssues() {
 		return Collections.unmodifiableSet(issuevalues.keySet());
+	}
+
+	/**
+	 * Merges this partial bid with another partial bid.
+	 * 
+	 * @param otherbid another partial bid.
+	 * @return a bid with the combined values of both partial bids.
+	 * @throws IllegalArgumentException if issues overlap.
+	 */
+	public Bid merge(Bid otherbid) {
+		Set<String> ourissues = new HashSet<>(issuevalues.keySet());
+		ourissues.retainAll(otherbid.getIssues());
+		if (!ourissues.isEmpty()) {
+			throw new IllegalArgumentException(
+					"Otherbid contains issues that are already set:"
+							+ ourissues);
+		}
+		HashMap<String, Value> newvalues = new HashMap<>(issuevalues);
+		newvalues.putAll(otherbid.issuevalues);
+		return new Bid(newvalues);
+
 	}
 
Index: /issuevalue/src/test/java/geniusweb/issuevalue/BidTest.java
===================================================================
--- /issuevalue/src/test/java/geniusweb/issuevalue/BidTest.java	(revision 3)
+++ /issuevalue/src/test/java/geniusweb/issuevalue/BidTest.java	(revision 4)
@@ -17,8 +17,4 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-import geniusweb.issuevalue.Bid;
-import geniusweb.issuevalue.DiscreteValue;
-import geniusweb.issuevalue.NumberValue;
-import geniusweb.issuevalue.Value;
 import tudelft.utilities.junit.GeneralTests;
 
@@ -125,3 +121,50 @@
 	}
 
+	@Test
+	public void testMerge() {
+		Map<String, Value> issuevalues = new HashMap<>();
+		issuevalues.put(ISSUE1, VALUE1);
+		Bid partial1 = new Bid(issuevalues);
+
+		issuevalues = new HashMap<>();
+		issuevalues.put(ISSUE2, VALUE2);
+		Bid partial2 = new Bid(issuevalues);
+
+		Bid mergedbid = partial1.merge(partial2);
+		assertEquals(bidc, mergedbid);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testMergeIssuesOverlap() {
+		Map<String, Value> issuevalues = new HashMap<>();
+		issuevalues.put(ISSUE1, VALUE1);
+		Bid partial1 = new Bid(issuevalues);
+
+		issuevalues = new HashMap<>();
+		issuevalues.put(ISSUE1, VALUE2);
+		Bid partial2 = new Bid(issuevalues);
+
+		partial1.merge(partial2);
+	}
+
+	@Test
+	public void smokeTestConstructor2() {
+		new Bid("issue", new DiscreteValue("dss"));
+	}
+
+	@Test
+	public void smokeTestConstructor2b() {
+		new Bid("issue", new NumberValue("0.5"));
+	}
+
+	@Test(expected = NullPointerException.class)
+	public void smokeTestConstructor2Null1() {
+		new Bid(null, new DiscreteValue("dss"));
+	}
+
+	@Test(expected = NullPointerException.class)
+	public void smokeTestConstructor2Null2() {
+		new Bid("issue", null);
+	}
+
 }
Index: /party/pom.xml
===================================================================
--- /party/pom.xml	(revision 3)
+++ /party/pom.xml	(revision 4)
@@ -154,4 +154,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /profile/pom.xml
===================================================================
--- /profile/pom.xml	(revision 3)
+++ /profile/pom.xml	(revision 4)
@@ -134,4 +134,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /profile/src/main/java/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.java
===================================================================
--- /profile/src/main/java/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.java	(revision 3)
+++ /profile/src/main/java/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.java	(revision 4)
@@ -8,11 +8,10 @@
 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.issuevalue.Bid;
 import geniusweb.issuevalue.Domain;
 import geniusweb.issuevalue.Value;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
 
 /**
@@ -221,5 +220,5 @@
 
 	/**
-	 * @return the map from issue names to valuesetutilities
+	 * @return the map from issue names to valuesetutilities (un-weighted)
 	 */
 	public Map<String, ValueSetUtilities> getUtilities() {
Index: /profile/src/main/java/geniusweb/profile/utilityspace/NumberValueSetUtilities.java
===================================================================
--- /profile/src/main/java/geniusweb/profile/utilityspace/NumberValueSetUtilities.java	(revision 3)
+++ /profile/src/main/java/geniusweb/profile/utilityspace/NumberValueSetUtilities.java	(revision 4)
@@ -106,4 +106,35 @@
 	}
 
+	/**
+	 * @return the lowest value
+	 */
+	public BigDecimal getLowValue() {
+		return lowValue;
+	}
+
+	/**
+	 * 
+	 * @return the highest value
+	 */
+	public BigDecimal getHighValue() {
+		return highValue;
+	}
+
+	/**
+	 * 
+	 * @return the utility of the lowest value
+	 */
+	public BigDecimal getLowUtility() {
+		return lowUtility;
+	}
+
+	/**
+	 * 
+	 * @return the utility of the highest value
+	 */
+	public BigDecimal getHighUtility() {
+		return highUtility;
+	}
+
 	@Override
 	public String toString() {
Index: /profileconnection/pom.xml
===================================================================
--- /profileconnection/pom.xml	(revision 3)
+++ /profileconnection/pom.xml	(revision 4)
@@ -143,4 +143,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
Index: /protocol/src/main/java/geniusweb/protocol/ProtocolException.java
===================================================================
--- /protocol/src/main/java/geniusweb/protocol/ProtocolException.java	(revision 3)
+++ /protocol/src/main/java/geniusweb/protocol/ProtocolException.java	(revision 4)
@@ -13,5 +13,4 @@
 
 	/**
-	 * 
 	 * @param message the error message
 	 * @param party   offending party, either the {@link PartyId} or a
@@ -19,9 +18,14 @@
 	 */
 	public ProtocolException(String message, String party) {
-		super(party + ":" + message);
-		this.party = party;
+		this(message, party, null);
 	}
 
 	/**
+	 * ProtocolException is special, in that it does not auto-fill the
+	 * stacktrace. This is needed because usually a ProtocolException is caused
+	 * by a party doing a bad action. Creating a stacktrace pointing to the
+	 * class reporting the protocol exception (usually, the protocol handler)
+	 * makes no sense as the protocol handler is doing the correct job there.
+	 * 
 	 * 
 	 * @param message the error message
@@ -31,5 +35,5 @@
 	 */
 	public ProtocolException(String message, String party, Throwable e) {
-		super(party + ":" + message, e);
+		super(party + ":" + message, e, true, false);
 		this.party = party;
 	}
Index: /protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java
===================================================================
--- /protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 3)
+++ /protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 4)
@@ -2,4 +2,6 @@
 
 import java.io.IOException;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
 import java.util.List;
 import java.util.Timer;
@@ -190,4 +192,6 @@
 							"BUG. Deadline timer has triggered but state is not final");
 				}
+				log.log(Level.WARNING,
+						"SAOP deadline reached. Terminating session.");
 				finish();
 			}
@@ -196,4 +200,8 @@
 		// early
 		deadlinetimer.schedule(task, deadline.getDuration() + TIME_MARGIN);
+		log.log(Level.INFO, "SAOP deadline set to "
+				+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
+						.format(new Date(System.currentTimeMillis()
+								+ deadline.getDuration() + TIME_MARGIN)));
 	}
 
@@ -251,5 +259,6 @@
 			final ConnectionWithParty partyconn, final Action action) {
 		if (action == null) {
-			handleError("Received null from " + partyconn + ". Closing session",
+			handleError(partyconn
+					+ " did null action, breaching the protocol. Closing session",
 					partyconn.getParty().getName(), null);
 			return;
@@ -259,5 +268,6 @@
 			if (!partyconn.getParty().equals(state.getNextActor())) {
 				// party does not have the turn.
-				throw new ProtocolException(" act without having the turn",
+				throw new ProtocolException(
+						"Party acts without having the turn",
 						partyconn.getParty().getName());
 			}
Index: /protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java
===================================================================
--- /protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java	(revision 3)
+++ /protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java	(revision 4)
@@ -189,5 +189,5 @@
 		}
 		if (!actor.equals(getNextActor())) {
-			throw new ProtocolException("actor does not have the turn ",
+			throw new ProtocolException("Party does not have the turn ",
 					actor.getName());
 
@@ -211,6 +211,6 @@
 			if (!bid.equals(((Accept) action).getBid())) {
 				throw new ProtocolException(
-						"Accepts a bid that was not offered (last offer =" + bid
-								+ ", action=" + action + ")",
+						"Party accepts a bid differing from the last offer ="
+								+ bid + ", action=" + action + ")",
 						actor.getName());
 			}
Index: /protocol/src/test/java/geniusweb/protocol/session/saop/SAOPStateTest.java
===================================================================
--- /protocol/src/test/java/geniusweb/protocol/session/saop/SAOPStateTest.java	(revision 3)
+++ /protocol/src/test/java/geniusweb/protocol/session/saop/SAOPStateTest.java	(revision 4)
@@ -284,5 +284,5 @@
 		assertTrue(state.isFinal(NOW));
 		assertEquals(
-				"geniusweb.protocol.ProtocolException: party1:actor does not have the turn ",
+				"geniusweb.protocol.ProtocolException: party1:Party does not have the turn ",
 				state.getError().toString());
 	}
@@ -297,6 +297,6 @@
 		state = state.with(party2, nullaccept);
 		assertTrue(state.isFinal(NOW));
-		assertTrue(state.getError().toString()
-				.matches(".*party2.*Accepts a bid that was not offered.*"));
+		assertTrue(state.getError().toString().matches(
+				".*party2.*Party accepts a bid differing from the last offer.*"));
 	}
 
Index: /timeline/pom.xml
===================================================================
--- /timeline/pom.xml	(revision 3)
+++ /timeline/pom.xml	(revision 4)
@@ -121,4 +121,16 @@
 					<target>1.8</target>
 				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
 			</plugin>
 
