diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
index d804aef56c4..2bb6505815f 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
@@ -194,6 +194,13 @@ public interface TrackedRace {
*/
TimePoint getTimePointOfNewestEvent();
+ /**
+ * @return the mark passings for competitor in this race received so far; the mark passing objects are
+ * returned such that their {@link MarkPassing#getWaypoint() waypoints} are ordered in the same way they are ordered
+ * in the race's {@link Course}. Note, that this doesn't necessarily guarantee ascending time points, particularly
+ * if premature mark passings have been detected accidentally as can be the case with some tracking providers such
+ * as TracTrac.
+ */
NavigableSet getMarkPassings(Competitor competitor);
void removeWind(Wind wind, WindSource windSource);
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java
index 18dd813b8a0..37f384df959 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java
@@ -100,10 +100,15 @@ public class DynamicGPSFixMovingTrackImpl extends DynamicTrackImpl fixesToUseForSpeedEstimation) {
// TODO factor out the obtaining of relevant fixes which should be the same in super.getEstimatedSpeed(at)
DummyGPSFixMoving atTimed = new DummyGPSFixMoving(at);
- NavigableSet beforeSet = fixesToUseForSpeedEstimation.headSet(atTimed, /* inclusive */ false);
- NavigableSet afterSet = fixesToUseForSpeedEstimation.tailSet(atTimed, /* inclusive */ true);
List relevantFixes = new LinkedList();
+ boolean beforeSetEmpty;
+ GPSFixMoving beforeSetLast = null;
synchronized (this) {
+ NavigableSet beforeSet = fixesToUseForSpeedEstimation.headSet(atTimed, /* inclusive */ false);
+ beforeSetEmpty = beforeSet.isEmpty(); // ask this while holding the lock
+ if (!beforeSetEmpty) {
+ beforeSetLast = beforeSet.last();
+ }
for (GPSFixMoving beforeFix : beforeSet.descendingSet()) {
if (at.asMillis() - beforeFix.getTimePoint().asMillis() > getMillisecondsOverWhichToAverage() / 2) {
break;
@@ -111,7 +116,14 @@ public class DynamicGPSFixMovingTrackImpl extends DynamicTrackImpl afterSet = fixesToUseForSpeedEstimation.tailSet(atTimed, /* inclusive */ true);
+ afterSetEmpty = afterSet.isEmpty(); // ask this while holding the lock
+ if (!afterSetEmpty) {
+ afterSetFirst = afterSet.first();
+ }
for (GPSFixMoving afterFix : afterSet) {
if (afterFix.getTimePoint().asMillis() - at.asMillis() > getMillisecondsOverWhichToAverage() / 2) {
break;
@@ -121,16 +133,16 @@ public class DynamicGPSFixMovingTrackImpl extends DynamicTrackImpl {
if (o1 == o2) {
result = 0;
} else {
+ // TODO see also bug 340/342; need to synchronize on TrackedRace to avoid concurrent updates to MarkPassings, although this is course-grained
NavigableSet o1MarkPassings = trackedRace.getMarkPassings(o1).headSet(
markPassingWithTimePoint, /* inclusive */true);
NavigableSet o2MarkPassings = trackedRace.getMarkPassings(o2).headSet(
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
index 14e40d57bec..1c068b93054 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.tracking.impl;
+import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NavigableSet;
@@ -39,6 +40,10 @@ public abstract class TrackImpl implements Track
this.fixes = fixes;
}
+ /**
+ * Callers that want to iterate over the collection returned need to synchronize on this object to avoid
+ * {@link ConcurrentModificationException}s.
+ */
protected NavigableSet getInternalRawFixes() {
@SuppressWarnings("unchecked")
NavigableSet result = (NavigableSet) fixes;
@@ -46,8 +51,11 @@ public abstract class TrackImpl implements Track
}
/**
- * @return the smoothened fixes; this implementation simply delegates to {@link #getInternalRawFixes()} because for only
- * {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the
+ * Callers that want to iterate over the collection returned need to synchronize on this object to
+ * avoid {@link ConcurrentModificationException}s.
+ *
+ * @return the smoothened fixes; this implementation simply delegates to {@link #getInternalRawFixes()} because for
+ * only {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the
* FixType may provide smoothening implementations.
*/
protected NavigableSet getInternalFixes() {