Merge branch 'master' into bug3529

This commit is contained in:
Steffen Schaefer committed 2017-02-06 11:58:09 +01:00
commit 0fa0e3cb6a
275 files changed
+9096 -1388

No files matched your search

+54 -55
View File
@@ -1,55 +1,54 @@
#!/bin/bash
# You need an installation of the Neon release of "Eclipse IDE for Eclipse Committers" matching you OS and JDK (32 vs 64 Bit):
# http://www.eclipse.org/downloads/packages/eclipse-ide-eclipse-committers/neonr
if [[ $1 == "" ]]; then
echo "You need to specify the Eclipse installation directory"
exit 1
fi
installPath="$1"
installPlugins() {
"$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -installIU $2
echo ""
}
updatePlugins() {
"$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -uninstallIU $2 -installIU $2
echo ""
}
# Not necessary if using Eclipse for JEE developers
echo "Installing webtools (WTP/WST/JPT) and m2e that are required by the GWT plugin..."
installPlugins http://download.eclipse.org/releases/neon org.eclipse.wst.web_ui.feature.feature.group,org.eclipse.jst.web_ui.feature.feature.group,org.eclipse.wst.xml_ui.feature.feature.group,org.eclipse.jpt.common.feature.feature.group,org.eclipse.jpt.jpa.feature.feature.group,org.eclipse.m2e.feature.feature.group,org.eclipse.m2e.wtp.feature.feature.group
echo "Installing GWT plugin..."
installPlugins http://storage.googleapis.com/gwt-eclipse-plugin/release com.google.gdt.eclipse.suite.e44.feature.feature.group
echo "Installing Android Tools..."
installPlugins https://dl-ssl.google.com/android/eclipse com.android.ide.eclipse.adt.feature.feature.group
echo "Installing GWT SDM debug bridge..."
installPlugins http://p2.sapsailing.com/p2/sdbg com.github.sdbg.feature.feature.group
# EasyShell is currently not Neon compatible, but 2.0 is coming: https://github.com/anb0s/EasyShell/issues/29
# echo "Installing EasyShell..."
# installPlugins http://anb0s.github.io/EasyShell com.tetrade.eclipse.plugins.easyshell.feature.feature.group
echo "Installing BIRT charts (requirement for MAT)..."
installPlugins http://download.eclipse.org/birt/update-site/4.6 org.eclipse.birt.chart.feature.group
echo "Installing Memory Analyzer..."
installPlugins http://download.eclipse.org/mat/1.6/update-site/ org.eclipse.mat.feature.feature.group,org.eclipse.mat.chart.feature.feature.group
echo "Installing latest version of Code Recommenders (Neon release version has bugs)..."
updatePlugins http://download.eclipse.org/recommenders/updates/stable/ org.eclipse.recommenders.rcp.feature.feature.group,org.eclipse.recommenders.mylyn.rcp.feature.feature.group,org.eclipse.recommenders.snipmatch.rcp.feature.feature.group,org.eclipse.recommenders.news.rcp.feature.feature.group
echo "Installing latest version of EGit (Neon release version has bugs)..."
updatePlugins http://download.eclipse.org/egit/updates org.eclipse.jgit.feature.group,org.eclipse.jgit.http.apache.feature.group,org.eclipse.egit.feature.group,org.eclipse.egit.mylyn.feature.group
echo "Installing latest version of Mylyn ..."
updatePlugins http://download.eclipse.org/mylyn/releases/latest org.eclipse.mylyn_feature.feature.group,org.eclipse.mylyn.bugzilla_feature.feature.group,org.eclipse.mylyn.builds.feature.group,org.eclipse.mylyn.commons.feature.group,org.eclipse.mylyn.commons.identity.feature.group,org.eclipse.mylyn.commons.notifications.feature.group,org.eclipse.mylyn.commons.repositories.feature.group,org.eclipse.mylyn.commons.repositories.http.feature.group,org.eclipse.mylyn.context_feature.feature.group,org.eclipse.mylyn.discovery.feature.group,org.eclipse.mylyn.gerrit.feature.feature.group,org.eclipse.mylyn.git.feature.group,org.eclipse.mylyn.hudson.feature.group,org.eclipse.mylyn.java_feature.feature.group,org.eclipse.mylyn.monitor.feature.group,org.eclipse.mylyn.reviews.feature.feature.group,org.eclipse.mylyn.team_feature.feature.group,org.eclipse.mylyn.versions.feature.group,org.eclipse.mylyn.wikitext_feature.feature.group
echo "Installation completed!"
#!/bin/bash
# You need an installation of the Neon SR2 release of "Eclipse IDE for Eclipse Committers" matching you OS and JDK (32 vs 64 Bit):
# http://www.eclipse.org/downloads/packages/eclipse-ide-eclipse-committers/neon2
if [[ $1 == "" ]]; then
echo "You need to specify the Eclipse installation directory"
exit 1
fi
installPath="$1"
installPlugins() {
"$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -installIU $2
echo ""
}
updatePlugins() {
"$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -uninstallIU $2 -installIU $2
echo ""
}
# Not necessary if using Eclipse for JEE developers
echo "Installing webtools (WTP/WST/JPT) and m2e that are required by the GWT plugin..."
installPlugins http://download.eclipse.org/releases/neon org.eclipse.wst.web_ui.feature.feature.group,org.eclipse.jst.web_ui.feature.feature.group,org.eclipse.wst.xml_ui.feature.feature.group,org.eclipse.jpt.common.feature.feature.group,org.eclipse.jpt.jpa.feature.feature.group,org.eclipse.m2e.feature.feature.group,org.eclipse.m2e.wtp.feature.feature.group
echo "Installing GWT plugin..."
installPlugins http://storage.googleapis.com/gwt-eclipse-plugin/release com.google.gdt.eclipse.suite.e44.feature.feature.group
echo "Installing Android Tools..."
installPlugins https://dl-ssl.google.com/android/eclipse com.android.ide.eclipse.adt.feature.feature.group
echo "Installing GWT SDM debug bridge..."
installPlugins http://p2.sapsailing.com/p2/sdbg com.github.sdbg.feature.feature.group
echo "Installing EasyShell..."
installPlugins http://anb0s.github.io/EasyShell de.anbos.eclipse.easyshell.feature.feature.group
echo "Installing BIRT charts (requirement for MAT)..."
installPlugins http://download.eclipse.org/birt/update-site/4.6 org.eclipse.birt.chart.feature.group
echo "Installing Memory Analyzer..."
installPlugins http://download.eclipse.org/mat/1.6/update-site/ org.eclipse.mat.feature.feature.group,org.eclipse.mat.chart.feature.feature.group
echo "Installing latest version of Code Recommenders (Neon release version has bugs)..."
updatePlugins http://download.eclipse.org/recommenders/updates/stable/ org.eclipse.recommenders.rcp.feature.feature.group,org.eclipse.recommenders.mylyn.rcp.feature.feature.group,org.eclipse.recommenders.snipmatch.rcp.feature.feature.group,org.eclipse.recommenders.news.rcp.feature.feature.group
echo "Installing latest version of EGit (Neon release version has bugs)..."
updatePlugins http://download.eclipse.org/egit/updates org.eclipse.jgit.feature.group,org.eclipse.jgit.http.apache.feature.group,org.eclipse.egit.feature.group,org.eclipse.egit.mylyn.feature.group
echo "Installing latest version of Mylyn ..."
updatePlugins http://download.eclipse.org/mylyn/releases/latest org.eclipse.mylyn_feature.feature.group,org.eclipse.mylyn.bugzilla_feature.feature.group,org.eclipse.mylyn.builds.feature.group,org.eclipse.mylyn.commons.feature.group,org.eclipse.mylyn.commons.identity.feature.group,org.eclipse.mylyn.commons.notifications.feature.group,org.eclipse.mylyn.commons.repositories.feature.group,org.eclipse.mylyn.commons.repositories.http.feature.group,org.eclipse.mylyn.context_feature.feature.group,org.eclipse.mylyn.discovery.feature.group,org.eclipse.mylyn.gerrit.feature.feature.group,org.eclipse.mylyn.git.feature.group,org.eclipse.mylyn.hudson.feature.group,org.eclipse.mylyn.java_feature.feature.group,org.eclipse.mylyn.monitor.feature.group,org.eclipse.mylyn.reviews.feature.feature.group,org.eclipse.mylyn.team_feature.feature.group,org.eclipse.mylyn.versions.feature.group,org.eclipse.mylyn.wikitext_feature.feature.group
echo "Installation completed!"
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.datamining,provider</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -0,0 +1,14 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Datamining_provider
Bundle-SymbolicName: com.sap.sailing.datamining.provider
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: com.sap.sse.datamining,
com.sap.sailing.server,
com.sap.sse.common
Import-Package: org.osgi.framework;version="1.8.0",
org.osgi.util.tracker
Export-Package: com.sap.sailing.datamining.provider
Bundle-ActivationPolicy: lazy
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.datamining.provider</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,4 +1,4 @@
package com.sap.sailing.datamining;
package com.sap.sailing.datamining.provider;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
@@ -1,5 +0,0 @@
eclipse.preferences.version=1
encoding//resources/stringmessages/Sailing_StringMessages_ja.properties=UTF-8
encoding//resources/stringmessages/Sailing_StringMessages_ru.properties=UTF-8
encoding//resources/stringmessages/Sailing_StringMessages_zh.properties=UTF-8
encoding//resources/stringmessages/Sailing_StringMessages_zh_CN.properties=UTF-8
@@ -17,7 +17,8 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse.datamining.shared,
com.sap.sse,
com.sap.sse.common,
org.eclipse.osgi
org.eclipse.osgi,
com.sap.sailing.datamining.provider
Bundle-ClassPath: .
Bundle-Activator: com.sap.sailing.datamining.Activator
Bundle-ActivationPolicy: lazy
@@ -20,7 +20,8 @@ CompetitorSearchTag=Competitor Search Tag
LeaderboardSailingDomainRetrieverChain=Leaderboards
RaceSailingDomainRetrieverChain=Races
LegSailingDomainRetrieverChain=Legs
GPSFixSailingDomainRetrieverChain=GPS-Fixes
GPSFixSailingDomainRetrieverChain=GPS Fixes
WindFixSailingDomainRetrieverChain=Wind Fixes
MarkPassingSailingDomainRetrieverChain=Mark Passings
RaceResultSailingDomainRetrieverChain=Race Results
RaceOfCompetitorSailingDomainRetrieverChain=Races of Competitors
@@ -30,7 +31,8 @@ Leaderboard=Leaderboard
Leg=Leg
MarkPassing=Mark Passing
LegOfCompetitor=Leg of competitor
GpsFix=GPS-Fix
GpsFix=GPS Fix
WindFix=Wind Fix
Maneuver=Maneuver
WindStrengthInBeaufort=Wind Strength in Beaufort
Bft0=Calm
@@ -72,6 +74,8 @@ Tack=Tack
SpeedBefore=Speed Before Maneuver
SpeedAfter=Speed After Maneuver
DirectionChange=Direction Change in Degrees
AbsoluteDirectionChange=Absolute Direction Change in Degrees (Positive)
ToSide=To Side (Port / Starboard)
ManeuverLoss=Maneuver Loss
Waypoint=Waypoint
PassingSide=Passing Side
@@ -82,4 +86,11 @@ WindwardDistanceToAdvantageousEndOfLine=Windward Distance to Advantageous End of
RankAfterHalfOfTheFirstLeg=Rank after the half of the first leg
AdvantageousEndOfLine=Advantageous End of Line
TimeSpentInSeconds=Time Spent in Seconds
MedalRace=Medal Race
MedalRace=Medal Race
WindSourceType=Wind Source Type
WindSourceName=Wind Source Name
windFrom=True Wind Direction (TWD)
windSpeedKnots=True Wind Speed (kts)
speedInKnots=Speed (kts)
bearing=Course over Ground (°T)
WindTrack=Wind Track
@@ -19,6 +19,7 @@ LeaderboardSailingDomainRetrieverChain=Leaderboards
RaceSailingDomainRetrieverChain=Rennen
LegSailingDomainRetrieverChain=Schenkel
GPSFixSailingDomainRetrieverChain=GPS-Fixes
WindFixSailingDomainRetrieverChain=Windmessungen
MarkPassingSailingDomainRetrieverChain=Tonnenrundungen
RaceResultSailingDomainRetrieverChain=Rennergebnisse
RaceOfCompetitorSailingDomainRetrieverChain=Rennen eines Teilnehmers
@@ -29,6 +30,7 @@ Leg=Schenkel
MarkPassing=Tonnenrundung
LegOfCompetitor=Schenkel eines Teilnehmers
GpsFix=GPS-Fix
WindFix=Windmessung
Maneuver=Manöver
Speed=Geschwindigkeit
SpeedTenSecondsBefore=Geschwindigkeit 10s vorher
@@ -72,6 +74,8 @@ Tack=Bug
SpeedBefore=Geschwindigkeit bevor dem Manöver
SpeedAfter=Geschwindigkeit nach dem Manöver
DirectionChange=Richtungsänderung in Grad
AbsoluteDirectionChange=Absolute Richtungsänderung in Grad (positiv)
ToSide=In Richtung (Backbord / Steuerbord)
ManeuverLoss=Manöververlust
Waypoint=Wegpunkt
PassingSide=Passierende Seite
@@ -82,4 +86,11 @@ WindwardDistanceToAdvantageousEndOfLine=Windw
RankAfterHalfOfTheFirstLeg=Platzierung nach der Hälfte des ersten Schenkels
AdvantageousEndOfLine=Bevorteiltes Ende der Startlinie
TimeSpentInSeconds=Benötigte Zeit in Sekunden
MedalRace=Medaillenrennen
MedalRace=Medaillenrennen
WindSourceType=Art der Windmessung
WindSourceName=Name der Windmessreihe
windFrom=Wind aus Richtung (TWD)
windSpeedKnots=Wahre Windgeschwindigkeit (kn)
speedInKnots=Fahrt (kn)
bearing=Kurs über Grund (°)
WindTrack=Windmessreihe
@@ -17,12 +17,16 @@ import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.data.HasWindFixContext;
import com.sap.sailing.datamining.data.HasWindTrackContext;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelBearingAverageDegreesAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelBoolSumAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceAverageAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMaxAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMedianAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMinAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceSumAggregationProcessor;
import com.sap.sailing.datamining.provider.RacingEventServiceProvider;
import com.sap.sse.datamining.DataSourceProvider;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition;
@@ -81,6 +85,8 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries
internalClasses.add(HasTrackedLegContext.class);
internalClasses.add(HasTrackedLegOfCompetitorContext.class);
internalClasses.add(HasGPSFixContext.class);
internalClasses.add(HasWindTrackContext.class);
internalClasses.add(HasWindFixContext.class);
internalClasses.add(HasManeuverContext.class);
internalClasses.add(HasMarkPassingContext.class);
internalClasses.add(HasRaceOfCompetitorContext.class);
@@ -112,6 +118,7 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries
aggregators.add(ParallelDistanceMaxAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMinAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMedianAggregationProcessor.getDefinition());
aggregators.add(ParallelBearingAverageDegreesAggregationProcessor.getDefinition());
return aggregators;
}
@@ -133,7 +140,6 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries
if (INSTANCE == null) {
INSTANCE = new Activator(); // probably non-OSGi case, as in test execution
}
return INSTANCE;
}
@@ -12,6 +12,8 @@ import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.data.HasWindFixContext;
import com.sap.sailing.datamining.data.HasWindTrackContext;
import com.sap.sailing.datamining.impl.components.CompetitorOfRaceInLeaderboardRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor;
@@ -22,6 +24,8 @@ import com.sap.sailing.datamining.impl.components.RaceOfCompetitorRetrievalProce
import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.WindFixRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.WindTrackRetrievalProcessor;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition;
@@ -69,6 +73,13 @@ public class SailingDataRetrievalChainDefinitions {
HasGPSFixContext.class, "GpsFix");
dataRetrieverChainDefinitions.add(gpsFixRetrieverChainDefinition);
final DataRetrieverChainDefinition<RacingEventService, HasWindFixContext> windFixRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>(
trackedRaceRetrieverChainDefinition, HasWindFixContext.class, "WindFixSailingDomainRetrieverChain");
windFixRetrieverChainDefinition.addAfter(TrackedRaceRetrievalProcessor.class, WindTrackRetrievalProcessor.class, HasWindTrackContext.class, "WindTrack");
windFixRetrieverChainDefinition.endWith(WindTrackRetrievalProcessor.class, WindFixRetrievalProcessor.class,
HasWindFixContext.class, "WindFix");
dataRetrieverChainDefinitions.add(windFixRetrieverChainDefinition);
final DataRetrieverChainDefinition<RacingEventService, HasManeuverContext> maneuverRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>(
legOfCompetitorRetrieverChainDefinition, HasManeuverContext.class, "ManeuverSailingDomainRetrieverChain");
maneuverRetrieverChainDefinition.endWith(TrackedLegOfCompetitorRetrievalProcessor.class, ManeuverRetrievalProcessor.class,
@@ -1,22 +1,24 @@
package com.sap.sailing.datamining.data;
import java.util.Locale;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
import com.sap.sse.i18n.ResourceBundleStringMessages;
public interface HasGPSFixContext {
public interface HasGPSFixContext extends HasWindOnTrackedLeg {
@Connector(scanForStatistics=false)
public HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext();
@Connector(ordinal=1)
public GPSFixMoving getGPSFix();
@Dimension(messageKey="WindStrengthInBeaufort", ordinal=11)
public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages);
@Override
default TimePoint getTimePoint() {
return getGPSFix().getTimePoint();
}
@Override
default Position getPosition() {
return getGPSFix().getPosition();
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Positioned;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
public interface HasManeuver extends Positioned, Timed {
Maneuver getManeuver();
@Override
default TimePoint getTimePoint() {
return getManeuver().getTimePoint();
}
@Override
default Position getPosition() {
return getManeuver().getPosition();
}
}
@@ -2,20 +2,17 @@ package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
public interface HasManeuverContext {
public interface HasManeuverContext extends HasWindOnTrackedLeg, HasManeuver, HasTrackedLegOfCompetitor {
@Connector(scanForStatistics=false)
HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext();
Maneuver getManeuver();
//TODO Clean-Up:
// Move Dimensions and Statistics to Maneuver and connect to it
@Dimension(messageKey="ManeuverType", ordinal=12)
@@ -31,6 +28,12 @@ public interface HasManeuverContext {
@Statistic(messageKey="DirectionChange", resultDecimals=2, ordinal=2)
Double getDirectionChangeInDegrees();
@Statistic(messageKey="AbsoluteDirectionChange", resultDecimals=2, ordinal=2)
Double getAbsoluteDirectionChangeInDegrees();
@Dimension(messageKey="ToSide")
NauticalSide getToSide();
@Statistic(messageKey="ManeuverLoss", resultDecimals=0, ordinal=3)
Distance getManeuverLoss();
@@ -4,18 +4,14 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
public interface HasMarkPassingContext {
public interface HasMarkPassingContext extends HasWindOnTrackedLeg, HasManeuver, HasTrackedLegOfCompetitor {
@Connector(scanForStatistics=false)
HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext();
MarkPassingManeuver getManeuver();
//TODO Clean-Up:
// Move Dimensions and Statistics to Maneuver/MarkPassingManeuver and connect to it
// Find a way to implement HasManeuverContext and HasMarkPassingManeuver with a base class
@@ -16,6 +16,7 @@ public interface HasRaceOfCompetitorContext {
public HasTrackedRaceContext getTrackedRaceContext();
@Connector(messageKey="Competitor")
@Statistic(messageKey="")
public Competitor getCompetitor();
@Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible
@@ -1,5 +1,6 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
@@ -12,6 +13,9 @@ public interface HasRaceResultOfCompetitorContext {
@Connector(messageKey="Competitor")
public Competitor getCompetitor();
@Connector(messageKey="BoatClass")
BoatClass getBoatClass();
@Dimension(messageKey="Regatta")
String getRegattaName();
@@ -0,0 +1,5 @@
package com.sap.sailing.datamining.data;
public interface HasTrackedLegOfCompetitor {
HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext();
}
@@ -8,8 +8,7 @@ import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public interface HasTrackedLegOfCompetitorContext {
public interface HasTrackedLegOfCompetitorContext extends HasWindOnTrackedLeg {
@Connector(scanForStatistics=false)
public HasTrackedLegContext getTrackedLegContext();
@@ -0,0 +1,23 @@
package com.sap.sailing.datamining.data;
import java.util.Locale;
import com.sap.sailing.datamining.Activator;
import com.sap.sailing.domain.common.Positioned;
import com.sap.sailing.domain.common.Wind;
import com.sap.sse.common.Timed;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
import com.sap.sse.i18n.ResourceBundleStringMessages;
public interface HasWind extends Positioned, Timed {
Wind getWind();
@Dimension(messageKey="WindStrengthInBeaufort", ordinal=11)
default ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages) {
Wind wind = getWind();
Cluster<?> cluster = Activator.getClusterGroups().getWindStrengthInBeaufortClusterGroup().getClusterFor(wind);
return new ClusterDTO(cluster.asLocalizedString(locale, stringMessages));
}
}
@@ -0,0 +1,35 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
public interface HasWindFixContext extends HasWind {
@Connector(scanForStatistics=false)
public HasTrackedRaceContext getTrackedRaceContext();
@Statistic(messageKey="")
public Wind getWind();
@Statistic(messageKey="windFrom", resultDecimals=1)
double getWindFromDegrees();
@Statistic(messageKey="windSpeedKnots", resultDecimals=1)
double getWindSpeedInKnots();
@Override
default TimePoint getTimePoint() {
return getWind().getTimePoint();
}
@Override
default Position getPosition() {
return getWind().getPosition();
}
@Dimension(messageKey="WindSourceType")
String getWindSourceType();
}
@@ -0,0 +1,17 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.common.Wind;
public interface HasWindOnTrackedLeg extends HasTrackedLegOfCompetitor, HasWind {
default Wind getWind() {
if (getWindInternal() == null) {
setWindInternal(getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getTrackedRace()
.getWind(getPosition(), getTimePoint()));
}
return getWindInternal();
}
Wind getWindInternal();
void setWindInternal(Wind wind);
}
@@ -0,0 +1,18 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
public interface HasWindTrackContext {
@Connector(scanForStatistics=false)
public HasTrackedRaceContext getTrackedRaceContext();
@Dimension(messageKey="WindSourceType")
String getWindSourceType();
@Dimension(messageKey="WindSourceName")
String getWindSourceName();
WindTrack getWindTrack();
}
@@ -2,7 +2,6 @@ package com.sap.sailing.datamining.impl.components;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.datamining.data.HasManeuverContext;
@@ -26,7 +25,7 @@ public class ManeuverRetrievalProcessor extends AbstractRetrievalProcessor<HasTr
TimePoint finishTime = element.getTrackedLegOfCompetitor().getFinishTime();
if (finishTime != null) {
try {
List<Maneuver> maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false);
Iterable<Maneuver> maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false);
for (Maneuver maneuver : maneuvers) {
maneuversWithContext.add(new ManeuverWithContext(element, maneuver));
}
@@ -2,7 +2,6 @@ package com.sap.sailing.datamining.impl.components;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.datamining.data.HasMarkPassingContext;
@@ -29,7 +28,7 @@ public class MarkPassingRetrievalProcessor extends AbstractRetrievalProcessor<Ha
TimePoint finishTime = element.getTrackedLegOfCompetitor().getFinishTime();
if (finishTime != null) {
try {
List<Maneuver> maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false);
Iterable<Maneuver> maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false);
for (Maneuver maneuver : maneuvers) {
if (maneuver.getType() == ManeuverType.MARK_PASSING) {
maneuversWithContext.add(new MarkPassingWithContext(element, (MarkPassingManeuver) maneuver));
@@ -0,0 +1,44 @@
package com.sap.sailing.datamining.impl.components;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.datamining.data.HasWindFixContext;
import com.sap.sailing.datamining.data.HasWindTrackContext;
import com.sap.sailing.datamining.impl.data.WindFixWithContext;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor;
/**
* Retrieves all wind fixes from a {@link TrackedRace} that are provided by wind sources that are not currently
* {@link TrackedRace#getWindSourcesToExclude() excluded}.
*
* @author Axel Uhl (d043530)
*
*/
public class WindFixRetrievalProcessor extends AbstractRetrievalProcessor<HasWindTrackContext, HasWindFixContext> {
public WindFixRetrievalProcessor(ExecutorService executor, Collection<Processor<HasWindFixContext, ?>> resultReceivers, int retrievalLevel) {
super(HasWindTrackContext.class, HasWindFixContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
protected Iterable<HasWindFixContext> retrieveData(HasWindTrackContext element) {
Collection<HasWindFixContext> windFixesWithContext = new ArrayList<>();
final WindTrack windTrack = element.getWindTrack();
windTrack.lockForRead();
try {
for (final Wind wind : windTrack.getFixes()) {
windFixesWithContext.add(new WindFixWithContext(element.getTrackedRaceContext(), wind, element.getWindSourceType()));
}
} finally {
windTrack.unlockAfterRead();
}
return windFixesWithContext;
}
}
@@ -0,0 +1,42 @@
package com.sap.sailing.datamining.impl.components;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.data.HasWindTrackContext;
import com.sap.sailing.datamining.impl.data.WindTrackWithContext;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor;
/**
* Retrieves all wind fixes from a {@link TrackedRace} that are provided by wind sources that are not currently
* {@link TrackedRace#getWindSourcesToExclude() excluded}.
*
* @author Axel Uhl (d043530)
*
*/
public class WindTrackRetrievalProcessor extends AbstractRetrievalProcessor<HasTrackedRaceContext, HasWindTrackContext> {
public WindTrackRetrievalProcessor(ExecutorService executor, Collection<Processor<HasWindTrackContext, ?>> resultReceivers, int retrievalLevel) {
super(HasTrackedRaceContext.class, HasWindTrackContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
protected Iterable<HasWindTrackContext> retrieveData(HasTrackedRaceContext element) {
Collection<HasWindTrackContext> windTracksWithContext = new ArrayList<>();
final TrackedRace trackedRace = element.getTrackedRace();
for (final WindSource windSource : trackedRace.getWindSources()) {
if (!trackedRace.getWindSourcesToExclude().contains(windSource)) {
final WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
windTracksWithContext.add(new WindTrackWithContext(element, windTrack, windSource));
}
}
return windTracksWithContext;
}
}
@@ -0,0 +1,56 @@
package com.sap.sailing.datamining.impl.components.aggregators;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.BearingCluster;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition;
import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataStoringAggregationProcessor;
import com.sap.sse.datamining.shared.GroupKey;
public class ParallelBearingAverageDegreesAggregationProcessor
extends AbstractParallelGroupedDataStoringAggregationProcessor<Bearing, Double> {
private static final AggregationProcessorDefinition<Bearing, Double> DEFINITION =
new SimpleAggregationProcessorDefinition<>(Bearing.class, Double.class, "Average", ParallelBearingAverageDegreesAggregationProcessor.class);
private final Map<GroupKey, BearingCluster> results;
public static AggregationProcessorDefinition<Bearing, Double> getDefinition() {
return DEFINITION;
}
public ParallelBearingAverageDegreesAggregationProcessor(ExecutorService executor,
Collection<Processor<Map<GroupKey, Double>, ?>> resultReceivers) {
super(executor, resultReceivers, "Average");
results = new HashMap<>();
}
@Override
protected void storeElement(GroupedDataEntry<Bearing> element) {
BearingCluster cluster = results.get(element.getKey());
if (cluster == null) {
cluster = new BearingCluster();
results.put(element.getKey(), cluster);
}
cluster.add(element.getDataEntry());
}
@Override
protected Map<GroupKey, Double> aggregateResult() {
Map<GroupKey, Double> result = new HashMap<>();
for (Entry<GroupKey, BearingCluster> clusterEntry : results.entrySet()) {
GroupKey key = clusterEntry.getKey();
result.put(key, clusterEntry.getValue().getAverage().getDegrees());
}
return result;
}
}
@@ -7,7 +7,6 @@ import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
@@ -56,7 +55,7 @@ public class ParallelDistanceAverageAggregationProcessor
Map<GroupKey, Distance> sumAggregation = sumAggregationProcessor.getResult();
for (Entry<GroupKey, Distance> sumAggregationEntry : sumAggregation.entrySet()) {
GroupKey key = sumAggregationEntry.getKey();
result.put(key, new MeterDistance(sumAggregationEntry.getValue().getMeters() / elementAmountPerKey.get(key).doubleValue()));
result.put(key, sumAggregationEntry.getValue().scale(1. / elementAmountPerKey.get(key).doubleValue()));
}
return result;
}
@@ -44,5 +44,4 @@ public class ParallelDistanceSumAggregationProcessor extends
protected Map<GroupKey, Distance> getResult() {
return results;
}
}
@@ -1,18 +1,13 @@
package com.sap.sailing.datamining.impl.data;
import java.util.Locale;
import com.sap.sailing.datamining.Activator;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
import com.sap.sse.i18n.ResourceBundleStringMessages;
public class GPSFixWithContext implements HasGPSFixContext {
private static final long serialVersionUID = -4537126043228674949L;
private final HasTrackedLegOfCompetitorContext trackedLegOfCompetitorContext;
private final GPSFixMoving gpsFix;
@@ -32,20 +27,14 @@ public class GPSFixWithContext implements HasGPSFixContext {
public GPSFixMoving getGPSFix() {
return gpsFix;
}
@Override
public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages) {
Wind wind = getWind();
Cluster<?> cluster = Activator.getClusterGroups().getWindStrengthInBeaufortClusterGroup().getClusterFor(wind);
return new ClusterDTO(cluster.asLocalizedString(locale, stringMessages));
}
private Wind getWind() {
if (wind == null) {
wind = getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getTrackedRace()
.getWind(gpsFix.getPosition(), gpsFix.getTimePoint());
}
@Override
public Wind getWindInternal() {
return wind;
}
@Override
public void setWindInternal(Wind wind) {
this.wind = wind;
}
}
@@ -4,14 +4,17 @@ import com.sap.sailing.datamining.data.HasManeuverContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.Maneuver;
public class ManeuverWithContext implements HasManeuverContext {
private static final long serialVersionUID = 7717196485074392156L;
private final HasTrackedLegOfCompetitorContext trackedLegOfCompetitor;
private final Maneuver maneuver;
private Wind wind;
public ManeuverWithContext(HasTrackedLegOfCompetitorContext trackedLegOfCompetitor, Maneuver maneuver) {
this.trackedLegOfCompetitor = trackedLegOfCompetitor;
@@ -53,9 +56,28 @@ public class ManeuverWithContext implements HasManeuverContext {
return getManeuver().getDirectionChangeInDegrees();
}
@Override
public Double getAbsoluteDirectionChangeInDegrees() {
return Math.abs(getManeuver().getDirectionChangeInDegrees());
}
@Override
public NauticalSide getToSide() {
return getManeuver().getDirectionChangeInDegrees() >= 0 ? NauticalSide.STARBOARD : NauticalSide.PORT;
}
@Override
public Distance getManeuverLoss() {
return getManeuver().getManeuverLoss();
}
@Override
public Wind getWindInternal() {
return wind;
}
@Override
public void setWindInternal(Wind wind) {
this.wind = wind;
}
}
@@ -7,18 +7,20 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Util;
public class MarkPassingWithContext implements HasMarkPassingContext {
private static final long serialVersionUID = -337042113749307686L;
private final HasTrackedLegOfCompetitorContext trackedLegOfCompetitor;
private final MarkPassingManeuver maneuver;
private Double absoluteRank;
private boolean rankHasBeenInitialized;
private Wind wind;
public MarkPassingWithContext(HasTrackedLegOfCompetitorContext trackedLegOfCompetitor, MarkPassingManeuver maneuver) {
this.trackedLegOfCompetitor = trackedLegOfCompetitor;
@@ -84,4 +86,13 @@ public class MarkPassingWithContext implements HasMarkPassingContext {
return absoluteRank;
}
@Override
public Wind getWindInternal() {
return wind;
}
@Override
public void setWindInternal(Wind wind) {
this.wind = wind;
}
}
@@ -7,6 +7,7 @@ import java.util.logging.Logger;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.RaceColumn;
@@ -61,6 +62,11 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
return competitor;
}
@Override
public BoatClass getBoatClass() {
return getLeaderboard().getBoatClass();
}
@Override
public String getRegattaName() {
Leaderboard leaderboard = getLeaderboard();;
@@ -6,7 +6,10 @@ import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
@@ -15,6 +18,7 @@ import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompetitorContext {
private static final long serialVersionUID = 5944904146286262768L;
private final HasTrackedLegContext trackedLegContext;
@@ -25,6 +29,7 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
private boolean isRankAtStartInitialized;
private Double rankAtFinish;
private boolean isRankAtFinishInitialized;
private Wind wind;
public TrackedLegOfCompetitorWithContext(HasTrackedLegContext trackedLegContext, TrackedLegOfCompetitor trackedLegOfCompetitor) {
this.trackedLegContext = trackedLegContext;
@@ -120,5 +125,52 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
return (finishTime.asMillis() - startTime.asMillis()) / 1000;
}
@Override
public Wind getWindInternal() {
return wind;
}
@Override
public void setWindInternal(Wind wind) {
this.wind = wind;
}
@Override
public Position getPosition() {
final TrackedLeg trackedLeg = getTrackedLegContext().getTrackedLeg();
final TrackedRace trackedRace = trackedLeg.getTrackedRace();
final TimePoint timepoint = getTimePointBetweenLegStartAndLegFinish(trackedRace);
final Position result;
if (timepoint == null) {
result = null;
} else {
result = trackedLeg.getMiddleOfLeg(timepoint);
}
return result;
}
private TimePoint getTimePointBetweenLegStartAndLegFinish(final TrackedRace trackedRace) {
final TimePoint competitorLegStartTime = getTrackedLegOfCompetitor().getStartTime();
final TimePoint competitorLegEndTime = getTrackedLegOfCompetitor().getFinishTime();
final TimePoint startTime = competitorLegStartTime != null ? competitorLegStartTime :
trackedRace.getStartOfRace() != null ? trackedRace.getStartOfRace() : trackedRace.getStartOfTracking();
final TimePoint endTime = competitorLegEndTime != null ? competitorLegEndTime :
trackedRace.getEndOfRace() != null ? trackedRace.getEndOfRace() : trackedRace.getEndOfTracking();
final TimePoint timepoint = endTime == null ? startTime : startTime == null ? null : startTime.plus(startTime.until(endTime).divide(2));
return timepoint;
}
@Override
public TimePoint getTimePoint() {
final TrackedLeg trackedLeg = getTrackedLegContext().getTrackedLeg();
final TrackedRace trackedRace = trackedLeg.getTrackedRace();
return getTimePointBetweenLegStartAndLegFinish(trackedRace);
}
@Override
public HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext() {
return this;
}
}
@@ -85,12 +85,15 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
private Integer calculateYear() {
TimePoint startOfRace = getTrackedRace().getStartOfRace();
TimePoint time = startOfRace != null ? startOfRace : getTrackedRace().getStartOfTracking();
final Integer result;
if (time == null) {
year = 0;
result = 0;
} else {
Calendar calendar = Calendar.getInstance();
calendar.setTime(time.asDate());
result = calendar.get(Calendar.YEAR);
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(time.asDate());
return calendar.get(Calendar.YEAR);
return result;
}
@Override
@@ -0,0 +1,52 @@
package com.sap.sailing.datamining.impl.data;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.data.HasWindFixContext;
import com.sap.sailing.domain.common.Wind;
public class WindFixWithContext implements HasWindFixContext {
private static final long serialVersionUID = -4537126043228674949L;
private final HasTrackedRaceContext trackedRaceContext;
private final String windSourceType;
private final Wind wind;
public WindFixWithContext(HasTrackedRaceContext trackedRaceContext, Wind wind, String windSourceType) {
this.trackedRaceContext = trackedRaceContext;
this.wind = wind;
this.windSourceType = windSourceType;
}
@Override
public HasTrackedRaceContext getTrackedRaceContext() {
return trackedRaceContext;
}
@Override
public Wind getWind() {
return wind;
}
@Override
public double getWindFromDegrees() {
return getWind().getFrom().getDegrees();
}
@Override
public double getWindSpeedInKnots() {
return getWind().getKnots();
}
@Override
public String getWindSourceType() {
return windSourceType;
}
@Override
public String toString() {
return "WindFixWithContext [trackedRaceContext=" + trackedRaceContext + ", windSourceType=" + windSourceType
+ ", wind=" + wind + "]";
}
}
@@ -0,0 +1,51 @@
package com.sap.sailing.datamining.impl.data;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.data.HasWindTrackContext;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.tracking.WindTrack;
public class WindTrackWithContext implements HasWindTrackContext {
private final HasTrackedRaceContext trackedRaceContext;
private final String windSourceType;
private final String windSourceName;
private final WindTrack windTrack;
public WindTrackWithContext(HasTrackedRaceContext trackedRaceContext, WindTrack windTrack, WindSource windSource) {
this.trackedRaceContext = trackedRaceContext;
this.windTrack = windTrack;
this.windSourceType = windSource.getType().name();
this.windSourceName = windSource.getType().name()+(windSource.getId()==null?"":" ("+windSource.getId()+")");
}
@Override
public HasTrackedRaceContext getTrackedRaceContext() {
return trackedRaceContext;
}
@Override
public String getWindSourceType() {
return windSourceType;
}
@Override
public String toString() {
return "WindTrackWithContext [trackedRaceContext=" + trackedRaceContext + ", windSourceType=" + windSourceType
+ ", windSourceName=" + windSourceName + "]";
}
@Override
public String getWindSourceName() {
return windSourceName;
}
@Override
public WindTrack getWindTrack() {
return windTrack;
}
}
@@ -57,7 +57,11 @@ public abstract class AbstractBearing implements Bearing {
@Override
public boolean equals(Object object) {
return object != null && object instanceof Bearing && getDegrees() == ((Bearing) object).getDegrees();
if (this == object) {
return true;
} else {
return object != null && object instanceof Bearing && getDegrees() == ((Bearing) object).getDegrees();
}
}
@Override
@@ -16,6 +16,8 @@ public class AbstractPosition implements Position {
public boolean equals(Object o) {
if (o == null) {
return false;
} if (this == o) {
return true;
} else {
return o instanceof Position && getLatRad() == ((Position) o).getLatRad()
&& getLngRad() == ((Position) o).getLngRad();
@@ -58,6 +58,12 @@ public enum DataImportSubProgress {
return messages.importWindTracks();
}
},
IMPORT_SENSOR_FIXES {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.importSensorFixes();
}
},
UPDATE_EVENT_LEADERBOARD_GROUP_LINKS {
@Override
public String getMessage(CommonStringMessages messages) {
@@ -3,6 +3,6 @@ package com.sap.sailing.domain.common;
import com.sap.sse.datamining.annotations.Connector;
public interface Moving {
@Connector(messageKey="Speed")
@Connector(messageKey="")
SpeedWithBearing getSpeed();
}
@@ -20,7 +20,7 @@ public interface Position extends Serializable {
Distance getDistance(Position p);
/**
* The bearing from this position towards <code>p</code> on a great circle.
* The bearing from this position towards <code>p</code> on a great circle, using the shortest path.
*
* @return {@code null} if {@code p} is {@code null}, otherwise the bearing from {@code this} {@link Position} to
* {@code p} along a great circle
@@ -67,7 +67,7 @@ public interface Speed extends Comparable<Speed>, Serializable {
}
};
@Statistic(messageKey="", resultDecimals=2)
@Statistic(messageKey="speedInKnots", resultDecimals=2)
double getKnots();
double getMetersPerSecond();
@@ -1,10 +1,12 @@
package com.sap.sailing.domain.common;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.annotations.Statistic;
public interface SpeedWithBearing extends Speed {
@Statistic(messageKey="bearing", resultDecimals=1)
Bearing getBearing();
/**
@@ -12,6 +12,7 @@ public interface CommonStringMessages {
String importWait();
String importLeaderboardGroups();
String importWindTracks();
String importSensorFixes();
String updateEventLeaderboardGroupLinks();
}
@@ -15,4 +15,16 @@ public class KnotSpeedImpl extends AbstractSpeedImpl implements Speed {
public double getKnots() {
return knots;
}
/**
* Saves extra work by only converting the other object's speed to knots; useful in particular
* if both speeds are knot-based speeds
*/
@Override
public boolean equals(Object object) {
if (object == null || !(object instanceof Speed)) {
return false;
}
return getKnots() == ((Speed) object).getKnots();
}
}
@@ -32,6 +32,39 @@ public class WindImpl extends KnotSpeedWithBearingImpl implements Wind {
return getBearing().reverse();
}
public static int hashCode(double latDeg, double lngDeg, long timePointAsMillis) {
return (31 * (int) (timePointAsMillis & Integer.MAX_VALUE)) ^ (int) (latDeg*lngDeg);
}
/**
* Wind hash is determined based on time point and position only to speed this up a little.
*/
@Override
public int hashCode() {
return hashCode(position.getLatDeg(), position.getLngDeg(), timepoint==null?0:timepoint.asMillis());
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (!(obj instanceof Wind))
return false;
Wind other = (Wind) obj;
if (position == null) {
if (other.getPosition() != null)
return false;
} else if (!position.equals(other.getPosition()))
return false;
if (timepoint == null) {
if (other.getTimePoint() != null)
return false;
} else if (!timepoint.equals(other.getTimePoint()))
return false;
return true;
}
@Override
public String toString() {
return ""+getTimePoint()+"@"+getPosition()+": "+getKnots()+"kn from "+getFrom();
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.common.racelog.tracking;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.util.UrlHelper;
/**
@@ -35,6 +36,12 @@ public class DeviceMappingConstants {
public static final String JSON_DEVICE_TYPE = "deviceType";
public static final String JSON_PUSH_DEVICE_ID = "pushDeviceId";
public static final String JSON_FROM_MILLIS = "fromMillis";
/**
* The parameter to specify the end of the mapping interval, inclusively. Note that when asking a {@link TimeRange} describing
* the interval, the {@link TimeRange#to()} method has to return a value "infinitesimally" later than the time point provided here
* because {@link TimeRange}s have an <em>exclusive</em> end.
*/
public static final String JSON_TO_MILLIS = "toMillis";
public static final String JSON_TEAM_IMAGE_URI = "teamImageUri";
@@ -37,6 +37,7 @@ public enum Permission implements com.sap.sse.security.shared.Permission {
LEADERBOARD,
LEADERBOARD_GROUP,
TRACKED_RACE,
DATA_MINING,
;
// TODO once we can use Java8 here, move this up into a "default" method on the Permission interface
@@ -6,7 +6,9 @@ import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.AbstractSpeedWithAbstractBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.AbstractTimePoint;
public class CompactWindImpl extends AbstractSpeedWithAbstractBearingImpl implements Wind {
@@ -32,6 +34,11 @@ public class CompactWindImpl extends AbstractSpeedWithAbstractBearingImpl implem
public double getLngDeg() {
return lngDeg;
}
@Override
public boolean equals(Object o) {
return this==o || o instanceof Position && getLatDeg() == ((Position) o).getLatDeg() && getLngDeg() == ((Position) o).getLngDeg();
}
}
private class CompactBearing extends AbstractBearing {
@@ -46,6 +53,11 @@ public class CompactWindImpl extends AbstractSpeedWithAbstractBearingImpl implem
public double getRadians() {
return degBearing / 180. * Math.PI;
}
@Override
public boolean equals(Object object) {
return this==object || object instanceof Bearing && getDegrees() == ((Bearing) object).getDegrees();
}
}
private class CompactTimePoint extends AbstractTimePoint implements TimePoint {
@@ -125,6 +137,23 @@ public class CompactWindImpl extends AbstractSpeedWithAbstractBearingImpl implem
}
}
@Override
public int hashCode() {
return WindImpl.hashCode(latDeg, lngDeg, timePointIsNull?0:timePointAsMillis);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (!(obj instanceof Wind))
return false;
Wind other = (Wind) obj;
return Util.equalsWithNull(getPosition(), other.getPosition()) && Util.equalsWithNull(getTimePoint(), other.getTimePoint());
}
@Override
public String toString() {
return ""+getTimePoint()+"@"+getPosition()+": "+getKnots()+"kn from "+getFrom();
@@ -8,8 +8,8 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: org.json.simple;bundle-version="1.1.0",
com.sun.jersey;bundle-version="1.17.0",
org.objectweb.asm;bundle-version="3.3.1",
org.apache.httpcomponents.httpclient;bundle-version="4.2.5",
org.apache.httpcomponents.httpcore;bundle-version="4.0.0",
org.apache.httpcomponents.httpclient;bundle-version="[4.2.5,4.3.0)",
org.apache.httpcomponents.httpcore;bundle-version="[4.0.0,4.3.0)",
com.sap.sailing.domain.common,
com.sap.sailing.domain.igtimiadapter.persistence,
com.sap.sailing.domain.shared.android,
@@ -37,7 +37,7 @@ public class ClientImpl implements Client {
@Override
public String getRedirectUri(String redirectProtocol, String redirectHost, String redirectPort) {
return redirectProtocol+"://"+redirectHost+(redirectPort==null?"":(":"+redirectPort))+
return redirectProtocol+"://"+redirectHost+(redirectPort==null||redirectPort.isEmpty()?"":(":"+redirectPort))+
(redirectUriPath.startsWith("/")?redirectUriPath:"/"+redirectUriPath);
}
}
@@ -1761,9 +1761,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, DBObject dbObject) {
Serializable deviceMappingEventId = Helpers.tryUuidConversion((Serializable) dbObject.get(
FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name()));
TimePoint closingTimePoint = loadTimePoint(dbObject, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
TimePoint closingTimePointInclusive = loadTimePoint(dbObject, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
return new RegattaLogCloseOpenEndedDeviceMappingEventImpl(createdAt, author, logicalTimePoint, id,
deviceMappingEventId, closingTimePoint);
deviceMappingEventId, closingTimePointInclusive);
}
private RegattaLogDeviceMarkMappingEvent loadRegattaLogDeviceMarkMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author,
@@ -975,8 +975,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
if (event.getFrom() != null) {
storeTimePoint(event.getFrom(), result, fromField);
}
if (event.getTo() != null) {
storeTimePoint(event.getTo(), result, toField);
if (event.getToInclusive() != null) {
storeTimePoint(event.getToInclusive(), result, toField);
}
}
@@ -1445,7 +1445,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
DBObject result = createBasicRegattaLogEventDBObject(event);
result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
result.put(FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
storeTimePoint(event.getClosingTimePoint(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
storeTimePoint(event.getClosingTimePointInclusive(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
storeRegattaLogEvent(regattaLikeId, result);
}
@@ -9,6 +9,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -87,10 +88,25 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
DBObject fixObject = (DBObject) object.get(FieldNames.GPSFIX.name());
return this.<T> findService(type).transformBack(fixObject);
}
@Override
public <FixT extends Timed> boolean loadOldestFix(Consumer<FixT> consumer, DeviceIdentifier device, TimeRange timeRangeToLoad) throws NoCorrespondingServiceRegisteredException, TransformationException {
return loadFixes(consumer, device, timeRangeToLoad.from(), timeRangeToLoad.to(), false, cursor -> cursor.sort(new BasicDBObject(FieldNames.TIME_AS_MILLIS.name(), /* ascending */ 1)).limit(1));
}
@Override
public <FixT extends Timed> boolean loadYoungestFix(Consumer<FixT> consumer, DeviceIdentifier device, TimeRange timeRangeToLoad) throws NoCorrespondingServiceRegisteredException, TransformationException {
return loadFixes(consumer, device, timeRangeToLoad.from(), timeRangeToLoad.to(), false, cursor -> cursor.sort(new BasicDBObject(FieldNames.TIME_AS_MILLIS.name(), /* descending */ -1)).limit(1));
}
@Override
public <FixT extends Timed> void loadFixes(Consumer<FixT> consumer, DeviceIdentifier device, TimePoint from,
TimePoint to, boolean inclusive) throws NoCorrespondingServiceRegisteredException, TransformationException {
loadFixes(consumer, device, from, to, inclusive, UnaryOperator.identity());
}
private <FixT extends Timed> boolean loadFixes(Consumer<FixT> consumer, DeviceIdentifier device, TimePoint from,
TimePoint to, boolean inclusive, UnaryOperator<DBCursor> dbCursorCallback) throws NoCorrespondingServiceRegisteredException, TransformationException {
final TimePoint loadFixesFrom = from == null ? TimePoint.BeginningOfTime : from;
final TimePoint loadFixesTo = to == null ? TimePoint.EndOfTime : to;
@@ -101,15 +117,17 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
queryBuilder.greaterThanEquals(loadFixesFrom.asMillis()).and(FieldNames.TIME_AS_MILLIS.name())
.lessThanEquals(loadFixesTo.asMillis());
} else {
queryBuilder.greaterThan(loadFixesFrom.asMillis()).and(FieldNames.TIME_AS_MILLIS.name()).lessThan(loadFixesTo.asMillis());
queryBuilder.greaterThanEquals(loadFixesFrom.asMillis()).and(FieldNames.TIME_AS_MILLIS.name()).lessThan(loadFixesTo.asMillis());
}
DBObject query = queryBuilder.get();
DBCursor result = fixesCollection.find(query);
DBCursor result = dbCursorCallback.apply(fixesCollection.find(query));
boolean fixLoaded = false;
for (DBObject fixObject : result) {
try {
@SuppressWarnings("unchecked")
FixT fix = (FixT) loadFix(fixObject);
consumer.accept(fix);
fixLoaded = true;
} catch (TransformationException e) {
logger.log(Level.WARNING, "Could not read fix from MongoDB: " + fixObject);
} catch (ClassCastException e) {
@@ -118,6 +136,7 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
"Unexpected fix type (" + type + ") encountered when trying to load track for " + device);
}
}
return fixLoaded;
}
/**
@@ -30,7 +30,9 @@ import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl;
@@ -70,6 +72,11 @@ public class AbstractGPSFixStoreTest {
return new GPSFixMovingImpl(new DegreePosition(lat, lng),
new MillisecondsTimePoint(millis), new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
}
protected GPSFix createFix(long millis, double lat, double lng) {
return new GPSFixImpl(new DegreePosition(lat, lng),
new MillisecondsTimePoint(millis));
}
@Before
public void setServiceAndRaceLog() {
@@ -32,6 +32,7 @@ import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifie
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sse.common.Timed;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
public class SensorFixStoreTest {
private static final long FIX_TIMESTAMP = 110;
@@ -114,10 +115,26 @@ public class SensorFixStoreTest {
}
@Test
public void testFixesOnExclusiveBoundsArentLoaded() throws Exception {
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
public void testFixesOnExclusiveEndBoundsArentLoaded() throws Exception {
DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
addBravoFix(device, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
verifyNoFix(FIX_TIMESTAMP, FIX_TIMESTAMP2, device, false);
verifySingleFix(fix, FIX_TIMESTAMP, FIX_TIMESTAMP2, device, false);
}
@Test
public void testYoungestFix() throws Exception {
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
addBravoFix(device, FIX_TIMESTAMP + 2, FIX_RIDE_HEIGHT2);
DoubleVectorFix youngestFix = addBravoFix(device, FIX_TIMESTAMP + 5, FIX_RIDE_HEIGHT2);
verifyYoungestFix(youngestFix, FIX_TIMESTAMP, FIX_TIMESTAMP2, device);
}
@Test
public void testOldestFix() throws Exception {
DoubleVectorFix oldestFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
addBravoFix(device, FIX_TIMESTAMP + 2, FIX_RIDE_HEIGHT2);
addBravoFix(device, FIX_TIMESTAMP + 5, FIX_RIDE_HEIGHT2);
verifyOldestFix(oldestFix, FIX_TIMESTAMP, FIX_TIMESTAMP2, device);
}
@Test
@@ -268,24 +285,43 @@ public class SensorFixStoreTest {
return listener;
}
private void verifySingleFix(Timed expectedFix, long start, long end, DeviceIdentifier device, boolean inclusive) throws Exception {
List<Timed> loadedFixes = loadFixes(start, end, device, inclusive);
private void verifySingleFix(Timed expectedFix, long start, long end, DeviceIdentifier device, boolean endIsInclusive) throws Exception {
List<Timed> loadedFixes = loadFixes(start, end, device, endIsInclusive);
assertEquals(1, loadedFixes.size());
assertEquals(expectedFix, loadedFixes.get(0));
}
private void verifyNoFix(long start, long end, DeviceIdentifier device, boolean inclusive) throws Exception {
List<Timed> loadedFixes = loadFixes(start, end, device, inclusive);
assertEquals(0, loadedFixes.size());
}
private List<Timed> loadFixes(long start, long end, DeviceIdentifier device, boolean inclusive)
private List<Timed> loadFixes(long start, long end, DeviceIdentifier device, boolean endIsInclusive)
throws TransformationException {
List<Timed> loadedFixes = new ArrayList<>();
store.loadFixes(loadedFixes::add, device, new MillisecondsTimePoint(start), new MillisecondsTimePoint(end), inclusive);
store.loadFixes(loadedFixes::add, device, new MillisecondsTimePoint(start), new MillisecondsTimePoint(end), endIsInclusive);
return loadedFixes;
}
private void verifyYoungestFix(Timed expectedFix, long start, long end, DeviceIdentifier device) throws Exception {
assertEquals(expectedFix, loadYoungestFix(start, end, device));
}
private Timed loadYoungestFix(long start, long end, DeviceIdentifier device)
throws TransformationException {
List<Timed> loadedFixes = new ArrayList<>();
store.loadYoungestFix(loadedFixes::add, device, new TimeRangeImpl(new MillisecondsTimePoint(start), new MillisecondsTimePoint(end)));
assert(loadedFixes.size() <= 1);
return loadedFixes.isEmpty() ? null : loadedFixes.get(0);
}
private void verifyOldestFix(Timed expectedFix, long start, long end, DeviceIdentifier device) throws Exception {
assertEquals(expectedFix, loadOldestFix(start, end, device));
}
private Timed loadOldestFix(long start, long end, DeviceIdentifier device)
throws TransformationException {
List<Timed> loadedFixes = new ArrayList<>();
store.loadOldestFix(loadedFixes::add, device, new TimeRangeImpl(new MillisecondsTimePoint(start), new MillisecondsTimePoint(end)));
assert(loadedFixes.size() <= 1);
return loadedFixes.isEmpty() ? null : loadedFixes.get(0);
}
private DoubleVectorFix addBravoFix(DeviceIdentifier device, long timestamp, double rideHeight) {
DoubleVectorFix fix = createBravoDoubleVectorFixWithRideHeight(timestamp, rideHeight);
@@ -25,11 +25,13 @@ import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker;
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.Timed;
@@ -60,9 +62,9 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
store.storeFix(device, createFix(100, 10, 20, 30, 40)); // before
store.storeFix(device, createFix(1100, 10, 20, 30, 40)); // in
store.storeFix(device, createFix(2100, 10, 20, 30, 40)); // after
store.storeFix(markDevice, createFix(100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(1100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(2100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(100, 10, 20));
store.storeFix(markDevice, createFix(1100, 10, 20));
store.storeFix(markDevice, createFix(2100, 10, 20));
final DynamicTrackedRaceImpl trackedRace = createDynamicTrackedRace(boatClass, raceDefinition);
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(1000));
@@ -100,8 +102,8 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
store.storeFix(device, createFix(100, 10, 20, 30, 40));
store.storeFix(device, createFix(200, 10, 20, 30, 40));
store.storeFix(device2, createFix(100, 10, 20, 30, 40));
store.storeFix(device3, createFix(100, 10, 20, 30, 40));
store.storeFix(device3, createFix(100, 10, 20, 30, 40));
store.storeFix(device3, createFix(100, 10, 20));
store.storeFix(device3, createFix(100, 10, 20));
for (int i = 0; i < 10000; i++) {
store.storeFix(device, createFix(i + 1000, 10, 20, 30, 40));
@@ -155,6 +157,26 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
store.storeFix(device, createFix(250, 10, 20, 30, 40)); // outside both mappings
store.storeFix(device, createFix(350, 10, 20, 30, 40)); // in second mapping
}, /* tests and expectations */ trackedRace -> {
final int[] fixInsertionCount = new int[1];
trackedRace.getTrack(comp).addListener(new GPSTrackListener<Competitor, GPSFixMoving>() {
private static final long serialVersionUID = 1933664210766279523L;
@Override
public boolean isTransient() {
return true;
}
@Override
public void gpsFixReceived(GPSFixMoving fix, Competitor item, boolean firstFixInTrack) {
fixInsertionCount[0]++;
}
@Override
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage,
long newMillisecondsOverWhichToAverage) {
// no-op
}
});
testNumberOfRawFixes(trackedRace.getTrack(comp), 3);
map(comp, device, 0, 500); // covers fix at 250ms; assert that only that fix is loaded
try {
@@ -163,6 +185,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
logger.log(Level.SEVERE, "Exception waiting for loading to finish", e);
}
testNumberOfRawFixes(trackedRace.getTrack(comp), 4);
assertEquals(1, fixInsertionCount[0]);
});
}
@@ -187,14 +210,23 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
});
}
/** Regression test for bug 4008 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4008 */
/**
* Regression test for bug 4008 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4008 <br>
* Updated for bug 4009 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4009
*/
@Test
public void testFixesForMarkAreLoadedIfMappingDoesNotIntersectWithTrackingInterval() throws InterruptedException {
testFixes(/* start of tracking */ 200, /* end of tracking */ 300, /* mappings and fixes */ () -> {
final long startOfTracking = 200;
final long bestFixOffset = 20;
testFixes(/* start of tracking */ startOfTracking, /* end of tracking */ 300, /* mappings and fixes */ () -> {
map(mark, device, 0, 100);
store.storeFix(device, createFix(10, 10, 20, 30, 40));
store.storeFix(device, createFix(20, 10, 20, 30, 40));
}, /* tests and expectations */ trackedRace -> testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2));
store.storeFix(device, createFix(10, 10, 20));
store.storeFix(device, createFix(bestFixOffset, 10, 20));
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
assertEquals(bestFixOffset, trackedRace.getOrCreateTrack(mark)
.getLastFixBefore(new MillisecondsTimePoint(startOfTracking)).getTimePoint().asMillis());
});
}
/** Regression test for bug 4008 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4008 */
@@ -203,9 +235,9 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws InterruptedException {
testFixes(/* start of tracking */ 400, /* end of tracking */ 500, /* mappings and fixes */ () -> {
map(mark, device, 0, 100);
store.storeFix(device, createFix(50, 10, 20, 30, 40));
store.storeFix(device, createFix(50, 10, 20));
map(mark2, device, 200, 300);
store.storeFix(device, createFix(250, 10, 20, 30, 40));
store.storeFix(device, createFix(250, 10, 20));
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark2), 1);
@@ -218,31 +250,131 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings and fixes */ () -> {
map(mark, device, 0, 200);
store.storeFix(device, createFix(50, 10, 20, 30, 40));
store.storeFix(device, createFix(150, 10, 20, 30, 40));
store.storeFix(device, createFix(250, 10, 20, 30, 40));
store.storeFix(device, createFix(50, 10, 20));
store.storeFix(device, createFix(150, 10, 20));
store.storeFix(device, createFix(250, 10, 20));
map(mark, device, 350, 500);
store.storeFix(device, createFix(400, 10, 20, 30, 40));
store.storeFix(device, createFix(450, 10, 20, 30, 40));
store.storeFix(device, createFix(400, 10, 20));
store.storeFix(device, createFix(450, 10, 20));
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
});
}
/** Regression test for bug 4008 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4008 */
/**
* Regression test for bug 4008 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4008 <br>
* Updated for bug 4009 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4009
*/
@Test
public void testFixesForMarkAreLoadedIfMappingDoesIntersectWithTrackingIntervalAllFixesBecauseOfNoFixesWithIntersection()
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings and fixes */ () -> {
final long bestFixBeforeStartOfTrackingOffset = 50;
final long bestFixAfterEndOfTrackingOffset = 400;
final long startOfTracking = 100;
final long endOfTracking = 300;
testFixes(/* start of tracking */ startOfTracking, /* end of tracking */ endOfTracking, /* mappings and fixes */ () -> {
map(mark, device, 0, 200);
store.storeFix(device, createFix(50, 10, 20, 30, 40));
store.storeFix(device, createFix(250, 10, 20, 30, 40));
store.storeFix(device, createFix(bestFixBeforeStartOfTrackingOffset, 10, 20));
store.storeFix(device, createFix(250, 10, 20));
map(mark, device, 350, 500);
store.storeFix(device, createFix(400, 10, 20, 30, 40));
store.storeFix(device, createFix(450, 10, 20, 30, 40));
store.storeFix(device, createFix(bestFixAfterEndOfTrackingOffset, 10, 20));
store.storeFix(device, createFix(450, 10, 20));
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 3);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
assertEquals(bestFixBeforeStartOfTrackingOffset, trackedRace.getOrCreateTrack(mark)
.getLastFixBefore(new MillisecondsTimePoint(startOfTracking)).getTimePoint().asMillis());
assertEquals(bestFixAfterEndOfTrackingOffset, trackedRace.getOrCreateTrack(mark)
.getFirstFixAfter(new MillisecondsTimePoint(endOfTracking)).getTimePoint().asMillis());
});
}
/** Test for changes of bug 4009 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4009 */
@Test
public void testThatOnlyTheBestFixesAreLoadedForMarksIfMultipleMappingsAreAvailable()
throws InterruptedException {
final long bestFixBeforeStartOfTrackingOffset = 140;
final long bestFixAfterEndOfTrackingOffset = 360;
final long startOfTracking = 200;
final long endOfTracking = 300;
testFixes(/* start of tracking */ startOfTracking, /* end of tracking */ endOfTracking, /* mappings and fixes */ () -> {
map(mark, device, 0, 80);
store.storeFix(device, createFix(50, 10, 20));
store.storeFix(device, createFix(70, 10, 20));
map(mark, device, 100, 150);
store.storeFix(device, createFix(120, 10, 20));
store.storeFix(device, createFix(bestFixBeforeStartOfTrackingOffset, 10, 20));
map(mark, device, 400, 500);
store.storeFix(device, createFix(420, 10, 20));
store.storeFix(device, createFix(450, 10, 20));
store.storeFix(device, createFix(480, 10, 20));
map(mark, device, 350, 390);
store.storeFix(device, createFix(bestFixAfterEndOfTrackingOffset, 10, 20));
store.storeFix(device, createFix(380, 10, 20));
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
assertEquals(bestFixBeforeStartOfTrackingOffset, trackedRace.getOrCreateTrack(mark)
.getLastFixBefore(new MillisecondsTimePoint(startOfTracking)).getTimePoint().asMillis());
assertEquals(bestFixAfterEndOfTrackingOffset, trackedRace.getOrCreateTrack(mark)
.getFirstFixAfter(new MillisecondsTimePoint(endOfTracking)).getTimePoint().asMillis());
});
}
/** Test for changes of bug 4009 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4009 */
@Test
public void testYoungestFixIsAccurateOnSeveralAdditionsOfFixes()
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
final long bestFix1 = 50;
final long bestFix2 = 100;
final long startOfTracking = 200;
final long endOfTracking = 300;
testFixes(/* start of tracking */ startOfTracking, /* end of tracking */ endOfTracking, /* mappings and fixes */ () -> {
map(mark, device, bestFix1 - 20, bestFix1 + 10);
store.storeFix(device, createFix(bestFix1 - 10, 10, 20));
store.storeFix(device, createFix(bestFix1, 10, 20));
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
assertEquals(bestFix1, trackedRace.getOrCreateTrack(mark)
.getLastFixBefore(new MillisecondsTimePoint(startOfTracking)).getTimePoint().asMillis());
store.storeFix(device, createFix(bestFix2 - 10, 10, 20));
store.storeFix(device, createFix(bestFix2, 10, 20));
map(mark, device, bestFix2 - 20, bestFix2 + 10);
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
assertEquals(bestFix2, trackedRace.getOrCreateTrack(mark)
.getLastFixBefore(new MillisecondsTimePoint(startOfTracking)).getTimePoint().asMillis());
});
}
/**
* Test for changes of bug 4009 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4009 <br>
*/
@Test
public void testLoadingOfFixesOutsideOfTrackingIntervalsEndsIfFixInIntervalIsFound()
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
final long bestFix1 = 50;
final long bestFix2 = 100;
final long startOfTracking = 200;
final long endOfTracking = 300;
testFixes(/* start of tracking */ startOfTracking, /* end of tracking */ endOfTracking, /* mappings and fixes */ () -> {
map(mark, device, bestFix1 - 10, bestFix1 + 10);
store.storeFix(device, createFix(bestFix1, 10, 20));
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
assertEquals(bestFix1, trackedRace.getOrCreateTrack(mark)
.getLastFixBefore(new MillisecondsTimePoint(startOfTracking)).getTimePoint().asMillis());
store.storeFix(device, createFix(startOfTracking + 10, 10, 20));
map(mark, device, startOfTracking, startOfTracking + 20);
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
// would be the new best fix if there wasn't a fix in the tracking interval
store.storeFix(device, createFix(bestFix2, 10, 20));
map(mark, device, bestFix2 - 10, bestFix2 + 10);
}, /* tests and expectations */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
assertEquals(bestFix1, trackedRace.getOrCreateTrack(mark)
.getLastFixBefore(new MillisecondsTimePoint(startOfTracking)).getTimePoint().asMillis());
});
}
@@ -251,7 +383,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings */ () -> map(mark, device, 0, 200),
/* fixes and tests/expectations */ trackedRace -> {
store.storeFix(device, createFix(50, 10, 20, 30, 40));
store.storeFix(device, createFix(50, 10, 20));
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
});
}
@@ -260,7 +392,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
public void testFixForMarkIsAddedIfAfterTrackingIntervalAndNoOtherFixExists()
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings */ () -> map(mark, device, 0, 400), /* fixes and tests/expectations */ trackedRace -> {
store.storeFix(device, createFix(350, 10, 20, 30, 40));
store.storeFix(device, createFix(350, 10, 20));
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
});
}
@@ -270,8 +402,8 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings */ () -> map(mark, device, 0, 400),
/* fixes and tests/expectations */ trackedRace -> {
store.storeFix(device, createFix(50, 10, 20, 30, 40));
store.storeFix(device, createFix(75, 10, 20, 30, 40));
store.storeFix(device, createFix(50, 10, 20));
store.storeFix(device, createFix(75, 10, 20));
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
});
}
@@ -281,8 +413,8 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings */ () -> map(mark, device, 0, 400),
/* fixes and tests/expectations */ trackedRace -> {
store.storeFix(device, createFix(75, 10, 20, 30, 40));
store.storeFix(device, createFix(50, 10, 20, 30, 40));
store.storeFix(device, createFix(75, 10, 20));
store.storeFix(device, createFix(50, 10, 20));
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
});
}
@@ -292,8 +424,8 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings */ () -> map(mark, device, 0, 400),
/* fixes and tests/expectations */ trackedRace -> {
store.storeFix(device, createFix(375, 10, 20, 30, 40));
store.storeFix(device, createFix(350, 10, 20, 30, 40));
store.storeFix(device, createFix(375, 10, 20));
store.storeFix(device, createFix(350, 10, 20));
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
});
}
@@ -303,8 +435,8 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings */ () -> map(mark, device, 0, 400),
/* fixes and tests/expectations */ trackedRace -> {
store.storeFix(device, createFix(350, 10, 20, 30, 40));
store.storeFix(device, createFix(375, 10, 20, 30, 40));
store.storeFix(device, createFix(350, 10, 20));
store.storeFix(device, createFix(375, 10, 20));
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
});
}
@@ -314,15 +446,16 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException {
testFixes(/* start of tracking */ 100, /* end of tracking */ 300, /* mappings */ () -> map(mark, device, 0, 400),
/* fixes and tests/expectations */ trackedRace -> {
store.storeFix(device, createFix(200, 10, 20, 30, 40));
store.storeFix(device, createFix(50, 10, 20, 30, 40));
store.storeFix(device, createFix(350, 10, 20, 30, 40));
store.storeFix(device, createFix(200, 10, 20));
store.storeFix(device, createFix(50, 10, 20));
store.storeFix(device, createFix(350, 10, 20));
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
});
}
private void testFixes(long startOfTracking, long endOfTracking, Runnable beforeTrackingStarted,
Consumer<DynamicTrackedRace> afterTrackingStarted) throws InterruptedException {
@SafeVarargs
private final void testFixes(long startOfTracking, long endOfTracking, Runnable beforeTrackingStarted,
Consumer<DynamicTrackedRace>... afterTrackingStartedConsumers) throws InterruptedException {
defineMarksOnRegattaLog(mark, mark2);
raceLog.add(new RaceLogStartOfTrackingEventImpl(new MillisecondsTimePoint(startOfTracking), author, 0));
raceLog.add(new RaceLogEndOfTrackingEventImpl(new MillisecondsTimePoint(endOfTracking), author, 0));
@@ -331,8 +464,10 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
new FixLoaderAndTracker(trackedRace, store, null);
trackedRace.waitForLoadingToFinish();
afterTrackingStarted.accept(trackedRace);
for(Consumer<DynamicTrackedRace> afterTrackingStarted : afterTrackingStartedConsumers) {
trackedRace.waitForLoadingToFinish();
afterTrackingStarted.accept(trackedRace);
}
}
@Test
@@ -64,13 +64,13 @@ public class RaceLogConnectivityParams extends AbstractRaceTrackingConnectivityP
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore,
RaceLogResolver raceLogResolver) {
return createRaceTracker(regatta, trackedRegattaRegistry, windStore, raceLogResolver);
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) {
return createRaceTracker(regatta, trackedRegattaRegistry, windStore, raceLogResolver, timeoutInMilliseconds);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
WindStore windStore, RaceLogResolver raceLogResolver) {
WindStore windStore, RaceLogResolver raceLogResolver, long timeoutInMilliseconds) {
if (regatta == null) {
BoatClass boatClass = new RaceInformationFinder(getRaceLog()).analyze().getBoatClass();
regatta = service.getOrCreateDefaultRegatta(
@@ -41,7 +41,7 @@ public class TrackFileImportDeviceIdentifierJsonHandler implements DeviceIdentif
String fileName = (String) json.get(Fields.FILE_NAME.name());
String trackName = (String) json.get(Fields.TRACK_NAME.name());
TimePoint uploaded = loadTimePoint(json.get(Fields.UPLOADED_MILLIS.name()));
return new TrackFileImportDeviceIdentifierImpl(uuid, fileName, trackName, uploaded);
return new TrackFileImportDeviceIdentifierImpl(uuid, stringRepresentation, fileName, trackName, uploaded);
}
@Override
@@ -24,7 +24,7 @@ public class TrackFileImportDeviceIdentifierMongoHandler implements DeviceIdenti
String trackName = (String) dbObject.get(Fields.TRACK_NAME.name());
TimePoint uploaded = TrackFileImportDeviceIdentifierJsonHandler.
loadTimePoint(dbObject.get(Fields.UPLOADED_MILLIS.name()));
return new TrackFileImportDeviceIdentifierImpl(uuid, fileName, trackName, uploaded);
return new TrackFileImportDeviceIdentifierImpl(uuid, stringRepresentation, fileName, trackName, uploaded);
}
@Override
@@ -1,5 +1,8 @@
package com.sap.sailing.domain.racelogtracking.impl.fixtracker;
import java.util.Collection;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
@@ -7,9 +10,9 @@ 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.RegattaLogDeviceCompetitorMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
@@ -19,23 +22,20 @@ import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.SensorFix;
import com.sap.sailing.domain.racelog.impl.GPSFixStoreImpl;
import com.sap.sailing.domain.racelog.tracking.FixReceivedListener;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.TrackingDataLoader;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
import com.sap.sse.common.MultiTimeRange;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
@@ -56,11 +56,14 @@ import com.sap.sse.util.ThreadPoolUtil;
* </ul>
*
*/
/**
* @author sschaefe
*
*/
public class FixLoaderAndTracker implements TrackingDataLoader {
private static final Logger logger = Logger.getLogger(FixLoaderAndTracker.class.getName());
protected final DynamicTrackedRace trackedRace;
private final SensorFixStore sensorFixStore;
private final GPSFixStore gpsFixStore;
private RegattaLogDeviceMappings<WithID> deviceMappings;
private final AtomicInteger activeLoaders = new AtomicInteger();
private final SensorFixMapperFactory sensorFixMapperFactory;
@@ -196,7 +199,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
public FixLoaderAndTracker(DynamicTrackedRace trackedRace, SensorFixStore sensorFixStore,
SensorFixMapperFactory sensorFixMapperFactory) {
this.sensorFixStore = sensorFixStore;
this.gpsFixStore = new GPSFixStoreImpl(sensorFixStore);
this.sensorFixMapperFactory = sensorFixMapperFactory;
this.trackedRace = trackedRace;
@@ -207,37 +209,50 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
* Loading fixes for {@link Mark}s needs special handling compared with {@link Competitor}s. If there are no fixes
* available in the tracking interval, it is necessary to load other available fixes before/after the tracking
* interval. this is due to the buoy pinger app can be used in the morning to ping some marks. The resulting fixes
* need to also be available for races later on the day. This method ensures that if available, fixes are initially
* loaded when starting tracking. In contrast to that, mappings for {@link Competitor}s have no special handling.
*
* @param mappings
* the added mappings
* @param item
* the item the mappings belong to
* need to also be available for races later on the day. This method ensures that if available, the best available
* fixes are initially loaded when starting tracking. In contrast to that, mappings for {@link Competitor}s have no
* special handling.
*/
private void loadFixesForNewMappings(Iterable<DeviceMappingWithRegattaLogEvent<WithID>> mappings, WithID item) {
private void loadFixesForNewlyCoveredTimeRanges(WithID item,
Map<RegattaLogDeviceMappingEvent<WithID>, MultiTimeRange> newlyCoveredTimeRanges) {
TimeRange trackingTimeRange = getTrackingTimeRange();
loadFixesInTrackingTimeRange(newlyCoveredTimeRanges, trackingTimeRange);
if (item instanceof Mark) {
Mark mark = (Mark) item;
if (containsMappingThatIntersectsTimeRange(mappings, trackingTimeRange)) {
loadFixesInTimeRange(mappings, trackingTimeRange);
}
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(mark);
// load all mapped fixes if there was no fix in the tracking TimeRange
final boolean loadAllMappedFixes = (track.getFirstRawFix() == null);
if (loadAllMappedFixes) {
// either got an empty track of there is no mapping for the TimeRange of the race at all.
// try again without constraining the mapping interval by start/end of tracking to at
// least attempt to get fixes at all in case there were any within the device mapping interval specified
mappings.forEach(mapping -> loadFixes(mapping.getTimeRange(), mapping));
GPSFix firstFixAfterStartOfTracking = track.getFirstFixAfter(trackingTimeRange.from());
if (firstFixAfterStartOfTracking == null
|| firstFixAfterStartOfTracking.getTimePoint().after(trackingTimeRange.to())) {
// There is no fix in the tracking interval -> looking for better fixes before start of tracking and
// after end of tracking
newlyCoveredTimeRanges.forEach((event, timeRange) -> {
loadBetterFixesIfAvailable(trackingTimeRange, timeRange, event);
});
}
} else {
loadFixesInTimeRange(mappings, trackingTimeRange);
}
}
private void loadFixesInTimeRange(Iterable<DeviceMappingWithRegattaLogEvent<WithID>> mappings, TimeRange trackingTimeRange) {
mappings.forEach(mapping -> loadFixes(trackingTimeRange.intersection(mapping.getTimeRange()), mapping));
/**
* Loads fixes defined by the given mapping and {@link MultiTimeRange}. Only those fixes that are in the mapping
* time range are being loaded.
*/
private void loadFixesInTrackingTimeRange(
Map<RegattaLogDeviceMappingEvent<WithID>, MultiTimeRange> newlyCoveredTimeRanges,
TimeRange trackingTimeRange) {
newlyCoveredTimeRanges.forEach((event, timeRange) -> {
loadFixesForMultiTimeRange(timeRange.intersection(trackingTimeRange), event);
});
}
/**
* Loads fixes for the parts of the given {@link MultiTimeRange} using the given mapping event.
*/
private void loadFixesForMultiTimeRange(MultiTimeRange effectiveRangeToLoad,
RegattaLogDeviceMappingEvent<WithID> event) {
if (!effectiveRangeToLoad.isEmpty()) {
effectiveRangeToLoad.forEach(timeRange -> loadFixes(timeRange, event));
}
}
/**
@@ -254,14 +269,17 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
return false;
}
private void loadFixes(TimeRange timeRangeToLoad, DeviceMappingWithRegattaLogEvent<? extends WithID> mapping) {
if(timeRangeToLoad == null) {
/**
* Loads the fixes in the specified {@link TimeRange} using a visitor of the given mapping event.
*/
private void loadFixes(TimeRange timeRangeToLoad, RegattaLogDeviceMappingEvent<? extends WithID> mappingEvent) {
if (timeRangeToLoad == null) {
return;
}
if (preemptiveStopRequested.get()) {
return;
}
mapping.getRegattaLogEvent().accept(new MappingEventVisitor() {
mappingEvent.accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
SensorFixMapper<Timed, DynamicTrack<Timed>, Competitor> mapper = sensorFixMapperFactory
@@ -272,11 +290,11 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
try {
sensorFixStore.loadFixes((DoubleVectorFix fix) -> mapper.addFix(track, fix), mapping.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), true);
sensorFixStore.<DoubleVectorFix> loadFixes(fix -> mapper.addFix(track, fix), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (NoCorrespondingServiceRegisteredException | TransformationException e) {
logger.log(Level.WARNING, "Could not load track for competitor: " + mapping.getMappedTo()
+ "; device: " + mapping.getDevice());
logger.log(Level.WARNING, "Could not load track for competitor: " + event.getMappedTo()
+ "; device: " + event.getDevice());
}
}
}
@@ -289,12 +307,11 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
try {
@SuppressWarnings({ "unchecked" })
DeviceMapping<Competitor> competitorMapping = (DeviceMapping<Competitor>) mapping;
gpsFixStore.loadCompetitorTrack(track, competitorMapping, timeRangeToLoad.from(),
timeRangeToLoad.to());
sensorFixStore.<GPSFixMoving> loadFixes(fix -> track.add(fix, true), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + mapping.getMappedTo());
logger.log(Level.WARNING, "Could not load competitor track " + event.getMappedTo() + "; device "
+ event.getDevice());
}
}
}
@@ -303,19 +320,87 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
public void visit(RegattaLogDeviceMarkMappingEvent event) {
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(event.getMappedTo());
try {
@SuppressWarnings("unchecked")
DeviceMapping<Mark> markMapping = (DeviceMapping<Mark>) mapping;
gpsFixStore.loadMarkTrack(track, markMapping, timeRangeToLoad.from(), timeRangeToLoad.to());
sensorFixStore.<GPSFix> loadFixes(fix -> track.add(fix, true), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load mark track " + mapping.getMappedTo());
logger.log(Level.WARNING, "Could not load mark track " + event.getMappedTo());
}
}
});
}
/**
* Loads better fallback fixes if there is no fix in the tracking interval found.
*/
private void loadBetterFixesIfAvailable(TimeRange trackingTimeRange, MultiTimeRange coveredTimeRanges,
RegattaLogDeviceMappingEvent<? extends WithID> mappingEvent) {
if (preemptiveStopRequested.get()) {
return;
}
mappingEvent.accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
throw new UnsupportedOperationException();
}
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
throw new UnsupportedOperationException();
}
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(event.getMappedTo());
final GPSFix lastFixAtOrBeforeStartOfTracking = track.getLastFixAtOrBefore(trackingTimeRange.from());
// A better fix before start of tracking must be after the current best fix
final MultiTimeRange beforeRange = coveredTimeRanges
.intersection(new TimeRangeImpl(
lastFixAtOrBeforeStartOfTracking != null
? lastFixAtOrBeforeStartOfTracking.getTimePoint() : TimePoint.BeginningOfTime,
trackingTimeRange.from()));
// starting to load newer ranges to make the first found fix the best available fix
Collection<TimeRange> inverseTimeRanges = Util.addAll(beforeRange,
new TreeSet<>((timeRange1, timeRange2) -> -timeRange1.from().compareTo(timeRange2.from())));
for (TimeRange timeRange : inverseTimeRanges) {
try {
if (sensorFixStore.<GPSFix> loadYoungestFix(fix -> track.add(fix, true),
event.getDevice(), timeRange)) {
// new best fix before start of tracking found
break;
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load better fix for mark track " + event.getMappedTo());
}
}
final GPSFix firstFixAtOrAfterEndOfTracking = track.getFirstFixAtOrAfter(trackingTimeRange.to());
// A better fix after end of tracking must be before the current best fix
MultiTimeRange afterRange = coveredTimeRanges.intersection(new TimeRangeImpl(trackingTimeRange.to(),
firstFixAtOrAfterEndOfTracking != null ? firstFixAtOrAfterEndOfTracking.getTimePoint()
: TimePoint.EndOfTime));
for (TimeRange timeRange : afterRange) {
try {
if (sensorFixStore.<GPSFix> loadOldestFix(fix -> track.add(fix, true), event.getDevice(),
timeRange)) {
// new best fix after end of tracking found
break;
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load better fix for mark track " + event.getMappedTo());
}
}
}
});
}
private TimeRange getTrackingTimeRange() {
return new TimeRangeImpl(getStartOfTracking() == null ? TimePoint.BeginningOfTime : getStartOfTracking(),
getEndOfTracking() == null ? TimePoint.EndOfTime : getEndOfTracking());
final TimePoint startOfTracking = trackedRace.getStartOfTracking();
final TimePoint endOfTracking = trackedRace.getEndOfTracking();
return new TimeRangeImpl(startOfTracking == null ? TimePoint.BeginningOfTime : startOfTracking,
endOfTracking == null ? TimePoint.EndOfTime : endOfTracking);
}
public void stop(boolean preemptive) {
@@ -334,7 +419,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
}
protected void startTracking() {
private void startTracking() {
trackedRace.addListener(raceChangeListener);
this.deviceMappings = new FixLoaderDeviceMappings(trackedRace.getAttachedRegattaLogs(),
trackedRace.getRace().getName());
@@ -350,19 +435,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
}
protected TimePoint getStartOfTracking() {
return trackedRace.getStartOfTracking();
}
protected TimePoint getEndOfTracking() {
return trackedRace.getEndOfTracking();
}
protected void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) {
private void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) {
final TimeRangeImpl extendedTimeRange = new TimeRangeImpl(loadFixesFrom, loadFixesTo);
deviceMappings.forEachMapping((mapping) -> {
loadFixes(extendedTimeRange.intersection(mapping.getTimeRange()), mapping);
});
deviceMappings.forEachItemAndCoveredTimeRanges((item, mappingsAndCoveredTimeRanges) -> loadFixesInTrackingTimeRange(mappingsAndCoveredTimeRanges, extendedTimeRange));
}
/**
@@ -390,6 +465,8 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
updateCallback.run();
}
} catch(Throwable t) {
logger.log(Level.SEVERE, "Error while updating device mappings and loading fixes for race: " + trackedRace.getRaceIdentifier(), t);
} finally {
try {
synchronized (FixLoaderAndTracker.this) {
@@ -408,14 +485,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
});
}
/**
* Tells if currently the race is loading GPS fixes from the {@link GPSFixStore}. Clients may {@link Object#wait()}
* on <code>this</code> object and will be notified whenever a change of this flag's value occurs.
*/
public boolean isLoadingFromGPSFixStore() {
return activeLoaders.get() > 0;
}
private void setStatusAndProgress(TrackedRaceStatusEnum status, double progress) {
trackedRace.onStatusChanged(this, new TrackedRaceStatusImpl(status, progress));
}
@@ -429,56 +498,21 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
protected void updateMappings() {
updateAsyncInternal(FixLoaderDeviceMappings.super::updateMappings);
}
@Override
protected void mappingRemoved(DeviceMappingWithRegattaLogEvent<WithID> mapping) {
final DeviceIdentifier device = mapping.getDevice();
if (!hasMappingForDevice(device)) {
sensorFixStore.removeListener(listener, device);
}
// TODO if tracks are always associated to only one device mapping, we could remove tracks here
protected void deviceIdAdded(DeviceIdentifier deviceIdentifier) {
sensorFixStore.addListener(listener, deviceIdentifier);
}
@Override
protected void mappingsAdded(Iterable<DeviceMappingWithRegattaLogEvent<WithID>> mappings, WithID item) {
// The listener is first added. This causes that no fix is lost but potentially fixes are being processes twice.
// There are the following cases:
// 1. A fix is added before the mapping update started
// -> the fix is dropped but during mapping update the whole mapping is being loaded including this fix.
// 2. A fix is added while the write lock is being held for the mappings
// -> The fix will be processed after the write lock is removed. In this state
// the mappings are up-to-date and in a consequence the fix is loaded into the track.
// In addition, the fix is loaded on mapping update, but both fix instances consistently have the same state.
// 3. A fix is added after a successful mapping update
// -> The fix is only added once, because the initial loading during mapping update is finished
mappings.forEach(mapping -> sensorFixStore.addListener(listener, mapping.getDevice()));
loadFixesForNewMappings(mappings, item);
protected void deviceIdRemoved(DeviceIdentifier deviceIdentifier) {
sensorFixStore.removeListener(listener, deviceIdentifier);
}
/**
* {@link #loadFixes loads fixes} that are captured by {@code newMapping} but not by {@code oldMapping}. This
* may require up to two {@link #loadFixes} calls: one for the fixes after and one for the fixes before the old
* mapping.
*/
@Override
protected void mappingChanged(DeviceMappingWithRegattaLogEvent<WithID> oldMapping,
DeviceMappingWithRegattaLogEvent<WithID> newMapping) {
final TimeRange newTimeRange = newMapping.getTimeRange();
final TimeRange oldTimeRange = oldMapping.getTimeRange();
if (newTimeRange.endsAfter(oldTimeRange)) {
final TimePoint oldTo = oldTimeRange.to();
final TimePoint newFrom = newTimeRange.from();
final TimePoint from = oldTo.after(newFrom) ? oldTo : newFrom;
final TimeRange rangeToLoad = new TimeRangeImpl(from, newTimeRange.to());
loadFixes(rangeToLoad, newMapping);
}
if (newTimeRange.startsBefore(oldTimeRange)) {
final TimePoint oldFrom = oldTimeRange.from();
final TimePoint newTo = newTimeRange.to();
final TimePoint to = oldFrom.after(newTo) ? oldFrom : newTo;
final TimeRange rangeToLoad = new TimeRangeImpl(newTimeRange.from(), to);
loadFixes(rangeToLoad, newMapping);
}
protected void newTimeRangesCovered(WithID item,
Map<RegattaLogDeviceMappingEvent<WithID>, MultiTimeRange> newlyCoveredTimeRanges) {
loadFixesForNewlyCoveredTimeRanges(item, newlyCoveredTimeRanges);
}
}
}
@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -18,6 +17,7 @@ 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.RegattaLogDeviceMappingEvent;
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;
@@ -27,9 +27,14 @@ import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sse.common.MultiTimeRange;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Timed;
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.MultiTimeRangeImpl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
@@ -125,9 +130,8 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
public void stop() {
LockUtil.executeWithWriteLock(knownRegattaLogsLock, () -> {
knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor));
knownRegattaLogs.clear();
});
}
}
protected void updateMappings() {
try {
@@ -137,40 +141,6 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
}
}
/**
* Calls the given callback for every known mapping that's currently known.
*
* @param callback the callback to call for every known mapping
*/
public void forEachMapping(Consumer<DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
forEachMapping((item, mapping) -> callback.accept(mapping));
}
/**
* Calls the given callback for every device mapped by at least one of the known mappings.
*
* @param callback the callback to call for every mapped device
*/
public void forEachDevice(Consumer<DeviceIdentifier> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> mappingsByDevice.keySet().forEach(callback::accept));
}
/**
* Calls the given callback for every known mapping that's currently known.
*
* @param callback the callback to call for every known mapping
*/
public void forEachMapping(BiConsumer<ItemT, DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> {
for (Map.Entry<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> entry : mappings.entrySet()) {
ItemT item = entry.getKey();
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : entry.getValue()) {
callback.accept(item, mapping);
}
}
});
}
/**
* Calls the given callback for every DeviceMapping that is known for the given {@link DeviceIdentifier} that
* includes the given {@link TimePoint}.
@@ -197,16 +167,31 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
});
}
public void forEachItemAndCoveredTimeRanges(final BiConsumer<ItemT, Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange>> consumer) {
HashMap<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> allMappings = LockUtil.executeWithReadLockAndResult(mappingsLock, () -> new HashMap<>(mappings));
allMappings.forEach((item, mappings) -> {
final Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> coveredTimeRanges = calculateCoveredTimeRanges(mappings);
consumer.accept(item, coveredTimeRanges);
});
}
/**
* @return true if there is at least one mapping for the given {@link DeviceIdentifier}, false otherwise
* Calculates an association of mapping events to the covered {@link MultiTimeRange}.
*/
public boolean hasMappingForDevice(DeviceIdentifier device) {
LockUtil.lockForRead(mappingsLock);
try {
return mappingsByDevice.containsKey(device);
} finally {
LockUtil.unlockAfterRead(mappingsLock);
private Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> calculateCoveredTimeRanges(
final List<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForItem) {
final Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> coveredTimeRanges = new HashMap<>();
if(mappingsForItem != null) {
Map<Pair<DeviceIdentifier, Class<?>>, Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> groupedMappings = groupMappingsByDeviceIdAndMappingType(mappingsForItem);
groupedMappings.entrySet().forEach(entry -> {
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForDeviceIdAndMappingType = entry.getValue();
final MultiTimeRange coveredTimeRange = getCoveredTimeRange(mappingsForDeviceIdAndMappingType);
if(!coveredTimeRange.isEmpty()) {
coveredTimeRanges.put(Util.get(mappingsForDeviceIdAndMappingType, 0).getRegattaLogEvent(), coveredTimeRange);
}
});
}
return coveredTimeRanges;
}
/**
@@ -237,11 +222,15 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* @throws DoesNotHaveRegattaLogException
*/
private final <FixT extends Timed, TrackT extends DynamicTrack<FixT>> void updateMappingsInternal() {
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings = calculateMappings();
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings;
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> oldMappings = new HashMap<>();
final Set<DeviceIdentifier> oldDeviceIds = new HashSet<>();
final Set<DeviceIdentifier> newDeviceIds = new HashSet<>();
LockUtil.lockForWrite(mappingsLock);
try {
newMappings = calculateMappings();
oldMappings.putAll(mappings);
oldDeviceIds.addAll(mappingsByDevice.keySet());
mappings.clear();
mappings.putAll(newMappings);
mappingsByDevice.clear();
@@ -255,10 +244,11 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
list.add(mapping);
}
}
newDeviceIds.addAll(mappingsByDevice.keySet());
} finally {
LockUtil.unlockAfterWrite(mappingsLock);
}
calculateDiff(oldMappings, newMappings);
calculateDiff(oldMappings, newMappings, oldDeviceIds, newDeviceIds);
}
/**
@@ -266,105 +256,141 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* and by loading and adding the fixes for extended or added mappings.
*/
private void calculateDiff(Map<ItemT, ? extends Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> previousMappings,
Map<ItemT, ? extends Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings) {
Set<ItemT> itemsToProcess = new HashSet<ItemT>(previousMappings.keySet());
itemsToProcess.addAll(newMappings.keySet());
for (ItemT item : itemsToProcess) {
if (!newMappings.containsKey(item)) {
previousMappings.get(item).forEach(this::mappingRemovedInternal);
} else {
final Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings = previousMappings.containsKey(item)
? previousMappings.get(item) : Collections.emptyList();
final List<DeviceMappingWithRegattaLogEvent<ItemT>> addedMappings = new ArrayList<>();
for (DeviceMappingWithRegattaLogEvent<ItemT> newMapping : newMappings.get(item)) {
DeviceMappingWithRegattaLogEvent<ItemT> oldMapping = findAndRemoveMapping(newMapping, oldMappings);
if (oldMapping == null) {
addedMappings.add(newMapping);
} else if (!newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
mappingChangedInternal(oldMapping, newMapping);
}
}
mappingsAddedInternal(addedMappings, item);
// oldMappings now still contains those mappings that were not entirely substituted by the new mappings
oldMappings.forEach(this::mappingRemovedInternal);
Map<ItemT, ? extends Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings,
Set<DeviceIdentifier> oldDeviceIds, Set<DeviceIdentifier> newDeviceIds) {
final Set<DeviceIdentifier> removedDeviceIds = new HashSet<>(oldDeviceIds);
removedDeviceIds.removeAll(newDeviceIds);
removedDeviceIds.forEach(this::deviceIdRemovedInternal);
final Set<DeviceIdentifier> addedDeviceIds = new HashSet<>(newDeviceIds);
addedDeviceIds.removeAll(oldDeviceIds);
addedDeviceIds.forEach(this::deviceIdAddedInternal);
// Only deviceIdentifier and mappingTypes are covered that are contained in newMappings.
// Those that are only found in oldMappings won't lead to new covered TimeRanges.
// DeviceIdentifiers, that aren't needed at all are already handled above.
newMappings.forEach((item, mappingsForItem) -> {
final Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> newlyCoveredTimeRanges = new HashMap<>();
// The mappings are processes grouped by DeviceIdentifier and mapping type
// to build a consistent overall mapping update without the risk of fixes
// to get loaded multiple times
this.processNewAndChangedMappingsByDeviceIdAndEventType(previousMappings.get(item), mappingsForItem,
(deviceIdentifier, mappingType, oldMappingsForDeviceIdAndMappingType,
newMappingsForDeviceIdAndMappingType) -> {
assert (newMappingsForDeviceIdAndMappingType != null);
assert (!Util.isEmpty(newMappingsForDeviceIdAndMappingType));
final MultiTimeRange newCoveredTimeRanges = getCoveredTimeRange(
newMappingsForDeviceIdAndMappingType)
.subtract(getCoveredTimeRange(oldMappingsForDeviceIdAndMappingType));
if (!newCoveredTimeRanges.isEmpty()) {
newlyCoveredTimeRanges.put(
Util.get(newMappingsForDeviceIdAndMappingType, 0).getRegattaLogEvent(),
newCoveredTimeRanges);
}
});
if (!newlyCoveredTimeRanges.isEmpty()) {
// all updates for one item are handled at once
newTimeRangesCoveredInternal(item, newlyCoveredTimeRanges);
}
}
});
}
/**
* Called when a {@link DeviceMapping} was removed.
*
* @param mapping the removed mapping
* Processes the old and new mappings for mapping update by {@link DeviceIdentifier} and mapping type (class of the
* mapping event). the old and new mappings are first grouped by the already mentioned criteria. For each group in
* the new mappings, the associated group in the old mappings is identified. For these pairs, the given callback is
* called. Groups only found in the old mappings are explicitly not handled because these can't lead to new covered
* time ranges.
*/
protected abstract void mappingRemoved(DeviceMappingWithRegattaLogEvent<ItemT> mapping);
private void processNewAndChangedMappingsByDeviceIdAndEventType(
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings,
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> newMappings,
GroupedOldAndNewMappingsCallback<ItemT> callback) {
final Map<Pair<DeviceIdentifier, Class<?>>, Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> groupedOldMappings = groupMappingsByDeviceIdAndMappingType(
oldMappings != null ? oldMappings : Collections.emptySet());
final Map<Pair<DeviceIdentifier, Class<?>>, Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> groupedNewMappings = groupMappingsByDeviceIdAndMappingType(
newMappings);
groupedNewMappings.forEach((key, newMappingsForDeviceIdAndMappingType) -> {
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappingsForDeviceIdAndMappingType = groupedOldMappings
.get(key);
callback.process(
key.getA(), key.getB(), oldMappingsForDeviceIdAndMappingType != null
? oldMappingsForDeviceIdAndMappingType : Collections.emptySet(),
newMappingsForDeviceIdAndMappingType);
});
}
private void mappingRemovedInternal(DeviceMappingWithRegattaLogEvent<ItemT> mapping) {
/**
* Groups the given {@link DeviceMapping}s by pairs of {@link DeviceIdentifier} and mapping type (class of the
* mapping event).
*/
private Map<Pair<DeviceIdentifier, Class<?>>, Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> groupMappingsByDeviceIdAndMappingType(
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> mappings) {
return Util.group(mappings, value -> new Pair<>(value.getDevice(), value.getEventType()), HashSet::new);
}
/**
* Calculates the {@link MultiTimeRange} as union of the {@link TimeRange}s of the given mappings.
*/
private MultiTimeRange getCoveredTimeRange(Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> mappings) {
MultiTimeRange result = new MultiTimeRangeImpl();
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : mappings) {
result = result.union(mapping.getTimeRange());
}
return result;
}
/**
* Internal callback interface for {@link #processNewAndChangedMappingsByDeviceIdAndEventType}.
*/
private interface GroupedOldAndNewMappingsCallback<ItemT extends WithID> {
void process(DeviceIdentifier deviceIdentifier, Class<?> mappingType,
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings,
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> newMappings);
}
/**
* Called when at least one mapping for a previously not available {@link DeviceIdentifier} was added.
*/
protected abstract void deviceIdAdded(DeviceIdentifier deviceIdentifier);
private void deviceIdAddedInternal(DeviceIdentifier deviceIdentifier) {
try {
mappingRemoved(mapping);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while removing mapping " + mapping, e);
deviceIdAdded(deviceIdentifier);
} catch (Exception e) {
logger.log(Level.SEVERE, "error while adding deviceIdentifier " + deviceIdentifier, e);
}
}
/**
* Called when a {@link DeviceMapping} was added.
*
* @param mappings the new mapping
* Called when the last available mapping for a {@link DeviceIdentifier} was removed.
*/
protected abstract void mappingsAdded(Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> mappings, ItemT item);
private void mappingsAddedInternal(Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> mappings, ItemT item) {
protected abstract void deviceIdRemoved(DeviceIdentifier deviceIdentifier);
private void deviceIdRemovedInternal(DeviceIdentifier deviceIdentifier) {
try {
mappingsAdded(mappings, item);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while adding mapping " + mappings, e);
deviceIdRemoved(deviceIdentifier);
} catch (Exception e) {
logger.log(Level.SEVERE, "error while removing deviceIdentifier " + deviceIdentifier, e);
}
}
/**
* Called when a {@link DeviceMapping} was changed regarding its mapped time range.
* This can occur if an open ended mapping is being closed or a close event gets revoked
* or a new mapping is added that may partly overlap with an old mapping such that only
* parts of the fixes covered by the new mapping need to be loaded (the ones not already
* covered by the old mapping).
*
* @param oldMapping the old mapping
* @param newMapping the new mapping
* Called when a new MultiTimeRange could be identified that is now covered by the mappings for a item,
* DeviceIdentifier and mappingType.
*/
protected abstract void mappingChanged(DeviceMappingWithRegattaLogEvent<ItemT> oldMapping,
DeviceMappingWithRegattaLogEvent<ItemT> newMapping);
private void mappingChangedInternal(DeviceMappingWithRegattaLogEvent<ItemT> oldMapping,
DeviceMappingWithRegattaLogEvent<ItemT> newMapping) {
protected abstract void newTimeRangesCovered(ItemT item, Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> newlyCoveredTimeRanges);
private void newTimeRangesCoveredInternal(ItemT item, Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> newlyCoveredTimeRanges) {
try {
mappingChanged(oldMapping, newMapping);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while changing mapping old: " + oldMapping + "; new: " + newMapping, e);
newTimeRangesCovered(item, newlyCoveredTimeRanges);
} catch (Exception e) {
logger.log(Level.SEVERE,
"error while calling newTimeRangeCovered for item " + item, e);
}
}
private DeviceMappingWithRegattaLogEvent<ItemT> findAndRemoveMapping(
DeviceMappingWithRegattaLogEvent<ItemT> mappingToFind,
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> newItemsToProcess) {
for (Iterator<DeviceMappingWithRegattaLogEvent<ItemT>> iterator = newItemsToProcess.iterator(); iterator.hasNext();) {
DeviceMappingWithRegattaLogEvent<ItemT> deviceMapping = iterator.next();
if (isSame(mappingToFind, deviceMapping)) {
iterator.remove();
return deviceMapping;
}
}
return null;
}
/**
* Compares two device mappings based on their device, the item mapped to and the race/regatta log event type
* that usually corresponds with the type of item to which the device is mapped. Note that in particular the
* mappings' time ranges are ignored for this comparison.
*/
private boolean isSame(DeviceMappingWithRegattaLogEvent<ItemT> mapping1,
DeviceMappingWithRegattaLogEvent<ItemT> mapping2) {
return mapping1.getDevice().equals(mapping2.getDevice()) && mapping1.getMappedTo().equals(mapping2.getMappedTo())
&& mapping1.getEventType().equals(mapping2.getEventType())
&& mapping1.getRegattaLogEvent().getId().equals(mapping2.getRegattaLogEvent().getId());
}
}
@@ -52,9 +52,9 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
return mapping.getId();
}
private void closeMapping(AbstractLogEventAuthor author, DeviceIdentifier device, Serializable mappingId, long millis) {
private void closeMapping(AbstractLogEventAuthor author, DeviceIdentifier device, Serializable mappingId, long closingTimePointInclusiveAsMillis) {
RegattaLogEvent mapping = new RegattaLogCloseOpenEndedDeviceMappingEventImpl(t(), author, t(), UUID.randomUUID(), mappingId,
new MillisecondsTimePoint(millis));
new MillisecondsTimePoint(closingTimePointInclusiveAsMillis));
log.add(mapping);
}
@@ -73,7 +73,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(201) /* exclusive vs. inclusive */, mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(150));
{
@@ -86,10 +86,10 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
for (final DeviceMappingWithRegattaLogEvent<Competitor> mapping : deviceMappings) {
if (mapping.getTimeRange().from().asMillis() == 100) {
found100 = true;
assertEquals(149, mapping.getTimeRange().to().asMillis());
assertEquals(150 /* exclusive */, mapping.getTimeRange().to().asMillis());
} else {
assertEquals(151, mapping.getTimeRange().from().asMillis());
assertEquals(200, mapping.getTimeRange().to().asMillis());
assertEquals(201 /* exclusive vs. inclusive */, mapping.getTimeRange().to().asMillis());
}
}
assertTrue(found100);
@@ -109,7 +109,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(201 /* exclusive vs. inclusive */), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(150));
{
@@ -120,10 +120,10 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(2, deviceMappings.size());
for (final DeviceMappingWithRegattaLogEvent<Competitor> mapping : deviceMappings) {
if (mapping.getTimeRange().from().asMillis() == 100) {
assertEquals(149, mapping.getTimeRange().to().asMillis());
assertEquals(150 /* exclusive */, mapping.getTimeRange().to().asMillis());
} else {
assertEquals(151, mapping.getTimeRange().from().asMillis());
assertEquals(200, mapping.getTimeRange().to().asMillis());
assertEquals(201 /* exclusive vs. inclusive */, mapping.getTimeRange().to().asMillis());
}
}
}
@@ -141,7 +141,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(201) /* exclusive vs. inclusive */, mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
@@ -151,7 +151,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
final List<DeviceMappingWithRegattaLogEvent<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(101, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(200, deviceMappings.get(0).getTimeRange().to().asMillis());
assertEquals(201 /* exclusive vs. inclusive */, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@@ -168,7 +168,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(201) /* exclusive vs. inclusive */, mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
@@ -178,7 +178,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
final List<DeviceMappingWithRegattaLogEvent<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(101, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(200, deviceMappings.get(0).getTimeRange().to().asMillis());
assertEquals(201 /* exclusive */, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@@ -194,7 +194,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(201) /* exclusive vs. inclusive */, mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(200));
{
@@ -204,7 +204,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
final List<DeviceMappingWithRegattaLogEvent<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(100, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(199, deviceMappings.get(0).getTimeRange().to().asMillis());
assertEquals(200 /* exclusive */, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@@ -221,7 +221,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(201) /* exclusive vs. inclusive */, mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(200));
{
@@ -231,7 +231,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
final List<DeviceMappingWithRegattaLogEvent<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(100, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(199, deviceMappings.get(0).getTimeRange().to().asMillis());
assertEquals(200 /* exclusive */, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@@ -247,7 +247,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(101) /* exclusive vs. inclusive */, mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
@@ -269,7 +269,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, deviceMappings.size());
final DeviceMappingWithRegattaLogEvent<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().to());
assertEquals(new MillisecondsTimePoint(101) /* exclusive vs. inclusive */, mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
@@ -301,7 +301,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
assertEquals(1, mappings.size());
DeviceMappingWithRegattaLogEvent<Competitor> mapping = mappings.get(0);
assertEquals(10, mapping.getTimeRange().from().asMillis());
assertEquals(20, mapping.getTimeRange().to().asMillis());
assertEquals(21 /* exclusive vs. inclusive */, mapping.getTimeRange().to().asMillis());
//another independent range
id = addMapping(author, device, 0L, null, competitor);
@@ -312,7 +312,7 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
//another closing mapping, that should take precedence (because added later)
closeMapping(author, device, id, 3);
assertEquals(2, getMappings().size());
assertEquals(3, getMappings().get(1).getTimeRange().to().asMillis());
assertEquals(4 /* exclusive vs. inclusive */, getMappings().get(1).getTimeRange().to().asMillis());
//open-ended at end, not closed
addMapping(author, device, 30L, null, competitor);
@@ -7,7 +7,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
import com.sap.sse.common.TimePoint;
/**
* Closes the time range of a {@link RegattaLogDeviceMappingEvent}. This means that the {@link #getClosingTimePoint() timepoint} provided
* Closes the time range of a {@link RegattaLogDeviceMappingEvent}. This means that the {@link #getClosingTimePointInclusive() timepoint} provided
* by this event is substituted for the missing timepoint in the {@code DeviceMappingEvent} it refers to.
* @author Fredrik Teschke
*
@@ -15,8 +15,10 @@ import com.sap.sse.common.TimePoint;
public interface RegattaLogCloseOpenEndedDeviceMappingEvent extends RegattaLogEvent, Revokable {
/**
* Returns the timepoint that shall substitute the missing one in the corresponding {@link RegattaLogDeviceMappingEvent}.
* As with {@link RegattaLogDeviceMappingEvent#getToInclusive()}, this time point marks the <em>inclusive</em> end
* of the interval.
*/
TimePoint getClosingTimePoint();
TimePoint getClosingTimePointInclusive();
/**
* Returns the {@code id} of the event for which this event provides the closing timepoint.
@@ -9,15 +9,16 @@ import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.WithID;
/**
* Event type for mapping {@link DeviceIdentifier devices} to {@code items} ({@link Competitor competitors} or {@link Mark marks}).
* <p>
* Not both {@link #getFrom()} and {@link #getTo()} may be {@code null} at the same time (which would be a mapping for "all times",
* Not both {@link #getFrom()} and {@link #getToInclusive()} may be {@code null} at the same time (which would be a mapping for "all times",
* as the semantics of somehow later limiting this range would become unclear
* <p>
* If one end of the time range enclosed by {@link #getFrom()} and {@link #getTo()} is open, this can be closed
* If one end of the time range enclosed by {@link #getFrom()} and {@link #getToInclusive()} is open, this can be closed
* by a {@link RegattaLogCloseOpenEndedDeviceMappingEvent}.
*
* @see RegattaLogDefinedMarkAnalyzer for rules by which {@link DeviceMapping}s are derived from these events.
@@ -29,15 +30,19 @@ public interface RegattaLogDeviceMappingEvent<ItemType extends WithID> extends R
/**
* The {@code return} value may be {@code null}, symbolizing an open range.
* In that case the device should be mapped to the item for any timepoint before that specified by {@link #getTo()}.
* In that case the device should be mapped to the item for any timepoint before that specified by {@link #getToInclusive()}.
*/
TimePoint getFrom();
/**
* The {@code return} value may be {@code null}, symbolizing an open range.
* In that case the device should be mapped to the item for any timepoint after that specified by {@link #getFrom()}.
* Otherwise, this marks the <em>inclusive</em> end of the interval, other than the {@link TimeRange#to()} semantics which
* uses an <em>exclusive</em> interval end. To bridge this gap, whenever mapping this event's from/to interval to a
* {@link TimeRange}, at least the resolution of the {@link TimePoint} representation must be added to the result of
* this method to make it into a valid end of a {@link TimeRange} that includes the time interval covered by this mapping.
*/
TimePoint getTo();
TimePoint getToInclusive();
/**
* Visitor patter implementation for event mapping handling differentiation.
@@ -15,23 +15,35 @@ RegattaLogDeviceMappingEvent<ItemType> {
private final ItemType mappedTo;
private final DeviceIdentifier device;
/**
* inclusive start time point
*/
private final TimePoint from;
private final TimePoint to;
/**
* inclusive end time point
*/
private final TimePoint toInclusive;
/**
* @param from inclusive
* @param toInclusive inclusive
*/
public RegattaLogDeviceMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint,
AbstractLogEventAuthor author, Serializable id, ItemType mappedTo,
DeviceIdentifier device, TimePoint from, TimePoint to) {
DeviceIdentifier device, TimePoint from, TimePoint toInclusive) {
super(createdAt, logicalTimePoint, author, id);
this.mappedTo = mappedTo;
this.device = device;
this.from = from;
this.to = to;
this.toInclusive = toInclusive;
}
public RegattaLogDeviceMappingEventImpl(TimePoint logicalTimePoint,
AbstractLogEventAuthor author, ItemType mappedTo,
DeviceIdentifier device, TimePoint from, TimePoint to) {
this(now(), logicalTimePoint, author, randId(), mappedTo, device, from, to);
DeviceIdentifier device, TimePoint from, TimePoint toInclusive) {
this(now(), logicalTimePoint, author, randId(), mappedTo, device, from, toInclusive);
}
public ItemType getMappedTo() {
@@ -43,12 +55,12 @@ RegattaLogDeviceMappingEvent<ItemType> {
public TimePoint getFrom() {
return from;
}
public TimePoint getTo() {
return to;
public TimePoint getToInclusive() {
return toInclusive;
}
@Override
public String getShortInfo() {
return "device: " + device + ", mapped to: " + mappedTo + ", from: " + from + ", to: " + to;
return "device: " + device + ", mapped to: " + mappedTo + ", from: " + from + ", to: " + toInclusive;
}
}
@@ -13,18 +13,18 @@ public class RegattaLogCloseOpenEndedDeviceMappingEventImpl extends AbstractLogE
private static final long serialVersionUID = -5114645637316367845L;
private final Serializable deviceMappingEventId;
private final TimePoint closingTimePoint;
private final TimePoint closingTimePointInclusive;
public RegattaLogCloseOpenEndedDeviceMappingEventImpl(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint,
Serializable pId, Serializable deviceMappingEventId, TimePoint closingTimePoint) {
Serializable pId, Serializable deviceMappingEventId, TimePoint closingTimePointInclusive) {
super(createdAt, logicalTimePoint, author, pId);
this.deviceMappingEventId = deviceMappingEventId;
this.closingTimePoint = closingTimePoint;
this.closingTimePointInclusive = closingTimePointInclusive;
}
public RegattaLogCloseOpenEndedDeviceMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author,
Serializable deviceMappingEventId, TimePoint closingTimePoint) {
this( now(), author, logicalTimePoint, randId(), deviceMappingEventId, closingTimePoint);
Serializable deviceMappingEventId, TimePoint closingTimePointInclusive) {
this(now(), author, logicalTimePoint, randId(), deviceMappingEventId, closingTimePointInclusive);
}
@Override
@@ -33,13 +33,13 @@ public class RegattaLogCloseOpenEndedDeviceMappingEventImpl extends AbstractLogE
}
@Override
public TimePoint getClosingTimePoint() {
return closingTimePoint;
public TimePoint getClosingTimePointInclusive() {
return closingTimePointInclusive;
}
@Override
public String getShortInfo() {
return "closing mapping (id: " + deviceMappingEventId + ") at " + closingTimePoint;
return "closing mapping (id: " + deviceMappingEventId + ") at " + closingTimePointInclusive;
}
@Override
@@ -15,13 +15,13 @@ public class RegattaLogDeviceCompetitorMappingEventImpl extends RegattaLogDevice
RegattaLogDeviceCompetitorMappingEvent {
public RegattaLogDeviceCompetitorMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint,
AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device,
TimePoint from, TimePoint to) {
super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to);
TimePoint from, TimePoint toInclusive) {
super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, toInclusive);
}
public RegattaLogDeviceCompetitorMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author,
Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) {
super(logicalTimePoint, author, mappedTo, device, from, to);
Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint toInclusive) {
super(logicalTimePoint, author, mappedTo, device, from, toInclusive);
}
private static final long serialVersionUID = -1494030544804758753L;
@@ -17,13 +17,13 @@ public class RegattaLogDeviceMarkMappingEventImpl extends RegattaLogDeviceMappin
public RegattaLogDeviceMarkMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint,
AbstractLogEventAuthor author, Serializable pId, Mark mappedTo, DeviceIdentifier device, TimePoint from,
TimePoint to) {
super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to);
TimePoint toInclusive) {
super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, toInclusive);
}
public RegattaLogDeviceMarkMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author,
Mark mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) {
super(logicalTimePoint, author, mappedTo, device, from, to);
Mark mappedTo, DeviceIdentifier device, TimePoint from, TimePoint toInclusive) {
super(logicalTimePoint, author, mappedTo, device, from, toInclusive);
}
@Override
@@ -51,8 +51,8 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
for (final RegattaLogDeviceMappingEvent<ItemT> event : events.get(item)) {
final TimePoint from = event.getFrom();
final RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = closingEvents.get(event.getId());
final TimePoint to = closingEvent != null ? closingEvent.getClosingTimePoint() : event.getTo();
final TimeRange mappingTimeRange = new TimeRangeImpl(from, to);
final TimePoint toInclusive = closingEvent != null ? closingEvent.getClosingTimePointInclusive() : event.getToInclusive();
final TimeRange mappingTimeRange = new TimeRangeImpl(from, toInclusive, /* inclusive */ true);
if (mappingTimeRange.includes(fixTimePoint)) {
if (closingEvent != null) {
log.revokeEvent(closingEvent.getAuthor(), closingEvent,
@@ -65,8 +65,8 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
if (!endOfFirstHalf.before(from)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice()));
}
if (to == null || !to.before(startOfSecondHalf)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, to,
if (toInclusive == null || !toInclusive.before(startOfSecondHalf)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, toInclusive,
event.getDevice()));
}
}
@@ -74,6 +74,6 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
}
protected abstract RegattaLogDeviceMappingEvent<ItemT> createDeviceMappingEvent(ItemT item,
AbstractLogEventAuthor author, TimePoint plus, TimePoint to, DeviceIdentifier deviceId);
AbstractLogEventAuthor author, TimePoint plus, TimePoint toInclusive, DeviceIdentifier deviceId);
}
@@ -25,8 +25,8 @@ public class RegattaLogDeviceCompetitorMappingFinder extends BaseRegattaLogDevic
@Override
protected RegattaLogDeviceCompetitorMappingEvent createDeviceMappingEvent(Competitor item, AbstractLogEventAuthor author,
TimePoint from, TimePoint to, DeviceIdentifier deviceId) {
TimePoint from, TimePoint toInclusive, DeviceIdentifier deviceId) {
final TimePoint now = MillisecondsTimePoint.now();
return new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, to);
return new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, toInclusive);
}
}
@@ -48,8 +48,8 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID>
}
protected DeviceMappingWithRegattaLogEvent<ItemT> createMapping(DeviceIdentifier device, ItemT item,
TimePoint from, TimePoint to, Serializable originalEventId, RegattaLogDeviceMappingEvent<ItemT> event) {
return new DeviceMappingWithRegattaLogEventImpl<ItemT>(item, device, new TimeRangeImpl(from, to),
TimePoint from, TimePoint toInclusive, Serializable originalEventId, RegattaLogDeviceMappingEvent<ItemT> event) {
return new DeviceMappingWithRegattaLogEventImpl<ItemT>(item, device, new TimeRangeImpl(from, toInclusive, /* inclusive */ true),
Collections.singletonList(originalEventId), event);
}
@@ -95,16 +95,16 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID>
List<DeviceMappingWithRegattaLogEvent<ItemT>> result = new ArrayList<>();
for (final RegattaLogDeviceMappingEvent<ItemT> event : events) {
TimePoint from = event.getFrom();
TimePoint to = event.getTo();
TimePoint closingTimePoint = closingEvents.containsKey(event.getId()) ? closingEvents.get(event.getId())
.getClosingTimePoint() : null;
TimePoint toInclusive = event.getToInclusive();
TimePoint closingTimePointInclusive = closingEvents.containsKey(event.getId()) ? closingEvents.get(event.getId())
.getClosingTimePointInclusive() : null;
if (from == null) {
logger.severe("No start time set for DeviceMappingEvent with ID: " + event.getId());
}
if (to == null) {
to = closingTimePoint;
if (toInclusive == null) {
toInclusive = closingTimePointInclusive;
}
result.add(createMapping(event.getDevice(), item, from, to, event.getId(), event));
result.add(createMapping(event.getDevice(), item, from, toInclusive, event.getId(), event));
}
return result;
}
@@ -24,20 +24,20 @@ public class RegattaLogOpenEndedDeviceMappingCloser extends
RegattaLogAnalyzer<List<RegattaLogCloseOpenEndedDeviceMappingEvent>> {
private final DeviceMapping<?> mapping;
protected final AbstractLogEventAuthor author;
protected final TimePoint closingTimePoint;
protected final TimePoint closingTimePointInclusive;
public RegattaLogOpenEndedDeviceMappingCloser(RegattaLog log, DeviceMapping<?> mapping,
AbstractLogEventAuthor author, TimePoint closingTimePoint) {
AbstractLogEventAuthor author, TimePoint closingTimePointInclusive) {
super(log);
this.mapping = mapping;
this.author = author;
this.closingTimePoint = closingTimePoint;
this.closingTimePointInclusive = closingTimePointInclusive;
}
protected RegattaLogCloseOpenEndedDeviceMappingEvent createCloseEvent(TimePoint logicalTimePoint,
Serializable eventToCloseId) {
return new RegattaLogCloseOpenEndedDeviceMappingEventImpl(logicalTimePoint, author, eventToCloseId,
closingTimePoint);
closingTimePointInclusive);
}
@Override
@@ -46,7 +46,7 @@ public class RegattaLogOpenEndedDeviceMappingCloser extends
for (Serializable eventId : mapping.getOriginalRaceLogEventIds()) {
RegattaLogDeviceMappingEvent<?> event = (RegattaLogDeviceMappingEvent<?>) getLog().getEventById(eventId);
if (event.getFrom() == null || event.getTo() == null) {
if (event.getFrom() == null || event.getToInclusive() == null) {
result.add(createCloseEvent(MillisecondsTimePoint.now(), event.getId()));
}
}
@@ -66,7 +66,8 @@ public class DeviceMappingImpl<ItemType extends WithID> implements DeviceMapping
}
public static <T extends WithID> DeviceMapping<T> convertToDeviceMapping(RegattaLogDeviceMappingEvent<T> event) {
return new DeviceMappingImpl<T>(event.getMappedTo(), event.getDevice(), new TimeRangeImpl(event.getFrom(), event.getTo()),
return new DeviceMappingImpl<T>(event.getMappedTo(), event.getDevice(),
new TimeRangeImpl(event.getFrom(), event.getToInclusive(), /* inclusive */ true),
event.getId(), event.getClass());
}
@@ -58,7 +58,7 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore,
RaceLogResolver raceLogResolver) throws Exception {
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception {
return swissTimingFactory.createRaceTracker(raceID, raceName, raceDescription, boatClass, hostname, port,
startList, delayToLiveInMillis, raceLogStore, regattaLogStore, windStore,
useInternalMarkPassingAlgorithm,
@@ -67,7 +67,7 @@ public class SwissTimingTrackingConnectivityParameters extends AbstractRaceTrack
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
WindStore windStore, RaceLogResolver raceLogResolver) throws Exception {
WindStore windStore, RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception {
return swissTimingFactory.createRaceTracker(regatta, raceID, raceName, raceDescription, boatClass, hostname,
port, startList, delayToLiveInMillis, windStore, useInternalMarkPassingAlgorithm,
domainFactory, trackedRegattaRegistry, raceLogResolver, raceLogStore, regattaLogStore, this);
@@ -132,7 +132,7 @@ public class SwissTimingReplayConnectivityParameters extends AbstractRaceTrackin
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, final WindStore windStore,
RaceLogResolver raceLogResolver) throws Exception {
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception {
SwissTimingReplayToDomainAdapter listener = new SwissTimingReplayToDomainAdapter(/* regatta */ null, raceName,
raceID, domainFactory.getBaseDomainFactory().getOrCreateBoatClass(boatClassName), domainFactory,
trackedRegattaRegistry, useInternalMarkPassingAlgorithm, raceLogResolver, raceLogStore,
@@ -143,7 +143,7 @@ public class SwissTimingReplayConnectivityParameters extends AbstractRaceTrackin
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
WindStore windStore, RaceLogResolver raceLogResolver) throws Exception {
WindStore windStore, RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception {
SwissTimingReplayToDomainAdapter listener = new SwissTimingReplayToDomainAdapter(regatta, raceName,
raceID, domainFactory.getBaseDomainFactory().getOrCreateBoatClass(boatClassName),
domainFactory, trackedRegattaRegistry, useInternalMarkPassingAlgorithm, raceLogResolver,
@@ -154,6 +154,6 @@ public class SwissTimingReplayServiceImpl implements SwissTimingReplayService {
boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore)
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception {
trackerManager.addRace(regattaToAddTo, new SwissTimingReplayConnectivityParameters(link, raceName, raceID,
boatClassName, useInternalMarkPassingAlgorithm, domainFactory, this, raceLogStore, regattaLogStore), /* timeout */ 0);
boatClassName, useInternalMarkPassingAlgorithm, domainFactory, this, raceLogStore, regattaLogStore), /* timeout */ -1);
}
}
@@ -0,0 +1,107 @@
package com.sap.sailing.domain.common.test;
import static com.sap.sailing.domain.common.test.TimeTestHelpers.create;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.sap.sse.common.MultiTimeRange;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MultiTimeRangeImpl;
public class MultiTimeRangeTest {
private MultiTimeRange createMulti(long... startAndEndSequence) {
assert startAndEndSequence != null;
assert startAndEndSequence.length % 2 == 0;
List<TimeRange> list = new ArrayList<>();
for (int i=0; i<startAndEndSequence.length; i+=2) {
list.add(create(startAndEndSequence[i], startAndEndSequence[i+1]));
}
return new MultiTimeRangeImpl(list);
}
@Test
public void testEmptyMultiTimeRange() {
assertTrue(createMulti().isEmpty());
assertTrue(createMulti(100, 200).intersection(create(200, 300)).isEmpty());
assertTrue(createMulti(100, 100).isEmpty());
}
@Test
public void equality() {
assertEquals(createMulti(300, 400, 400, 500, 200, 350), createMulti(400, 500, 300, 400, 200, 350));
assertEquals(createMulti(300, 400, 400, 500, 200, 350), createMulti(200, 500));
assertFalse(createMulti(300, 400, 400, 500, 200, 350).equals(createMulti(300, 600)));
assertFalse(createMulti(300, 400).equals(createMulti(70, 500)));
assertEquals(createMulti(500, 600, 300, 400), createMulti(300, 400, 500, 600, 300, 400));
assertEquals(createMulti(500, 600, 300, 400), createMulti(300, 400, 500, 600));
}
@Test
public void testAdd() {
MultiTimeRange mtr = createMulti(100, 200);
MultiTimeRange multi = mtr.union(createMulti(300, 400));
assertEquals(2, Util.size(multi));
assertEquals(mtr.iterator().next(), multi.iterator().next());
assertTrue(multi.includes(create(150)));
assertTrue(multi.includes(create(350)));
assertFalse(multi.includes(create(250)));
assertFalse(multi.includes(create(200))); // exclusive first time range
assertFalse(multi.includes(create(400))); // exclusive last time range
}
@Test
public void testMinimality() {
assertEquals(1, Util.size(createMulti(300, 400, 400, 500, 200, 350)));
assertEquals(create(200), Util.first(createMulti(300, 400, 400, 500, 200, 350)).from());
assertEquals(create(500), Util.last(createMulti(300, 400, 400, 500, 200, 350)).to());
assertEquals(2, Util.size(createMulti(500, 600, 300, 400)));
assertEquals(create(300), Util.first(createMulti(500, 600, 300, 400)).from());
assertEquals(create(600), Util.last(createMulti(500, 600, 300, 400)).to());
assertTrue(createMulti(500, 600, 300, 400).includes(create(350)));
assertTrue(createMulti(500, 600, 300, 400).includes(create(550)));
assertFalse(createMulti(500, 600, 300, 400).includes(create(450)));
assertFalse(createMulti(500, 600, 300, 400).includes(create(400)));
assertFalse(createMulti(500, 600, 300, 400).includes(create(600)));
}
@Test
public void intersection() {
assertEquals(createMulti(100, 200, 300, 400), createMulti(100, 200, 300, 400).intersection(createMulti(100, 400)));
assertEquals(createMulti(100, 400), createMulti(100, 350, 300, 400).intersection(createMulti(100, 400)));
assertTrue(createMulti(100, 200).intersection(createMulti()).isEmpty());
assertTrue(createMulti(100, 200).intersection(createMulti(300, 400)).isEmpty());
assertTrue(createMulti(100, 200).intersection(createMulti(200, 400)).isEmpty());
assertEquals(createMulti(100, 101), createMulti(100, 200).intersection(createMulti(50, 101)));
assertEquals(createMulti(120, 200, 300, 380), createMulti(100, 380).intersection(createMulti(120, 200, 300, 400)));
assertEquals(createMulti(100, 150, 160, 180, 190, 200, 300, 350, 370, 400), createMulti(100, 200, 300, 400).intersection(createMulti(50, 150, 160, 180, 190, 220, 280, 350, 370, 410)));
}
@Test
public void intersects() {
assertTrue(createMulti(100, 200, 300, 400).intersects(createMulti(50, 150, 160, 180, 190, 220, 280, 350, 370, 410)));
assertFalse(createMulti(100, 200).intersects(createMulti(220, 300)));
assertFalse(createMulti(100, 200).intersects(createMulti(200, 300)));
assertTrue(createMulti(100, 200, 300, 400).intersects(createMulti(150, 350)));
assertTrue(createMulti(100, 200, 300, 400).intersects(createMulti(350, 370)));
assertFalse(createMulti(100, 200, 300, 400).intersects(createMulti(250, 270)));
}
@Test
public void subtract() {
assertEquals(createMulti(100, 120, 180, 200), createMulti(100, 200).subtract(createMulti(120, 180)));
assertTrue(createMulti(100, 200).subtract(createMulti(100, 200)).isEmpty());
assertEquals(createMulti(100, 120), createMulti(100, 200).subtract(createMulti(120, 200)));
assertEquals(createMulti(100, 120, 300, 400), createMulti(100, 200, 300, 400).subtract(createMulti(120, 250)));
assertEquals(createMulti(100, 120, 180, 200, 300, 400), createMulti(100, 200, 300, 400).subtract(createMulti(120, 180)));
assertEquals(createMulti(100, 120, 300, 400), createMulti(100, 200, 300, 400).subtract(createMulti(120, 300)));
assertEquals(createMulti(100, 120, 350, 400), createMulti(100, 200, 300, 400).subtract(createMulti(120, 350)));
assertEquals(createMulti(100, 120, 350, 360, 370, 400), createMulti(100, 200, 300, 400).subtract(createMulti(120, 350, 360, 370)));
}
}
@@ -11,6 +11,7 @@ import java.util.GregorianCalendar;
import org.junit.Test;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -39,12 +40,34 @@ public class TimePointTest {
}
@Test
public void testEqualForEuqal() {
public void testEqualForEqual() {
TimePoint t1 = new MillisecondsTimePoint(Long.MIN_VALUE);
TimePoint t2 = new MillisecondsTimePoint(Long.MIN_VALUE);
assertThat(t1, is(t2));
assertThat(t2, is(t1));
}
@Test
public void testAvoidOverflowBeyondEndOfTime() {
TimePoint t1 = TimePoint.EndOfTime;
assertEquals(TimePoint.EndOfTime, t1.plus(1));
assertEquals(TimePoint.EndOfTime, t1.plus(100));
assertEquals(TimePoint.EndOfTime, t1.plus(Duration.ONE_MINUTE));
TimePoint t2 = TimePoint.EndOfTime.minus(100);
assertEquals(TimePoint.EndOfTime, t2.plus(200));
assertEquals(TimePoint.EndOfTime, t2.plus(Duration.ONE_HOUR));
}
@Test
public void testAvoidUnderflowBeyondBeginningOfTime() {
TimePoint t1 = TimePoint.BeginningOfTime;
assertEquals(TimePoint.BeginningOfTime, t1.minus(1));
assertEquals(TimePoint.BeginningOfTime, t1.minus(100));
assertEquals(TimePoint.BeginningOfTime, t1.minus(Duration.ONE_MINUTE));
TimePoint t2 = TimePoint.BeginningOfTime.plus(100);
assertEquals(TimePoint.BeginningOfTime, t2.minus(200));
assertEquals(TimePoint.BeginningOfTime, t2.minus(Duration.ONE_HOUR));
}
@Test
public void testEqualForNull() {
@@ -1,27 +1,23 @@
package com.sap.sailing.domain.common.test;
import static com.sap.sailing.domain.common.test.TimeTestHelpers.create;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import junit.framework.Assert;
import org.junit.Test;
import com.sap.sse.common.MultiTimeRange;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
import junit.framework.Assert;
public class TimeRangeTest {
protected TimeRange create(long from, long to) {
return new TimeRangeImpl(new MillisecondsTimePoint(from), new MillisecondsTimePoint(to));
}
protected TimePoint create(long millis) {
return new MillisecondsTimePoint(millis);
}
private void liesWithin(long fromOuter, long toOuter, long fromInner, long toInner) {
TimeRange outer = create(fromOuter, toOuter);
TimeRange inner = create(fromInner, toInner);
@@ -44,10 +40,18 @@ public class TimeRangeTest {
TimeRange range = create(0, 100);
Assert.assertTrue(range.includes(create(0)));
Assert.assertTrue(range.includes(create(1)));
Assert.assertTrue(range.includes(create(100)));
Assert.assertTrue(range.includes(create(99)));
Assert.assertFalse(range.includes(create(100))); // exclusive end
Assert.assertFalse(range.includes(create(101)));
}
@Test
public void empty() {
assertTrue(create(100, 100).isEmpty());
assertFalse(create(100, 101).isEmpty());
assertFalse(create(null, 100l).isEmpty());
assertFalse(create(100l, null).isEmpty());
}
@Test
public void intersects() {
TimeRange one = create(0, 50);
@@ -84,6 +88,11 @@ public class TimeRangeTest {
two = create(11, 16);
assertNull(one.union(two));
assertEquals(create(5, 20), one.union(create(10, 20)));
assertEquals(create(5, 10), create(5, 10).union(create(5, 10)));
assertEquals(create(5, 10), create(5, 10).union(create(6, 7)));
assertEquals(create(5, 10), create(5, 10).union(create(6, 6)));
two = create(7, TimePoint.EndOfTime.asMillis());
union = two.union(one);
TimeRange union2 = one.union(two);
@@ -122,6 +131,26 @@ public class TimeRangeTest {
assertEquals(10, intersection.to().asMillis());
}
@Test
public void startAfterTest() {
assertTrue(create(100, 200).startsAtOrAfter(create(100)));
assertTrue(create(100, 200).startsAtOrAfter(create(99)));
assertFalse(create(100, 200).startsAtOrAfter(create(101)));
assertTrue(create(100, 200).startsAfter(create(50, 100)));
assertFalse(create(100, 200).startsAfter(create(50, 150)));
assertFalse(create(100, 200).startsAfter(create(50l, null))); // can't start after a range that extends until the end of time
}
@Test
public void touches() {
assertTrue(create(100, 200).touches(create(100, 200))); // touches is reflexive
assertTrue(create(100, 200).touches(create(50, 150)));
assertTrue(create(100, 200).touches(create(50, 100)));
assertTrue(create(100, 200).touches(create(200, 350)));
assertFalse(create(100, 200).touches(create(201, 250)));
assertFalse(create(100, 200).touches(create(50, 99)));
}
@Test
public void timeDifferenceTest() {
TimeRange one = create(5, 10);
@@ -138,17 +167,17 @@ public class TimeRangeTest {
@Test
public void testSubtractionWithMinuendCompletelyWithin() {
testSubtraction(5, 10, 7, 8, /* expected */ 5, 6, 9, 10);
testSubtraction(5, 10, 7, 8, /* expected */ 5, 7, 8, 10);
}
@Test
public void testSubtractionWithMinuendStartingAtFrom() {
testSubtraction(5, 10, 5, 8, /* expected */ 9, 10);
testSubtraction(5, 10, 5, 8, /* expected */ 8, 10);
}
@Test
public void testSubtractionWithMinuendEndingAtTo() {
testSubtraction(5, 10, 7, 10, /* expected */ 5, 6);
testSubtraction(5, 10, 7, 10, /* expected */ 5, 7);
}
@Test
@@ -158,24 +187,46 @@ public class TimeRangeTest {
@Test
public void testSubtractionWithMinuendOverlappingAtFrom() {
testSubtraction(5, 10, 4, 7, /* expected */ 8, 10);
testSubtraction(5, 10, 4, 7, /* expected */ 7, 10);
}
@Test
public void testSubtractionWithMinuendOverlappingAtto() {
testSubtraction(5, 10, 7, 12, /* expected */ 5, 6);
testSubtraction(5, 10, 7, 12, /* expected */ 5, 7);
}
@Test
public void testExclusiveEnd() {
assertFalse(create(100, 200).includes(create(200)));
assertTrue(create(100, 200).includes(create(199)));
}
@Test
public void testEndsBeforeWithExclusiveEnd() {
assertTrue(create(100, 200).endsBefore(create(200)));
assertTrue(create(100, 200).endsBefore(create(201)));
assertFalse(create(100, 200).endsBefore(create(199)));
}
@Test
public void testEndsAfterWithExclusiveEnd() {
assertFalse(create(100, 200).endsAfter(create(100, 200)));
assertFalse(create(100, 200).endsAfter(create(100, 201)));
assertTrue(create(100, 200).endsAfter(create(100, 199)));
}
private void testSubtraction(long allFrom, long allTo, long minuendFrom, long minuendTo, long... expected) {
TimeRange all = create(allFrom, allTo);
TimeRange minuend = create(minuendFrom, minuendTo);
TimeRange[] diff = all.subtract(minuend);
assertEquals("Expected to obtain "+expected.length/2+" time ranges but got "+diff.length, expected.length/2, diff.length);
for (int i=0; i<expected.length/2; i++) {
assertEquals("expected "+i+"-th time range's \"from\" time point with millis "+expected[2*i]+" but got "+diff[i].from().asMillis(),
create(expected[2*i]), diff[i].from());
assertEquals("expected "+i+"-th time range's \"to\" time point with millis "+expected[2*i+1]+" but got "+diff[i].to().asMillis(),
create(expected[2*i+1]), diff[i].to());
MultiTimeRange diff = all.subtract(minuend);
assertEquals("Expected to obtain "+expected.length/2+" time ranges but got "+Util.size(diff), expected.length/2, Util.size(diff));
int i=0;
for (final TimeRange timeRangeFromDiff : diff) {
assertEquals("expected "+i+"-th time range's \"from\" time point with millis "+expected[2*i]+" but got "+timeRangeFromDiff.from().asMillis(),
create(expected[2*i]), timeRangeFromDiff.from());
assertEquals("expected "+i+"-th time range's \"to\" time point with millis "+expected[2*i+1]+" but got "+timeRangeFromDiff.to().asMillis(),
create(expected[2*i+1]), timeRangeFromDiff.to());
i++;
}
}
}
@@ -0,0 +1,30 @@
package com.sap.sailing.domain.common.test;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
public class TimeTestHelpers {
protected static TimeRange create(Long from, Long toExclusive) {
return new TimeRangeImpl(from==null?null:new MillisecondsTimePoint(from), toExclusive==null?null:new MillisecondsTimePoint(toExclusive));
}
protected static TimeRange create(int from, int toExclusive) {
return create((long) from, (long) toExclusive);
}
protected static TimeRange create(int from, long toExclusive) {
return create((long) from, toExclusive);
}
protected static TimeRange create(long from, int toExclusive) {
return create(from, (long) toExclusive);
}
protected static TimePoint create(long millis) {
return new MillisecondsTimePoint(millis);
}
}
@@ -5,12 +5,18 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
public class UtilTest {
private List<Integer> l;
@@ -42,4 +48,21 @@ public class UtilTest {
assertEquals(0, Util.indexOf(l, 1));
assertEquals(2, Util.indexOf(l, null));
}
@Test
public void testGroup() {
Pair<String, String> a1 = new Pair<>("a", "a");
Pair<String, String> a2 = new Pair<>("a", "b");
Pair<String, String> a3 = new Pair<>("a", "c");
Pair<String, String> b1 = new Pair<>("b", "a");
Pair<String, String> b2 = new Pair<>("b", "b");
Pair<String, String> c1 = new Pair<>("c", "a");
Map<String, Iterable<Pair<String, String>>> expected = new HashMap<>();
expected.put("a", new HashSet<Pair<String, String>>(Arrays.asList(a1, a2, a3)));
expected.put("b", new HashSet<Pair<String, String>>(Arrays.asList(b1, b2)));
expected.put("c", new HashSet<Pair<String, String>>(Collections.singleton(c1)));
assertEquals(expected, Util.group(Arrays.asList(a1, b1, a2, c1, b2, a3), Pair::getA, HashSet::new));
}
}
@@ -41,7 +41,7 @@ public abstract class AbstractManeuverDetectionTestCase extends OnlineTracTracBa
* @param toleranceInMillis
* The tolerance of time, the maneuver should have happened in milliseconds.
*/
protected void assertManeuver(List<Maneuver> maneuverList, ManeuverType maneuverType,
protected void assertManeuver(Iterable<Maneuver> maneuverList, ManeuverType maneuverType,
TimePoint maneuverTimePoint, int toleranceInMillis) {
for (Maneuver maneuver : maneuverList) {
assertNotNull(maneuver.getTimePoint());
@@ -40,6 +40,7 @@ import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -94,7 +95,7 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
},
/* delayToLiveInMillis */0l, /* millisecondsOverWhichToAverageWind */30000, /* simulator */null, /* courseDesignUpdateURI */
null, /* tracTracUsername */null, /* tracTracPassword */null, getEventSubscriber(), getRaceSubscriber(),
/*ignoreTracTracMarkPassings*/false, mock(RaceLogResolver.class)) {
/*ignoreTracTracMarkPassings*/false, mock(RaceLogResolver.class), RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS) {
@Override
protected void handleEvent(Triple<IControlRoute, Long, Void> event) {
super.handleEvent(event);
@@ -27,6 +27,7 @@ import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -62,9 +63,10 @@ public class ManeuverAnalysis505Test extends AbstractManeuverDetectionTestCase {
Date toDate = dateFormat.parse("06/23/2011-15:29:50");
assertNotNull(fromDate);
assertNotNull(toDate);
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
Iterable<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
new MillisecondsTimePoint(toDate), /* waitForLatest */ true);
maneuversInvalid = new ArrayList<Maneuver>(maneuvers);
maneuversInvalid = new ArrayList<Maneuver>();
Util.addAll(maneuvers, maneuversInvalid);
assertManeuver(maneuvers, ManeuverType.TACK,
new MillisecondsTimePoint(dateFormat.parse("06/23/2011-15:28:24")), TACK_TOLERANCE);
@@ -85,9 +87,10 @@ public class ManeuverAnalysis505Test extends AbstractManeuverDetectionTestCase {
Date toDate = dateFormat.parse("06/23/2011-16:38:01");
assertNotNull(fromDate);
assertNotNull(toDate);
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
Iterable<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
new MillisecondsTimePoint(toDate), /* waitForLatest */ true);
maneuversInvalid = new ArrayList<Maneuver>(maneuvers);
maneuversInvalid = new ArrayList<Maneuver>();
Util.addAll(maneuvers, maneuversInvalid);
assertManeuver(maneuvers, ManeuverType.TACK,
new MillisecondsTimePoint(dateFormat.parse("06/23/2011-15:28:24")), TACK_TOLERANCE);
@@ -38,6 +38,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.impl.TrackedLegImpl;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -100,9 +101,10 @@ public class ManeuverAnalysisIDMChampionsFinalTest extends AbstractManeuverDetec
Date fromDate = new Date(toDate.getTime()-450000l);
final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
TimePoint maneuverTime = new MillisecondsTimePoint(dateFormatter.parse("2011-10-03T15:52:30.000+0200"));
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
Iterable<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
new MillisecondsTimePoint(toDate), /* waitForLatest */ true);
maneuversInvalid = new ArrayList<Maneuver>(maneuvers);
maneuversInvalid = new ArrayList<Maneuver>();
Util.addAll(maneuvers, maneuversInvalid);
assertManeuver(maneuvers, ManeuverType.PENALTY_CIRCLE, maneuverTime, PENALTYCIRCLE_TOLERANCE);
List<ManeuverType> maneuverTypesFound = new ArrayList<ManeuverType>();
maneuverTypesFound.add(ManeuverType.PENALTY_CIRCLE);
@@ -12,7 +12,6 @@ import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -27,6 +26,7 @@ import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -68,9 +68,10 @@ public class ManeuverAnalysisQingdao2014Test extends AbstractManeuverDetectionTe
Date toDate = dateFormat.parse("05/01/2014-09:10:00");
assertNotNull(fromDate);
assertNotNull(toDate);
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
Iterable<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
new MillisecondsTimePoint(toDate), /* waitForLatest */ true);
maneuversInvalid = new ArrayList<Maneuver>(maneuvers);
maneuversInvalid = new ArrayList<Maneuver>();
Util.addAll(maneuvers, maneuversInvalid);
assertManeuver(maneuvers, ManeuverType.JIBE, new MillisecondsTimePoint(dateFormat.parse("05/01/2014-09:08:15")), 5000);
assertManeuver(maneuvers, ManeuverType.TACK, new MillisecondsTimePoint(dateFormat.parse("05/01/2014-09:08:51")), 5000);
@@ -87,7 +87,7 @@ public class ManeuverDetectionOnKielerWoche505Race2DataTest extends OnlineTracTr
public void testManeuversForHasso() throws NoWindException {
Competitor hasso = getCompetitorByName("Dr.Plattner");
NavigableSet<MarkPassing> hassosMarkPassings = getTrackedRace().getMarkPassings(hasso);
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(hasso, hassosMarkPassings.first().getTimePoint(),
Iterable<Maneuver> maneuvers = getTrackedRace().getManeuvers(hasso, hassosMarkPassings.first().getTimePoint(),
hassosMarkPassings.last().getTimePoint(), /* waitForLatest */ true);
Calendar c = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin"));
c.clear();
@@ -105,7 +105,7 @@ public class ManeuverDetectionOnKielerWoche505Race2DataTest extends OnlineTracTr
assertManeuver(maneuvers, ManeuverType.JIBE, Tack.PORT, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
}
private void assertManeuver(List<Maneuver> maneuvers, ManeuverType type, Tack newTack,
private void assertManeuver(Iterable<Maneuver> maneuvers, ManeuverType type, Tack newTack,
MillisecondsTimePoint timePoint, int toleranceInMilliseconds) {
for (Maneuver maneuver : maneuvers) {
if (maneuver.getType() == type && (newTack == null || newTack == maneuver.getNewTack()) &&
@@ -9,7 +9,6 @@ import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TimeZone;
@@ -77,7 +76,7 @@ public class ManeuverDetectionOnMdM2011SemifinalTest extends OnlineTracTracBased
public void testManeuversForDennis() throws NoWindException {
Competitor dennis = getCompetitorByName("Gehrlein");
NavigableSet<MarkPassing> dennisMarkPassings = getTrackedRace().getMarkPassings(dennis);
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(dennis, dennisMarkPassings.first().getTimePoint(),
Iterable<Maneuver> maneuvers = getTrackedRace().getManeuvers(dennis, dennisMarkPassings.first().getTimePoint(),
dennisMarkPassings.last().getTimePoint(), /* waitForLatest */ true);
Calendar c = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin"));
c.set(2011, 10-1, 30, 13, 32, 42);
@@ -96,7 +95,7 @@ public class ManeuverDetectionOnMdM2011SemifinalTest extends OnlineTracTracBased
assertManeuver(maneuvers, ManeuverType.JIBE, Tack.STARBOARD, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
}
private void assertManeuver(List<Maneuver> maneuvers, ManeuverType type, Tack newTack,
private void assertManeuver(Iterable<Maneuver> maneuvers, ManeuverType type, Tack newTack,
MillisecondsTimePoint timePoint, int toleranceInMilliseconds) {
for (Maneuver maneuver : maneuvers) {
if (maneuver.getType() == type && (newTack == null || newTack == maneuver.getNewTack()) &&
@@ -4,7 +4,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.mockito.Mockito.mock;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
@@ -18,13 +17,13 @@ import org.junit.Test;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
import com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
import com.sap.sailing.domain.tractracadapter.impl.RaceTrackingConnectivityParametersImpl;
public class MultipleClassesInRegattaTest {
private static final boolean tractracTunnel = Boolean.valueOf(System.getProperty("tractrac.tunnel", "false"));
@@ -41,7 +40,7 @@ public class MultipleClassesInRegattaTest {
}
@Test
public void testLoadTwoRacesWithEqualEventNameButDifferentClasses() throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException {
public void testLoadTwoRacesWithEqualEventNameButDifferentClasses() throws Exception {
String httpAndHost = "http://" + TracTracConnectionConstants.HOST_NAME;
String liveURI = "tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_LIVE;
String storedURI = "tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_STORED;
@@ -54,48 +53,28 @@ public class MultipleClassesInRegattaTest {
}
kiwotest1 = domainFactory
.createRaceTracker(
new URL(
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
new DummyTrackedRegattaRegistry(), mock(RaceLogResolver.class), /* connectivityParams */ null);
EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry(),
mock(RaceLogResolver.class), createConnectivityParams(httpAndHost, liveURI, storedURI, courseDesignUpdateURI,
tracTracUsername, tracTracPassword, "cce678c8-97e6-11e0-9aed-406186cbf87c"), RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS);
kiwotest2 = domainFactory
.createRaceTracker(
new URL(
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
new DummyTrackedRegattaRegistry(), mock(RaceLogResolver.class), /* connectivityParams */ null);
EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry(),
mock(RaceLogResolver.class), createConnectivityParams(httpAndHost, liveURI, storedURI, courseDesignUpdateURI,
tracTracUsername, tracTracPassword, "11290bd6-97e7-11e0-9aed-406186cbf87c"), RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS);
kiwotest3 = domainFactory
.createRaceTracker(
new URL(
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
new DummyTrackedRegattaRegistry(), mock(RaceLogResolver.class), /* connectivityParams */ null);
EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry(),
mock(RaceLogResolver.class), createConnectivityParams(httpAndHost, liveURI, storedURI, courseDesignUpdateURI,
tracTracUsername, tracTracPassword, "39635b24-97e7-11e0-9aed-406186cbf87c"), RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS);
weym470may112014_2 = domainFactory
.createRaceTracker(
new URL(
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
new DummyTrackedRegattaRegistry(), mock(RaceLogResolver.class), /* connectivityParams */ null);
EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry(),
mock(RaceLogResolver.class), createConnectivityParams(httpAndHost, liveURI, storedURI, courseDesignUpdateURI,
tracTracUsername, tracTracPassword, "04498426-7dfd-11e0-8236-406186cbf87c"), RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS);
assertEquals("STG", kiwotest1.getRegatta().getBoatClass().getName());
assertEquals("5O5", kiwotest2.getRegatta().getBoatClass().getName());
@@ -105,6 +84,20 @@ public class MultipleClassesInRegattaTest {
assertNotSame(kiwotest1.getRegatta(), kiwotest3.getRegatta());
assertNotSame(kiwotest2.getRegatta(), kiwotest3.getRegatta());
}
private RaceTrackingConnectivityParametersImpl createConnectivityParams(String httpAndHost, String liveURI,
String storedURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword, String raceId)
throws Exception, MalformedURLException, URISyntaxException {
return new RaceTrackingConnectivityParametersImpl(new URL(
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race="+raceId),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l,
/* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, domainFactory,
tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
/* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ true, /* preferReplayIfAvailable */ false);
}
@After
public void tearDown() throws MalformedURLException, IOException, InterruptedException {
@@ -34,6 +34,7 @@ import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.TrackingDataLoader;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
@@ -123,7 +124,7 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest i
for (Receiver r : domainFactory.getUpdateReceivers(trackedRegatta, getTracTracRace(), EmptyWindStore.INSTANCE, /* delayToLiveInMillis */0l, /* simulator */null, createRaceDefinitionSet(),
/* trackedRegattaRegistry */null,
mock(RaceLogResolver.class), /* courseDesignUpdateURI */null, /* tracTracUsername */null, /* tracTracPassword */
null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false, receiverTypes)) {
null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false, RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, receiverTypes)) {
receivers.add(r);
}
getRaceSubscriber().subscribeConnectionStatus(new IConnectionStatusListener() {
@@ -13,7 +13,6 @@ import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -28,6 +27,7 @@ import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -83,9 +83,10 @@ public class PhilippBuhlsDoublePenaltyCircleAtKielerWoche2014Test extends Abstra
assertNotNull(fromDate);
assertNotNull(toDate);
assertNotNull(competitor);
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
Iterable<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
new MillisecondsTimePoint(toDate), /* waitForLatest */ true);
maneuversInvalid = new ArrayList<Maneuver>(maneuvers);
maneuversInvalid = new ArrayList<Maneuver>();
Util.addAll(maneuvers, maneuversInvalid);
for (Maneuver maneuver : maneuvers) {
if (maneuver.getType() == ManeuverType.PENALTY_CIRCLE) {
assertTrue(Math.abs(maneuver.getDirectionChangeInDegrees()) < 700); // the second penalty has to count for its own
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
@@ -109,8 +110,8 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest {
}
}, /* trackedRegattaRegistry */null,
mock(RaceLogResolver.class), /* courseDesignUpdateURI */null, /* tracTracUsername */null, /* tracTracPassword */
null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false, ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS,
ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false, RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, ReceiverType.RACECOURSE,
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
receivers.add(r);
addReceiverToStopDuringTearDown(r);
}
Loaded 100 of 275 files, more files were not shown because too many files have changed in this diff. Show more