Merge branch 'master' into bug3785

This commit is contained in:
Steffen Schaefer committed 2016-07-21 09:04:00 +02:00
commit 98b9b5d4b8
10 files changed
+475 -281

No files matched your search

+28 -10
View File
@@ -155,7 +155,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "http://$HOSTNAME/dashboards/RibDashboard.html?leaderboardName=$LEADERBOARD_NAME&%{QUERY_STRING}" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "http://$HOSTNAME/dashboards/RibDashboard.html?leaderboardName=$LEADERBOARD_NAME&%1" [L,NE]
Use Rewrite $IP $PORT Use Rewrite $IP $PORT
</VirtualHost> </VirtualHost>
@@ -216,7 +218,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html?%{QUERY_STRING}#/event/:eventId=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html?%1#/event/:eventId=$EVENTUUID" [L,NE]
Use Rewrite $IP $PORT Use Rewrite $IP $PORT
</VirtualHost> </VirtualHost>
@@ -236,7 +240,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "https://$HOSTNAME/gwt/Home.html?%{QUERY_STRING}#/event/:eventId=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "https://$HOSTNAME/gwt/Home.html?%1#/event/:eventId=$EVENTUUID" [L,NE]
Use Rewrite $IP $PORT Use Rewrite $IP $PORT
</VirtualHost> </VirtualHost>
@@ -251,7 +257,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html?%{QUERY_STRING}#/series/:seriesId=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html?%1#/series/:seriesId=$EVENTUUID" [L,NE]
Use Rewrite $IP $PORT Use Rewrite $IP $PORT
</VirtualHost> </VirtualHost>
@@ -271,7 +279,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "https://$HOSTNAME/gwt/Home.html?%{QUERY_STRING}#/series/:seriesId=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "https://$HOSTNAME/gwt/Home.html?%1#/series/:seriesId=$EVENTUUID" [L,NE]
Use Rewrite $IP $PORT Use Rewrite $IP $PORT
</VirtualHost> </VirtualHost>
@@ -286,7 +296,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html#/regatta/overview/:eventId=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html?%1#/regatta/overview/:eventId=$EVENTUUID" [L,NE]
Use Rewrite $IP $PORT Use Rewrite $IP $PORT
</VirtualHost> </VirtualHost>
@@ -316,7 +328,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html#/event/:eventId=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/Home.html?%1#/event/:eventId=$EVENTUUID" [L,NE]
Use Rewrite 172.31.30.89 8888 Use Rewrite 172.31.30.89 8888
</VirtualHost> </VirtualHost>
@@ -336,7 +350,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "https://$HOSTNAME/gwt/Home.html#/event/:eventId=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "https://$HOSTNAME/gwt/Home.html?%1#/event/:eventId=$EVENTUUID" [L,NE]
Use Rewrite 172.31.30.89 8888 Use Rewrite 172.31.30.89 8888
</VirtualHost> </VirtualHost>
@@ -352,7 +368,9 @@
RewriteEngine on RewriteEngine on
RewriteCond %{REQUEST_URI} "^(/)?$" RewriteCond %{REQUEST_URI} "^(/)?$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/RegattaOverview.html?event=$EVENTUUID" [L,NE] # The following matches the query string and captures it in group %1 for the RewriteRule to use before the fragment
RewriteCond %{QUERY_STRING} "^(.*)$"
RewriteRule ^(/)?$ "http://$HOSTNAME/gwt/RegattaOverview.html?event=$EVENTUUID&%1" [L,NE]
Use Rewrite $IP $PORT Use Rewrite $IP $PORT
</VirtualHost> </VirtualHost>
@@ -556,4 +574,4 @@
Use Rewrite 172.31.18.218 8888 Use Rewrite 172.31.18.218 8888
</VirtualHost> </VirtualHost>
</Macro> </Macro>
@@ -287,7 +287,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
for (Competitor competitor : competitors) { for (Competitor competitor : competitors) {
final String toAddress = competitor.getEmail(); final String toAddress = competitor.getEmail();
if (toAddress != null) { if (toAddress != null) {
String leaderboardName = leaderboard.getName(); String leaderboardName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String competitorName = competitor.getName(); String competitorName = competitor.getName();
String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash,
event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING,
@@ -299,8 +299,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
} }
try { try {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES; final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName, competitorName, url, B.get(locale, "sailInSightAppName"), sendInvitationEmail(locale, toAddress, leaderboardName, event.getName(), competitorName, url,
iOSAppUrl, androidAppUrl, logoUrl); B.get(locale, "sailInSightAppName"), iOSAppUrl, androidAppUrl, logoUrl);
} catch (MailException e) { } catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n"); occuredExceptions.append(e.getMessage() + "\r\n");
} }
@@ -311,8 +311,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
} }
} }
private void sendInvitationEmail(Locale locale, final String toAddress, String leaderboardName, String invitee, private void sendInvitationEmail(Locale locale, final String toAddress, String leaderboardName, String eventName,
String url, String appName, String iOSAppUrl, String androidAppUrl, String logoUrl) throws MailException { String invitee, String url, String appName, String iOSAppUrl, String androidAppUrl, String logoUrl) throws MailException {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES; final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
String subject = String.format("%s %s", B.get(locale, "trackingInvitationFor"), invitee); String subject = String.format("%s %s", B.get(locale, "trackingInvitationFor"), invitee);
@@ -325,8 +325,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
StringBuilder htmlText = new StringBuilder(); StringBuilder htmlText = new StringBuilder();
htmlText.append("<!doctype html>\n"); htmlText.append("<!doctype html>\n");
htmlText.append("<html>"); htmlText.append("<html>");
htmlText.append("<head><title>"); final String welcomeText = B.get(locale, "welcomeTo")+" "+eventName+", "+leaderboardName;
htmlText.append(B.get(locale, "welcomeTo")).append(" ").append(leaderboardName).append("</title>"); htmlText.append("<head><title>").append(welcomeText).append("</title>");
htmlText.append("<style type=\"text/css\">\n"); htmlText.append("<style type=\"text/css\">\n");
htmlText.append(".b,.b:active,.b:visited {padding:15px;margin:10px;width:200px;display:inline-block;background-color:#337ab7;border-radius:4px;color:#ffffff;border:1px solid #2e6da4;text-decoration:none;}\n"); htmlText.append(".b,.b:active,.b:visited {padding:15px;margin:10px;width:200px;display:inline-block;background-color:#337ab7;border-radius:4px;color:#ffffff;border:1px solid #2e6da4;text-decoration:none;}\n");
htmlText.append(".b:hover {background-color:#2b618e;border:1px solid #204d74;}\n"); htmlText.append(".b:hover {background-color:#2b618e;border:1px solid #204d74;}\n");
@@ -338,7 +338,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
if (hasLogoUrl) { if (hasLogoUrl) {
htmlText.append("<p><img src=\"").append(logoUrl).append("\" /></p> "); htmlText.append("<p><img src=\"").append(logoUrl).append("\" /></p> ");
} }
htmlText.append("<h1>").append(B.get(locale, "welcomeTo")).append(" ").append(leaderboardName).append("</h1> "); htmlText.append("<h1>").append(welcomeText).append("</h1> ");
htmlText.append("<p>").append(B.get(locale, "scanQRCodeOrVisitUrlToRegisterAs", appName)).append(" <b>").append(invitee) htmlText.append("<p>").append(B.get(locale, "scanQRCodeOrVisitUrlToRegisterAs", appName)).append(" <b>").append(invitee)
.append("</b></p> "); .append("</b></p> ");
htmlText.append("<p class=\"qr\"><img src=\"cid:image\" title=\"").append(url).append("\" /></p> "); htmlText.append("<p class=\"qr\"><img src=\"cid:image\" title=\"").append(url).append("\" /></p> ");
@@ -392,7 +392,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
String emails, String iOSAppUrl, String androidAppUrl, Locale locale) throws MailException { String emails, String iOSAppUrl, String androidAppUrl, Locale locale) throws MailException {
StringBuilder occuredExceptions = new StringBuilder(); StringBuilder occuredExceptions = new StringBuilder();
String[] emailArray = emails.split(","); String[] emailArray = emails.split(",");
String leaderboardName = leaderboard.getName(); String leaderboardName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String eventId = event.getId().toString(); String eventId = event.getId().toString();
String logoUrl = null; String logoUrl = null;
List<ImageDescriptor> imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO); List<ImageDescriptor> imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO);
@@ -406,8 +406,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
try { try {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES; final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName, sendInvitationEmail(locale, toAddress, leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, B.get(locale, "buoyPingerAppName"), iOSAppUrl, event.getName(), RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, B.get(locale, "buoyPingerAppName"),
androidAppUrl, logoUrl); iOSAppUrl, androidAppUrl, logoUrl);
} catch (MailException e) { } catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n"); occuredExceptions.append(e.getMessage() + "\r\n");
} }
@@ -55,7 +55,6 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall; import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
@@ -70,7 +69,6 @@ import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndZeroRanks;
import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -82,10 +80,8 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ArrayListNavigableSet; import com.sap.sse.util.impl.ArrayListNavigableSet;
public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRankingTestBase {
private ArrayList<Series> series; protected Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
private Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds); ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds);
return new RegattaLeaderboardImpl(regatta, discardingRules); return new RegattaLeaderboardImpl(regatta, discardingRules);
} }
@@ -1250,19 +1246,6 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
checkScoresAfterSomeRaces(leaderboard, raceColumnsToConsider, scoresAfter6Races, later, c); checkScoresAfterSomeRaces(leaderboard, raceColumnsToConsider, scoresAfter6Races, later, c);
} }
private void checkScoresAfterSomeRaces(Leaderboard leaderboard, List<RaceColumn> raceColumnsToConsider,
double[][] scoresAfterNRaces, TimePoint timePoint, Competitor[] competitors) throws NoWindException {
for (int competitorIndex=0; competitorIndex<scoresAfterNRaces.length; competitorIndex++) {
final Set<RaceColumn> discardedRaceColumns = leaderboard.getResultDiscardingRule()
.getDiscardedRaceColumns(competitors[competitorIndex], leaderboard, raceColumnsToConsider, timePoint);
for (int raceColumnIndex=0; raceColumnIndex<raceColumnsToConsider.size(); raceColumnIndex++) {
assertEquals(scoresAfterNRaces[competitorIndex][raceColumnIndex],
leaderboard.getNetPoints(competitors[competitorIndex], raceColumnsToConsider.get(raceColumnIndex),
timePoint, discardedRaceColumns), 0.00000001);
}
}
}
@Test @Test
public void testTieBreakByMedalRaceScoreOnlyIfEqualNetScore() throws NoWindException { public void testTieBreakByMedalRaceScoreOnlyIfEqualNetScore() throws NoWindException {
Competitor[] c = createCompetitors(2).toArray(new Competitor[0]); Competitor[] c = createCompetitors(2).toArray(new Competitor[0]);
@@ -1863,177 +1846,6 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
assertEquals(0., leaderboard1.getNetPoints(c[3], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 assertEquals(0., leaderboard1.getNetPoints(c[3], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1
} }
private TimePoint createAndAttachTrackedRaces(Series theSeries, String fleetName, boolean withScores, Competitor[]... competitorLists) {
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
Iterator<? extends RaceColumn> columnIter = theSeries.getRaceColumns().iterator();
for (Competitor[] competitorList : competitorLists) {
RaceColumn raceColumn = columnIter.next();
final TrackedRace trackedRace;
if (withScores) {
trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitorList));
} else {
trackedRace = new MockedTrackedRaceWithStartTimeAndZeroRanks(now, Arrays.asList(competitorList));
}
raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace);
}
return later;
}
private void createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes(
Series theSeries, String fleetName, Competitor[][] competitorLists, TimePoint[] startTimes,
Map<Competitor, TimePoint>[] lastMarkPassingTimesForCompetitors) {
Iterator<? extends RaceColumn> columnIter = theSeries.getRaceColumns().iterator();
int i=0;
for (Competitor[] competitorList : competitorLists) {
RaceColumn raceColumn = columnIter.next();
final Map<Competitor, TimePoint> lastMarkPassingTimes = lastMarkPassingTimesForCompetitors[i];
final Waypoint start = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("Left StartBuoy"), new MarkImpl("Right StartBuoy"), "Start"));
final Waypoint finish = new WaypointImpl(new MarkImpl("FinishBuoy"));
TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(startTimes[i], Arrays.asList(competitorList)) {
private static final long serialVersionUID = 1L;
@Override
public NavigableSet<MarkPassing> getMarkPassings(Competitor competitor) {
ArrayListNavigableSet<MarkPassing> result = new ArrayListNavigableSet<>(new TimedComparator());
result.add(new MarkPassingImpl(lastMarkPassingTimes.get(competitor), finish, competitor));
return result;
}
};
trackedRace.getRace().getCourse().addWaypoint(0, start);
trackedRace.getRace().getCourse().addWaypoint(1, finish);
raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace);
i++;
}
}
private List<Competitor> createCompetitors(int numberOfCompetitorsToCreate) {
List<Competitor> result = new ArrayList<Competitor>();
for (int i=1; i<=numberOfCompetitorsToCreate; i++) {
result.add(createCompetitor("C"+i));
}
return result;
}
private Regatta createSimpleRegatta(final int numberOfRaces, final String regattaName, BoatClass boatClass, ScoringScheme scoringScheme) {
series = new ArrayList<Series>();
List<Fleet> fleets = new ArrayList<Fleet>();
fleets.add(new FleetImpl("Default"));
List<String> raceColumnNames = new ArrayList<String>();
for (int i = 1; i <= numberOfRaces; i++) {
raceColumnNames.add("R" + i);
}
Series defaultSeries = new SeriesImpl("Default", /* isMedal */false, /* isFleetsCanRunInParallel */true,
fleets, raceColumnNames, /* trackedRegattaRegistry */null);
series.add(defaultSeries);
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass,
/* startDate */null, /* endDate */null, series, /* persistent */false, scoringScheme, "123", null,
OneDesignRankingMetric::new);
return regatta;
}
private Regatta createRegatta(final int numberOfQualifyingRaces, String[] qualifyingFleetNames, final int numberOfFinalRaces,
String[] finalFleetNames, boolean medalRaceAndSeries, final int numberOfMedalRaces, final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) {
series = new ArrayList<Series>();
// -------- qualifying series ------------
if (qualifyingFleetNames != null && qualifyingFleetNames.length > 0) {
List<Fleet> qualifyingFleets = new ArrayList<Fleet>();
for (String qualifyingFleetName : qualifyingFleetNames) {
qualifyingFleets.add(new FleetImpl(qualifyingFleetName));
}
List<String> qualifyingRaceColumnNames = new ArrayList<String>();
for (int i = 1; i <= numberOfQualifyingRaces; i++) {
qualifyingRaceColumnNames.add("Q" + i);
}
Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets,
qualifyingRaceColumnNames, /* trackedRegattaRegistry */null);
series.add(qualifyingSeries);
}
// -------- final series ------------
if (finalFleetNames != null && finalFleetNames.length > 0) {
List<Fleet> finalFleets = new ArrayList<Fleet>();
int fleetOrdering = 1;
for (String finalFleetName : finalFleetNames) {
finalFleets.add(new FleetImpl(finalFleetName, fleetOrdering++));
}
List<String> finalRaceColumnNames = new ArrayList<String>();
for (int i = 1; i <= numberOfFinalRaces; i++) {
finalRaceColumnNames.add("F" + i);
}
Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(finalSeries);
}
if (medalRaceAndSeries) {
// ------------ medal --------------
List<Fleet> medalFleets = new ArrayList<Fleet>();
medalFleets.add(new FleetImpl("Medal"));
List<String> medalRaceColumnNames = new ArrayList<String>();
if(numberOfMedalRaces == 1) {
medalRaceColumnNames.add("M");
} else if(numberOfMedalRaces > 1) {
for (int i = 1; i <= numberOfMedalRaces; i++) {
medalRaceColumnNames.add("M" + i);
}
}
Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, /* isFleetsCanRunInParallel */ true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(medalSeries);
}
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
/*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme, "123", null, OneDesignRankingMetric::new);
return regatta;
}
private Regatta createRegattaWithEliminations(final int numberOfEliminations, final int[] numbersOfHeatsPerRound,
final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) {
series = new ArrayList<Series>();
// example for numbersOfHeatsPerRound: [8, 4, 2, 2]
for (int elimination=1; elimination<=numberOfEliminations; elimination++) {
int heatNumber = 1;
// create one elimination consisting of a number of rounds, each consisting of a number of heats
int roundNumber = 1;
for (int numberOfHeatsPerRound : numbersOfHeatsPerRound) {
final boolean isFinalRound = roundNumber == numbersOfHeatsPerRound.length;
// create one round as a series that has one fleet per heat
List<Fleet> fleetsInRound = new ArrayList<Fleet>();
for (int heatInRound=1; heatInRound<=numberOfHeatsPerRound; heatInRound++) {
final int ordering = numbersOfHeatsPerRound.length-roundNumber+1+
// in final round distinguish Final and Losers Final
(isFinalRound ? heatInRound-1 : 1);
fleetsInRound.add(new FleetImpl("Heat "+(heatNumber++), ordering));
}
List<String> raceColumnNameForRound = new ArrayList<String>();
raceColumnNameForRound.add("E"+elimination+"R"+roundNumber);
final String roundName;
if (numbersOfHeatsPerRound.length-roundNumber == 2) {
roundName = "Quarter-Final";
} else if (numbersOfHeatsPerRound.length-roundNumber == 1) {
roundName = "Semi-Final";
} else if (numbersOfHeatsPerRound.length-roundNumber == 0) {
roundName = "Final";
} else {
roundName = "Round "+roundNumber;
}
Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, /* isFleetsCanRunInParallel */ true, fleetsInRound,
raceColumnNameForRound, /* trackedRegattaRegistry */null);
if (isFinalRound) {
// last "Final" round; here, the fleets are contiguously scored
seriesForRound.setSplitFleetContiguousScoring(true);
}
series.add(seriesForRound);
roundNumber++;
}
}
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
/*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme,
/* ID */ "123", /* course area */ null, OneDesignRankingMetric::new);
return regatta;
}
/** /**
* See bug 1260. There must be a possibility to have ordered fleets that are scored such that the winner of the race of the best fleet * See bug 1260. There must be a possibility to have ordered fleets that are scored such that the winner of the race of the best fleet
* gets the best score in that column; and the winner of the second-best fleet gets the n-th best score in the column with n being the * gets the best score in that column; and the winner of the second-best fleet gets the n-th best score in the column with n being the
@@ -0,0 +1,233 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
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.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndZeroRanks;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.TimedComparator;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ArrayListNavigableSet;
public class LeaderboardScoringAndRankingTestBase extends AbstractLeaderboardTest {
protected ArrayList<Series> series;
protected Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds);
return new RegattaLeaderboardImpl(regatta, discardingRules);
}
protected void checkScoresAfterSomeRaces(Leaderboard leaderboard, List<RaceColumn> raceColumnsToConsider,
double[][] scoresAfterNRaces, TimePoint timePoint, Competitor[] competitors) throws NoWindException {
for (int competitorIndex=0; competitorIndex<scoresAfterNRaces.length; competitorIndex++) {
final Set<RaceColumn> discardedRaceColumns = leaderboard.getResultDiscardingRule()
.getDiscardedRaceColumns(competitors[competitorIndex], leaderboard, raceColumnsToConsider, timePoint);
for (int raceColumnIndex=0; raceColumnIndex<raceColumnsToConsider.size(); raceColumnIndex++) {
assertEquals(scoresAfterNRaces[competitorIndex][raceColumnIndex],
leaderboard.getNetPoints(competitors[competitorIndex], raceColumnsToConsider.get(raceColumnIndex),
timePoint, discardedRaceColumns), 0.00000001);
}
}
}
protected TimePoint createAndAttachTrackedRaces(Series theSeries, String fleetName, boolean withScores, Competitor[]... competitorLists) {
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
Iterator<? extends RaceColumn> columnIter = theSeries.getRaceColumns().iterator();
for (Competitor[] competitorList : competitorLists) {
RaceColumn raceColumn = columnIter.next();
final TrackedRace trackedRace;
if (withScores) {
trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitorList));
} else {
trackedRace = new MockedTrackedRaceWithStartTimeAndZeroRanks(now, Arrays.asList(competitorList));
}
raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace);
}
return later;
}
protected void createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes(
Series theSeries, String fleetName, Competitor[][] competitorLists, TimePoint[] startTimes,
Map<Competitor, TimePoint>[] lastMarkPassingTimesForCompetitors) {
Iterator<? extends RaceColumn> columnIter = theSeries.getRaceColumns().iterator();
int i=0;
for (Competitor[] competitorList : competitorLists) {
RaceColumn raceColumn = columnIter.next();
final Map<Competitor, TimePoint> lastMarkPassingTimes = lastMarkPassingTimesForCompetitors[i];
final Waypoint start = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("Left StartBuoy"), new MarkImpl("Right StartBuoy"), "Start"));
final Waypoint finish = new WaypointImpl(new MarkImpl("FinishBuoy"));
TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(startTimes[i], Arrays.asList(competitorList)) {
private static final long serialVersionUID = 1L;
@Override
public NavigableSet<MarkPassing> getMarkPassings(Competitor competitor) {
ArrayListNavigableSet<MarkPassing> result = new ArrayListNavigableSet<>(new TimedComparator());
result.add(new MarkPassingImpl(lastMarkPassingTimes.get(competitor), finish, competitor));
return result;
}
};
trackedRace.getRace().getCourse().addWaypoint(0, start);
trackedRace.getRace().getCourse().addWaypoint(1, finish);
raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace);
i++;
}
}
public static List<Competitor> createCompetitors(int numberOfCompetitorsToCreate) {
List<Competitor> result = new ArrayList<Competitor>();
for (int i=1; i<=numberOfCompetitorsToCreate; i++) {
result.add(createCompetitor("C"+i));
}
return result;
}
protected Regatta createSimpleRegatta(final int numberOfRaces, final String regattaName, BoatClass boatClass, ScoringScheme scoringScheme) {
series = new ArrayList<Series>();
List<Fleet> fleets = new ArrayList<Fleet>();
fleets.add(new FleetImpl("Default"));
List<String> raceColumnNames = new ArrayList<String>();
for (int i = 1; i <= numberOfRaces; i++) {
raceColumnNames.add("R" + i);
}
Series defaultSeries = new SeriesImpl("Default", /* isMedal */false, /* isFleetsCanRunInParallel */true,
fleets, raceColumnNames, /* trackedRegattaRegistry */null);
series.add(defaultSeries);
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass,
/* startDate */null, /* endDate */null, series, /* persistent */false, scoringScheme, "123", null,
OneDesignRankingMetric::new);
return regatta;
}
protected Regatta createRegatta(final int numberOfQualifyingRaces, String[] qualifyingFleetNames, final int numberOfFinalRaces,
String[] finalFleetNames, boolean medalRaceAndSeries, final int numberOfMedalRaces, final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) {
series = new ArrayList<Series>();
// -------- qualifying series ------------
if (qualifyingFleetNames != null && qualifyingFleetNames.length > 0) {
List<Fleet> qualifyingFleets = new ArrayList<Fleet>();
for (String qualifyingFleetName : qualifyingFleetNames) {
qualifyingFleets.add(new FleetImpl(qualifyingFleetName));
}
List<String> qualifyingRaceColumnNames = new ArrayList<String>();
for (int i = 1; i <= numberOfQualifyingRaces; i++) {
qualifyingRaceColumnNames.add("Q" + i);
}
Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets,
qualifyingRaceColumnNames, /* trackedRegattaRegistry */null);
series.add(qualifyingSeries);
}
// -------- final series ------------
if (finalFleetNames != null && finalFleetNames.length > 0) {
List<Fleet> finalFleets = new ArrayList<Fleet>();
int fleetOrdering = 1;
for (String finalFleetName : finalFleetNames) {
finalFleets.add(new FleetImpl(finalFleetName, fleetOrdering++));
}
List<String> finalRaceColumnNames = new ArrayList<String>();
for (int i = 1; i <= numberOfFinalRaces; i++) {
finalRaceColumnNames.add("F" + i);
}
Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(finalSeries);
}
if (medalRaceAndSeries) {
// ------------ medal --------------
List<Fleet> medalFleets = new ArrayList<Fleet>();
medalFleets.add(new FleetImpl("Medal"));
List<String> medalRaceColumnNames = new ArrayList<String>();
if(numberOfMedalRaces == 1) {
medalRaceColumnNames.add("M");
} else if(numberOfMedalRaces > 1) {
for (int i = 1; i <= numberOfMedalRaces; i++) {
medalRaceColumnNames.add("M" + i);
}
}
Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, /* isFleetsCanRunInParallel */ true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(medalSeries);
}
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
/*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme, "123", null, OneDesignRankingMetric::new);
return regatta;
}
protected Regatta createRegattaWithEliminations(final int numberOfEliminations, final int[] numbersOfHeatsPerRound,
final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) {
series = new ArrayList<Series>();
// example for numbersOfHeatsPerRound: [8, 4, 2, 2]
for (int elimination=1; elimination<=numberOfEliminations; elimination++) {
int heatNumber = 1;
// create one elimination consisting of a number of rounds, each consisting of a number of heats
int roundNumber = 1;
for (int numberOfHeatsPerRound : numbersOfHeatsPerRound) {
final boolean isFinalRound = roundNumber == numbersOfHeatsPerRound.length;
// create one round as a series that has one fleet per heat
List<Fleet> fleetsInRound = new ArrayList<Fleet>();
for (int heatInRound=1; heatInRound<=numberOfHeatsPerRound; heatInRound++) {
final int ordering = numbersOfHeatsPerRound.length-roundNumber+1+
// in final round distinguish Final and Losers Final
(isFinalRound ? heatInRound-1 : 1);
fleetsInRound.add(new FleetImpl("Heat "+(heatNumber++), ordering));
}
List<String> raceColumnNameForRound = new ArrayList<String>();
raceColumnNameForRound.add("E"+elimination+"R"+roundNumber);
final String roundName;
if (numbersOfHeatsPerRound.length-roundNumber == 2) {
roundName = "Quarter-Final";
} else if (numbersOfHeatsPerRound.length-roundNumber == 1) {
roundName = "Semi-Final";
} else if (numbersOfHeatsPerRound.length-roundNumber == 0) {
roundName = "Final";
} else {
roundName = "Round "+roundNumber;
}
Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, /* isFleetsCanRunInParallel */ true, fleetsInRound,
raceColumnNameForRound, /* trackedRegattaRegistry */null);
if (isFinalRound) {
// last "Final" round; here, the fleets are contiguously scored
seriesForRound.setSplitFleetContiguousScoring(true);
}
series.add(seriesForRound);
roundNumber++;
}
}
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
/*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme,
/* ID */ "123", /* course area */ null, OneDesignRankingMetric::new);
return regatta;
}
}
@@ -26,7 +26,29 @@ import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithM
*/ */
public abstract class ExpandableSortableColumn<C> extends LeaderboardSortableColumnWithMinMax<LeaderboardRowDTO, C> { public abstract class ExpandableSortableColumn<C> extends LeaderboardSortableColumnWithMinMax<LeaderboardRowDTO, C> {
private boolean enableExpansion; private boolean enableExpansion;
/**
* Set when the {@link #toggleExpansion()} method begins with its work. Three things can happen:
* <ol>
* <li>the column is to be collapsed. This can happen synchronously, and the flag is cleared again before the method
* returns</li>
* <li>the column is to be expanded and all information required for this is already available; again, this type
* of request can be handled synchronously, and the flag is cleared before the method returns</li>
* <li>the column is to be expanded but not all information required is available yet; it has to be requested using
* an asynchronous call; the method returns with the flag still set, and the callback will clear it when done</li>
* </ol>
*/
private boolean togglingInProcess; private boolean togglingInProcess;
/**
* If {@link #togglingInProcess} is {@code true} and a call to {@link #toggleExpansion()} is received,
* instead of running the method, the request is "queued" only by incrementing this counter. When the
* asynchronous task completes the running toggle action, the counter is decremented by one and
* {@link #toggleExpansion()} is called. This way, overlapping asynchronous requests triggered by calls
* to {@link #toggleExpansion()} are avoiided.
*/
private int numberOfQueuedToggleRequests;
private boolean suppressSortingOnce; private boolean suppressSortingOnce;
private final LeaderboardPanel leaderboardPanel; private final LeaderboardPanel leaderboardPanel;
private final Map<DetailType, AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?>> detailColumnsMap; private final Map<DetailType, AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?>> detailColumnsMap;
@@ -145,42 +167,63 @@ public abstract class ExpandableSortableColumn<C> extends LeaderboardSortableCol
* <p> * <p>
* *
* Precondition: this column must currently be contained in the {@link CellTable} showing the * Precondition: this column must currently be contained in the {@link CellTable} showing the
* {@link #leaderboardPanel leaderboard}. * {@link #leaderboardPanel leaderboard}.<p>
*
* When the method starts to toggle the expansion state, it sets the {@link #togglingInProcess} flag. When the
* method returns the {@link #isExpanded()} method yields the new expansion state. However, when the column contents
* need to be loaded asynchronously first, the expansion will not have affected the table structure yet and is
* still "pending." This state is indicated by the {@link #isTogglingInProcess()} method returning {@code true}
* after this method has returned. Only after the asynchronous data loading has happened and the table has
* actually been updated to reflect the expanded state, the {@link #isTogglingInProcess()} will then again
* return {@code false}.
*/ */
public void toggleExpansion() { public void toggleExpansion() {
if (isExpansionEnabled()) { if (isExpansionEnabled()) {
final boolean oldBusyState = getLeaderboardPanel().isBusy(); if (isTogglingInProcess()) {
getLeaderboardPanel().setBusyState(true); numberOfQueuedToggleRequests++;
setTogglingInProcess(true);
final CellTable<LeaderboardRowDTO> table = getLeaderboardPanel().getLeaderboardTable();
if (isExpanded()) {
for (AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?> column : getAllVisibleChildren()) {
getLeaderboardPanel().removeColumn(column); // removes only the children currently displayed
}
getLeaderboardPanel().setBusyState(false);
setTogglingInProcess(false);
// important: toggle expanded state after asking for all visible children
setExpanded(!isExpanded());
} else { } else {
// important: toggle expanded state before asking for all visible children final boolean oldBusyState = getLeaderboardPanel().isBusy();
setExpanded(!isExpanded()); getLeaderboardPanel().setBusyState(true);
ensureExpansionDataIsLoaded(new Runnable() { setTogglingInProcess(true);
public void run() { final CellTable<LeaderboardRowDTO> table = getLeaderboardPanel().getLeaderboardTable();
int insertIndex = table.getColumnIndex(ExpandableSortableColumn.this); if (isExpanded()) {
// The check "insertIndex != -1" is necessary, because the child-columns might be deleted asynchronously for (AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?> column : getAllVisibleChildren()) {
// while toggling the columns. getLeaderboardPanel().removeColumn(column); // removes only the children currently displayed
if (insertIndex != -1) {
insertIndex++;
for (AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?> column : getAllVisibleChildren()) {
column.updateMinMax();
getLeaderboardPanel().insertColumn(insertIndex++, column);
}
getLeaderboardPanel().getLeaderboardTable().redraw();
}
getLeaderboardPanel().setBusyState(oldBusyState);
setTogglingInProcess(false);
} }
}); getLeaderboardPanel().setBusyState(false);
setTogglingInProcess(false);
// important: toggle expanded state after asking for all visible children
setExpanded(!isExpanded());
} else {
// important: toggle expanded state before asking for all visible children
setExpanded(!isExpanded());
ensureExpansionDataIsLoaded(new Runnable() {
public void run() {
// column may have been collapsed meanwhile, so that the columns must not be added to the table right now
if (isExpanded()) {
int insertIndex = table.getColumnIndex(ExpandableSortableColumn.this);
// The check "insertIndex != -1" is necessary, because the child-columns might be deleted
// asynchronously while toggling the columns.
if (insertIndex != -1) {
insertIndex++;
for (AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?> column : getAllVisibleChildren()) {
column.updateMinMax();
if (table.getColumnIndex(column) < 0) {
getLeaderboardPanel().insertColumn(insertIndex++, column);
}
}
getLeaderboardPanel().getLeaderboardTable().redraw();
}
}
getLeaderboardPanel().setBusyState(oldBusyState);
setTogglingInProcess(false);
if (numberOfQueuedToggleRequests > 0) {
numberOfQueuedToggleRequests--;
toggleExpansion();
}
}
});
}
} }
} }
} }
@@ -2484,7 +2484,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
} }
} }
/** /**
* Due to a course change, a race may change its number of legs. All expanded race columns that show leg columns and * Due to a course change, a race may change its number of legs. All expanded race columns that show leg columns and
* whose leg count changed need to be collapsed before the leaderboard is replaced, and expanded afterwards again. * whose leg count changed need to be collapsed before the leaderboard is replaced, and expanded afterwards again.
* Race columns whose toggling is {@link ExpandableSortableColumn#isTogglingInProcess() currently in progress} are * Race columns whose toggling is {@link ExpandableSortableColumn#isTogglingInProcess() currently in progress} are
@@ -0,0 +1,111 @@
package com.sap.sailing.server.test;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl;
import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl;
import com.sap.sailing.domain.abstractlog.race.state.RaceState;
import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.RacingProcedureFactoryImpl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.NationalityImpl;
import com.sap.sailing.domain.base.impl.PersonImpl;
import com.sap.sailing.domain.base.impl.TeamImpl;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.test.LeaderboardScoringAndRankingTestBase;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTestBase {
/**
* See also bug 3794: make sure that different variants of scores and max points reasons / penalties are applied to the
* leaderboard.
*/
@Test
public void testApplicationOfScoresFromRaceLog() throws NoWindException {
final RacingEventService service = new RacingEventServiceImpl();
final List<Competitor> competitors = new ArrayList<>();
for (int i=0; i<20; i++) {
final String competitorName = "C"+i;
competitors.add(service.getBaseDomainFactory().getCompetitorStore().getOrCreateCompetitor(UUID.randomUUID(),
competitorName, /* displayColor */ Color.RED, /* email */ null, /* flagImageURI */ null,
new TeamImpl("STG", Collections.singleton(
new PersonImpl(competitorName, new NationalityImpl("GER"),
/* dateOfBirth */ null, "This is famous "+competitorName)),
new PersonImpl("Rigo van Maas", new NationalityImpl("NED"),
/* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat",
new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null),
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null));
}
final Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1,
new String[] { "Default" },
/* medal */false, /* medal */ 0, "testOneStartedRaceWithDifferentScores",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
TrackedRace f1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors);
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
f1Column.setTrackedRace(f1Column.getFleets().iterator().next(), f1);
final Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
service.addLeaderboard(leaderboard); // should add a RaceLogScoringReplicator as listener to the leaderboard
service.addRegattaWithoutReplication(regatta);
final Map<Competitor, Double> scores = new HashMap<>();
final Map<Competitor, MaxPointsReason> mprs = new HashMap<>();
int oneBasedRank = 1;
final CompetitorResults results = new CompetitorResultsImpl();
for (final Competitor c : competitors) {
final MaxPointsReason mpr = new MaxPointsReason[] { null, MaxPointsReason.NONE, MaxPointsReason.DNF, MaxPointsReason.OCS }[oneBasedRank%4];
final Double score = oneBasedRank%5 == 0 ? null : 20*Math.random();
scores.put(c, score);
mprs.put(c, mpr);
results.add(new CompetitorResultImpl(c.getId(), c.getName(),
oneBasedRank++, mpr, score, /* finishingTime */ null, /* comment */ null));
}
final RaceLog f1RaceLog = f1Column.getRaceLog(f1Column.getFleets().iterator().next());
final LogEventAuthorImpl author = new LogEventAuthorImpl("Axel", 0);
final RaceState f1RaceState = new RaceStateImpl(service, f1RaceLog, author,
new RacingProcedureFactoryImpl(author, new EmptyRegattaConfiguration()));
f1RaceState.setFinishPositioningListChanged(now, results);
final List<Competitor> rankedCompetitorsBeforeApplying = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(competitors, rankedCompetitorsBeforeApplying); // no effects of preliminary results list yet
f1RaceState.setFinishPositioningConfirmed(now);
for (final Competitor c : competitors) {
assertEquals(scores.get(c)==null?competitors.indexOf(c)+1:scores.get(c), leaderboard.getTotalPoints(c, f1Column, now), 0.00000001);
}
final List<Competitor> expectedNewOrder = new ArrayList<>(competitors);
expectedNewOrder.sort((c1, c2)->
new Double(scores.get(c1)==null?competitors.indexOf(c1)+1:scores.get(c1)).compareTo(
new Double(scores.get(c2)==null?competitors.indexOf(c2)+1:scores.get(c2))));
final List<Competitor> rankedCompetitorsAfterApplying = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(expectedNewOrder, rankedCompetitorsAfterApplying);
}
}
@@ -27,7 +27,7 @@ boatClassUpcomingRaceSubject=A race for boat class {0} will start soon
# 0: boatClassName, 1: raceDescription, 2: time # 0: boatClassName, 1: raceDescription, 2: time
boatClassUpcomingRaceBody=For boat class {0} {1} will start approximately at {2}. boatClassUpcomingRaceBody=For boat class {0} {1} will start approximately at {2}.
subscriptionInformation=You are receiving notifications from SAP Sailing Analytics. To turn off notifications, please visit user your profile at the link below. subscriptionInformation=You are receiving notifications from SAP Sailing Analytics. To turn off notifications, please visit your user profile at the link below.
userProfile=User Profile userProfile=User Profile
imprint=Imprint imprint=Imprint
privacy=Privacy privacy=Privacy
@@ -1,7 +1,5 @@
package com.sap.sailing.server.impl; package com.sap.sailing.server.impl;
import java.util.concurrent.Callable;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult; import com.sap.sailing.domain.abstractlog.race.CompetitorResult;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -15,7 +13,6 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogIdentifier;
@@ -148,21 +145,13 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
if (positioningList != null) { if (positioningList != null) {
for (CompetitorResult positionedCompetitor : positioningList) { for (CompetitorResult positionedCompetitor : positioningList) {
Competitor competitor = service.getBaseDomainFactory().getExistingCompetitorById(positionedCompetitor.getCompetitorId()); Competitor competitor = service.getBaseDomainFactory().getExistingCompetitorById(positionedCompetitor.getCompetitorId());
if (positionedCompetitor.getMaxPointsReason().equals(MaxPointsReason.NONE)) { int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor);
try { correctScoreInLeaderboard(leaderboard, raceColumn, timePoint, numberOfCompetitorsInRace,
resetMaxPointsReasonIfNecessary(leaderboard, raceColumn, timePoint, competitor); competitor, rankByRaceCommittee, positionedCompetitor.getScore());
int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor); setMaxPointsReasonInLeaderboardIfNecessary(leaderboard, raceColumn, timePoint, positionedCompetitor, competitor);
correctScoreInLeaderboard(leaderboard, raceColumn, timePoint, numberOfCompetitorsInRace,
competitor, rankByRaceCommittee, positionedCompetitor.getScore());
} catch (NoWindException ex) {
ex.printStackTrace();
}
} else {
setMaxPointsReasonInLeaderboardIfNecessary(leaderboard, raceColumn, timePoint, positionedCompetitor, competitor);
}
} }
//Since the metadata update is used by the Sailing suite to determine the final state of a race, it has to be triggered, even though // Since the metadata update is used by the Sailing suite to determine the final state of a race, it has to
//no score correction was performed // be triggered, even though no score correction may have been performed
applyMetadataUpdate(leaderboard, timePoint, COMMENT_TEXT_ON_SCORE_CORRECTION); applyMetadataUpdate(leaderboard, timePoint, COMMENT_TEXT_ON_SCORE_CORRECTION);
} }
} }
@@ -170,9 +159,9 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
private boolean setMaxPointsReasonInLeaderboardIfNecessary(Leaderboard leaderboard, RaceColumn raceColumn, private boolean setMaxPointsReasonInLeaderboardIfNecessary(Leaderboard leaderboard, RaceColumn raceColumn,
TimePoint timePoint, CompetitorResult positionedCompetitor, Competitor competitor) { TimePoint timePoint, CompetitorResult positionedCompetitor, Competitor competitor) {
boolean scoreHasBeenCorrected = false; boolean scoreHasBeenCorrected = false;
MaxPointsReason trackedMaxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint); MaxPointsReason oldMaxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint);
MaxPointsReason maxPointsReasonByRaceCommittee = positionedCompetitor.getMaxPointsReason(); MaxPointsReason maxPointsReasonByRaceCommittee = positionedCompetitor.getMaxPointsReason();
if (!maxPointsReasonByRaceCommittee.equals(trackedMaxPointsReason)) { if (!Util.equalsWithNull(maxPointsReasonByRaceCommittee, oldMaxPointsReason)) {
applyMaxPointsReasonOperation(leaderboard, raceColumn, competitor, maxPointsReasonByRaceCommittee, timePoint); applyMaxPointsReasonOperation(leaderboard, raceColumn, competitor, maxPointsReasonByRaceCommittee, timePoint);
scoreHasBeenCorrected = true; scoreHasBeenCorrected = true;
} }
@@ -181,16 +170,11 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
private void correctScoreInLeaderboard(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint timePoint, private void correctScoreInLeaderboard(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint timePoint,
final int numberOfCompetitorsInRace, final int numberOfCompetitorsInRace,
Competitor competitor, int rankByRaceCommittee, Double optionalExplicitScore) throws NoWindException { Competitor competitor, int rankByRaceCommittee, Double optionalExplicitScore) {
final Double scoreByRaceCommittee; final Double scoreByRaceCommittee;
if (optionalExplicitScore == null) { if (optionalExplicitScore == null) {
scoreByRaceCommittee = leaderboard.getScoringScheme().getScoreForRank(leaderboard, raceColumn, competitor, scoreByRaceCommittee = leaderboard.getScoringScheme().getScoreForRank(leaderboard, raceColumn, competitor,
rankByRaceCommittee, new Callable<Integer>() { rankByRaceCommittee, ()->numberOfCompetitorsInRace, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), timePoint);
@Override
public Integer call() {
return numberOfCompetitorsInRace;
}
}, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), timePoint);
} else { } else {
scoreByRaceCommittee = optionalExplicitScore; scoreByRaceCommittee = optionalExplicitScore;
} }
@@ -198,15 +182,6 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
applyScoreCorrectionOperation(leaderboard, raceColumn, competitor, scoreByRaceCommittee, timePoint); applyScoreCorrectionOperation(leaderboard, raceColumn, competitor, scoreByRaceCommittee, timePoint);
} }
private boolean resetMaxPointsReasonIfNecessary(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint timePoint, Competitor competitor) {
boolean scoreHasBeenCorrected = false;
if (!leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint).equals(MaxPointsReason.NONE)) {
applyMaxPointsReasonOperation(leaderboard, raceColumn, competitor, MaxPointsReason.NONE, timePoint);
scoreHasBeenCorrected = true;
}
return scoreHasBeenCorrected;
}
private void applyScoreCorrectionOperation(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, Double correctedScore, TimePoint timePoint) { private void applyScoreCorrectionOperation(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, Double correctedScore, TimePoint timePoint) {
RacingEventServiceOperation<?> operation = new UpdateLeaderboardScoreCorrection(leaderboard.getName(), raceColumn.getName(), competitor.getId().toString(), correctedScore, timePoint); RacingEventServiceOperation<?> operation = new UpdateLeaderboardScoreCorrection(leaderboard.getName(), raceColumn.getName(), competitor.getId().toString(), correctedScore, timePoint);
service.apply(operation); service.apply(operation);
@@ -22,8 +22,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:ellipsize="middle"
android:gravity="center" android:gravity="center"
android:maxLines="1" android:singleLine="true"
android:textColor="@color/toolbar_text_color" android:textColor="@color/toolbar_text_color"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -32,8 +33,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:ellipsize="middle"
android:gravity="center" android:gravity="center"
android:maxLines="1" android:singleLine="true"
android:textColor="@color/toolbar_text_color" android:textColor="@color/toolbar_text_color"
android:textSize="12sp" /> android:textSize="12sp" />
</LinearLayout> </LinearLayout>