diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java index 4a97ac5a46b..7a619ea19e3 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -75,9 +75,7 @@ import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; import com.sap.sse.common.WithID; -import com.sap.sse.common.impl.MillisecondsTimePoint; import difflib.PatchFailedException; @@ -239,50 +237,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { return new RegattaLogDeviceMarkMappingFinder(params.getRegattaLog()).analyze(); } - /** - * Use mapping time ranges to set time start and end of tracking time for race - */ - private void updateStartAndEndOfTracking() { - // TODO bug 3196: this would need to be evaluated lazily in TrackedRaceImpl.getStartOfTracking / getEndOfTracking - final Pair trackingTimesFromRaceLog = trackedRace.getTrackingTimesFromRaceLogs(); - if (trackingTimesFromRaceLog == null) { - TimePoint earliestMappingStart = new MillisecondsTimePoint(Long.MAX_VALUE); - TimePoint latestMappingEnd = new MillisecondsTimePoint(Long.MIN_VALUE); - for (List> list : competitorMappings.values()) { - for (DeviceMapping mapping : list) { - earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from()); - latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to()); - } - } - for (List> list : markMappings.values()) { - for (DeviceMapping mapping : list) { - earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from()); - latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to()); - } - } - trackedRace.setStartOfTrackingReceived(earliestMappingStart == null ? TimePoint.BeginningOfTime : earliestMappingStart); - trackedRace.setEndOfTrackingReceived(latestMappingEnd == null ? TimePoint.EndOfTime : latestMappingEnd); - } - } - - private TimePoint getUpdatedEarliestMappingStart(TimePoint earliestMappingStart, final TimePoint from) { - if (from == null) { - earliestMappingStart = null; // no further updates; one open interval means startOfTracking shall be the beginning of time - } else if (earliestMappingStart != null && from.before(earliestMappingStart)) { - earliestMappingStart = from; - } - return earliestMappingStart; - } - - private TimePoint getUpdatedLatestMappingEnd(TimePoint latestMappingEnd, final TimePoint to) { - if (to == null) { - latestMappingEnd = null; // no further updates; one open interval means startOfTracking shall be the beginning of time - } else if (latestMappingEnd != null && to.after(latestMappingEnd)) { - latestMappingEnd = to; - } - return latestMappingEnd; - } - private boolean hasMappingAlreadyBeenLoaded( DeviceMapping newMapping, List> oldMappings) { if (oldMappings == null) { @@ -355,7 +309,7 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { markMappingsByDevices.clear(); markMappingsByDevices.putAll(transformToMappingsByDevice(markMappings)); - updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(); } private void onDeviceMarkMappingEvent(RegattaLogDeviceMarkMappingEvent event) { @@ -429,7 +383,7 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { competitorMappings.putAll(newMappings); competitorMappingsByDevices.clear(); competitorMappingsByDevices.putAll(transformToMappingsByDevice(competitorMappings)); - updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(); } private void onStartTrackingEvent(RaceLogStartTrackingEvent event) { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 9b361a08525..de2a29faa94 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -1029,4 +1029,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public void updateMarkPassingsAfterRaceLogChanges() { } + + @Override + public void updateStartAndEndOfTracking() { + } + + @Override + public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index 95715cebb2c..eb16408e1d3 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -649,4 +649,8 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { public IsManagedByCache resolve(SharedDomainFactory domainFactory) { return this; } + + @Override + public void updateStartAndEndOfTracking() { + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java index 3c4177fce24..f7159f12c42 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java @@ -128,6 +128,7 @@ public interface DynamicTrackedRace extends TrackedRace { void onCourseDesignChangedByRaceCommittee(CourseBase courseDesign); void onStartTimeChangedByRaceCommittee(TimePoint newStartTime); + void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime); void onAbortedByRaceCommittee(Flags flag); 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 94fde1cf210..f8383086ef8 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 @@ -865,6 +865,10 @@ public interface TrackedRace extends Serializable, IsManagedByCache getTrackingTimesFromRaceLogs() { return null; } + + default Pair getStartAndFinishedTimeFromRaceLogs() { + return null; + } /** * Returns all marks found in the {@link #markTracks} map and the mark device mappings and mark @@ -875,4 +879,15 @@ public interface TrackedRace extends Serializable, IsManagedByCache getMarksFromRegattaLogs() { return getMarks(); } + + /** + * Forces update of start and end of tracking. + * Adheres to the following precedence order + * + * 1) RaceLogStartOfTrackingEvent/RaceLogEndOfTrackingEvent + * 2) RaceLogStartTimeEvent - x Minutes, Race finished + x Minutes + * 3) Earliest Mapping, Latest Mapping (of Marks and Competitors) + * 4) Leave previously (manually set start/end of tracking) + */ + public void updateStartAndEndOfTracking(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index c6b2997f97c..25ac24e8a64 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -978,6 +978,13 @@ DynamicTrackedRace, GPSTrackListener { } catch (IOException e) { logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e); } + updateStartAndEndOfTracking(); + } + + @Override + public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { + logger.info("Finished time of race "+getRace().getName()+" updated by race committee to "+newFinishedTime); + updateStartAndEndOfTracking(); } @Override @@ -1010,5 +1017,4 @@ DynamicTrackedRace, GPSTrackListener { public DynamicGPSFixTrack getTrack(Mark mark) { return (DynamicGPSFixTrack) super.getTrack(mark); } - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java index 2c27235f626..20a970cba88 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -13,6 +13,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmed import com.sap.sailing.domain.abstractlog.race.RaceLogFixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; @@ -31,6 +32,7 @@ import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.markpassingcalculation.MarkPassingUpdateListener; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.RaceLogWindFixDeclinationHelper; @@ -309,6 +311,14 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { markPassingUpdateListener.removeFixedPassing(revokedEvent.getInvolvedBoats().get(0), ((RaceLogFixedMarkPassingEvent) revokedEvent).getZeroBasedIndexOfPassedWaypoint()); } + if (revokedEvent instanceof RaceLogDependentStartTimeEvent || revokedEvent instanceof RaceLogStartTimeEvent){ + analyzeStartTime(null); + } + if (revokedEvent instanceof RaceLogRaceStatusEvent){ + if (((RaceLogRaceStatusEvent) revokedEvent).getNextStatus().equals(RaceLogRaceStatus.FINISHED)){ + trackedRace.invalidateEndTime(); + } + } } } @@ -326,4 +336,11 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { public void visit(RaceLogEndOfTrackingEvent event) { trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint()); } + + @Override + public void visit(RaceLogRaceStatusEvent event) { + if (event.getNextStatus().equals(RaceLogRaceStatus.FINISHED)){ + trackedRace.invalidateEndTime(); + } + } } 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 135ca76e571..bf7d47fb01e 100644 --- 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 @@ -40,6 +40,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder; @@ -49,6 +50,8 @@ import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDefinedMarkAnalyzer; +import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceCompetitorMappingFinder; +import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMarkMappingFinder; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; @@ -111,6 +114,7 @@ import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.ranking.RankingMetric; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; @@ -156,6 +160,8 @@ import difflib.Patch; import difflib.PatchFailedException; public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials implements CourseListener { + private static final int TRACKING_BUFFER_IN_MINUTES = 5; + private static final long serialVersionUID = -4825546964220003507L; private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName()); @@ -722,11 +728,100 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public TimePoint getStartOfTracking() { + updateStartAndEndOfTracking();//FIXME: unclear whether change listeners etc similar to DynamicTrackedRace can be used here for caching and invalidation return startOfTrackingReceived; } + + /** + * Use mapping time ranges to set time start and end of tracking time for race + */ + public void updateStartAndEndOfTracking() { + final Pair trackingTimesFromRaceLog = this.getTrackingTimesFromRaceLogs(); + final Pair startAndFinishedTimesFromRaceLog = this.getStartAndFinishedTimeFromRaceLogs(); + + boolean startOfTrackingFound = false; + boolean endOfTrackingFound = false; + + if (trackingTimesFromRaceLog != null){ + if (trackingTimesFromRaceLog.getA() != null){ + setStartOfTrackingReceived(trackingTimesFromRaceLog.getA(), false); + startOfTrackingFound = true; + } + if (trackingTimesFromRaceLog.getB() != null){ + setEndOfTrackingReceived(trackingTimesFromRaceLog.getB(), false); + endOfTrackingFound = true; + } + } + + if(!startOfTrackingFound || !endOfTrackingFound){ + if (startAndFinishedTimesFromRaceLog != null){ + if (!startOfTrackingFound && startAndFinishedTimesFromRaceLog.getA() != null){ + setStartOfTrackingReceived(startAndFinishedTimesFromRaceLog.getA().minus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)), false); + startOfTrackingFound = true; + } + if (!endOfTrackingFound && startAndFinishedTimesFromRaceLog.getB() != null){ + setEndOfTrackingReceived(startAndFinishedTimesFromRaceLog.getB().plus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)), false); + endOfTrackingFound = true; + } + } + } + + if(!startOfTrackingFound || !endOfTrackingFound){ + TimePoint earliestMappingStart = new MillisecondsTimePoint(Long.MAX_VALUE); + TimePoint latestMappingEnd = new MillisecondsTimePoint(Long.MIN_VALUE); + Map>> markMappings = new HashMap>>(); + Map>> competitorMappings = new HashMap>>(); + for (RegattaLog regattaLog: attachedRegattaLogs.values()){ + competitorMappings.putAll(new RegattaLogDeviceCompetitorMappingFinder(regattaLog).analyze()); + markMappings.putAll(new RegattaLogDeviceMarkMappingFinder(regattaLog).analyze()); + } + for (List> list : competitorMappings.values()) { + for (DeviceMapping mapping : list) { + earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from()); + latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to()); + } + } + for (List> list : markMappings.values()) { + for (DeviceMapping mapping : list) { + earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from()); + latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to()); + } + } + + if (!startOfTrackingFound && earliestMappingStart != null){ + setStartOfTrackingReceived(earliestMappingStart, false); + } + + if (!endOfTrackingFound && latestMappingEnd != null){ + setEndOfTrackingReceived(latestMappingEnd, false); + } + } + } + + private TimePoint getUpdatedEarliestMappingStart(TimePoint earliestMappingStart, final TimePoint from) { + if (from == null) { + earliestMappingStart = null; // no further updates; one open interval means startOfTracking shall be the beginning of time + } else if (earliestMappingStart != null && from.before(earliestMappingStart)) { + earliestMappingStart = from; + } + return earliestMappingStart; + } + + private TimePoint getUpdatedLatestMappingEnd(TimePoint latestMappingEnd, final TimePoint to) { + if (to == null) { + latestMappingEnd = null; // no further updates; one open interval means startOfTracking shall be the beginning of time + } else if (latestMappingEnd != null && to.after(latestMappingEnd)) { + latestMappingEnd = to; + } + return latestMappingEnd; + } + + + @Override public TimePoint getEndOfTracking() { + updateStartAndEndOfTracking();//FIXME: unclear whether change listeners etc similar to DynamicTrackedRace can be used here for caching and invalidation return endOfTrackingReceived; } @@ -762,6 +857,20 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } return null; } + + @Override + public Pair getStartAndFinishedTimeFromRaceLogs() { + //Only one of the RaceLogs should have valid data, so one doesn't have to compare all the + //start/finished time values in order to find the earliest startTime/latestFinishedTime + for (final RaceLog raceLog : attachedRaceLogs.values()) { + TimePoint startTime = new StartTimeFinder(raceLogResolver, raceLog).analyze().getStartTime(); + TimePoint finishedTime = new FinishedTimeFinder(raceLog).analyze(); + if (startTime != null || finishedTime != null){ + return new Util.Pair(startTime, finishedTime); + } + } + return null; + } /** * Calculates the start time of the race from various sources. The highest precedence take the {@link #attachedRaceLogs race logs}, diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index d35c075b9f6..c6d813eb2a2 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -790,4 +790,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public void updateMarkPassingsAfterRaceLogChanges() { } + + @Override + public void updateStartAndEndOfTracking() { + } + + @Override + public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { + } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index 23b95da6c7e..e7b476f6081 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java @@ -624,4 +624,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { public IsManagedByCache resolve(SharedDomainFactory domainFactory) { return this; } + + @Override + public void updateStartAndEndOfTracking() { + } }