diff --git a/build.gradle b/build.gradle index 6a7d068b950..a485afff7b7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.android.tools.build:gradle:1.1.1' } } diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index ba468eeeae8..3c405d73557 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -619,10 +619,10 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then echo "ANDROID_HOME=$ANDROID_HOME" PATH=$PATH:$ANDROID_HOME/tools PATH=$PATH:$ANDROID_HOME/platform-tools - ANDROID="$ANDROID_HOME/tools/android" - if [ \! -x "$ANDROID" ]; then - ANDROID="$ANDROID_HOME/tools/android.bat" - fi + ANDROID="$ANDROID_HOME/tools/android" + if [ \! -x "$ANDROID" ]; then + ANDROID="$ANDROID_HOME/tools/android.bat" + fi RC_APP_VERSION=`grep "android:versionCode=" mobile/com.sap.$PROJECT_TYPE.racecommittee.app/AndroidManifest.xml | cut -d "\"" -f 2` echo "RC_APP_VERSION=$RC_APP_VERSION" @@ -654,8 +654,12 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then if [[ ${PIPESTATUS[0]} != 0 ]]; then exit 100 fi - # testing deactivated due to errors in hudson if [ $testing -eq 1 ]; then + echo "Starting JUnit tests..." + ./gradlew test | tee -a $START_DIR/build.log + if [[ ${PIPESTATUS[0]} != 0 ]]; then + exit 103 + fi adb emu kill echo "Downloading image (sys-img-${ANDROID_ABI}-android-${TEST_API})..." | tee -a $START_DIR/build.log echo yes | "$ANDROID" update sdk $ANDROID_OPTIONS --filter sys-img-${ANDROID_ABI}-android-${TEST_API} --no-ui --force --all > /dev/null diff --git a/java/com.sap.sailing.dashboards.gwt/RibDashboard.css b/java/com.sap.sailing.dashboards.gwt/RibDashboard.css index ef3be51375d..cb096f5372f 100644 --- a/java/com.sap.sailing.dashboards.gwt/RibDashboard.css +++ b/java/com.sap.sailing.dashboards.gwt/RibDashboard.css @@ -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; } \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/RibDashboard.html b/java/com.sap.sailing.dashboards.gwt/RibDashboard.html index 5d6d9768d35..eb761aa4f82 100644 --- a/java/com.sap.sailing.dashboards.gwt/RibDashboard.html +++ b/java/com.sap.sailing.dashboards.gwt/RibDashboard.html @@ -10,10 +10,12 @@ - - -ESS Dashboard + + + + +Dashboard diff --git a/java/com.sap.sailing.dashboards.gwt/build.properties b/java/com.sap.sailing.dashboards.gwt/build.properties index 2632144a33f..f5f5657c7a4 100644 --- a/java/com.sap.sailing.dashboards.gwt/build.properties +++ b/java/com.sap.sailing.dashboards.gwt/build.properties @@ -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/ diff --git a/java/com.sap.sailing.dashboards.gwt/fontface.css b/java/com.sap.sailing.dashboards.gwt/fontface.css deleted file mode 100644 index 2bede9b22a5..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/fontface.css +++ /dev/null @@ -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; */ - -/* } */ diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/Ribstyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/Ribstyle.css index d472d3887f2..14b4df0e958 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/Ribstyle.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/Ribstyle.css @@ -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%; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java index 39971c2fd18..b797aeb3568 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java @@ -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() { @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 diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css index 423d17e5c25..a84dd94f1bf 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css @@ -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 { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysis.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysis.css index f1229de3a4e..01b8e89e2c3 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysis.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysis.css @@ -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; } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.java index ad19b946bde..034fd34c083 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.java @@ -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, diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.css index 3449c35582d..5bee6ef9130 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.css @@ -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 { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.java index fd378ee94a6..4628cf14334 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.java @@ -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 { } + + 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()+"
"+stringConstants.dashboardAverageWindMinutes(15)); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.ui.xml index 9223d82f4f2..49cb75c6be2 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.ui.xml +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.ui.xml @@ -2,7 +2,7 @@ - + diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponent.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponent.java index 03ebbcb4ae7..df70c920c0a 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponent.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponent.java @@ -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( diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartStyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartStyle.css index e222fb0b733..5f192987e9c 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartStyle.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartStyle.css @@ -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%; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponentStyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponentStyle.css index 22ff8fe345b..90bfd8f5188 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponentStyle.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponentStyle.css @@ -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; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.css index 8dd041bad7c..4db1b4107b7 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.css @@ -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; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java index dcd5857ee73..e95c3198a40 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -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; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java new file mode 100644 index 00000000000..1d96073f872 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java @@ -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 null 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. 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. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. 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. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. 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. 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. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. 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. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. 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. emptyList(), + MillisecondsTimePoint.now()); + fleet = new FleetImpl(FLEET); + Set fleets = new HashSet<>(); + fleets.add(fleet); + series = new SeriesImpl(SERIES, false, fleets, new HashSet(), null); + Set 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); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SimpleRaceIdentifierEscapingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SimpleRaceIdentifierEscapingTest.java deleted file mode 100755 index 5f149359391..00000000000 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SimpleRaceIdentifierEscapingTest.java +++ /dev/null @@ -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 - * FleetIfentifierImpl. - * - * @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 parsed = parse(marshalled); - assertEquals(leaderboardName, parsed.getA()); - assertEquals(raceColumnName, parsed.getB()); - assertEquals(fleetName, parsed.getC()); - } - - private Triple parse(String s) { - int arrayIndex = 0; - StringBuilder[] split = new StringBuilder[3]; - for (int i=0; i(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(".", "\\."); - } -} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index 84aa1415a54..ac5a08d55fc 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -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))); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index a84e8d3c8b2..481323374f0 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -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) { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index fc669816f33..a07b567922f 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -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) { + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java index e109fa6f0ed..64a34b1f1fc 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java @@ -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(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java index 873954d9379..cbc21268ee3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java @@ -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); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index 4efdb1cae14..6415cd5901f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -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()); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java index 6fae2a3dc8c..995c0e715a7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java @@ -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); } - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceExecutionOrderProvider.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceExecutionOrderProvider.java new file mode 100755 index 00000000000..07da1de7bed --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceExecutionOrderProvider.java @@ -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>, 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> getPreviousRacesByRace() { + return previousRacesByRaceCache.get(RACES_ORDER_LIST_CACHE_KEY, /* waitForLatest */true); + } + + protected abstract Map> getRaceColumnsOfSeries(); + + private Map> reloadAndGetPreviousRacesByRace() { + final Map> previousRacesByRace = new HashMap<>(); + for (Entry> raceColumnsInSeries : getRaceColumnsOfSeries().entrySet()) { + addPreviousRaces(previousRacesByRace, raceColumnsInSeries.getKey(), raceColumnsInSeries.getValue()); + } + return previousRacesByRace; + } + + private void addPreviousRaces(final Map> previousRacesByRace, Fleet fleet, + final Iterable raceColumns) { + TrackedRace previousRace = null; + for (RaceColumn currentRaceColumn : raceColumns) { + final TrackedRace trackedRaceInColumnForFleet = currentRaceColumn.getTrackedRace(fleet); + if (trackedRaceInColumnForFleet != null) { + Set previousRaces = previousRacesByRace.get(trackedRaceInColumnForFleet); + if (previousRaces == null) { + previousRaces = new HashSet<>(); + previousRacesByRace.put(trackedRaceInColumnForFleet, previousRaces); + } + if (previousRace != null) { + previousRaces.add(previousRace); + } + previousRace = trackedRaceInColumnForFleet; + } + } + } + + private SmartFutureCache>, EmptyUpdateInterval> createRacesOrderCache() { + return new SmartFutureCache>, SmartFutureCache.EmptyUpdateInterval>( + new SmartFutureCache.AbstractCacheUpdater>, SmartFutureCache.EmptyUpdateInterval>() { + @Override + public Map> computeCacheUpdate(String key, + EmptyUpdateInterval updateInterval) throws Exception { + if (key.equals(RACES_ORDER_LIST_CACHE_KEY)) { + return reloadAndGetPreviousRacesByRace(); + } else { + final Map> 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 getPreviousRacesInExecutionOrder(TrackedRace race) { + Set result = Collections.emptySet(); + final Map> previousRacesByRace = getPreviousRacesByRace(); + if (previousRacesByRace != null) { + result = previousRacesByRace.get(race); + } + return result; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnInSeriesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnInSeriesImpl.java index e6d97e325ff..2554e5d157d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnInSeriesImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnInSeriesImpl.java @@ -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; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java new file mode 100644 index 00000000000..2db919863ad --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java @@ -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; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index a0faa71ce3e..e8c17204a89 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -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 getSeries() { - return Collections.unmodifiableCollection(series); + final Iterable 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> getRaceColumnsOfSeries() { + final Map> result = new HashMap<>(); + final Iterable 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; + } + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java index 0ca5220251b..e8cd4ce23b8 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java @@ -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 fleetsByName; @@ -79,8 +84,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe fleetsInAscendingOrder = new ArrayList(); Util.addAll(fleets, fleetsInAscendingOrder); Collections.sort(fleetsInAscendingOrder); - List myRaceColumns = new ArrayList(); - this.raceColumns = myRaceColumns; + this.raceColumns = new ArrayList(); 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 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); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java index 80d35ca3c51..bdb5c767ba1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java @@ -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); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java index d7953c39b1c..3ebbe1e7727 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java @@ -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(); + 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 getRaceColumns() { - return Collections.unmodifiableCollection(new ArrayList(races)); + final Iterable result; + if (races != null) { + result = Collections.unmodifiableCollection(new ArrayList(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 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> getRaceColumnsOfSeries() { + final Map> result = new HashMap<>(); + result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns()); + return result; + } + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java index 0182667e6ce..12709513ea8 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java @@ -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; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java index 81acb8e8054..df62cb5e664 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java @@ -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; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceExecutionOrderProvider.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceExecutionOrderProvider.java new file mode 100644 index 00000000000..0de22b6c5c8 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceExecutionOrderProvider.java @@ -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 null, the current {@link TrackedRace} would save the wind fixes earlier. + * + * @author Alexander Ries (D062114) + * + */ +public interface RaceExecutionOrderProvider { + + /** + * Parameter race 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 race can start. + */ + Set 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 readObject method, the owner's fields may not all be initialized. The + * readResolve() method is usually a good place to do so. + */ + void triggerUpdate(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index c98280a45e8..2a8c9168b16 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -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. + *

