Merge remote-tracking branch 'origin/master' into design2015

This commit is contained in:
Steffen Schaefer committed 2015-06-19 14:38:11 +02:00
commit 4941775fb6
64 files changed
+1076 -360

No files matched your search

@@ -22,7 +22,7 @@
}
.highcharts-container {
position: absulute;
position: absolute;
top: 0pt;
bottom: 0pt;
left: 0pt;
@@ -38,11 +38,10 @@
width: 100%;
bottom: 0pt;
text-align: center;
font-weight: bold;
font-size: 15pt;
line-height: 30pt;
color: black;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
z-index: 2;
}
@@ -134,5 +133,5 @@
font-weight: bold;
font-size: 13px;
color: grey;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
}
@@ -10,10 +10,12 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link href='images/dashboardicon.png' rel='apple-touch-icon'>
<title>ESS Dashboard</title>
<link rel="shortcut icon" type="image/x-icon" href="images/dashboardicon.png" />
<link href='images/dashboardicon.png' rel='apple-touch-icon'>
<link rel="stylesheet" type="text/css" href="/sailing-fontface-1.0.cache.css">
<title>Dashboard</title>
<script type="text/javascript" src="js/jquery-1.5.2.min.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts.js"></script>
@@ -12,6 +12,6 @@ bin.includes = META-INF/,\
js/,\
WEB-INF/lib/gwt-ajaxloader.jar,\
fonts/,\
fontface.css
images/
output.. = WEB-INF/classes/
bin.excludes = WEB-INF/lib/
@@ -1,115 +0,0 @@
/* Generated by Font Squirrel (http://www.fontsquirrel.com) on October 17, 2012 06:01:42 AM America/New_York */
/* @font-face { */
/* font-family: 'OpenSansLightItalic'; */
/* src: url('fonts/OpenSans-LightItalic-webfont.eot'); */
/* src: url('fonts/OpenSans-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-LightItalic-webfont.woff') format('woff'), url('fonts/OpenSans-LightItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-LightItalic-webfont.svg#OpenSansLightItalic') format('svg'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
@font-face {
font-family: 'OpenSansRegular';
src: url('fonts/OpenSans-Regular-webfont.eot');
src: url('fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Regular-webfont.woff') format('woff'), url('fonts/OpenSans-Regular-webfont.ttf') format('truetype'), url('fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg');
font-weight: normal;
font-style: normal;
}
/* @font-face { */
/* font-family: 'OpenSansItalic'; */
/* src: url('fonts/OpenSans-Italic-webfont.eot'); */
/* src: url('fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Italic-webfont.woff') format('woff'), url('fonts/OpenSans-Italic-webfont.ttf') format('truetype'), url('fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
/* @font-face { */
/* font-family: 'OpenSansSemibold'; */
/* src: url('fonts/OpenSans-Semibold-webfont.eot'); */
/* src: url('fonts/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Semibold-webfont.woff') format('woff'), url('fonts/OpenSans-Semibold-webfont.ttf') format('truetype'), url('fonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold') format('svg'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
/* @font-face { */
/* font-family: 'OpenSansSemiboldItalic'; */
/* src: url('fonts/OpenSans-SemiboldItalic-webfont.eot'); */
/* src: url('fonts/OpenSans-SemiboldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-SemiboldItalic-webfont.woff') format('woff'), url('fonts/OpenSans-SemiboldItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-SemiboldItalic-webfont.svg#OpenSansSemiboldItalic') format('svg'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
@font-face {
font-family: 'OpenSansBold';
src: url('fonts/OpenSans-Bold-webfont.eot');
src: url('fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Bold-webfont.woff') format('woff'), url('fonts/OpenSans-Bold-webfont.ttf') format('truetype'), url('fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg');
font-weight: normal;
font-style: normal;
}
/* @font-face { */
/* font-family: 'OpenSansBoldItalic'; */
/* src: url('fonts/OpenSans-BoldItalic-webfont.eot'); */
/* src: url('fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), url('fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
/* @font-face { */
/* font-family: 'OpenSansExtrabold'; */
/* src: url('fonts/OpenSans-ExtraBold-webfont.eot'); */
/* src: url('fonts/OpenSans-ExtraBold-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-ExtraBold-webfont.woff') format('woff'), url('fonts/OpenSans-ExtraBold-webfont.ttf') format('truetype'), url('fonts/OpenSans-ExtraBold-webfont.svg#OpenSansExtrabold') format('svg'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
/* @font-face { */
/* font-family: 'OpenSansExtraboldItalic'; */
/* src: url('fonts/OpenSans-ExtraBoldItalic-webfont.eot'); */
/* src: url('fonts/OpenSans-ExtraBoldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-ExtraBoldItalic-webfont.woff') format('woff'), url('fonts/OpenSans-ExtraBoldItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-ExtraBoldItalic-webfont.svg#OpenSansExtraboldItalic') format('svg'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
/* @font-face { */
/* font-family: 'OpenSansCondensedLight'; */
/* src: url('fonts/OpenSans-CondLight-webfont.eot'); */
/* src: url('fonts/OpenSans-CondLight-webfont.eot?#iefix') format('embedded-opentype'), */
/* url('fonts/OpenSans-CondLight-webfont.woff') format('woff'), */
/* url('fonts/OpenSans-CondLight-webfont.ttf') format('truetype'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
/* @font-face { */
/* font-family: 'OpenSansCondensedLightItalic'; */
/* src: url('fonts/OpenSans-CondLightItalic-webfont.eot'); */
/* src: url('fonts/OpenSans-CondLightItalic-webfont.eot?#iefix') format('embedded-opentype'), */
/* url('fonts/OpenSans-CondLightItalic-webfont.woff') format('woff'), */
/* url('fonts/OpenSans-CondLightItalic-webfont.ttf') format('truetype'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
/* @font-face { */
/* font-family: 'OpenSansCondensedBold'; */
/* src: url('fonts/OpenSans-CondBold-webfont.eot'); */
/* src: url('fonts/OpenSans-CondBold-webfont.eot?#iefix') format('embedded-opentype'), */
/* url('fonts/OpenSans-CondBold-webfont.woff') format('woff'), */
/* url('fonts/OpenSans-CondBold-webfont.ttf') format('truetype'); */
/* font-weight: normal; */
/* font-style: normal; */
/* } */
@@ -1,4 +1,3 @@
@import url(fontface.css);
.bar {
height: 100%;
@@ -6,7 +5,7 @@
line-height: 15pt;
vertical-align: middle;
font-size: 15pt;
font-family: 'OpenSansRegular', Arial, Verdana, sans-serif;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
font-weight: bold;
@@ -67,7 +66,7 @@
line-height: 25pt;
vertical-align: middle;
font-size: 15pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
font-weight: bold;
@@ -165,7 +164,7 @@
margin-top: 0pt;
vertical-align: middle;
font-size: 12pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
}
@@ -178,7 +177,7 @@
margin-top: 0pt;
vertical-align: middle;
font-size: 12pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
}
@@ -312,7 +311,7 @@ windchartcontainer {
margin-top: 0pt;
vertical-align: middle;
font-size: 12pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
top: 50%;
@@ -3,6 +3,7 @@ package com.sap.sailing.dashboards.gwt.client.eventlogo;
import java.util.UUID;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -23,12 +24,17 @@ public class EventLogo extends Image {
sailingServiceAsync.getEventById(eventUUID, true, new AsyncCallback<EventDTO>() {
@Override
public void onSuccess(final EventDTO event) {
final ImageDTO logo = event.getLogoImage();
if (logo != null) {
eventLogo.getElement().setAttribute("src", logo.getSourceRef());
} else {
eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
if (event != null) {
final ImageDTO logo = event.getLogoImage();
if (logo != null) {
eventLogo.getElement().setAttribute("src", logo.getSourceRef());
} else {
eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
if (event.getName() != null) {
Window.setTitle(event.getName() + " Dashboard");
}
}
}
@Override
@@ -61,6 +61,7 @@
padding: 0px;
margin-top: 5pt;
margin-left: 0px;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
}
.popup_button:active {
@@ -76,7 +77,7 @@
font-weight: bold;
font-size: 15px;
color: grey;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
}
.blurred {
@@ -16,7 +16,7 @@
line-height: 30pt;
vertical-align: middle;
font-size: 15pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
font-weight: bold;
@@ -91,7 +91,7 @@
line-height: 25pt;
vertical-align: middle;
font-size: 11pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
text-align: center;
}
@@ -113,7 +113,7 @@
color: white;
line-height: 25pt;
font-size: 11pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
z-index: 1;
}
@@ -141,7 +141,7 @@ public class StartlineAnalysisCard extends Composite implements HasWidgets, Star
zoomTypes.add(ZoomTypes.BUOYS);
} else {
timer.setTime(startAnalysisDTO.timeOfStartInMilliSeconds);
zoomTypes.add(ZoomTypes.TAILS);
zoomTypes.add(ZoomTypes.BOATS);
}
AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingServiceAsync,
@@ -21,7 +21,7 @@
line-height: 15pt;
vertical-align: middle;
font-size: 12.5pt;
font-family: "Arial;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
overflow: visible;
@@ -55,19 +55,27 @@
}
.liveAverageCompoment_panel_value_number {
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-size: 27pt;
}
.liveAverageCompoment_panel_value_unit {
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-size: 12pt;
}
.liveAverageCompoment_panel_value_unit_degrees {
position: absolute;
font-size: 20pt;
font-weight: lighter;
}
.liveAverageCompoment_value_panel {
margin: 0px;
width: 100%;
height: 90%;
text-align: center;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-weight: bolder;
}
@@ -78,7 +86,7 @@
text-align: center;
font-size: 12pt;
color: grey;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
}
.liveAverageCompoment_liveLabelContainer {
@@ -5,9 +5,7 @@ import java.util.Iterator;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
@@ -32,6 +30,11 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti
interface LiveAverageComponentUiBinder extends UiBinder<Widget, LiveAverageComponent> {
}
interface LiveAverageComponentStyle extends CssResource {
String liveAverageCompoment_panel_value_unit_degrees();
}
@UiField
HTMLPanel liveAveragePanel;
@@ -65,6 +68,9 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti
@UiField
HTMLPanel averagePanel;
@UiField
LiveAverageComponentStyle style;
private StringMessages stringConstants;
public LiveAverageComponent() {
@@ -87,12 +93,8 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti
this.averageUnit.setInnerText(unit);
// Lifts the degrees unit string "°". Otherwise it would be too small.
if (unit.equals("°")) {
this.liveUnit.getStyle().setFontSize(15, Unit.PT);
this.averageUnit.getStyle().setFontSize(15, Unit.PT);
this.liveUnit.getStyle().setFontWeight(Style.FontWeight.LIGHTER);
this.averageUnit.getStyle().setFontWeight(Style.FontWeight.LIGHTER);
this.liveUnit.getStyle().setPosition(Position.ABSOLUTE);
this.averageUnit.getStyle().setPosition(Position.ABSOLUTE);
this.liveUnit.addClassName(style.liveAverageCompoment_panel_value_unit_degrees());
this.averageUnit.setClassName(style.liveAverageCompoment_panel_value_unit_degrees());
}
this.liveLabel.setInnerHTML(stringConstants.dashboardLiveWind());
this.averageLabel.setInnerHTML(stringConstants.dashboardAverageWind()+"<br>"+stringConstants.dashboardAverageWindMinutes(15));
@@ -2,7 +2,7 @@
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style src="LiveAverageComponent.css" />
<ui:style type='com.sap.sailing.dashboards.gwt.client.startlineadvantage.LiveAverageComponent.LiveAverageComponentStyle' src='LiveAverageComponent.css'></ui:style>
<g:HTMLPanel ui:field="liveAveragePanel" styleName="{style.liveAverageCompoment_panel}">
@@ -22,14 +22,12 @@ public class StartLineAdvantageComponent extends LiveAverageComponent implements
private StartLineAdvantageComponentState startLineAdvantageComponentState;
private StartLineAdvantageComponentStateShowsAdvantageByWind startLineAdvantageComponentStateShowsAdvantageByWind;
private StartLineAdvantageComponentStateShowsAdvantageByGeometry startLineAdvantageComponentStateShowsAdvantageByGeometry;
private StringMessages stringConstants;
private static StringMessages stringConstants = StringMessages.INSTANCE;
public StartLineAdvantageComponent(RibDashboardDataRetriever ribDashboardDataRetriever) {
super();
stringConstants = StringMessages.INSTANCE;
super(stringConstants.dashboardStartlineAdvantageByWind(), "m");
initAndSetStartLineAdvantageStates();
StartLineAdvantageComponentRessources.INSTANCE.css().ensureInjected();
this.header.setInnerText(stringConstants.dashboardStartlineAdvantageByWind());
this.header.addClassName(StartLineAdvantageComponentRessources.INSTANCE.css()
.startLineAdvantageComponent_header());
liveAveragePanel.getElement().addClassName(
@@ -29,7 +29,7 @@
line-height: 35px;
vertical-align: middle;
font-size: 12px;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
text-align: center;
color: black;
}
@@ -53,7 +53,7 @@
margin-top: 0pt;
vertical-align: middle;
font-size: 12pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
top: 50%;
@@ -12,7 +12,7 @@
line-height: 45pt;
vertical-align: middle;
font-size: 15pt;
font-family: "Arial";
font-family: 'Open Sans', Arial, Verdana, sans-serif;
width: 100%;
text-align: center;
font-weight: bold;
@@ -22,6 +22,7 @@
top: -5pt;
text-align: center;
vertical-align: middle;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-size: 9pt;
font-weight: bold;
color: white;
@@ -34,6 +35,7 @@
bottom: -17pt;
text-align: center;
vertical-align: middle;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
line-height: 10pt;
font-weight: bold;
font-size: 9pt;
@@ -59,7 +59,13 @@ public enum BoatClassMasterdata {
FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL, true),
DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL, true),
INTERNATIONAL_14("International 14", true, 4.27, 1.83, BoatHullType.MONOHULL, true, "I14"),
OPEN_BIC("O'pen BIC", true, 2.75, 1.14, BoatHullType.MONOHULL, false, "OpenBIC");
OPEN_BIC("O'pen BIC", true, 2.75, 1.14, BoatHullType.MONOHULL, false, "OpenBIC"),
// multi-class "boat classes"; to be replaced at some later point in time by something like a RegattaClass
ORC ("ORC", true, 13.83, 3.91, BoatHullType.MONOHULL, true),
ORC_CLUB ("ORC Club", true, 13.83, 3.91, BoatHullType.MONOHULL, true),
ORC_INTERNATIONAL ("ORC International", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "ORC Int.");
private final String displayName;
private final String[] alternativeNames;
@@ -0,0 +1,218 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.junit.Test;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.base.impl.TrackedRaces;
import com.sap.sailing.domain.common.TimingConstants;
import com.sap.sailing.domain.common.Wind;
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.impl.WindImpl;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* Covers the linking and unlinking between {@link RaceColumn}s and {@link TrackedRaces}s
* and tests whether {@link RaceExecutionOrderProvider}s get correctly attached and detached. Also tests
* late attaching and detaching of {@link RaceExecutionOrderProvider}s in case the {@link RaceExecutionOrderProvider}
* was <code>null</code> when {@link RaceColumn} was linked to {@link TrackedRace}.
*
* @author Alexander Ries (D062114)
*
*/
public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest {
private FlexibleLeaderboard flexibleLeaderboard;
private RaceColumnInSeries raceColumnInSeries;
private Fleet fleet;
private TrackedRaceImpl trackedRace;
private Regatta regatta;
private Series series;
private final String REGATTA = "TestRegatta";
private final String RACE = "TestRace";
private final String FLEET = "TestFleet";
private final String BOATCLASS = "TestClass";
private final String SERIES = "TestSeries";
private final String FLEXIBLELEADERBOARD = "TestFlexibleLeaderboard";
private final String RACECOLUMN_SERIES = "TestSeriesRaceColumn";
private final String RACECOLUMN_FLEXIBLELEADERBOARD = "TestFlexibleLeaderboardRaceColumn";
@Test
public void testRaceExecutionOrderProviderAttachDetachWithRaceCollumn() {
trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections.<Competitor> emptyList(),
MillisecondsTimePoint.now());
flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD,
new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null);
flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false);
assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached());
flexibleLeaderboard.removeRaceColumn(RACECOLUMN_FLEXIBLELEADERBOARD);
assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached());
}
@Test
public void testWindInRegularIntervalWithPreviousRaceStillTracking() {
final TimePoint startOfFirstRace = MillisecondsTimePoint.now();
final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_MINUTE.times(5));
DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections.<Competitor> emptyList(), startOfFirstRace);
previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace);
trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections.<Competitor> emptyList(), startOfSecondRace);
flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD,
new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null);
flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false);
flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false);
Wind wind = new WindImpl(new DegreePosition(12, 13), startOfSecondRace.plus(Duration.ONE_MINUTE), new KnotSpeedWithBearingImpl(
/* speedInKnots */18, new DegreeBearingImpl(185)));
assertTrue(previousTrackedRace.takesWindFix(wind)); // previous race has tracking still open and takes the fix
assertTrue(trackedRace.takesWindFix(wind)); // tracked race also needs to take the fix as it falls into the regular tracking interval
}
@Test
public void testWindInExtendedLeadIntervalWithPreviousRaceStillTracking() {
final TimePoint startOfFirstRace = MillisecondsTimePoint.now();
final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND);
final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR);
DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections.<Competitor> emptyList(), startOfFirstRace);
previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace);
previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race...
trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections.<Competitor> emptyList(), startOfSecondRace);
flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD,
new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null);
flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false);
flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false);
// the wind fix is after the grace period of the first race's end, so won't be accepted by it, but within the extended
// time range before the second race, so shall be accepted by it.
Wind wind = new WindImpl(new DegreePosition(12, 13),
endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period
new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185)));
assertFalse(previousTrackedRace.takesWindFix(wind)); // previous race has tracking closed one second after it started and doesn't accept the fix
assertTrue(trackedRace.takesWindFix(wind)); // tracked race also needs to take the fix as it falls into the regular tracking interval
}
@Test
public void testWindInExtendedLeadIntervalWithNoPreviousRace() {
final TimePoint startOfFirstRace = MillisecondsTimePoint.now();
final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND);
final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR);
trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections.<Competitor> emptyList(), startOfSecondRace);
flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD,
new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null);
flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false);
// the wind fix is within the extended time range before the second race, and there is no previous race, so shall be accepted by it
Wind wind = new WindImpl(new DegreePosition(12, 13),
endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period
new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185)));
assertTrue(trackedRace.takesWindFix(wind));
}
@Test
public void testWindInExtendedLeadIntervalWithPreviousRaceLongAgo() {
final TimePoint startOfFirstRace = MillisecondsTimePoint.now();
final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND);
final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.times(2));
DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections.<Competitor> emptyList(), startOfFirstRace);
previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace);
previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race...
trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections.<Competitor> emptyList(), startOfSecondRace);
flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD,
new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null);
flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false);
flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false);
// the wind fix is outside the extended time range before the second race, and the previous race doesn't accept it; shall not be accepted
Wind wind = new WindImpl(new DegreePosition(12, 13),
endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period
new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185)));
assertFalse(previousTrackedRace.takesWindFix(wind)); // fix is after first race's end plus grace period
assertFalse(trackedRace.takesWindFix(wind));
}
@Test
public void testWindInExtendedLeadIntervalButStillRecordedByPreviousRace() {
final TimePoint startOfFirstRace = MillisecondsTimePoint.now();
final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND);
final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2));
DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections.<Competitor> emptyList(), startOfFirstRace);
previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace);
previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race...
trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections.<Competitor> emptyList(), startOfSecondRace);
flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD,
new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null);
flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false);
flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false);
// the wind fix is inside the extended time range before the second race, but the previous race still accepts it; shall not be accepted
Wind wind = new WindImpl(new DegreePosition(12, 13),
startOfFirstRace.plus(Duration.ONE_SECOND), // fix is recorded by previous race
new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185)));
assertTrue(previousTrackedRace.takesWindFix(wind)); // fix is after first race's end plus grace period
assertFalse(trackedRace.takesWindFix(wind));
}
@Test
public void testRaceExecutionOrderProviderAttachDetachWithRaceCollumnInSeries() {
createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */true);
raceColumnInSeries.setTrackedRace(fleet, trackedRace);
assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached());
raceColumnInSeries.releaseTrackedRace(fleet);
assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached());
}
@Test
public void testRaceExecutionOrderProviderAttachDetachWhenSeriesRegattaIsSetAndRemovedAfterTrackedRaceHaveBeenSetToRaceColumns() {
createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false);
raceColumnInSeries.setTrackedRace(fleet, trackedRace);
assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached());
series.setRegatta(regatta);
assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached());
series.setRegatta(null);
assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached());
}
private void createTestSetupWithRegattaAndSeries(boolean linkSeriesToRegatta) {
trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections.<Competitor> emptyList(),
MillisecondsTimePoint.now());
fleet = new FleetImpl(FLEET);
Set<Fleet> fleets = new HashSet<>();
fleets.add(fleet);
series = new SeriesImpl(SERIES, false, fleets, new HashSet<String>(), null);
Set<Series> seriesSet = new HashSet<>();
if (linkSeriesToRegatta) {
seriesSet.add(series);
}
BoatClass boatClass = new BoatClassImpl(BOATCLASS, true);
raceColumnInSeries = series.addRaceColumn(RACECOLUMN_SERIES, null);
ScoringScheme scoringScheme = new LowPoint();
regatta = new RegattaImpl(RegattaImpl.getDefaultName(REGATTA, boatClass.getName()), boatClass,
/* startDate */null, /* endDate */null, seriesSet, false, scoringScheme, UUID.randomUUID(), null,
OneDesignRankingMetric::new);
}
}
@@ -1,74 +0,0 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.sap.sse.common.Util.Triple;
/**
* This test verifies the algorithm used to escape and unescape SimpleRaceIdentifier components as used in
* <code>FleetIfentifierImpl</code>.
*
* @author Axel Uhl (D043530)
*
*/
public class SimpleRaceIdentifierEscapingTest {
@Test
public void testUnescapingSimpleRaceIdentifier() {
test("Leaderboard with trailing backslash and dot\\.", "raceColumn", "fleet");
test("Leaderboard\\.with\\.two dots", "raceColumn", "fleet");
test("Leaderboard with double backslash\\\\", "raceColumn", "fleet");
test("Leaderboard with double backslash and dot\\\\.", "raceColumn", "fleet");
test("\\Leaderboard with leading backslash", "raceColumn", "fleet");
test("\\.Leaderboard with leading backslash and dot", "raceColumn", "fleet");
test("\\\\.Leaderboard with leading double backslash and dot", "raceColumn", "fleet");
test("\\\\.Leaderboard with leading double backslash and two dots", "raceColumn", "fleet");
}
private void test(String leaderboardName, String raceColumnName, String fleetName) {
final String marshalled = build(leaderboardName, raceColumnName, fleetName);
final Triple<String, String, String> parsed = parse(marshalled);
assertEquals(leaderboardName, parsed.getA());
assertEquals(raceColumnName, parsed.getB());
assertEquals(fleetName, parsed.getC());
}
private Triple<String, String, String> parse(String s) {
int arrayIndex = 0;
StringBuilder[] split = new StringBuilder[3];
for (int i=0; i<split.length; i++) {
split[i] = new StringBuilder();
}
boolean escaped = false;
for (int i=0; i<s.length(); i++) {
if (escaped) {
split[arrayIndex].append(s.charAt(i));
escaped = false;
} else if (s.charAt(i) == '\\') {
escaped = true; // next character is escaped
} else if (s.charAt(i) == '.') {
// an unescaped dot
arrayIndex++;
} else {
// unescaped non-escape, non-dot character
split[arrayIndex].append(s.charAt(i));
}
}
String leaderboardName = split[0].toString();
String raceColumnName = split[1].toString();
String fleetName = split[2].toString();
return new Triple<>(leaderboardName, raceColumnName, fleetName);
}
private String build(String leaderboardName, String raceColumnName, String fleetName) {
return String.format("%s.%s.%s",
escape(leaderboardName),
escape(raceColumnName),
escape(fleetName));
}
private String escape(String s) {
return s.replace("\\", "\\\\").replace(".", "\\.");
}
}
@@ -122,7 +122,8 @@ public abstract class TrackBasedTest {
OneDesignRankingMetric::new);
// in this simplified artificial course, the top mark is exactly north of the right leeward gate
DegreePosition topPosition = new DegreePosition(54.48, 10.24);
TimePoint afterTheRace = new MillisecondsTimePoint(timePointForFixes.asMillis() + 36000000); // 10h after the fix time
TimePoint afterTheRace = new MillisecondsTimePoint(timePointForFixes.asMillis() + 36000000); // 10h after the fix timed
trackedRace.setStartOfTrackingReceived(timePointForFixes);
trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(new DegreePosition(54.4680424, 10.234451), new MillisecondsTimePoint(0)));
trackedRace.getOrCreateTrack(right).addGPSFix(new GPSFixImpl(new DegreePosition(54.4680424, 10.24), new MillisecondsTimePoint(0)));
trackedRace.getOrCreateTrack(windwardMark).addGPSFix(new GPSFixImpl(topPosition, new MillisecondsTimePoint(0)));
@@ -59,6 +59,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -442,6 +443,11 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) {
}
@Override
public RaceExecutionOrderProvider getRaceExecutionOrderProvider() {
return null;
}
@Override
public RankingMetricConstructor getRankingMetricConstructor() {
return null;
@@ -783,10 +789,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public void detachAllRaceLogs() {
}
@Override
public void invalidateStartTime() {
}
@@ -898,7 +900,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public boolean isUsingMarkPassingCalculator() {
// TODO Auto-generated method stub
return false;
}
@@ -947,10 +948,17 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException,
NoWindException {
// TODO Auto-generated method stub
return null;
}
@Override
public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
@Override
public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
@Override
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode, RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
@@ -43,6 +43,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
@@ -470,10 +471,6 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return null;
}
@Override
public void detachAllRaceLogs() {
}
@Override
public boolean isLive(TimePoint at) {
return false;
@@ -639,8 +636,14 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
@Override
public Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException,
NoWindException {
// TODO Auto-generated method stub
return null;
}
@Override
public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
@Override
public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
}
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.leaderboard.MetaLeaderboard;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.util.impl.RaceColumnListeners;
import com.sap.sse.common.Named;
@@ -224,4 +225,6 @@ public interface RaceColumn extends Named {
boolean hasSplitFleetContiguousScoring();
boolean hasSplitFleets();
RaceExecutionOrderProvider getRaceExecutionOrderProvider();
}
@@ -20,7 +20,9 @@ public interface RaceColumnListener extends Serializable {
void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring);
boolean canAddRaceColumnToContainer(RaceColumn raceColumn);
default boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return true;
}
void raceColumnAddedToContainer(RaceColumn raceColumn);
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
import com.sap.sailing.domain.ranking.RankingMetricsFactory;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sse.common.Named;
@@ -187,6 +188,8 @@ public interface Regatta extends Named, WithID, IsRegattaLike {
* opposite end of the association needs to be maintained on the event's side.
*/
void adjustEventToRegattaAssociation(EventFetcher eventFetcher);
RaceExecutionOrderProvider getRaceExecutionOrderProvider();
default RankingMetrics getRankingMetricType() {
return RankingMetricsFactory.getForClass(getRankingMetricConstructor().apply(/* trackedRace */ null).getClass());
@@ -74,6 +74,7 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
}
if (trackedRace != null) {
trackedRace.attachRaceLog(getRaceLog(fleet));
trackedRace.attachRaceExecutionProvider(getRaceExecutionOrderProvider());
getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(this, fleet, trackedRace);
}
}
@@ -98,6 +99,7 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
if (previouslyLinkedRace != null && regattaLikeParent != null) {
RaceLogIdentifier identifier = getRaceLogIdentifier(fleet);
previouslyLinkedRace.detachRaceLog(identifier.getIdentifier());
previouslyLinkedRace.detachRaceExecutionOrderProvider(getRaceExecutionOrderProvider());
getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(this, fleet, previouslyLinkedRace);
}
}
@@ -197,5 +199,4 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
public void setMasterDataExportOngoingThreadFlag(boolean flagValue) {
trackedRaces.setMasterDataExportOngoingThreadFlag(flagValue);
}
}
@@ -0,0 +1,106 @@
package com.sap.sailing.domain.base.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
public abstract class AbstractRaceExecutionOrderProvider implements RaceExecutionOrderProvider, RaceColumnListenerWithDefaultAction {
private static final long serialVersionUID = 4795731834688229568L;
private transient SmartFutureCache<String, Map<TrackedRace, Set<TrackedRace>>, EmptyUpdateInterval> previousRacesByRaceCache;
private final String RACES_ORDER_LIST_CACHE_KEY = "racesOrderCacheKey";
private final String RACES_ORDER_LIST_LOCKS_NAME = getClass().getName();
public AbstractRaceExecutionOrderProvider() {
previousRacesByRaceCache = createRacesOrderCache();
triggerUpdate();
}
@Override
public void defaultAction() {
triggerUpdate();
}
@Override
public void triggerUpdate() {
previousRacesByRaceCache.triggerUpdate(RACES_ORDER_LIST_CACHE_KEY, /* update interval */null);
}
private Map<TrackedRace, Set<TrackedRace>> getPreviousRacesByRace() {
return previousRacesByRaceCache.get(RACES_ORDER_LIST_CACHE_KEY, /* waitForLatest */true);
}
protected abstract Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries();
private Map<TrackedRace, Set<TrackedRace>> reloadAndGetPreviousRacesByRace() {
final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = new HashMap<>();
for (Entry<Fleet, Iterable<? extends RaceColumn>> raceColumnsInSeries : getRaceColumnsOfSeries().entrySet()) {
addPreviousRaces(previousRacesByRace, raceColumnsInSeries.getKey(), raceColumnsInSeries.getValue());
}
return previousRacesByRace;
}
private void addPreviousRaces(final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace, Fleet fleet,
final Iterable<? extends RaceColumn> raceColumns) {
TrackedRace previousRace = null;
for (RaceColumn currentRaceColumn : raceColumns) {
final TrackedRace trackedRaceInColumnForFleet = currentRaceColumn.getTrackedRace(fleet);
if (trackedRaceInColumnForFleet != null) {
Set<TrackedRace> previousRaces = previousRacesByRace.get(trackedRaceInColumnForFleet);
if (previousRaces == null) {
previousRaces = new HashSet<>();
previousRacesByRace.put(trackedRaceInColumnForFleet, previousRaces);
}
if (previousRace != null) {
previousRaces.add(previousRace);
}
previousRace = trackedRaceInColumnForFleet;
}
}
}
private SmartFutureCache<String, Map<TrackedRace, Set<TrackedRace>>, EmptyUpdateInterval> createRacesOrderCache() {
return new SmartFutureCache<String, Map<TrackedRace, Set<TrackedRace>>, SmartFutureCache.EmptyUpdateInterval>(
new SmartFutureCache.AbstractCacheUpdater<String, Map<TrackedRace, Set<TrackedRace>>, SmartFutureCache.EmptyUpdateInterval>() {
@Override
public Map<TrackedRace, Set<TrackedRace>> computeCacheUpdate(String key,
EmptyUpdateInterval updateInterval) throws Exception {
if (key.equals(RACES_ORDER_LIST_CACHE_KEY)) {
return reloadAndGetPreviousRacesByRace();
} else {
final Map<TrackedRace, Set<TrackedRace>> emptyMap = Collections.emptyMap();
return emptyMap;
}
}
}, RACES_ORDER_LIST_LOCKS_NAME);
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
this.previousRacesByRaceCache = createRacesOrderCache();
// don't call triggerUpdate() as of now because the cache's owner may not yet be fully initialized,
// so the getRaceColumnsOfSeries() method may not yet be able to do its work. The owner must call
// triggerUpdate when fully initialized.
}
@Override
public Set<TrackedRace> getPreviousRacesInExecutionOrder(TrackedRace race) {
Set<TrackedRace> result = Collections.emptySet();
final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = getPreviousRacesByRace();
if (previousRacesByRace != null) {
result = previousRacesByRace.get(race);
}
return result;
}
}
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -119,6 +120,15 @@ public class RaceColumnInSeriesImpl extends AbstractRaceColumn implements RaceCo
public boolean hasSplitFleetContiguousScoring() {
return getSeries().hasSplitFleetContiguousScoring();
}
@Override
public RaceExecutionOrderProvider getRaceExecutionOrderProvider() {
final RaceExecutionOrderProvider result;
if (getRegatta() != null) {
result = getRegatta().getRaceExecutionOrderProvider();
} else {
result = null;
}
return result;
}
}
@@ -0,0 +1,101 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* Delegates all listener operations to a default action which is implemented here to do nothing. This way, subclasses
* can easily provide a default action to be executed for all callback methods except for maybe a few which then need to
* be explicitly overridden. If not most methods need to perform the same action then instead of implementing a default
* action subclasses should rather override the {@link RaceColumnListener} operations individually.
*
* @author Alexander Ries (D062114)
*
*/
public interface RaceColumnListenerWithDefaultAction extends RaceColumnListener {
/**
* This implementation of the default action to which all other operations delegate does nothing. Subclasses can
* choose to override this method to provide this as the default behavior for all other methods and then still
* override individual {@link RaceColumnListener} methods, or they can leave the default action empty and just
* override {@link RaceColumnListener} methods.
*/
void defaultAction();
@Override
default void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
defaultAction();
}
@Override
default void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
defaultAction();
}
@Override
default void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
defaultAction();
}
@Override
default void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) {
defaultAction();
}
@Override
default void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn,
boolean firstColumnIsNonDiscardableCarryForward) {
defaultAction();
}
@Override
default void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) {
defaultAction();
}
@Override
default void raceColumnAddedToContainer(RaceColumn raceColumn) {
defaultAction();
}
@Override
default void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
defaultAction();
}
@Override
default void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
defaultAction();
}
@Override
default void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
defaultAction();
}
@Override
default void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
defaultAction();
}
@Override
default void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule,
ResultDiscardingRule newDiscardingRule) {
defaultAction();
}
@Override
default void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
defaultAction();
}
@Override
default boolean isTransient() {
return false;
}
}
@@ -2,11 +2,14 @@ package com.sap.sailing.domain.base.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
@@ -46,6 +49,7 @@ import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeListener;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -88,7 +92,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
private CourseArea defaultCourseArea;
private RegattaConfiguration configuration;
private RaceExecutionOrderCache raceExecutionOrderCache;
/**
* Regattas may be constructed as implicit default regattas in which case they won't need to be stored
* durably and don't contain valuable information worth being preserved; or they are constructed explicitly
@@ -192,6 +197,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
this.defaultCourseArea = courseArea;
this.configuration = null;
this.regattaLikeHelper = new BaseRegattaLikeImpl(new RegattaAsRegattaLikeIdentifier(this), regattaLogStore);
this.raceExecutionOrderCache = new RaceExecutionOrderCache();
}
@Override
@@ -242,6 +248,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
}
}
protected Object readResolve() throws ObjectStreamException {
raceExecutionOrderCache.triggerUpdate(); // now we're fully initialized and the cache can do its job
return this;
}
/**
* {@link RaceColumnListeners} may not be de-serialized (yet) when the regatta
* is de-serialized. Do avoid re-registering empty objects most probably leading
@@ -263,7 +274,13 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
@Override
public Iterable<? extends Series> getSeries() {
return Collections.unmodifiableCollection(series);
final Iterable<? extends Series> result;
if (series != null) {
result = Collections.unmodifiableCollection(series);
} else {
result = null;
}
return result;
}
@Override
@@ -628,4 +645,36 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
}
}
@Override
public RaceExecutionOrderProvider getRaceExecutionOrderProvider() {
return raceExecutionOrderCache;
}
private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider {
private static final long serialVersionUID = 1658153438012186894L;
public RaceExecutionOrderCache() {
super();
addRaceColumnListener(this);
}
@Override
protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() {
final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
final Iterable<? extends Series> mySeries = getSeries();
if (mySeries != null) {
for (Series currentSeries : mySeries) {
if (currentSeries.getFleets() != null) {
for (Fleet fleet : currentSeries.getFleets()) {
if (currentSeries.getRaceColumns() != null) {
result.put(fleet, currentSeries.getRaceColumns());
}
}
}
}
}
return result;
}
}
}
@@ -25,6 +25,11 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.util.impl.RaceColumnListeners;
import com.sap.sse.common.Util;
/**
* A series listens on its columns; however, a veto for column addition isn't done here but in a {@link RegattaLeaderboard}.
*
* @see #addRaceColumn(String, TrackedRegattaRegistry)
*/
public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListener {
private static final long serialVersionUID = -1640404303144907381L;
private final Map<String, Fleet> fleetsByName;
@@ -79,8 +84,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
fleetsInAscendingOrder = new ArrayList<Fleet>();
Util.addAll(fleets, fleetsInAscendingOrder);
Collections.sort(fleetsInAscendingOrder);
List<RaceColumnInSeries> myRaceColumns = new ArrayList<RaceColumnInSeries>();
this.raceColumns = myRaceColumns;
this.raceColumns = new ArrayList<RaceColumnInSeries>();
this.isMedal = isMedal;
this.raceColumnListeners = new RaceColumnListeners();
for (String raceColumnName : raceColumnNames) {
@@ -105,7 +109,15 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
@Override
public void setRegatta(Regatta regatta) {
if (this.regatta != null) {
detachRaceExecutionOrderProviderFromTrackedRacesInRaceColumns();
}
this.regatta = regatta;
if (this.regatta != null) {
attachRaceExecutionOrderProviderToTrackedRacesInRaceColumns();
} else {
detachRaceExecutionOrderProviderFromTrackedRacesInRaceColumns();
}
}
public Iterable<? extends Fleet> getFleets() {
@@ -162,6 +174,28 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
this,
trackedRegattaRegistry);
}
private void attachRaceExecutionOrderProviderToTrackedRacesInRaceColumns() {
for (RaceColumnInSeries raceColumnInSeries : raceColumns) {
for (Fleet fleet : raceColumnInSeries.getFleets()) {
TrackedRace trackedRace = raceColumnInSeries.getTrackedRace(fleet);
if (trackedRace != null && regatta != null) {
trackedRace.attachRaceExecutionProvider(regatta.getRaceExecutionOrderProvider());
}
}
}
}
private void detachRaceExecutionOrderProviderFromTrackedRacesInRaceColumns() {
for (RaceColumnInSeries raceColumnInSeries : raceColumns) {
for (Fleet fleet : raceColumnInSeries.getFleets()) {
TrackedRace trackedRace = raceColumnInSeries.getTrackedRace(fleet);
if (trackedRace != null && regatta != null && regatta.getRaceExecutionOrderProvider() != null) {
trackedRace.detachRaceExecutionOrderProvider(regatta.getRaceExecutionOrderProvider());
}
}
}
}
@Override
public void moveRaceColumnUp(String raceColumnName) {
@@ -280,16 +314,6 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
raceColumnListeners.notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward);
}
/**
* A series listens on its columns; individual columns, however, don't ask whether they can be added; the series itself does.
*
* @see #addRaceColumn(String, TrackedRegattaRegistry)
*/
@Override
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return true;
}
@Override
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
raceColumnListeners.notifyListenersAboutRaceColumnAddedToContainer(raceColumn);
@@ -305,11 +305,6 @@ public class LeaderboardCacheManager {
removeFromCache(leaderboard);
}
@Override
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return true;
}
@Override
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
removeFromCache(leaderboard);
@@ -3,11 +3,14 @@ package com.sap.sailing.domain.leaderboard.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
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 java.util.Set;
import java.util.logging.Logger;
@@ -18,6 +21,7 @@ import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
@@ -31,6 +35,7 @@ import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeListener;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Duration;
@@ -54,6 +59,7 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
private String name;
private transient RaceLogStore raceLogStore;
private CourseArea courseArea;
private RaceExecutionOrderProvider raceExecutionOrderProvider;
/**
* @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog}
@@ -75,10 +81,11 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = name;
this.races = new ArrayList<FlexibleRaceColumn>();
this.races = new ArrayList<>();
this.raceLogStore = raceLogStore;
this.courseArea = courseArea;
this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore);
this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
}
/**
@@ -94,6 +101,11 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
}
}
protected Object readResolve() throws ObjectStreamException {
raceExecutionOrderProvider.triggerUpdate();
return this;
}
@Override
public String getName() {
@@ -160,6 +172,9 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
if (raceColumn != null) {
for (Fleet fleet : raceColumn.getFleets()) {
raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
if (raceColumn.getTrackedRace(fleet) != null) {
raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider);
}
}
races.remove(raceColumn);
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
@@ -169,11 +184,17 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
@Override
public Iterable<RaceColumn> getRaceColumns() {
return Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
final Iterable<RaceColumn> result;
if (races != null) {
result = Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
} else {
result = null;
}
return result;
}
protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
return new RaceColumnImpl(column, medalRace);
return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider);
}
protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
@@ -302,4 +323,20 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
result.addAll(viaLog);
return result;
}
private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider {
private static final long serialVersionUID = 652833386555762661L;
public RaceExecutionOrderCache() {
super();
addRaceColumnListener(this);
}
@Override
protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() {
final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns());
return result;
}
}
}
@@ -5,17 +5,20 @@ import java.util.Collections;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.impl.AbstractRaceColumn;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceColumn {
private static final long serialVersionUID = -7801617988982540470L;
private String name;
private boolean medalRace;
private final RaceExecutionOrderProvider raceExecutionOrderProvider;
public RaceColumnImpl(String name, boolean medalRace) {
public RaceColumnImpl(String name, boolean medalRace, RaceExecutionOrderProvider raceExecutionOrderProvider) {
super();
this.name = name;
this.medalRace = medalRace;
this.raceExecutionOrderProvider = raceExecutionOrderProvider;
}
@Override
@@ -44,4 +47,8 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo
return Collections.singleton(FlexibleLeaderboardImpl.defaultFleet);
}
@Override
public RaceExecutionOrderProvider getRaceExecutionOrderProvider() {
return raceExecutionOrderProvider;
}
}
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
@@ -197,4 +198,9 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
@Override
public void reloadRaceLog(Fleet fleet) {
}
@Override
public RaceExecutionOrderProvider getRaceExecutionOrderProvider() {
return null;
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.tracking;
import java.util.Set;
import com.sap.sailing.domain.base.Regatta;
/**
* Sometimes {@link TrackedRace}s want to know which {@link DynamicTrackedRace} is in front of them in the execution order
* of a {@link Regatta}. This is i.e interesting to know in the {@link DynamicTrackedRace #recordWind(com.sap.sailing.domain.common.Wind, com.sap.sailing.domain.common.WindSource)}
* method, when the race has to decide whether to record the wind. If a previous {@link TrackedRace}
* is <code>null</code>, the current {@link TrackedRace} would save the wind fixes earlier.
*
* @author Alexander Ries (D062114)
*
*/
public interface RaceExecutionOrderProvider {
/**
* Parameter <code>race</code> is a {@link TrackedRace} from which the method returns the previous
* {@link TrackedRace}s in the execution order of a {@link Regatta}. This expresses the fact that the races returned
* by this method must all have been finished before <code>race</code> can start.
*/
Set<TrackedRace> getPreviousRacesInExecutionOrder(TrackedRace race);
/**
* When the owner of this cache-like provider has initialized all its structures that this cache requires
* it shall call this method. This applies in particular after de-serializing the owner because during de-serialization,
* even during the <code>readObject</code> method, the owner's fields may not all be initialized. The
* <code>readResolve()</code> method is usually a good place to do so.
*/
void triggerUpdate();
}
@@ -275,6 +275,27 @@ public interface TrackedRace extends Serializable {
* is returned. Otherwise, the center of gravity between the mark positions is computed and returned.
*/
Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint);
/**
* Checks whether the {@link Wind#getTimePoint()} is in range of start and end {@link TimePoint}s plus extra time
* for wind recording. If, based on a {@link RaceExecutionOrderProvider}, there is no previous race that takes the
* wind fix, an extended time range lead (see
* {@link TrackedRaceImpl#EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS}) is used to record wind even a long
* time before the race start.
* <p>
*
* A race does not record wind when both, {@link #getStartOfTracking()} and {@link #getStartOfRace()} are
* <code>null</code>. Wind is not recorded when it is after the later of {@link #getEndOfRace()} and
* {@link #getEndOfTracking()} and one of the two is not <code>null</code>.
* <p>
*
* This default implementation returns true which may be useful for tests and mocked implementations; however, real
* implementations shall override this and provide a meaningful implementation according to the specification given
* above.
*/
default boolean takesWindFix(Wind wind) {
return true;
}
/**
* Same as {@link #getWind(Position, TimePoint, Set) getWind(p, at, Collections.emptyList())}
@@ -652,9 +673,9 @@ public interface TrackedRace extends Serializable {
void detachRaceLog(Serializable identifier);
/**
* Detaches all {@link RaceLog} instances from this race
* Detaches the link {@link RaceExecutionOrderProvider}
*/
void detachAllRaceLogs();
void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider);
/**
* Attaches the passed race log with this {@link TrackedRace}.
@@ -674,6 +695,12 @@ public interface TrackedRace extends Serializable {
*/
void attachRegattaLog(RegattaLog regattaLog);
/**
* Attaches a {@link RaceExecutionOrderProvider} to make a {@link TrackedRace} aware
* which races are scheduled around it in the execution order of a {@link Regatta}.
* */
void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider);
/**
* Returns the attached race log event track for this race if any.
* Otherwise <code>null</code>.
@@ -823,4 +850,5 @@ public interface TrackedRace extends Serializable {
Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException, NoWindException;
void setPolarDataService(PolarDataService polarDataService);
}
@@ -24,7 +24,6 @@ import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.TimingConstants;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
@@ -658,17 +657,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public boolean recordWind(Wind wind, WindSource windSource) {
final boolean result;
// TODO check what a good filter is; remember that start/end of tracking may change over time; what if we have discarded valuable wind fixes?
TimePoint startOfRace = getStartOfRace();
TimePoint startOfTracking = getStartOfTracking();
TimePoint endOfRace = getEndOfRace();
TimePoint endOfTracking = getEndOfTracking();
if ((startOfTracking == null || !startOfTracking.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()) ||
(startOfRace != null && !startOfRace.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint())))
&&
// Caution: don't add to endOfTracking; it may be the end of time, leading to a wrap-around / overflow
(endOfTracking == null || endOfTracking.after(wind.getTimePoint().minus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS)) ||
(endOfRace != null && endOfRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint())))) {
if (takesWindFix(wind)) {
result = getOrCreateWindTrack(windSource).add(wind);
updated(/* time point */null); // wind events shouldn't advance race time
triggerManeuverCacheRecalculationForAllCompetitors();
@@ -759,7 +748,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
}
super.detachRaceLog(identifier);
}
@Override
public void addCourseDesignChangedListener(CourseDesignChangedListener listener) {
this.courseDesignChangedListeners.add(listener);
@@ -818,4 +807,5 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
public DynamicGPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return (DynamicGPSFixTrack<Mark, GPSFix>) super.getTrack(mark);
}
}
@@ -116,6 +116,7 @@ import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -156,7 +157,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// TODO make this variable
private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500;
public static final long TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = 4 * 60 * 1000l; // let wind start four minutes before race
public static final Duration TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = Duration.ONE_MINUTE.times(4); // let wind start four minutes before race
public static final Duration EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = Duration.ONE_HOUR;
private TrackedRaceStatus status;
@@ -283,6 +286,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* Keys are the {@link RegattaLog#getId() IDs} of the regatta logs that are stored as values.
*/
protected transient ConcurrentHashMap<Serializable, RegattaLog> attachedRegattaLogs;
private transient ConcurrentHashMap<RaceExecutionOrderProvider, RaceExecutionOrderProvider> attachedRaceExecutionOrderProviders;
/**
* The time delay to the current point in time in milliseconds.
@@ -376,6 +381,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
locksForMarkPassings = new IdentityHashMap<>();
attachedRaceLogs = new ConcurrentHashMap<>();
attachedRegattaLogs = new ConcurrentHashMap<>();
attachedRaceExecutionOrderProviders = new ConcurrentHashMap<>();
this.status = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0);
this.statusNotifier = new Object[0];
this.loadingFromWindStoreLock = new NamedReentrantReadWriteLock("Loading from wind store lock for tracked race "
@@ -573,6 +579,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
ois.defaultReadObject();
raceStates = new WeakHashMap<>();
attachedRaceLogs = new ConcurrentHashMap<>();
attachedRaceExecutionOrderProviders = new ConcurrentHashMap<>();
markPassingsTimes = new ArrayList<com.sap.sse.common.Util.Pair<Waypoint, com.sap.sse.common.Util.Pair<TimePoint, TimePoint>>>();
// The short time wind cache needs to be there before operations such as maneuver recalculation try to access it
shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
@@ -1491,6 +1498,62 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
return result;
}
/**
* Checks whether the {@link Wind#getTimePoint()} is in range of start and end {@link TimePoint}s plus extra time
* for wind recording. If, based on a {@link RaceExecutionOrderProvider}, there is no previous race that takes the
* wind fix, an extended time range lead (see {@link TrackedRaceImpl#EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS})
* is used to record wind even a long time before the race start.<p>
*
* A race does not record wind when both, {@link #getStartOfTracking()} and {@link #getStartOfRace()} are <code>null</code>.
* Wind is not recorded when it is after the later of {@link #getEndOfRace()} and {@link #getEndOfTracking()} and one of the
* two is not <code>null</code>.
*/
@Override
public boolean takesWindFix(Wind wind) {
final boolean result;
final TimePoint earliestStartTimePoint = Util.getEarliestOfTimePoints(getStartOfRace(), getStartOfTracking());
final TimePoint latestEndTimePoint = Util.getLatestOfTimePoints(getEndOfRace(), getEndOfTracking());
if (earliestStartTimePoint != null) {
// first check if the fix meets the criteria set by the latestEndTimePoint: either the latestEndTimePoint is null, meaning an
// open interval which will continue to accept late wind fixes, or the fix time point is before the latestEndTimePoint plus a grace
// interval:
if (latestEndTimePoint == null || wind.getTimePoint().minus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).before(latestEndTimePoint)) {
// then check, if fix is accepted anyway because it's after earliestStartTimePoint.minus(TIME_BEFORE_START_TO_TRACK_WIND_MILLIS)
// and before latestEndTimePoint.plus(IS_LIVE_GRACE_PERIOD_IN_MILLIS) or latestEndTimePoint is null. In this case, no expensive
// recursive check whether previous races take the fix are required.
if (wind.getTimePoint().plus(TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(earliestStartTimePoint)) {
result = true;
} else {
// if the fix is older than even the extended lead interval would accept, don't accept the fix:
if (wind.getTimePoint().plus(EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).before(earliestStartTimePoint)) {
result = false;
} else {
// the fix is in the critical interval between EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS and
// TIME_BEFORE_START_TO_TRACK_WIND_MILLIS before the earliestStartTimePoint; the fix shall only be accepted
// if no previous race exists that accepts it
result = noPreviousRaceTakesWind(wind);
}
}
} else {
result = false; // don't accept the fix if it's after the latest end time point plus some grace interval
}
} else {
result = false; // don't accept a fix if we don't have any start time information about the race
}
return result;
}
private boolean noPreviousRaceTakesWind(Wind wind) {
final boolean result;
Set<TrackedRace> previousRacesInExecutionOrder = getPreviousRacesFromAttachedRaceExecutionOrderProviders();
if (previousRacesInExecutionOrder == null || !previousRacesInExecutionOrder.stream().filter(tr -> tr.takesWindFix(wind) == true).findAny().isPresent()) {
result = true;
} else {
result = false;
}
return result;
}
@Override
public Wind getWind(Position p, TimePoint at) {
@@ -3041,7 +3104,35 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public void attachRaceLog(final RaceLog raceLog) {
loadFixesForLog(raceLog, attachedRaceLogs);
}
@Override
public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
if (raceExecutionOrderProvider != null && !attachedRaceExecutionOrderProviders.containsKey(raceExecutionOrderProvider)) {
attachedRaceExecutionOrderProviders.put(raceExecutionOrderProvider, raceExecutionOrderProvider);
}
}
protected Set<TrackedRace> getPreviousRacesFromAttachedRaceExecutionOrderProviders() {
final Set<TrackedRace> result;
if (attachedRaceExecutionOrderProviders != null) {
result = attachedRaceExecutionOrderProviders.values().stream().map(reop->reop.getPreviousRacesInExecutionOrder(this)).collect(HashSet::new, (r, e)->r.addAll(e), (r, e)->r.addAll(e));
} else {
result = Collections.emptySet();
}
return result;
}
@Override
public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
if (raceExecutionOrderProvider != null) {
attachedRaceExecutionOrderProviders.remove(raceExecutionOrderProvider);
}
}
public boolean hasRaceExecutionOrderProvidersAttached(){
return !attachedRaceExecutionOrderProviders.isEmpty();
}
private ReadonlyRaceState getRaceState(RaceLog raceLog) {
ReadonlyRaceState result;
synchronized (raceStates) {
@@ -3064,11 +3155,6 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public void detachRaceLog(Serializable identifier) {
this.attachedRaceLogs.remove(identifier);
}
@Override
public void detachAllRaceLogs() {
this.attachedRaceLogs.clear();
}
@Override
public RaceLog getRaceLog(Serializable identifier) {
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -94,11 +95,12 @@ public class TrackedRegattaImpl implements TrackedRegatta {
unlockTrackedRacesAfterRead();
}
}
/**
* Resolving replaces this de-serialized object (which has a <code>null</code> {@link #raceListeners} collection) by
* a new one into which all other collection contents are copied.
*/
private Object readResolve() {
private Object readResolve() throws ObjectStreamException {
TrackedRegattaImpl result = new TrackedRegattaImpl(this.regatta);
result.trackedRaces.putAll(this.trackedRaces);
result.trackedRacesByBoatClass.putAll(this.trackedRacesByBoatClass);
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -590,10 +591,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public void detachAllRaceLogs() {
}
@Override
public void invalidateStartTime() {
}
@@ -779,4 +776,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public RankingMetric getRankingMetric() {
return null;
}
@Override
public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
@Override
public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
}
@@ -5,12 +5,9 @@
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.regattaoverview.RegattaOverview"/>
<listEntry value="com.sap.sailing.gwt.ui.RaceBoard"/>
<listEntry value="com.sap.sailing.gwt.Home"/>
<listEntry value="com.sap.sailing.gwt.ui.DataMining"/>
<listEntry value="com.sap.sailing.gwt.ui.LeaderboardEditing"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.ui.Spectator"/>
<listEntry value="com.sap.sailing.gwt.regattaoverview.RegattaOverview"/>
<listEntry value="com.sap.sailing.gwt.ui.PolarSheets"/>
@@ -18,8 +15,12 @@
<listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
<listEntry value="com.sap.sailing.gwt.ui.YoutubePopup"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.home.Mobile"/>
<listEntry value="com.sap.sailing.gwt.home.Desktop"/>
<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.LOG_LEVEL" value="INFO"/>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
@@ -72,7 +73,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.ui.PolarSheets com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.Home com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.regattaoverview.RegattaOverview"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Mobile -startupUrl /gwt/HomeDesktop.html com.sap.sailing.gwt.home.Desktop -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.home.Desktop com.sap.sailing.gwt.home.Mobile com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.ui.PolarSheets com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m"/>
</launchConfiguration>
@@ -18,6 +18,11 @@ public class BoatClassImageResolver {
static {
boatClassIconsMap = new HashMap<String, ImageResource>();
// handicap sailing icons
boatClassIconsMap.put(BoatClassMasterdata.ORC.getDisplayName(), imageResources.ORCIcon());
boatClassIconsMap.put(BoatClassMasterdata.ORC_CLUB.getDisplayName(), imageResources.ORCIcon());
boatClassIconsMap.put(BoatClassMasterdata.ORC_INTERNATIONAL.getDisplayName(), imageResources.ORCIcon());
boatClassIconsMap.put(BoatClassMasterdata._12M.getDisplayName(), imageResources._12mRIcon());
boatClassIconsMap.put(BoatClassMasterdata._2_4M.getDisplayName(), imageResources._2_4mIcon());
@@ -153,4 +153,8 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS800.png")
ImageResource RS800Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png")
ImageResource ORCIcon();
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

@@ -106,10 +106,10 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
JSONArray jsonLeaderboards = new JSONArray();
Map<String, Leaderboard> leaderboards = getService().getLeaderboards();
for (String leaderboardName : leaderboards.keySet()) {
if (!leaderboardName.equals(LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME))
if (!leaderboardName.equals(LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME)) {
jsonLeaderboards.add(leaderboardName);
}
}
String json = jsonLeaderboards.toJSONString();
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
@@ -889,8 +889,8 @@ public class RegattasResource extends AbstractSailingServerResource {
throw re;
}
}
jsonCompetitorInLeg.put("gapToLeader-s",
trackedLegOfCompetitor.getGapToLeader(timePoint, rankingInfo, WindPositionMode.LEG_MIDDLE));
Duration gapToLeaderDuration = trackedLegOfCompetitor.getGapToLeader(timePoint, rankingInfo, WindPositionMode.LEG_MIDDLE);
jsonCompetitorInLeg.put("gapToLeader-s", gapToLeaderDuration != null ? gapToLeaderDuration.asSeconds() : 0.0);
jsonCompetitorInLeg.put("started", trackedLegOfCompetitor.hasStartedLeg(timePoint));
jsonCompetitorInLeg.put("finished", trackedLegOfCompetitor.hasFinishedLeg(timePoint));
jsonCompetitors.add(jsonCompetitorInLeg);
@@ -135,7 +135,7 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl
DynamicTrackedRace masterTrackedRace = master.createTrackedRace(new RegattaNameAndRaceName(masterRegatta.getName(), masterRace.getName()),
master.getWindStore(), master.getGPSFixStore(), /* delayToLiveInMillis */ 3000,
/* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 10000, /*ignoreTracTracMarkPassings*/ false);
masterTrackedRace.setStartOfTrackingReceived(MillisecondsTimePoint.now());
/* Leaderboard */
final String leaderboardName = "Great Leaderboard";
final int[] discardThresholds = new int[] { 17, 23 };
@@ -55,6 +55,7 @@ import com.sap.sailing.server.operationaltransformation.AddRaceDefinition;
import com.sap.sailing.server.operationaltransformation.CreateTrackedRace;
import com.sap.sailing.server.operationaltransformation.TrackRegatta;
import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -142,7 +143,9 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio
@Test
public void testWindAdditionReplication() throws InterruptedException {
final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(3456),
TimePoint now = MillisecondsTimePoint.now();
trackedRace.setStartOfTrackingReceived(now);
final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(now.asMillis()+1234),
new KnotSpeedWithBearingImpl(13, new DegreeBearingImpl(234)));
WindSource webWindSource = new WindSourceImpl(WindSourceType.WEB);
trackedRace.recordWind(wind, webWindSource);
@@ -190,12 +193,14 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio
@Test
public void testReplicationOfLoadingOfStoredWindTrack() throws UnknownHostException, MongoException, InterruptedException {
TimePoint now = MillisecondsTimePoint.now();
trackedRace.setStartOfTrackingReceived(now);
MongoWindStore windStore = MongoWindStoreFactory.INSTANCE.getMongoWindStore(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(),
PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory());
WindSource webWindSource = new WindSourceImpl(WindSourceType.WEB);
WindTrack windTrack = windStore.getWindTrack(trackedRegatta.getRegatta().getName(), trackedRace, webWindSource, /* millisecondsOverWhichToAverage */ 10000,
/* delayForWindEstimationCacheInvalidation */ 10000);
final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(3456),
final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(now.asMillis()+1234),
new KnotSpeedWithBearingImpl(13, new DegreeBearingImpl(234)));
windTrack.add(wind);
Thread.sleep(1000); // give MongoDB time to read its own writes in a separate session
@@ -101,6 +101,7 @@ public class WindByRaceLogTest {
service.apply(new TrackRegatta(raceIdentifier));
trackedRace = (DynamicTrackedRace) service.apply(new CreateTrackedRace(raceIdentifier, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,/* delayToLiveInMillis */ 5000,
/* millisecondsOverWhichToAverageWind */ 10000, /* millisecondsOverWhichToAverageSpeed */10000));
trackedRace.setStartOfTrackingReceived(MillisecondsTimePoint.now());
defaultFleet = Util.get(raceColumn.getFleets(), 0);
}
@@ -90,11 +90,6 @@ public class RaceLogReplicator implements RaceColumnListener {
public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) {
}
@Override
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return true;
}
@Override
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
}
@@ -62,11 +62,6 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) {
}
@Override
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return true;
}
@Override
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
}
@@ -42,6 +42,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
@@ -438,10 +439,6 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public void detachRaceLog(Serializable identifier) {
}
@Override
public void detachAllRaceLogs() {
}
@Override
public void attachRaceLog(RaceLog raceLog) {
}
@@ -616,4 +613,12 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public RankingMetric getRankingMetric() {
return null;
}
@Override
public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
@Override
public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
}
}
@@ -362,4 +362,46 @@ public class Util {
}
return null;
}
/**
* Returns the earlier {@link TimePoint} of a and b. If one of them is <code>null</code> and the other
* <code>!null</code>, the TimePoint that is not <code>!null</code> gets returned. If both are <code>null</code>,
* <code>null</code> is the result.
*/
public static TimePoint getEarliestOfTimePoints(TimePoint a, TimePoint b) {
TimePoint result = null;
if (a != null && b != null) {
result = a.before(b) ? a : b;
} else {
result = (a != null && b == null) ? a : (a == null && b != null) ? b : null;
}
return result;
}
/**
* Returns the latest {@link TimePoint} of a and b. If one of them is <code>null</code> and the other
* <code>!null</code>, the TimePoint that is not <code>!null</code> gets returned. If both are <code>null</code>,
* <code>null</code> is the result.
*/
public static TimePoint getLatestOfTimePoints(TimePoint a, TimePoint b) {
TimePoint result = null;
if (a != null && b != null) {
result = a.after(b) ? a : b;
} else {
result = (a != null && b == null) ? a : (a == null && b != null) ? b : null;
}
return result;
}
/**
* Returns <code>true</code> if <code>timePoint</code> is after <code>a</code> an before <code>b</code>.
* If one of the parameters is <code>null</code> the method returns <code>false</code>.
*/
public static boolean isTimePointInRangeOfTimePointsAandB(TimePoint timePoint, TimePoint a, TimePoint b) {
boolean result = false;
if (timePoint != null && a != null && b != null) {
result = timePoint.after(a) && timePoint.before(b);
}
return result;
}
}