Merge branch 'master' into bug3785

This commit is contained in:
Axel Uhl committed 2016-07-20 18:38:20 +02:00
commit 1dd5db458b
8 files changed
+386 -248

No files matched your search

@@ -287,7 +287,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
for (Competitor competitor : competitors) {
final String toAddress = competitor.getEmail();
if (toAddress != null) {
String leaderboardName = leaderboard.getName();
String leaderboardName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String competitorName = competitor.getName();
String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash,
event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING,
@@ -299,8 +299,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
try {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName, competitorName, url, B.get(locale, "sailInSightAppName"),
iOSAppUrl, androidAppUrl, logoUrl);
sendInvitationEmail(locale, toAddress, leaderboardName, event.getName(), competitorName, url,
B.get(locale, "sailInSightAppName"), iOSAppUrl, androidAppUrl, logoUrl);
} catch (MailException e) {
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,
String url, String appName, String iOSAppUrl, String androidAppUrl, String logoUrl) throws MailException {
private void sendInvitationEmail(Locale locale, final String toAddress, String leaderboardName, String eventName,
String invitee, String url, String appName, String iOSAppUrl, String androidAppUrl, String logoUrl) throws MailException {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
String subject = String.format("%s %s", B.get(locale, "trackingInvitationFor"), invitee);
@@ -325,8 +325,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
StringBuilder htmlText = new StringBuilder();
htmlText.append("<!doctype html>\n");
htmlText.append("<html>");
htmlText.append("<head><title>");
htmlText.append(B.get(locale, "welcomeTo")).append(" ").append(leaderboardName).append("</title>");
final String welcomeText = B.get(locale, "welcomeTo")+" "+eventName+", "+leaderboardName;
htmlText.append("<head><title>").append(welcomeText).append("</title>");
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:hover {background-color:#2b618e;border:1px solid #204d74;}\n");
@@ -338,7 +338,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
if (hasLogoUrl) {
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)
.append("</b></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 {
StringBuilder occuredExceptions = new StringBuilder();
String[] emailArray = emails.split(",");
String leaderboardName = leaderboard.getName();
String leaderboardName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String eventId = event.getId().toString();
String logoUrl = null;
List<ImageDescriptor> imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO);
@@ -406,8 +406,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
try {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, B.get(locale, "buoyPingerAppName"), iOSAppUrl,
androidAppUrl, logoUrl);
event.getName(), RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, B.get(locale, "buoyPingerAppName"),
iOSAppUrl, androidAppUrl, logoUrl);
} catch (MailException e) {
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.Leaderboard;
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.HighPoint;
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.regattalog.impl.EmptyRegattaLogStore;
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.TrackedRegattaRegistry;
@@ -82,10 +80,8 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ArrayListNavigableSet;
public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
private ArrayList<Series> series;
private Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRankingTestBase {
protected Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds);
return new RegattaLeaderboardImpl(regatta, discardingRules);
}
@@ -1250,19 +1246,6 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
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
public void testTieBreakByMedalRaceScoreOnlyIfEqualNetScore() throws NoWindException {
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
}
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
* 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;
}
}
@@ -166,16 +166,21 @@ public abstract class ExpandableSortableColumn<C> extends LeaderboardSortableCol
setExpanded(!isExpanded());
ensureExpansionDataIsLoaded(new Runnable() {
public void run() {
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();
getLeaderboardPanel().insertColumn(insertIndex++, column);
// 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().getLeaderboardTable().redraw();
}
getLeaderboardPanel().setBusyState(oldBusyState);
setTogglingInProcess(false);
@@ -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
* 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
@@ -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);
}
}
@@ -1,7 +1,5 @@
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.CompetitorResults;
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.RaceColumnListener;
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.ResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
@@ -148,21 +145,13 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
if (positioningList != null) {
for (CompetitorResult positionedCompetitor : positioningList) {
Competitor competitor = service.getBaseDomainFactory().getExistingCompetitorById(positionedCompetitor.getCompetitorId());
if (positionedCompetitor.getMaxPointsReason().equals(MaxPointsReason.NONE)) {
try {
resetMaxPointsReasonIfNecessary(leaderboard, raceColumn, timePoint, competitor);
int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor);
correctScoreInLeaderboard(leaderboard, raceColumn, timePoint, numberOfCompetitorsInRace,
competitor, rankByRaceCommittee, positionedCompetitor.getScore());
} catch (NoWindException ex) {
ex.printStackTrace();
}
} else {
setMaxPointsReasonInLeaderboardIfNecessary(leaderboard, raceColumn, timePoint, positionedCompetitor, competitor);
}
int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor);
correctScoreInLeaderboard(leaderboard, raceColumn, timePoint, numberOfCompetitorsInRace,
competitor, rankByRaceCommittee, positionedCompetitor.getScore());
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
//no score correction was performed
// 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 no score correction may have been performed
applyMetadataUpdate(leaderboard, timePoint, COMMENT_TEXT_ON_SCORE_CORRECTION);
}
}
@@ -170,9 +159,9 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
private boolean setMaxPointsReasonInLeaderboardIfNecessary(Leaderboard leaderboard, RaceColumn raceColumn,
TimePoint timePoint, CompetitorResult positionedCompetitor, Competitor competitor) {
boolean scoreHasBeenCorrected = false;
MaxPointsReason trackedMaxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint);
MaxPointsReason oldMaxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint);
MaxPointsReason maxPointsReasonByRaceCommittee = positionedCompetitor.getMaxPointsReason();
if (!maxPointsReasonByRaceCommittee.equals(trackedMaxPointsReason)) {
if (!Util.equalsWithNull(maxPointsReasonByRaceCommittee, oldMaxPointsReason)) {
applyMaxPointsReasonOperation(leaderboard, raceColumn, competitor, maxPointsReasonByRaceCommittee, timePoint);
scoreHasBeenCorrected = true;
}
@@ -181,16 +170,11 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
private void correctScoreInLeaderboard(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint timePoint,
final int numberOfCompetitorsInRace,
Competitor competitor, int rankByRaceCommittee, Double optionalExplicitScore) throws NoWindException {
Competitor competitor, int rankByRaceCommittee, Double optionalExplicitScore) {
final Double scoreByRaceCommittee;
if (optionalExplicitScore == null) {
scoreByRaceCommittee = leaderboard.getScoringScheme().getScoreForRank(leaderboard, raceColumn, competitor,
rankByRaceCommittee, new Callable<Integer>() {
@Override
public Integer call() {
return numberOfCompetitorsInRace;
}
}, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), timePoint);
rankByRaceCommittee, ()->numberOfCompetitorsInRace, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), timePoint);
} else {
scoreByRaceCommittee = optionalExplicitScore;
}
@@ -198,15 +182,6 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
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) {
RacingEventServiceOperation<?> operation = new UpdateLeaderboardScoreCorrection(leaderboard.getName(), raceColumn.getName(), competitor.getId().toString(), correctedScore, timePoint);
service.apply(operation);
@@ -22,8 +22,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:ellipsize="middle"
android:gravity="center"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/toolbar_text_color"
android:textSize="16sp" />
@@ -32,8 +33,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:ellipsize="middle"
android:gravity="center"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/toolbar_text_color"
android:textSize="12sp" />
</LinearLayout>