+ * + * A race does not record wind when both, {@link #getStartOfTracking()} and {@link #getStartOfRace()} are + * null. Wind is not recorded when it is after the later of {@link #getEndOfRace()} and + * {@link #getEndOfTracking()} and one of the two is not null. + *

+ * + * 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 null. @@ -823,4 +850,5 @@ public interface TrackedRace extends Serializable { Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException, NoWindException; void setPolarDataService(PolarDataService polarDataService); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index 59827408828..e1463636149 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -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 { @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 { } super.detachRaceLog(identifier); } - + @Override public void addCourseDesignChangedListener(CourseDesignChangedListener listener) { this.courseDesignChangedListeners.add(listener); @@ -818,4 +807,5 @@ DynamicTrackedRace, GPSTrackListener { public DynamicGPSFixTrack getTrack(Mark mark) { return (DynamicGPSFixTrack) super.getTrack(mark); } + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index a43c6f91836..399d4a7625d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -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 attachedRegattaLogs; + + private transient ConcurrentHashMap 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>>(); // 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.

+ * + * A race does not record wind when both, {@link #getStartOfTracking()} and {@link #getStartOfRace()} are null. + * Wind is not recorded when it is after the later of {@link #getEndOfRace()} and {@link #getEndOfTracking()} and one of the + * two is not null. + */ + @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 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 getPreviousRacesFromAttachedRaceExecutionOrderProviders() { + final Set 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) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java index f34596a7855..442ca68ede2 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java @@ -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 null {@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); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index 128b68ed781..9eca28dba48 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -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) { + } } diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index 328b5babf96..a00d6c0ff91 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -5,12 +5,9 @@ - - - @@ -18,8 +15,12 @@ + + + + @@ -72,7 +73,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java index e5c0ed29a8a..0933c567da3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java @@ -18,6 +18,11 @@ public class BoatClassImageResolver { static { boatClassIconsMap = new HashMap(); + + // 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()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java index 2a8e2a1102e..cb36d3fe29c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java @@ -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(); + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png new file mode 100644 index 00000000000..9fb9b24bed7 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png differ diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index dbef3735dcc..89587544b76 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -106,10 +106,10 @@ public class LeaderboardsResource extends AbstractSailingServerResource { JSONArray jsonLeaderboards = new JSONArray(); Map 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(); } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 6db76168f62..a8efcff988e 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -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); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java index 48a8ddd7da4..0aff92a2f88 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java @@ -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 }; diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java index 8467de586e4..1c3728ba8cb 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java @@ -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 diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java index eafaf3b1e0a..e9f8e91e88c 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java @@ -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); } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java index 6ccd2bcc396..9aa44d096da 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java @@ -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) { } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java index eaa614b551e..8fb76bc3920 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java @@ -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) { } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index e0218afdb82..370826f1ecd 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java @@ -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) { + } } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index 6a56acbdfbe..3c1d8d300fc 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -362,4 +362,46 @@ public class Util { } return null; } + + /** + * Returns the earlier {@link TimePoint} of a and b. If one of them is null and the other + * !null, the TimePoint that is not !null gets returned. If both are null, + * null 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 null and the other + * !null, the TimePoint that is not !null gets returned. If both are null, + * null 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 true if timePoint is after a an before b. + * If one of the parameters is null the method returns false. + */ + 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; + } } \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/.classpath b/mobile/com.sap.sailing.racecommittee.app/.classpath index 361be950b40..b32ef90cb7e 100644 --- a/mobile/com.sap.sailing.racecommittee.app/.classpath +++ b/mobile/com.sap.sailing.racecommittee.app/.classpath @@ -1,11 +1,13 @@ - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/mobile/com.sap.sailing.racecommittee.app/SimpleRaceIdentifierEscapingTest.launch b/mobile/com.sap.sailing.racecommittee.app/SimpleRaceIdentifierEscapingTest.launch new file mode 100644 index 00000000000..5dcdc3de012 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/SimpleRaceIdentifierEscapingTest.launch @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/com.sap.sailing.racecommittee.app/build.gradle b/mobile/com.sap.sailing.racecommittee.app/build.gradle index 644b342601b..84e5765a0bf 100644 --- a/mobile/com.sap.sailing.racecommittee.app/build.gradle +++ b/mobile/com.sap.sailing.racecommittee.app/build.gradle @@ -29,9 +29,13 @@ android { } androidTest { - java.srcDirs = ["tests/src"] - res.srcDirs = ["tests/res"] - assets.srcDirs = ["tests/assets"] + java.srcDirs = ["tests-ui/src"] + res.srcDirs = ["tests-ui/res"] + assets.srcDirs = ["tests-ui/assets"] + } + + test { + java.srcDirs = ["tests-jvm/src"] } } @@ -83,14 +87,19 @@ dependencies { /* all local jar-libs */ compile fileTree(dir: "libs", include: ["*.jar"]) + /* JVM tests */ + testCompile 'junit:junit:4.12' + testCompile "org.mockito:mockito-core:1.9.5" + /* UI testing with espresso */ + androidTestCompile "com.android.support:support-annotations:22.1.1" androidTestCompile "com.android.support.test:runner:0.2" androidTestCompile "com.android.support.test:rules:0.2" androidTestCompile "com.android.support.test.espresso:espresso-core:2.1" - androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.1') { - exclude group: 'com.android.support', module: 'appcompat' - exclude group: 'com.android.support', module: 'support-v4' - exclude module: 'recyclerview-v7' + androidTestCompile ("com.android.support.test.espresso:espresso-contrib:2.1") { + exclude group: "com.android.support", module: "appcompat" + exclude group: "com.android.support", module: "support-v4" + exclude module: "recyclerview-v7" } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java index 152965cad8c..0ab155c64b0 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java @@ -41,9 +41,13 @@ public class FleetIdentifierImpl implements FleetIdentifier { return fragment.replace("\\", "\\\\").replace(".", "\\."); } + /** + * @return a triple consisting of regattaLikeName/raceColumnName/fleetName; note that the series name which is + * encoded in the escapedId in second place (zero-based index 1) is silently ignored + */ public static Triple unescape(String escapedId) { int arrayIndex = 0; - StringBuilder[] split = new StringBuilder[3]; + StringBuilder[] split = new StringBuilder[4]; for (int i=0; i(leaderboardName, raceColumnName, fleetName); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java index 128ade4f94e..55940938aee 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java @@ -1,10 +1,5 @@ package com.sap.sailing.racecommittee.app.utils.autoupdate; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; @@ -12,14 +7,18 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.support.v7.app.AlertDialog; - import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.util.FileHandlerUtils; import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.ui.activities.SettingsActivity; +import com.sap.sailing.racecommittee.app.ui.activities.PreferenceActivity; import com.sap.sailing.racecommittee.app.ui.fragments.preference.GeneralPreferenceFragment; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + public class AutoUpdater { private static final String TAG = AutoUpdater.class.getName(); @@ -70,8 +69,8 @@ public class AutoUpdater { OnClickListener okListener = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(context, SettingsActivity.class); - intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, GeneralPreferenceFragment.class.getName()); + Intent intent = new Intent(context, PreferenceActivity.class); + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeneralPreferenceFragment.class.getName()); context.startActivity(intent); dialog.dismiss(); } diff --git a/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java b/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java new file mode 100755 index 00000000000..fbb81c2f423 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java @@ -0,0 +1,71 @@ +package com.sap.sailing.racecommittee.app.domain.impl; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.impl.RaceGroupImpl; +import com.sap.sailing.racecommittee.app.domain.ManagedRaceIdentifier; +import com.sap.sse.common.Util.Triple; + +/** + * This test verifies the algorithm used to escape and unescape SimpleRaceIdentifier components as used in + * FleetIfentifierImpl. + * + * @author Axel Uhl (D043530) + * + */ +public class SimpleRaceIdentifierEscapingTest { + @Test + public void testUnescapingSimpleRaceIdentifier() { + test("Leaderboard with trailing backslash and dot\\.", "empty", "fleet", "raceColumn"); + test("Leaderboard\\.with\\.two dots", "empty", "fleet", "raceColumn"); + test("Leaderboard with double backslash\\\\", "empty", "fleet", "raceColumn"); + test("Leaderboard with double backslash and dot\\\\.", "empty", "fleet", "raceColumn"); + test("\\Leaderboard with leading backslash", "empty", "fleet", "raceColumn"); + test("\\.Leaderboard with leading backslash and dot", "empty", "fleet", "raceColumn"); + test("\\\\.Leaderboard with leading double backslash and dot", "empty", "fleet", "raceColumn"); + test("\\\\.Leaderboard with leading double backslash and two dots", "empty", "fleet", "raceColumn"); + } + + private void test(String leaderboardName, String empty, String fleetName, String raceColumnName) { + final String marshalled = build(leaderboardName, empty, fleetName, raceColumnName); + final Triple parsed = FleetIdentifierImpl.unescape(marshalled); + assertEquals(leaderboardName, parsed.getA()); + assertEquals(raceColumnName, parsed.getB()); + assertEquals(fleetName, parsed.getC()); + } + + private String build(final String leaderboardName, final String seriesName, final String fleetName, final String raceColumnName) { + final Fleet fleet = new FleetImpl(fleetName); + final SeriesBase series = new SeriesBase() { + private static final long serialVersionUID = 1139740910228343375L; + + @Override + public String getName() { + return seriesName; + } + + @Override + public void setName(String newName) { + } + + @Override + public boolean isMedal() { + return false; + } + + @Override + public Iterable getFleets() { + return null; + } + }; + RaceGroup raceGroup = new RaceGroupImpl(leaderboardName, /* displayName */ null, /* boatClass */ null, /* courseArea */ null, /* series */ null, /* regattaConfiguration */ null); + ManagedRaceIdentifier identifier = new ManagedRaceIdentifierImpl(raceColumnName, fleet, series, raceGroup); + return identifier.getId(); + } +} diff --git a/mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ApplicationTest.java b/mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ApplicationTest.java similarity index 100% rename from mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ApplicationTest.java rename to mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ApplicationTest.java diff --git a/mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java b/mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java similarity index 100% rename from mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java rename to mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java