diff --git a/java/com.googlecode.java-diff-utils/src/difflib/Delta.java b/java/com.googlecode.java-diff-utils/src/difflib/Delta.java index d038d0dc9c5..4d6921ac554 100755 --- a/java/com.googlecode.java-diff-utils/src/difflib/Delta.java +++ b/java/com.googlecode.java-diff-utils/src/difflib/Delta.java @@ -18,7 +18,9 @@ package difflib; import java.util.*; /** - * Describes the delta between original and revised texts. + * Describes the delta between original and revised object lists. Applying the patch only uses {@link List#add(int, Object)} + * and {@link List#remove(int)} as modifying operations. In particular, no call to {@link List#set(int, Object)} needs to be + * expected. * * @author Dmitry Naumenko */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceAsWaypointList.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceAsWaypointList.java new file mode 100755 index 00000000000..a54f3ddd55a --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceAsWaypointList.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.tracking.impl; + +import java.util.AbstractList; + +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sse.common.Util; + +public class TrackedRaceAsWaypointList extends AbstractList { + private final TrackedRaceImpl trackedRace; + + protected TrackedRaceAsWaypointList(TrackedRaceImpl trackedRace) { + this.trackedRace = trackedRace; + } + + @Override + public Waypoint get(int index) { + final Waypoint result; + final int numberOfLegs = Util.size(trackedRace.getTrackedLegs()); + if (index > numberOfLegs) { + result = null; + } else if (index == numberOfLegs) { + result = Util.get(trackedRace.getTrackedLegs(), numberOfLegs-1).getLeg().getTo(); + } else { + result = Util.get(trackedRace.getTrackedLegs(), index).getLeg().getFrom(); + } + return result; + } + + @Override + public int size() { + return Util.size(trackedRace.getTrackedLegs())+1; + } + + @Override + public void add(int index, Waypoint element) { + trackedRace.waypointAdded(index, element); + } + + @Override + public Waypoint remove(int zeroBasedIndex) { + final Waypoint waypointThatGotRemoved = get(zeroBasedIndex); + if (waypointThatGotRemoved != null) { + trackedRace.waypointRemoved(zeroBasedIndex, waypointThatGotRemoved); + } + return waypointThatGotRemoved; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index c9bc25b522b..ce3b30df17e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -117,6 +117,10 @@ import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import difflib.DiffUtils; +import difflib.Patch; +import difflib.PatchFailedException; + public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials implements CourseListener { private static final long serialVersionUID = -4825546964220003507L; @@ -465,7 +469,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl * When de-serializing, a possibly remote {@link #windStore} is ignored because it is transient. Instead, an * {@link EmptyWindStore} is used for the de-serialized instance. */ - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException, PatchFailedException { ois.defaultReadObject(); attachedRaceLogs = new ConcurrentHashMap<>(); markPassingsTimes = new ArrayList>>(); @@ -480,6 +484,25 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl triggerManeuverCacheRecalculationForAllCompetitors(); logger.info("Deserialized race " + getRace().getName()); shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2); + // considering the unlikely possibility that the course and this tracked race's internal structures + // may be inconsistent, e.g., due to non-atomic serialization of course and tracked race; see bug 2223 + adjustStructureToCourse(); + } + + /** + * When the {@link TrackedRace} object and the {@link RaceDefinition} and in particular its {@link CourseImpl} objects are not + * atomically serialized, inconsistencies may occur during de-serialization. In particular, the tracked race's leg-oriented + * structures may not consistently reflect the course's leg sequence because a course update could have happened between + * course serialization and tracked race serialization.

+ * + * To fix this, the list of waypoints as found in this tracked race's leg-oriented structures, compared to the course's + * waypoint list, produces a patch that can be applied to this tracked race, resulting in the necessary + * {@link #waypointAdded(int, Waypoint)} and {@link #waypointRemoved(int, Waypoint)} calls. + */ + private void adjustStructureToCourse() throws PatchFailedException { + final TrackedRaceAsWaypointList trackedRaceAsWaypointList = new TrackedRaceAsWaypointList(this); + Patch diff = DiffUtils.diff(getRace().getCourse().getWaypoints(), trackedRaceAsWaypointList); + diff.applyTo(trackedRaceAsWaypointList); } @Override