diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java index d08a89ecfc2..ee2b9eca7a5 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java @@ -1,26 +1,16 @@ package com.sap.sailing.domain.racelogtracking.impl.fixtracker; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.regatta.MappingEventVisitor; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; -import com.sap.sailing.domain.abstractlog.regatta.impl.BaseRegattaLogEventVisitor; -import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceCompetitorSensordataMappingFinder; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; @@ -70,17 +60,10 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock; public class FixLoaderAndTracker implements TrackingDataLoader { private static final Logger logger = Logger.getLogger(FixLoaderAndTracker.class.getName()); protected final DynamicTrackedRace trackedRace; - /** - * We maintain our own collection that holds the RegattaLogs. The known RegattaLogs should by in sync with the ones that - * can be obtained from the TrackedRace. When stopping, there could be a concurrency issue that leads to a listener - * not being removed. This is prevented by remembering all RegattaLogs to which we attached a listener. So we can be - * sure to not produce a memory leak. - */ - private final Set knownRegattaLogs = new HashSet<>(); private final NamedReentrantReadWriteLock loadingFromFixStoreLock; private final SensorFixStore sensorFixStore; private final GPSFixStore gpsFixStore; - private final RegattaLogDeviceMappings deviceMappings; + private RegattaLogDeviceMappings deviceMappings; private final AtomicInteger activeLoaders = new AtomicInteger(); private final SensorFixMapperFactory sensorFixMapperFactory; private AtomicBoolean preemptiveStopRequested = new AtomicBoolean(false); @@ -102,45 +85,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader { } public void regattaLogAttached(RegattaLog regattaLog) { - synchronized (knownRegattaLogs) { - addRegattaLogUnlocked(regattaLog); - } - updateMappingsAndAddListeners(); + deviceMappings.addRegattaLog(regattaLog); } }; - private final RegattaLogEventVisitor regattaLogEventVisitor = new BaseRegattaLogEventVisitor() { - @Override - public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) { - logger.log(Level.FINE, "New mapping for: " + event.getMappedTo() + "; device: " + event.getDevice()); - updateMappingsAndAddListeners(); - } - - @Override - public void visit(RegattaLogDeviceMarkMappingEvent event) { - logger.log(Level.FINE, - "New DeviceMarkMapping for: " + event.getMappedTo() + "; device: " + event.getDevice()); - updateMappingsAndAddListeners(); - } - - @Override - public void visit(RegattaLogDeviceCompetitorMappingEvent event) { - logger.log(Level.FINE, - "New DeviceCompetitorMapping for: " + event.getMappedTo() + "; device: " + event.getDevice()); - updateMappingsAndAddListeners(); - } - - @Override - public void visit(RegattaLogCloseOpenEndedDeviceMappingEvent event) { - logger.log(Level.FINE, "CloseOpenEndedDeviceMapping closed: " + event.getDeviceMappingEventId()); - updateMappingsAndAddListeners(); - } - - @Override - public void visit(RegattaLogRevokeEvent event) { - logger.log(Level.FINE, "Mapping revoked for: " + event.getRevokedEventId()); - updateMappingsAndAddListeners(); - }; - }; private final FixReceivedListener listener = new FixReceivedListener() { @Override public void fixReceived(DeviceIdentifier device, Timed fix) { @@ -213,33 +160,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader { this.trackedRace = trackedRace; loadingFromFixStoreLock = new NamedReentrantReadWriteLock( "Loading from SensorFix store lock for tracked race " + trackedRace.getRace().getName(), false); - this.deviceMappings = new RegattaLogDeviceMappings() { - @Override - protected Map>> calculateMappings() { - Map>> result = new HashMap<>(); - forEachRegattaLog( - (log) -> result.putAll(new RegattaLogDeviceCompetitorSensordataMappingFinder(log).analyze())); - return result; - } - - @Override - protected void mappingRemoved(DeviceMappingWithRegattaLogEvent mapping) { - // TODO if tracks are always associated to only one device mapping, we could remove tracks here - // TODO remove listener from store if there is no mapping left for the DeviceIdentifier - } - - @Override - protected void mappingAdded(DeviceMappingWithRegattaLogEvent mapping) { - loadFixes(getTrackingTimeRange().intersection(mapping.getTimeRange()), mapping); - } - - @Override - protected void mappingChanged(DeviceMappingWithRegattaLogEvent oldMapping, - DeviceMappingWithRegattaLogEvent newMapping) { - // TODO can the new time range be bigger than the old one? In this case we would need to load the - // additional time range. - } - }; + startTracking(); } @@ -305,24 +226,11 @@ public class FixLoaderAndTracker implements TrackingDataLoader { getEndOfTracking() == null ? TimePoint.EndOfTime : getEndOfTracking()); } - protected void updateMappingsAndAddListenersImpl() { - try { - deviceMappings.updateMappings(true); - } catch (Exception e) { - logger.warning("Could not load update competitor mappings as RegattaLog couldn't be found"); - } - // add listeners for devices in mappings already present - deviceMappings.forEachDevice((device) -> sensorFixStore.addListener(listener, device)); - } - public void stop(boolean preemptive) { preemptiveStopRequested.set(preemptive); stopRequested.set(true); trackedRace.removeListener(raceChangeListener); - synchronized (knownRegattaLogs) { - knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor)); - knownRegattaLogs.clear(); - } + deviceMappings.stop(); synchronized (this) { if (activeLoaders.get() == 0) { setStatusAndProgress(TrackedRaceStatusEnum.FINISHED, 1.0); @@ -336,14 +244,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader { protected void startTracking() { trackedRace.addListener(raceChangeListener); - final boolean hasRegattaLogs; - synchronized (knownRegattaLogs) { - trackedRace.getAttachedRegattaLogs().forEach(this::addRegattaLogUnlocked); - hasRegattaLogs = !knownRegattaLogs.isEmpty(); - } - if (hasRegattaLogs) { - updateMappingsAndAddListeners(); - } + this.deviceMappings = new FixLoaderDeviceMappings(trackedRace.getAttachedRegattaLogs()); } private synchronized void waitForLoadingToFinishRunning() { @@ -366,17 +267,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader { return trackedRace.getEndOfTracking(); } - private void addRegattaLogUnlocked(RegattaLog log) { - log.addListener(regattaLogEventVisitor); - knownRegattaLogs.add(log); - } - - protected void forEachRegattaLog(Consumer regattaLogConsumer) { - synchronized (knownRegattaLogs) { - knownRegattaLogs.forEach(regattaLogConsumer); - } - } - protected void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) { final TimeRangeImpl extendedTimeRange = new TimeRangeImpl(loadFixesFrom, loadFixesTo); deviceMappings.forEachMapping((mapping) -> { @@ -387,7 +277,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader { }); } - protected void updateMappingsAndAddListeners() { + private void updateConcurrent(final Runnable updateCallback) { synchronized (FixLoaderAndTracker.this) { activeLoaders.incrementAndGet(); setStatusAndProgress(TrackedRaceStatusEnum.LOADING, 0.5); @@ -404,7 +294,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader { synchronized (FixLoaderAndTracker.this) { FixLoaderAndTracker.this.notifyAll(); } - updateMappingsAndAddListenersImpl(); + updateCallback.run(); } } finally { LockUtil.unlockAfterWrite(loadingFromFixStoreLock); @@ -436,4 +326,37 @@ public class FixLoaderAndTracker implements TrackingDataLoader { private void setStatusAndProgress(TrackedRaceStatusEnum status, double progress) { trackedRace.onStatusChanged(this, new TrackedRaceStatusImpl(status, progress)); } + + private class FixLoaderDeviceMappings extends RegattaLogDeviceMappings { + public FixLoaderDeviceMappings(Iterable initialRegattaLogs) { + super(initialRegattaLogs); + } + + @Override + protected void updateMappings() { + updateConcurrent(() -> { + FixLoaderDeviceMappings.super.updateMappings(); + // add listeners for devices in mappings already present + forEachDevice((device) -> sensorFixStore.addListener(listener, device)); + }); + } + + @Override + protected void mappingRemoved(DeviceMappingWithRegattaLogEvent mapping) { + // TODO if tracks are always associated to only one device mapping, we could remove tracks here + // TODO remove listener from store if there is no mapping left for the DeviceIdentifier + } + + @Override + protected void mappingAdded(DeviceMappingWithRegattaLogEvent mapping) { + loadFixes(getTrackingTimeRange().intersection(mapping.getTimeRange()), mapping); + } + + @Override + protected void mappingChanged(DeviceMappingWithRegattaLogEvent oldMapping, + DeviceMappingWithRegattaLogEvent newMapping) { + // TODO can the new time range be bigger than the old one? In this case we would need to load the + // additional time range. + } + } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java index 152971c1852..264745ad25f 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java @@ -10,7 +10,18 @@ import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; +import com.sap.sailing.domain.abstractlog.regatta.impl.BaseRegattaLogEventVisitor; +import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceCompetitorSensordataMappingFinder; import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; @@ -27,10 +38,90 @@ import com.sap.sse.common.WithID; * @param The type of items the DeviceMappings are mapped to */ public abstract class RegattaLogDeviceMappings { + private static final Logger logger = Logger.getLogger(RegattaLogDeviceMappings.class.getName()); + /** + * We maintain our own collection that holds the RegattaLogs. The known RegattaLogs should by in sync with the ones that + * can be obtained from the TrackedRace. When stopping, there could be a concurrency issue that leads to a listener + * not being removed. This is prevented by remembering all RegattaLogs to which we attached a listener. So we can be + * sure to not produce a memory leak. + */ + private final Set knownRegattaLogs = new HashSet<>(); private final Map>> mappings = new HashMap<>(); private final Map>> mappingsByDevice = new HashMap<>(); + private final RegattaLogEventVisitor regattaLogEventVisitor = new BaseRegattaLogEventVisitor() { + @Override + public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) { + logger.log(Level.FINE, "New mapping for: " + event.getMappedTo() + "; device: " + event.getDevice()); + updateMappings(); + } + + @Override + public void visit(RegattaLogDeviceMarkMappingEvent event) { + logger.log(Level.FINE, + "New DeviceMarkMapping for: " + event.getMappedTo() + "; device: " + event.getDevice()); + updateMappings(); + } + + @Override + public void visit(RegattaLogDeviceCompetitorMappingEvent event) { + logger.log(Level.FINE, + "New DeviceCompetitorMapping for: " + event.getMappedTo() + "; device: " + event.getDevice()); + updateMappings(); + } + + @Override + public void visit(RegattaLogCloseOpenEndedDeviceMappingEvent event) { + logger.log(Level.FINE, "CloseOpenEndedDeviceMapping closed: " + event.getDeviceMappingEventId()); + updateMappings(); + } + + @Override + public void visit(RegattaLogRevokeEvent event) { + logger.log(Level.FINE, "Mapping revoked for: " + event.getRevokedEventId()); + updateMappings(); + }; + }; + + public RegattaLogDeviceMappings(Iterable initialRegattaLogs) { + final boolean hasRegattaLogs; + synchronized (knownRegattaLogs) { + initialRegattaLogs.forEach(this::addRegattaLogUnlocked); + hasRegattaLogs = !knownRegattaLogs.isEmpty(); + } + if (hasRegattaLogs) { + updateMappings(); + } + } + + public void addRegattaLog(RegattaLog regattaLog) { + synchronized (knownRegattaLogs) { + addRegattaLogUnlocked(regattaLog); + } + updateMappings(); + } + + private void addRegattaLogUnlocked(RegattaLog log) { + log.addListener(regattaLogEventVisitor); + knownRegattaLogs.add(log); + } + + public void stop() { + synchronized (knownRegattaLogs) { + knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor)); + knownRegattaLogs.clear(); + } + } + + protected void updateMappings() { + try { + updateMappings(true); + } catch (Exception e) { + logger.warning("Could not update device mappings"); + } + } + /** * Used internally to access {@link #mappings} with a defensive copy to not run into concurrency issues if this is * called while an update is done. @@ -115,7 +206,18 @@ public abstract class RegattaLogDeviceMappings { * * @return All currently known DeviceMappings */ - protected abstract Map>> calculateMappings(); + protected Map>> calculateMappings() { + Map>> result = new HashMap<>(); + forEachRegattaLog( + (log) -> result.putAll(new RegattaLogDeviceCompetitorSensordataMappingFinder(log).analyze())); + return result; + } + + protected void forEachRegattaLog(Consumer regattaLogConsumer) { + synchronized (knownRegattaLogs) { + knownRegattaLogs.forEach(regattaLogConsumer); + } + } /** * Adjusts the {@link #mappings} map according to the device mappings provided from the {@link #calculateMappings()} @@ -128,7 +230,7 @@ public abstract class RegattaLogDeviceMappings { * * @throws DoesNotHaveRegattaLogException */ - public final > void updateMappings(boolean loadIfNotCovered) { + private final > void updateMappings(boolean loadIfNotCovered) { // TODO remove fixes, if mappings have been removed // check if there are new time ranges not covered so far Map>> newMappings = calculateMappings(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorSensordataMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorSensordataMappingFinder.java index 0ff9283e1ab..7a2184e4244 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorSensordataMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorSensordataMappingFinder.java @@ -4,7 +4,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; import com.sap.sse.common.WithID; -public class RegattaLogDeviceCompetitorSensordataMappingFinder extends RegattaLogDeviceMappingFinder { +public class RegattaLogDeviceCompetitorSensordataMappingFinder extends RegattaLogDeviceMappingFinder { public RegattaLogDeviceCompetitorSensordataMappingFinder(RegattaLog log) { super(log);