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

Conflicts:
	java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode
only).launch
	java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
	java/com.sap.sailing.gwt.ui/SailingGWT.launch
	java/com.sap.sse.security.ui/Security UI sdm.launch
This commit is contained in:
Benjamin Barth committed 2015-09-23 15:45:40 +02:00
commit cea18823c7
47 files changed
+1108 -222

No files matched your search

@@ -31,8 +31,6 @@ public class WindBotDataRetriever implements TimeListener, RaceSelectionChangeLi
private final int ONE_HOUR_IN_MILLISECONDS = 1000*60*60;
private final String LODA_WIND_CHART_DATA_CATEGORY = "loadWindChartData";
private boolean didInitialLoading;
public WindBotDataRetriever(SailingServiceAsync sailingService) {
this.sailingService = sailingService;
@@ -35,11 +35,11 @@ public class DeclinationServiceTest extends AbstractDeclinationTest {
@Test
public void testDeclinationQueryNotMatchedInStore() throws IOException, ClassNotFoundException, ParseException {
Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2016-02-03").getTime()),
Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2019-02-03").getTime()),
new DegreePosition(51, -5), /* timeoutForOnlineFetchInMilliseconds */ 30000);
assertNotNull(result);
assertEquals(-2.41498, result.getBearing().getDegrees(), 0.0001);
assertEquals(0.15318, result.getAnnualChange().getDegrees(), 0.0001);
assertEquals(-1.9565, result.getBearing().getDegrees(), 0.0001);
assertEquals(0.15244, result.getAnnualChange().getDegrees(), 0.0001);
}
@Test
@@ -20,11 +20,13 @@ public enum BoatClassMasterdata {
A_CAT ("A-Catamaran", true, 5.49, 2.30, BoatHullType.CATAMARAN, false, "A-Cat", "ACat", "A-Class Catamaran"),
ALBIN_EXPRESS ("Albin Express", true, 7.77, 2.50, BoatHullType.MONOHULL, false),
ALBIN_BALLAD ("Albin Ballad", true, 9.12, 2.95, BoatHullType.MONOHULL, false),
B_ONE ("B/ONE", true, 7.80, 2.49, BoatHullType.MONOHULL, true, "B-ONE"),
CANOE_IC ("International Canoe", true, 5.20, 1.01, BoatHullType.MONOHULL, true, "Canoe IC", "Canoe-IC", "IC", "Kanu IC", "International Canoe", "International 10 Sq.m. Sailing Canoe"),
CANOE_TAIFUN ("Canoe Taifun", true, 5.20, 1.32, BoatHullType.MONOHULL, false, "Taifun", "Taifun Kanu", "Kanu Taifun"),
B_ONE ("B/ONE", true, 7.80, 2.49, BoatHullType.MONOHULL, true, "B-ONE"),
CONTENDER ("Contender", true, 4.88, 1.42, BoatHullType.MONOHULL, false),
D_ONE ("D-One", true, 4.23, 2.31, BoatHullType.MONOHULL, true, "Devoti D-One", "DOne", "D_One"),
DRAGON_INT ("Dragon Int.", true, 8.89, 1.96, BoatHullType.MONOHULL, true, "Drachen", "Dragon"),
DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL, true),
EXTREME_40 ("Extreme 40", false, 12.2, 6.60, BoatHullType.CATAMARAN, true, "Extreme-40", "Extreme40", "ESS40"),
D_35 ("D35", false, 10.81, 6.89, BoatHullType.CATAMARAN, false),
ELLIOTT_6M ("Elliott 6m", true, 6.0, 2.35, BoatHullType.MONOHULL, true, "Elliott6m"),
@@ -32,6 +34,7 @@ public enum BoatClassMasterdata {
F_18 ("Formula 18", true, 6.85, 2.25, BoatHullType.CATAMARAN, true, "F18", "F-18"),
FARR_30 ("Farr 30", true, 9.42, 3.08, BoatHullType.MONOHULL, true, "F30", "F-30", "Farr-30"),
FINN ("Finn", true, 4.50, 1.51, BoatHullType.MONOHULL, false),
FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL, true),
FLYING_PHANTOM ("Flying Phantom", true, 5.52, 3.00, BoatHullType.CATAMARAN, true),
FOLKBOAT ("Folkboat", true, 7.68, 2.20, BoatHullType.MONOHULL, false, "Folke", "Folkeboot"),
FUN ("FUN", true, 7.20, 2.45, BoatHullType.MONOHULL, true, "FUN O.D.", "FUN OD", "Open FUN"),
@@ -40,6 +43,7 @@ public enum BoatClassMasterdata {
H_BOAT ("H-Boat", true, 8.28, 2.18, BoatHullType.MONOHULL, true, "HB"),
HOBIE_TIGER ("Hobie Tiger", true, 5.51, 2.60, BoatHullType.CATAMARAN, true),
HOBIE_WILD_CAT ("Hobie Wild Cat", true, 5.49, 2.59, BoatHullType.CATAMARAN, true, "Hobie Wild Cat F18"),
INTERNATIONAL_14("International 14", true, 4.27, 1.83, BoatHullType.MONOHULL, true, "I14", "Int.14", "Int14"),
J22 ("J/22", true, 6.86, 2.44, BoatHullType.MONOHULL, true, "J22", "J-22"),
J24 ("J/24", true, 7.32, 2.67, BoatHullType.MONOHULL, true, "J24", "J-24"),
J70 ("J/70", true, 6.93, 2.25, BoatHullType.MONOHULL, true, "J70", "J-70"),
@@ -58,9 +62,12 @@ public enum BoatClassMasterdata {
NACRA_17 ("Nacra 17", true, 5.25, 2.59, BoatHullType.CATAMARAN, true, "N17", "Nacra-17"),
O_JOLLE ("O-Jolle", true, 5.00, 1.66, BoatHullType.MONOHULL, false, "O Jolle", "OJolle", "Olympiajolle"),
OK ("OK Dinghy", true, 5.25, 2.59, BoatHullType.MONOHULL, false, "OK-Dinghy", "OK-Jolle", "OK"),
OPEN_BIC("O'pen BIC", true, 2.75, 1.14, BoatHullType.MONOHULL, false, "OpenBIC"),
OPTIMIST ("Optimist", true, 2.34, 1.07, BoatHullType.MONOHULL, false, "Opti", "Optimist Dinghy"),
PIRATE ("Pirate", true, 5.00, 1.61, BoatHullType.MONOHULL, false, "Pirat", "Piraten"),
PLATU_25 ("Platu 25", true, 7.53, 2.62, BoatHullType.MONOHULL, true, "Platu", "Platu-25", "PLA", "B25"),
PWA ("PWA", true, 5.0, 0.6, BoatHullType.MONOHULL, true, "Professional Windsurfers Association", "PWA World Tour"),
RC44 ("RC44", true, 13.35, 2.75, BoatHullType.MONOHULL, true),
RS_X ("RS:X", true, 2.86, 0.93, BoatHullType.SURFERBOARD, false, "RS-X", "RSX", "RS:X"),
RS_FEVA ("RS Feva", true, 3.64, 1.42, BoatHullType.MONOHULL, true, "RSFeva"),
SONAR ("Sonar", true, 7.01, 2.39, BoatHullType.MONOHULL, true),
@@ -71,12 +78,6 @@ public enum BoatClassMasterdata {
TORNADO ("Tornado Catamaran", true, 6.10, 3.02, BoatHullType.CATAMARAN, true, "Tornado", "Tornado Cat"),
TRIAS ("Trias", true, 9.20, 2.12, BoatHullType.MONOHULL, true),
X_99 ("X-99", true, 9.96, 2.95, BoatHullType.MONOHULL, true, "X99"),
CONTENDER ("Contender", true, 4.88, 1.42, BoatHullType.MONOHULL, false),
RC44 ("RC44", true, 13.35, 2.75, BoatHullType.MONOHULL, true),
FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL, true),
DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL, true),
INTERNATIONAL_14("International 14", true, 4.27, 1.83, BoatHullType.MONOHULL, true, "I14", "Int.14", "Int14"),
OPEN_BIC("O'pen BIC", true, 2.75, 1.14, BoatHullType.MONOHULL, false, "OpenBIC"),
// multi-class "boat classes"; to be replaced at some later point in time by something like a RegattaClass
ORC ("ORC", true, 13.83, 3.91, BoatHullType.MONOHULL, true),
@@ -3,5 +3,5 @@ package com.sap.sailing.domain.common;
public enum ScoringSchemeType {
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, HIGH_POINT_WINNER_GETS_EIGHT,
HIGH_POINT_WINNER_GETS_EIGHT_AND_INTERPOLATION, HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT
HIGH_POINT_WINNER_GETS_EIGHT_AND_INTERPOLATION, HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT, LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07
}
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.tracking.impl.TimedComparator;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util;
/**
* Comparator sorting by pass, then by {@link AbstractLogEventAuthor}, then by {@link RaceLogEvent#getCreatedAt()}
@@ -43,7 +44,7 @@ public class LogEventComparator implements Comparator<Timed>, Serializable {
if (result != 0) return result;
//compare logical timepoints
result = e1.getLogicalTimePoint().compareTo(e2.getLogicalTimePoint());
result = Util.compareToWithNull(e1.getLogicalTimePoint(), e2.getLogicalTimePoint(), /* nullIsLess */ false);
if (result != 0) return result;
//compare ids
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* Analysis returns the most recent {@link RaceLogFlagEvent}s.
@@ -42,7 +43,7 @@ public class LastFlagsFinder extends RaceLogAnalyzer<List<RaceLogFlagEvent>> {
List<RaceLogFlagEvent> result = new ArrayList<RaceLogFlagEvent>();
TimePoint logicalTime = flagEvent.getLogicalTimePoint();
while (flagEvent.getLogicalTimePoint().equals(logicalTime)) {
while (Util.compareToWithNull(flagEvent.getLogicalTimePoint(), logicalTime, /* nullIsLess */ false) == 0) {
result.add(flagEvent);
flagEvent = getNextFlagEvent(iterator);
if (flagEvent == null) {
@@ -84,7 +85,7 @@ public class LastFlagsFinder extends RaceLogAnalyzer<List<RaceLogFlagEvent>> {
Collections.sort(sortedEvents, new Comparator<RaceLogFlagEvent>() {
@Override
public int compare(RaceLogFlagEvent left, RaceLogFlagEvent right) {
int result = right.getLogicalTimePoint().compareTo(left.getLogicalTimePoint());
int result = Util.compareToWithNull(right.getLogicalTimePoint(), left.getLogicalTimePoint(), /* nullIsLess */ false);
if (result == 0) {
result = Boolean.valueOf(right.isDisplayed()).compareTo(left.isDisplayed());
if (result == 0) {
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -69,6 +70,7 @@ 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;
@@ -293,10 +295,14 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
}
// expect all results to be valid because a fleet will have its score counted even if not all fleets have raced;
// see discussion for bug 961, but more importantly later on bug 1023.
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
for (final Competitor competitor : competitors) {
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, competitor, later));
}
// now add a score correction for Q2/Blue to make it count:
leaderboard.getScoreCorrection().correctScore(competitors.get(9), q2Column, 42.);
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
for (final Competitor competitor : competitors) {
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, competitor, later));
}
// the new order in Q2 expected to be { (9, 3), ... } (we don't know about any competitor in q2Blue but #9
// therefore the new total points for #9 are
// { 9: 5+42=47 }
@@ -644,7 +650,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
/* medal */ false, "testTieBreakWithTwoVersusOneWins",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001);
assertEquals(Arrays.asList(new Competitor[] { c[0], c[1], c[2] }), rankedCompetitors);
@@ -663,12 +669,382 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
/* medal */ false, "testTieBreakWithTwoVersusOneSeconds",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3, f4, f5, f6);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5, f6);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001);
assertTrue(rankedCompetitors.indexOf(c[0]) == rankedCompetitors.indexOf(c[1])-1);
}
@Test
public void testBasicElminationScoringScheme() throws NoWindException {
Regatta regatta = createRegattaWithEliminations(1, new int[] { 8, 4, 2, 2 }, "testBasicElminationScoringScheme",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
Competitor[] c = createCompetitors(64).toArray(new Competitor[64]);
// first round with 64 competitors, eight per heat:
Competitor[][] competitorsForHeatsInRound1 = new Competitor[8][];
TimePoint later = null;
for (int heat=0; heat<8; heat++) {
competitorsForHeatsInRound1[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInRound1[heat][i] = c[8*heat+i];
}
later = createAndAttachTrackedRaces(series.get(0), "Heat "+(heat+1), /* withScores */ true, competitorsForHeatsInRound1[heat]);
}
// quarter-finals has promoted top four competitors of first round in heats with eight competitors each:
Competitor[][] competitorsForHeatsInQuarterFinals = new Competitor[4][];
for (int heat=0; heat<4; heat++) {
competitorsForHeatsInQuarterFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInQuarterFinals[heat][i] = c[8*(2*heat+(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(1), "Heat "+(heat+9), /* withScores */ true, competitorsForHeatsInQuarterFinals[heat]);
}
// semi-finals has promoted top four competitors of quarter finals which are the top four of each other first-round heat:
Competitor[][] competitorsForHeatsInSemiFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInSemiFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInSemiFinals[heat][i] = c[8*(4*heat+2*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(2), "Heat "+(heat+13), /* withScores */ true, competitorsForHeatsInSemiFinals[heat]);
}
// finals has promoted top four competitors of semi finals which are the top four of first and fifth first-round heats
// for the final, and the top four of the first round's third and seventh heat
Competitor[][] competitorsForHeatsInFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInFinals[heat][i] = c[8*(2*heat+4*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(3), "Heat "+(heat+15), /* withScores */ true, competitorsForHeatsInFinals[heat]);
}
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertSame(c[0], rankedCompetitors.get(0)); // should be the winner of the final round's Final heat and take the "crown" for the elimination
assertEquals(0.7, leaderboard.getTotalPoints(c[0], later), 0.000000001);
assertSame(c[1], rankedCompetitors.get(1)); // should be the winner of the final round's Final heat and take the "crown" for the elimination
assertEquals(2, leaderboard.getTotalPoints(c[1], later), 0.000000001);
// first four of second heat get promoted to quarter final but lose their heat
assertNull(leaderboard.getTotalPoints(c[ 8], series.get(0).getRaceColumns().iterator().next(), later));
assertNull(leaderboard.getTotalPoints(c[ 9], series.get(0).getRaceColumns().iterator().next(), later));
assertNull(leaderboard.getTotalPoints(c[10], series.get(0).getRaceColumns().iterator().next(), later));
assertNull(leaderboard.getTotalPoints(c[11], series.get(0).getRaceColumns().iterator().next(), later));
assertEquals(18.5, leaderboard.getTotalPoints(c[ 8], later), 0.000000001);
assertEquals(22.5, leaderboard.getTotalPoints(c[ 9], later), 0.000000001);
assertEquals(26.5, leaderboard.getTotalPoints(c[10], later), 0.000000001);
assertEquals(30.5, leaderboard.getTotalPoints(c[11], later), 0.000000001);
// last four competitors in last heat of first round don't get promoted and have null scores in all other rounds but the first
assertEquals(36.5, leaderboard.getTotalPoints(c[60], later), 0.000000001);
assertEquals(44.5, leaderboard.getTotalPoints(c[61], later), 0.000000001);
assertEquals(52.5, leaderboard.getTotalPoints(c[62], later), 0.000000001);
assertEquals(60.5, leaderboard.getTotalPoints(c[63], later), 0.000000001);
for (int i=1; i<=3; i++) {
for (int j=60; j<=63; j++) {
assertNull(leaderboard.getTotalPoints(c[j], series.get(i).getRaceColumns().iterator().next(), later));
}
}
}
@Test
public void testMultiElminationScoringScheme() throws NoWindException {
final int NUMBER_OF_ELIMINATIONS = 3;
Regatta regatta = createRegattaWithEliminations(NUMBER_OF_ELIMINATIONS, new int[] { 8, 4, 2, 2 }, "testBasicElminationScoringScheme",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07));
// also test that no discards occur for three eliminations when discards start to apply only with four races completed
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[] { NUMBER_OF_ELIMINATIONS + 1 });
Competitor[] c = createCompetitors(64).toArray(new Competitor[64]);
TimePoint later = null;
for (int elimination=0; elimination<NUMBER_OF_ELIMINATIONS; elimination++) {
// first round with 64 competitors, eight per heat:
Competitor[][] competitorsForHeatsInRound1 = new Competitor[8][];
for (int heat=0; heat<8; heat++) {
competitorsForHeatsInRound1[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInRound1[heat][i] = c[8*heat+i];
}
later = createAndAttachTrackedRaces(series.get(4*elimination+0), "Heat "+(heat+1), /* withScores */ true, competitorsForHeatsInRound1[heat]);
}
// quarter-finals has promoted top four competitors of first round in heats with eight competitors each:
Competitor[][] competitorsForHeatsInQuarterFinals = new Competitor[4][];
for (int heat=0; heat<4; heat++) {
competitorsForHeatsInQuarterFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInQuarterFinals[heat][i] = c[8*(2*heat+(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(4*elimination+1), "Heat "+(heat+9), /* withScores */ true, competitorsForHeatsInQuarterFinals[heat]);
}
// semi-finals has promoted top four competitors of quarter finals which are the top four of each other first-round heat:
Competitor[][] competitorsForHeatsInSemiFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInSemiFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInSemiFinals[heat][i] = c[8*(4*heat+2*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(4*elimination+2), "Heat "+(heat+13), /* withScores */ true, competitorsForHeatsInSemiFinals[heat]);
}
// finals has promoted top four competitors of semi finals which are the top four of first and fifth first-round heats
// for the final, and the top four of the first round's third and seventh heat
Competitor[][] competitorsForHeatsInFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInFinals[heat][i] = c[8*(2*heat+4*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(4*elimination+3), "Heat "+(heat+15), /* withScores */ true, competitorsForHeatsInFinals[heat]);
}
}
for (int numberOfEliminationsExpectedToScore=NUMBER_OF_ELIMINATIONS; numberOfEliminationsExpectedToScore>=NUMBER_OF_ELIMINATIONS-1; numberOfEliminationsExpectedToScore--) {
assertEquals(numberOfEliminationsExpectedToScore*18.5, leaderboard.getTotalPoints(c[ 8], later), 0.000000001);
assertEquals(numberOfEliminationsExpectedToScore*22.5, leaderboard.getTotalPoints(c[ 9], later), 0.000000001);
assertEquals(numberOfEliminationsExpectedToScore*26.5, leaderboard.getTotalPoints(c[10], later), 0.000000001);
assertEquals(numberOfEliminationsExpectedToScore*30.5, leaderboard.getTotalPoints(c[11], later), 0.000000001);
// last four competitors in last heat of first round don't get promoted and have null scores in all other rounds but the first
assertEquals(numberOfEliminationsExpectedToScore*36.5, leaderboard.getTotalPoints(c[60], later), 0.000000001);
assertEquals(numberOfEliminationsExpectedToScore*44.5, leaderboard.getTotalPoints(c[61], later), 0.000000001);
assertEquals(numberOfEliminationsExpectedToScore*52.5, leaderboard.getTotalPoints(c[62], later), 0.000000001);
assertEquals(numberOfEliminationsExpectedToScore*60.5, leaderboard.getTotalPoints(c[63], later), 0.000000001);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertSame(c[0], rankedCompetitors.get(0)); // should be the winner of the final round's Final heat and take the "crown" for the elimination
assertEquals(numberOfEliminationsExpectedToScore*0.7, leaderboard.getTotalPoints(c[0], later), 0.000000001);
assertSame(c[1], rankedCompetitors.get(1)); // should be the winner of the final round's Final heat and take the "crown" for the elimination
assertEquals(numberOfEliminationsExpectedToScore*2, leaderboard.getTotalPoints(c[1], later), 0.000000001);
for (int elimination=0; elimination<NUMBER_OF_ELIMINATIONS; elimination++) {
// first four of second heat get promoted to quarter final but lose their heat
assertNull(leaderboard.getTotalPoints(c[ 8], series.get(4*elimination+0).getRaceColumns().iterator().next(), later));
assertNull(leaderboard.getTotalPoints(c[ 9], series.get(4*elimination+0).getRaceColumns().iterator().next(), later));
assertNull(leaderboard.getTotalPoints(c[10], series.get(4*elimination+0).getRaceColumns().iterator().next(), later));
assertNull(leaderboard.getTotalPoints(c[11], series.get(4*elimination+0).getRaceColumns().iterator().next(), later));
for (int i=1; i<=3; i++) {
for (int j=60; j<=63; j++) {
assertNull(leaderboard.getTotalPoints(c[j], series.get(4*elimination+i).getRaceColumns().iterator().next(), later));
}
}
}
// for next loop iteration discard the appropriate number of races
leaderboard.setCrossLeaderboardResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { numberOfEliminationsExpectedToScore }));
}
}
@Test
public void testElminationScoringSchemeWithDifferentlySizedHeatsInFirstRound() throws NoWindException {
Regatta regatta = createRegattaWithEliminations(1, new int[] { 8, 4, 2, 2 }, "testBasicElminationScoringScheme",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
Competitor[] c = createCompetitors(65).toArray(new Competitor[64]);
// first round with 64 competitors, eight per heat:
Competitor[][] competitorsForHeatsInRound1 = new Competitor[8][];
TimePoint later = null;
for (int heat=0; heat<8; heat++) {
final int numberOfCompetitorsInHeat = heat==7?9:8;
competitorsForHeatsInRound1[heat] = new Competitor[numberOfCompetitorsInHeat];
for (int i=0; i<numberOfCompetitorsInHeat; i++) {
competitorsForHeatsInRound1[heat][i] = c[8*heat+i];
}
later = createAndAttachTrackedRaces(series.get(0), "Heat "+(heat+1), /* withScores */ true, competitorsForHeatsInRound1[heat]);
}
// quarter-finals has promoted top four competitors of first round in heats with eight competitors each:
Competitor[][] competitorsForHeatsInQuarterFinals = new Competitor[4][];
for (int heat=0; heat<4; heat++) {
competitorsForHeatsInQuarterFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInQuarterFinals[heat][i] = c[8*(2*heat+(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(1), "Heat "+(heat+9), /* withScores */ true, competitorsForHeatsInQuarterFinals[heat]);
}
// semi-finals has promoted top four competitors of quarter finals which are the top four of each other first-round heat:
Competitor[][] competitorsForHeatsInSemiFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInSemiFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInSemiFinals[heat][i] = c[8*(4*heat+2*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(2), "Heat "+(heat+13), /* withScores */ true, competitorsForHeatsInSemiFinals[heat]);
}
// finals has promoted top four competitors of semi finals which are the top four of first and fifth first-round heats
// for the final, and the top four of the first round's third and seventh heat
Competitor[][] competitorsForHeatsInFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInFinals[heat][i] = c[8*(2*heat+4*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(3), "Heat "+(heat+15), /* withScores */ true, competitorsForHeatsInFinals[heat]);
}
// validate first-round points for drop-outs; competitor #64 (the 9th in heat #8) shall have 65 points
assertEquals(65, leaderboard.getTotalPoints(c[64], later), 0.0000001);
for (int heat = 0; heat < 8; heat++) {
for (int i = 4; i < 8; i++) {
assertEquals((8.0*i+1.0 // best overall rank for drop-outs ranking i+1 in own fleet
+8.0*i+8.0) // worst overall rank for drop-outs ranking i+1 in own fleet
/2.0, leaderboard.getTotalPoints(c[8*heat+i], later), 0.0000001);
}
}
}
@Test
public void testElminationScoringSchemeWithFinalNotSailed() throws NoWindException {
Regatta regatta = createRegattaWithEliminations(1, new int[] { 8, 4, 2, 2 }, "testBasicElminationScoringScheme",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
Competitor[] c = createCompetitors(64).toArray(new Competitor[64]);
// first round with 64 competitors, eight per heat:
Competitor[][] competitorsForHeatsInRound1 = new Competitor[8][];
TimePoint later = null;
for (int heat=0; heat<8; heat++) {
competitorsForHeatsInRound1[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInRound1[heat][i] = c[8*heat+i];
}
later = createAndAttachTrackedRaces(series.get(0), "Heat "+(heat+1), /* withScores */ true, competitorsForHeatsInRound1[heat]);
}
// quarter-finals has promoted top four competitors of first round in heats with eight competitors each:
Competitor[][] competitorsForHeatsInQuarterFinals = new Competitor[4][];
for (int heat=0; heat<4; heat++) {
competitorsForHeatsInQuarterFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInQuarterFinals[heat][i] = c[8*(2*heat+(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(1), "Heat "+(heat+9), /* withScores */ true, competitorsForHeatsInQuarterFinals[heat]);
}
// semi-finals has promoted top four competitors of quarter finals which are the top four of each other first-round heat:
Competitor[][] competitorsForHeatsInSemiFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInSemiFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInSemiFinals[heat][i] = c[8*(4*heat+2*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(2), "Heat "+(heat+13), /* withScores */ true, competitorsForHeatsInSemiFinals[heat]);
}
// finals has promoted top four competitors of semi finals which are the top four of first and fifth first-round heats
// for the final, and the top four of the first round's third and seventh heat; we assume here that the finals have not
// been sailed and therefore expect the scores to be obtained by interpolating, using 1.0 instead of 0.7 for averaging.
Competitor[][] competitorsForHeatsInFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInFinals[heat][i] = c[8*(2*heat+4*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(3), "Heat "+(heat+15), /* withScores */ false, competitorsForHeatsInFinals[heat]);
}
// 1 instead of 0.7 for a final that was not sailed; clarified with Juergen Bonne in an e-mail as of 18-09-2015T09:03:00Z
final double expectedPointsForFirstEightBoats = (1.0 + 2.0 + 3.0 + 4.0 + 5.0 + 6.0 + 7.0 + 8.0) / 8.0;
for (int i=0; i<4; i++) {
assertEquals(expectedPointsForFirstEightBoats, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
for (int i=32; i<36; i++) {
assertEquals(expectedPointsForFirstEightBoats, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
final double expectedPointsForLastEightBoats = (9.0 + 10.0 + 11.0 + 12.0 + 13.0 + 14.0 + 15.0 + 16.0) / 8.0;
for (int i=16; i<20; i++) {
assertEquals(expectedPointsForLastEightBoats, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
for (int i=48; i<52; i++) {
assertEquals(expectedPointsForLastEightBoats, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
}
@Test
public void testElminationScoringSchemeWithFinalSailedButNotTracked() throws NoWindException {
Regatta regatta = createRegattaWithEliminations(1, new int[] { 8, 4, 2, 2 }, "testBasicElminationScoringScheme",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
Competitor[] c = createCompetitors(64).toArray(new Competitor[64]);
// first round with 64 competitors, eight per heat:
Competitor[][] competitorsForHeatsInRound1 = new Competitor[8][];
TimePoint later = null;
for (int heat=0; heat<8; heat++) {
competitorsForHeatsInRound1[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInRound1[heat][i] = c[8*heat+i];
}
later = createAndAttachTrackedRaces(series.get(0), "Heat "+(heat+1), /* withScores */ true, competitorsForHeatsInRound1[heat]);
}
// quarter-finals has promoted top four competitors of first round in heats with eight competitors each:
Competitor[][] competitorsForHeatsInQuarterFinals = new Competitor[4][];
for (int heat=0; heat<4; heat++) {
competitorsForHeatsInQuarterFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInQuarterFinals[heat][i] = c[8*(2*heat+(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(1), "Heat "+(heat+9), /* withScores */ true, competitorsForHeatsInQuarterFinals[heat]);
}
// semi-finals has promoted top four competitors of quarter finals which are the top four of each other first-round heat:
Competitor[][] competitorsForHeatsInSemiFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInSemiFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInSemiFinals[heat][i] = c[8*(4*heat+2*(i/4))+(i%4)];
}
later = createAndAttachTrackedRaces(series.get(2), "Heat "+(heat+13), /* withScores */ true, competitorsForHeatsInSemiFinals[heat]);
}
// finals has promoted top four competitors of semi finals which are the top four of first and fifth first-round heats
// for the final, and the top four of the first round's third and seventh heat; we assume here that the finals have not
// been sailed and therefore expect the scores to be obtained by interpolating, using 1.0 instead of 0.7 for averaging.
Competitor[][] competitorsForHeatsInFinals = new Competitor[2][];
for (int heat=0; heat<2; heat++) {
competitorsForHeatsInFinals[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
final Competitor comp = c[8*(2*heat+4*(i/4))+(i%4)];
competitorsForHeatsInFinals[heat][i] = comp;
leaderboard.getScoreCorrection().correctScore(comp, series.get(3).getRaceColumns().iterator().next(), heat==0&&i==0 ? 0.7 : 8*heat+i+1);
}
}
for (int i=0; i<4; i++) {
assertEquals(i==0?0.7:i+1, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
for (int i=32; i<36; i++) {
assertEquals(i-27, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
for (int i=16; i<20; i++) {
assertEquals(i-7, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
for (int i=48; i<52; i++) {
assertEquals(i-35, leaderboard.getTotalPoints(c[i], later), 0.000000001);
}
}
@Test
public void testElminationScoringSchemeWithOnlyFirstRoundSailed() throws NoWindException {
Regatta regatta = createRegattaWithEliminations(1, new int[] { 8, 4, 2, 2 }, "testBasicElminationScoringScheme",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
Competitor[] c = createCompetitors(64).toArray(new Competitor[64]);
// first round with 64 competitors, eight per heat:
Competitor[][] competitorsForHeatsInRound1 = new Competitor[8][];
TimePoint later = null;
for (int heat=0; heat<8; heat++) {
competitorsForHeatsInRound1[heat] = new Competitor[8];
for (int i=0; i<8; i++) {
competitorsForHeatsInRound1[heat][i] = c[8*heat+i];
}
later = createAndAttachTrackedRaces(series.get(0), "Heat "+(heat+1), /* withScores */ true, competitorsForHeatsInRound1[heat]);
}
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
// Clarified with Juergen Bonne in an e-mail as of 18-09-2015T09:03:00Z that a final race's winner
// is scored with 0.7 only if the final race has actually been sailed. If the competitors are qualified
// for the final race but it's not sailed, average scores are to be assigned to all competitors qualified
// for the final race, but this average assumes 1.0 points for the first rank instead of the 0.7 assigned
// to the winner if the race is actually sailed. Similarly, if not even the semi-finals have been sailed,
// score averaging uses 1.0 for the first rank.
final double expectedPointsForFirstEightBoats = (1.0 + 2.0 + 3.0 + 4.0 + 5.0 + 6.0 + 7.0 + 8.0) / 8.0;
for (int i=0; i<8; i++) {
assertEquals(expectedPointsForFirstEightBoats, leaderboard.getTotalPoints(c[8*i], later), 0.0000001);
assertEquals("Competitor "+rankedCompetitors.get(i)+" not in list of best eight ",
0, Arrays.asList(c).indexOf(rankedCompetitors.get(i))%8); // each first competitor in a heat ranks in the top 8
}
for (int i=1; i<=3; i++) {
for (Competitor comp : c) {
assertNull(leaderboard.getTotalPoints(comp, series.get(i).getRaceColumns().iterator().next(), later));
}
}
}
@Test
public void testScoringConsideringNotAllRaces() throws NoWindException {
// one discard at four races
@@ -710,7 +1086,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[] { 4 });
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3, f4, f5, f6);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5, f6);
Map<RaceColumn, List<Competitor>> rankedCompetitorsFromBestToWorstAfterEachRaceColumn =
leaderboard.getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(later);
assertEquals(Arrays.asList(c[2], c[0], c[1], c[3]),
@@ -763,8 +1139,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
/* medal */ true, "testTieBreakWithTwoVersusOneSeconds",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2);
createAndAttachTrackedRaces(series.get(2), "Medal", m1);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2);
createAndAttachTrackedRaces(series.get(2), "Medal", /* withScores */ true, m1);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
// assert that both have equal score
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001);
@@ -786,7 +1162,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3, f4, f5, f6);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5, f6);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001);
assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1);
@@ -806,7 +1182,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3, f4, f5, f6);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5, f6);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001);
assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1);
@@ -1268,13 +1644,18 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
assertEquals(0., leaderboard1.getTotalPoints(c[3], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1
}
private TimePoint createAndAttachTrackedRaces(Series theSeries, String fleetName, Competitor[]... competitorLists) {
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();
TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitorList));
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;
@@ -1363,6 +1744,52 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
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, 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,40 @@
package com.sap.sailing.domain.test.mock;
import java.util.List;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sse.common.TimePoint;
/**
* Returns 0 for {@link #getRank(Competitor)} and {@link #getRank(Competitor, TimePoint)} in all cases. Still, the competitor
* set participating in this race is maintained. This can be used, e.g., to mock a race that hasn't started yet and therefore
* the competitors have no rank yet.
*
* @author Axel Uhl (d043530)
*
*/
public class MockedTrackedRaceWithStartTimeAndZeroRanks extends MockedTrackedRaceWithStartTimeAndRanks {
private static final long serialVersionUID = 7726627832998127727L;
protected MockedTrackedRaceWithStartTimeAndZeroRanks(TimePoint startTime,
List<Competitor> competitors, Regatta regatta) {
super(startTime, competitors, regatta);
}
public MockedTrackedRaceWithStartTimeAndZeroRanks(TimePoint startTime, List<Competitor> competitors) {
super(startTime, competitors);
}
@Override
public int getRank(Competitor competitor) throws NoWindException {
return 0;
}
@Override
public int getRank(Competitor competitor, TimePoint timePoint) {
return 0;
}
}
@@ -50,6 +50,7 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive;
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
import com.sap.sailing.domain.leaderboard.impl.LowPointWithEliminationsAndRoundsWinnerGets07;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -113,6 +114,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
return new HighPointWinnerGetsEightAndInterpolation();
case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT:
return new HighPointFirstGets10Or8AndLastBreaksTie();
case LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07:
return new LowPointWithEliminationsAndRoundsWinnerGets07();
}
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
}
@@ -272,8 +272,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
/**
* {@link RaceColumnListeners} may not be de-serialized (yet) when the regatta
* is de-serialized. Do avoid re-registering empty objects most probably leading
* to null pointer exception one need to initialize all listeners after
* is de-serialized. To avoid re-registering empty objects most probably leading
* to null pointer exception one needs to initialize all listeners after
* all objects have been read.
*/
public void initializeSeriesAfterDeserialize() {
@@ -354,7 +354,7 @@ public interface Leaderboard extends LeaderboardBase {
* that column shall be considered for discarding and counts for determining the number of races so far. Also, if a
* tracked race is connected to the column and has started already, the column is to be considered for discarding
* unless the column has several unordered fleets and not all fleets have started their race yet (see
* {@link ScoringScheme#isValidInTotalScore(Leaderboard, RaceColumn, TimePoint)}).
* {@link ScoringScheme#isValidInTotalScore(Leaderboard, RaceColumn, Competitor, TimePoint)}).
*/
boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint);
@@ -50,10 +50,11 @@ public interface ScoringScheme extends Serializable {
* If the <code>competitor</code> has no {@link RaceColumn#getTrackedRace(Competitor) tracked race} in the column in which
* the competitor participated, <code>null</code> is returned, meaning the competitor has no score assigned for that
* race.
* @param timePoint TODO
*/
Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank,
Callable<Integer> numberOfCompetitorsInRaceFetcher,
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher);
Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor,
int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher,
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint);
/**
* If a competitor is disqualified, a penalty score is attributed by this scoring scheme. Some schemes require to
@@ -94,9 +95,11 @@ public interface ScoringScheme extends Serializable {
* Under certain circumstances, a scoring scheme may decide that the scores of a column are not (yet) to be used
* for the leaderboard's total scores. This may, e.g., be the case if a column is split into more than one fleet and
* those fleets are unordered. In that case, scores need to be available for all fleets before the column counts
* for the total scores.
* for the total scores. Another example is a scoring scheme that defines elimination rounds and awards no points
* to a competitor in a round from which the competitor got promoted to the next round. Such promotion columns
* then have no scores and don't count in the number of races starting from where discards are applied.<p>
*/
boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at);
boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at);
/**
* Some scoring schemes are applied to {@link LeaderboardGroupMetaLeaderboard} instances. These instances of a
@@ -77,7 +77,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
* more races.
*/
@Override
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) {
boolean result;
Iterable<? extends Fleet> fleets = raceColumn.getFleets();
if (Util.size(fleets) <= 1 || allFleetsOrdered(fleets)) {
@@ -532,7 +532,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
if (raceColumn.isStartsWithZeroScore()) {
needToResetScoreUponNextNonEmptyEntry = true;
}
if (getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint)) {
if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) {
final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
if (totalPoints != null) {
if (needToResetScoreUponNextNonEmptyEntry) {
@@ -651,7 +651,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
TrackedRace trackedRaceForCompetitorInColumn;
return getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint) &&
return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) &&
(getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) ||
((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null &&
trackedRaceForCompetitorInColumn.hasStarted(timePoint)));
@@ -27,9 +27,9 @@ public class HighPoint extends AbstractScoringSchemeImpl {
}
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank,
Callable<Integer> numberOfCompetitorsInRaceFetcher,
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
public Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor,
int rank,
Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
Double result;
if (rank == 0) {
result = null;
@@ -74,7 +74,7 @@ public class HighPoint extends AbstractScoringSchemeImpl {
}
@Override
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) {
return true;
}
@@ -36,8 +36,8 @@ public class HighPointExtremeSailingSeriesOverall extends HighPoint {
private static final int MIN_RACES_REQUIRED_TO_BE_SCORED = 5;
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank,
Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
public Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor,
int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
Double result;
if (rank == 0) {
result = null;
@@ -11,7 +11,9 @@ 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.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
@@ -87,8 +89,8 @@ public class HighPointFirstGets10Or8AndLastBreaksTie extends HighPointFirstGetsF
}
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
Double effectiveScore = super.getScoreForRank(raceColumn, competitor, rank, numberOfCompetitorsInRaceFetcher, numberOfCompetitorsInLeaderboardFetcher);
public Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
Double effectiveScore = super.getScoreForRank(leaderboard, raceColumn, competitor, rank, numberOfCompetitorsInRaceFetcher, numberOfCompetitorsInLeaderboardFetcher, timePoint);
return checkForOverwrittenScore(raceColumn, rank, effectiveScore);
}
@@ -5,7 +5,9 @@ import java.util.concurrent.Callable;
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.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sse.common.TimePoint;
/**
@@ -41,7 +43,7 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint {
}
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
public Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
final int effectiveRank = getEffectiveRank(raceColumn, competitor, rank);
final Double result;
if (effectiveRank == 0) {
@@ -5,7 +5,9 @@ import java.util.concurrent.Callable;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sse.common.TimePoint;
/**
* High point system with a fixed 'highest score' (eight points) and
@@ -31,7 +33,7 @@ public class HighPointWinnerGetsEightAndInterpolation extends HighPointFirstGets
}
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
public Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
final int effectiveRank = getEffectiveRank(raceColumn, competitor, rank);
final Double result;
if (effectiveRank == 0) {
@@ -110,7 +110,8 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
for (RaceColumn raceColumn : getLeaderboard().getRaceColumns()) {
needToResetO1ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore();
needToResetO2ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore();
if (getLeaderboard().getScoringScheme().isValidInTotalScore(getLeaderboard(), raceColumn, timePoint)) {
if (getLeaderboard().getScoringScheme().isValidInTotalScore(getLeaderboard(), raceColumn, o1, timePoint)
&& getLeaderboard().getScoringScheme().isValidInTotalScore(getLeaderboard(), raceColumn, o2, timePoint)) {
int preemptiveColumnResult = 0;
final Double o1Score = totalPointsCache.get(new Util.Pair<Competitor, RaceColumn>(o1, raceColumn));
if (o1Score != null) {
@@ -27,10 +27,23 @@ public class LowPoint extends AbstractScoringSchemeImpl {
}
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
final int effectiveRank;
public Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, int rank,
Callable<Integer> numberOfCompetitorsInRaceFetcher,
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
final Double result;
int competitorFleetOrdering;
final int effectiveRank = getEffectiveRank(raceColumn, competitor, rank);
result = effectiveRank == 0 ? null : (double) effectiveRank;
return result;
}
/**
* Considers contiguous scoring for split-fleet columns, finding out in which fleet the <code>competitor</code> races
* in the <code>raceColumn</code> and figuring out how many competitors race in better fleets. For non-contiguous
* scoring, the effective rank equals the <code>rank</code> in the competitor's fleet.
*/
protected int getEffectiveRank(RaceColumn raceColumn, Competitor competitor, int rank) {
final int competitorFleetOrdering;
final int effectiveRank;
if (rank == 0) {
effectiveRank = 0;
} else if (raceColumn.hasSplitFleetContiguousScoring() && (competitorFleetOrdering=raceColumn.getFleetOfCompetitor(competitor).getOrdering()) != 0) {
@@ -39,8 +52,7 @@ public class LowPoint extends AbstractScoringSchemeImpl {
} else {
effectiveRank = rank;
}
result = effectiveRank == 0 ? null : (double) effectiveRank;
return result;
return effectiveRank;
}
@Override
@@ -61,7 +73,7 @@ public class LowPoint extends AbstractScoringSchemeImpl {
}
@Override
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) {
return true;
}
@@ -5,7 +5,9 @@ import java.util.concurrent.Callable;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sse.common.TimePoint;
/**
* As compared to the regular low-point scheme, the 2013 ISAF World Cup will score the winner of a race with
@@ -31,7 +33,7 @@ public class LowPointWinnerGetsZero extends LowPoint {
private static final long serialVersionUID = 3405249102284162690L;
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRacefetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
public Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRacefetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
return rank == 0 ? null : rank == 1 ? 0.0 : (double) rank;
}
@@ -0,0 +1,324 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.Iterator;
import java.util.concurrent.Callable;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* Implements an elimination scheme as used by surfing championships, based on a general low point scoring scheme. A
* regatta is divided into "eliminations" where each elimination consists of a sequence of "rounds." Each round is in
* turn divided into a number of "heats" (races). The final round in an elimination consists of a "final" race and a
* "losers final" race. They constitute, in our terminology, one series with contiguous scoring. The winner of the
* "losers final" gets one point more than the competitor ranking last in the "final" race. The winner of the final race
* obtains 0.7 points. All other ranks are assigned points equal to the rank or the average of the ranks attained by
* competitors ranking equal.
* <p>
*
* The semi-final has two heats, and the better half of the competitors in those heats get promoted to the "final," the
* competitors ranking worse will participate in the "losers final." Points are usually not awarded in the semi-final
* (exceptions for races not sailed see below).
* <p>
*
* In the quarter-final there are four heats. Usually, the competitors ranking in the top half of their heat are
* promoted to the semi-final and are not awarded points for the quarter-final round (exceptions for races not sailed
* see below). However, the race committee may decide how many competitors are promoted, also based on the number of
* participant and resulting number and set-up of heats. Therefore, successful promotion can only be detected by looking
* at the competitor assignments in the next round.
* <p>
*
* Those competitors ranking in the bottom part of their heat are not promoted to the next round and are instead awarded
* points for the elimination based on their rank in their heat. Across all quarter-final heats, competitors not
* promoted and having the same rank obtain equal points regardless their heat. The number of points is calculated as
* the average of their rank in the elimination. Quarter-final participants rank better than those eliminated in earlier
* rounds. Eliminated competitors rank better in the elimination if they obtained a better rank in their heat than other
* competitors eliminated in the same round. For example, if each of the four quarter-final heat has eight competitors,
* the four competitors ranking last (8th) in their heat get the average points for ranks 29-32 (30.5 points), whereas
* the four competitors ranking 7th in their heat get the average points for ranks 25-28 (26.5 points), and so on.
* <p>
*
* Other rounds preceding the quarter-final work by the same principle as the quarter-final.
* <p>
*
* Should a planned heat not be sailed then all competitors assigned to that heat will obtain equal points based on the
* average of the ranks for which they would have sailed. For example, if the final heat cannot be sailed, all
* competitors who qualified for the final race obtain the average of 0.7, 2, 3, 4, 5, 6, 7 and 8 points (4.46 points).
* <p>
*
* During live tracking promotions cannot generally be decided due to the race committee's decision authority.
* Therefore, also those competitors who ultimately will be promoted have to be given scores in their heat based on the
* average points obtained by all competitors taking the same rank in their respective heat in the same round. This will
* usually lead to a change in points awarded to the competitors promoted as soon as the promotion becomes "official"
* which technically needs to be expressed by assigning the competitors to their heat in the next round. As soon as this
* happens, the scores in the heat from which a competitor got promoted are set to <code>null</code>, and a score
* corresponding to the average points given to the competitors of the heat to which the competitor got promoted is
* inferred for that heat. Only when the race in that next heat produces non-zero ranks, the scoring rules for that
* heat are applied again normally.<p>
*
* The regatta is expected to be modeled such that fleet ordering numbers are used in a special way. Ordering
* <code>1</code> is to be used to identify the Final heat, <code>2</code> for the Losers Final heat. For each earlier
* round the ordering is increased by one and applied equally to all heats of that round. Therefore, the two semi-final
* heats use ordering <code>3</code>, the four quarter-final heats use <code>4</code>, and so on.
*
* @author Axel Uhl (d043530)
*
*/
public class LowPointWithEliminationsAndRoundsWinnerGets07 extends LowPoint {
private static final long serialVersionUID = -2318652113347853873L;
private static int LOSERS_FINAL_FLEET_ORDERING = 2;
@Override
public ScoringSchemeType getType() {
return ScoringSchemeType.LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07;
}
/**
* The competitor gets a score in <code>raceColumn</code> if she does not participate in a race in the round that
* follows the round represented by <code>raceColumn</code>. If <code>raceColumn</code> represents the last round in
* the elimination, <code>competitor</code> can not participate in a next round and therefore obtains a score. This
* can either happen when <code>raceColumn</code> is a final round or if the elimination has been aborted and no
* further rounds exist in that elimination. The competitor is considered to participate in the next round if an
* explicit score correction exists for the <code>competitor</code> for the next round, or if a tracked race is
* attached to the race column following <code>raceColumn</code> that lists <code>competitor</code> as part of its
* competitors.
* <p>
*
* If the <code>competitor</code> is to receive points for <code>raceColumn</code>, the following rules apply. For a
* <code>0</code> rank this method assumes that the race has not started yet (and it could happen that it is never
* started at all); in this case the number of points is calculated as the arithmetic mean of all points for which
* the participants of the heat would race, with the first rank counting as 1.0 for this average. If the
* <code>rank</code> is not <code>0</code> then the competitor receives the average of the points given to all
* competitors from all heats in this round that rank equal when sorting the competitors of that round in the
* leaderboard by their rank. For example, if there are two heats in the semi-final with eight competitors each, the
* two competitors ranking second in their heat will get the average of 3.0 and 4.0 points, so 3.5 points.<p>
*
* In the exceptional case of the final round, 0.7 points are awarded to the first rank. This special rules only
* applies if a <code>rank</code> 1 is actually observed. It does not apply to the averaging process that occurs
* should the final not have been sailed (yet) such that the final participants receive the points average of
* 1.0, 2.0, ..., &lt;<i>number-of-final-participants</i>&gt;<p>
*/
@Override
public Double getScoreForRank(final Leaderboard leaderboard, final RaceColumn raceColumn, final Competitor competitor, final int rank,
final Callable<Integer> numberOfCompetitorsInRaceFetcher,
final NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
assert raceColumn instanceof RaceColumnInSeries;
// Clarified with Juergen Bonne in an e-mail as of 18-09-2015T09:03:00Z that a final race's winner
// is scored with 0.7 only if the final race has actually been sailed. If the competitors are qualified
// for the final race but it's not sailed, average scores are to be assigned to all competitors qualified
// for the final race, but this average assumes 1.0 points for the first rank instead of the 0.7 assigned
// to the winner if the race is actually sailed.
final RaceColumnInSeries raceColumnInSeries = (RaceColumnInSeries) raceColumn;
final Double result;
final Series series = ((RaceColumnInSeries) raceColumn).getSeries();
final Regatta regatta = series.getRegatta();
Iterator<? extends Series> seriesInRegattaIter=regatta.getSeries().iterator();
Series seriesInRegatta = null;
while (seriesInRegattaIter.hasNext() && (seriesInRegatta=seriesInRegattaIter.next()) != series)
;
if (seriesInRegatta == null) {
throw new IllegalArgumentException("Race column "+raceColumn+" not found in its owning regatta");
}
final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor);
if (fleetOfCompetitor == null || participatesInNextRound(leaderboard, raceColumnInSeries, competitor, timePoint, seriesInRegattaIter)) {
// the competitor does not participate in this round according to the set of tracked races
// attached to the raceColumn or obtains a score in the next round which means that the
// competitor will not get a score yet for the raceColumn passed
result = null;
} else {
assert fleetOfCompetitor != null;
if (rank == 0) {
// calculate the average rank of all competitors of the heat; for contiguous scoring across heats (final round)
// this is the average of the points (giving 1.0 for first rank in the final) in the single heat; for rounds
// with unordered heats (fleets all having equal ordering, non-contiguous series scoring) this is the average
// of all the points assigned to all the ranks occupied in the elimination by the participants of this round.
if (series.hasSplitFleetContiguousScoring()) {
// final round; check how many competitors are in better fleet; then average between (that number plus 1)
// and (that number plus the number of competitors in competitor's fleet)
final int competitorFleetOrdering = fleetOfCompetitor.getOrdering();
final int numberOfCompetitorsInBetterFleet = getNumberOfCompetitorsInBetterFleets(raceColumnInSeries, competitorFleetOrdering);
final TrackedRace finalTrackedRaceOfCompetitor = raceColumn.getTrackedRace(competitor); // assumed to be != null because fleetOfCompetitor != null
final int numberOfCompetitorsInCompetitorsFleet = Util.size(finalTrackedRaceOfCompetitor.getRace().getCompetitors());
result = ((double)numberOfCompetitorsInBetterFleet + 1.0 + // best score in competitor's fleet
(double)numberOfCompetitorsInBetterFleet + (double)numberOfCompetitorsInCompetitorsFleet) // worst score in competitor's fleet
/2.0;
} else {
// count competitors in this round and average the points, assuming that the participants that got promoted
// into this non-final round will race for scores 1.0 .. <number-of-competitors-in-the-round>.
int numberOfCompetitorsInRound = 0;
for (final Fleet fleet : raceColumn.getFleets()) {
final TrackedRace trackedRaceInRound = raceColumn.getTrackedRace(fleet);
if (trackedRaceInRound != null) {
numberOfCompetitorsInRound += Util.size(trackedRaceInRound.getRace().getCompetitors());
}
}
result = ((double) numberOfCompetitorsInRound + 1.0) / 2.0;
}
} else {
// rank is non-zero; there is a tracked race that provides a valid score for the competitor
final int effectiveRank = getEffectiveRank(raceColumn, competitor, rank);
if (series.hasSplitFleetContiguousScoring()) {
result = effectiveRank == 1 ? 0.7 : effectiveRank;
} else {
final int numberOfCompetitorsInBetterFleets = getNumberOfCompetitorsInBetterFleets(raceColumnInSeries, fleetOfCompetitor.getOrdering());
final int numberOfFleetsWithEqualOrder = getNumberOfFleetsWithOrdering(series, fleetOfCompetitor.getOrdering());
final int numberOfCompetitorsWithSameRankInRound = getNumberOfCompetitorsWithSameRankInRound(raceColumnInSeries, competitor, effectiveRank);
final int bestOverallRankForAnyEqualRankingCompetitor =
numberOfCompetitorsInBetterFleets + 1 + // best possible score for any competitor in competitor's fleet
numberOfFleetsWithEqualOrder*(effectiveRank-1); // number of places assigned to better competitors in own and other equal fleets
final int worstOverallRankForAnyEqualRankingCompetitor =
bestOverallRankForAnyEqualRankingCompetitor + numberOfCompetitorsWithSameRankInRound - 1;
result = ((double) bestOverallRankForAnyEqualRankingCompetitor + (double) worstOverallRankForAnyEqualRankingCompetitor) / 2.0;
}
}
}
return result;
}
@Override
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) {
final RaceColumnInSeries raceColumnInSeries = (RaceColumnInSeries) raceColumn;
final Iterator<? extends Series> seriesInRegattaIter = raceColumnInSeries.getSeries().getRegatta().getSeries().iterator();
while (seriesInRegattaIter.hasNext() && seriesInRegattaIter.next() != raceColumnInSeries.getSeries())
;
return !participatesInNextRound(leaderboard, (RaceColumnInSeries) raceColumn, competitor, at, seriesInRegattaIter);
}
private int getNumberOfFleetsWithOrdering(Series series, int ordering) {
int result = 0;
for (final Fleet fleet : series.getFleets()) {
if (fleet.getOrdering() == ordering) {
result++;
}
}
return result;
}
/**
* A competitor can participate in the next round of an elimination after having been promoted from
* <code>raceColumnInSeries</code>. If the round represented by <code>raceColumnInSeries</code> is the last round in
* the elimination, the competitor obviously cannot participate in any next round, therefore <code>false</code> is
* returned in this case. Otherwise, next round participation is decided by first looking for a score correction.
* The score correction for the next column has to be valid at <code>timePoint</code>, then the competitor is considered
* having participated in that next round at <code>timePoint</code>. If no score correction is found for the next column,
* the next column is checked for a tracked race in which <code>competitor</code> competes. If such a race is found,
* the question remains whether this participation is considered "active" at <code>timePoint</code>. This is assumed
* if in the current round represented by <code>raceColumnInSeries</code> no tracked race is found for <code>competitor</code>
* (unlikely, but it means we cannot tell whether <code>competitor</code> has already finished her current race, and by
* default we then have to assume that it's over) or the end of that race was before <code>timePoint</code>.<p>
*
* This will keep <code>competitor</code>'s score in <code>raceColumnInSeries</code> for time points where the race
* in this column is still running. Only after the race has finished will the promotion become active and competitor
* lose her points in the current column.
*/
private boolean participatesInNextRound(Leaderboard leaderboard, RaceColumnInSeries raceColumnInSeries,
Competitor competitor, TimePoint timePoint, Iterator<? extends Series> seriesInRegattaIter) {
final boolean result;
if (isLastRoundInElimination(raceColumnInSeries, seriesInRegattaIter)) {
result = false;
} else {
final Series nextSeries = seriesInRegattaIter.next();
final TrackedRace trackedRace;
result = leaderboard.getScoreCorrection().isScoreCorrected(competitor, nextSeries.getRaceColumns().iterator().next(), timePoint) ||
(nextSeries.getRaceColumns().iterator().next().getFleetOfCompetitor(competitor) != null &&
((trackedRace=raceColumnInSeries.getTrackedRace(competitor)) == null || trackedRace.getEndOfRace() == null ||
trackedRace.getEndOfRace().before(timePoint)));
}
return result;
}
/**
* Assumption: no fleet has a number of competitors exceeding that of any other fleet in the same round by two or
* more.
* <p>
*
* Background: an additional competitor will always be added to the fleet with the fewest competitors, implying
* above rule.
* <p>
*
* Reason for assumption: with this, we can infer that there will be as many competitors ranking one rank better
* than "rank" as there are fleets; were the assumption not fulfilled then there could be a fleet in the round that
* has no competitor ranking one rank better. With this assumption we easily infer the best possible rank for all
* competitors with "rank" which is the number of fleets in the round times "rank" plus 1.
* <p>
*
* Example: If rank is 7 and we have eight fleets, the best rank for all competitors with this rank will be 7*8+1 =
* 57. The worst rank for all competitors with "rank" therefore can be calculated by the adding to the best rank the
* number of competitors with this rank. We have to assume that this number may not be equal to the number of heats
* in this round because with different heat sizes in a round the worst rank may be different for different heats
* (e.g., 7 in some, and 8 in others, therefore fewer 8th ranks than 7th ranks will exist). This "uneven" case can
* only apply if the competitor ranks last in the heat which is easy to exclude by looking at the tracked race, from
* there the RaceDefinition and for that the number of competitors in that race.
*/
private int getNumberOfCompetitorsWithSameRankInRound(final RaceColumnInSeries raceColumn, final Competitor competitor, final int rank) {
final int numberOfCompetitorsWithSameRankInRound;
if (raceColumn.getSeries().hasSplitFleetContiguousScoring()) {
numberOfCompetitorsWithSameRankInRound = 1;
} else {
final Fleet fleet = raceColumn.getFleetOfCompetitor(competitor);
if (fleet == null) {
// Can't determine number of competitors in heat; assuming equal fleet distribution
numberOfCompetitorsWithSameRankInRound = Util.size(raceColumn.getSeries().getFleets());
} else {
final TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
final boolean isWorstRank = rank == Util.size(trackedRace.getRace().getCompetitors());
if (!isWorstRank) {
numberOfCompetitorsWithSameRankInRound = getNumberOfFleetsWithSameOrderingAsCompetitor(raceColumn, competitor, fleet);
} else {
// for all other fleets in the round that have tracked races attached, we can count how many of them
// have fewer than "rank" competitors; for fleets with no tracked race attached we then again assume
// maximum competitor number
int count = 0;
for (final Fleet f : raceColumn.getSeries().getFleets()) {
if (f.getOrdering() == fleet.getOrdering()) { // only consider fleets with equal order to that of competitor's
final TrackedRace trackedRaceForFleet = raceColumn.getTrackedRace(f);
if (trackedRaceForFleet == null) {
count++; // by default assume that other race has at least as many competitors as the competitor's heat
} else {
if (Util.size(trackedRaceForFleet.getRace().getCompetitors()) >= rank) {
count++;
}
}
}
}
numberOfCompetitorsWithSameRankInRound = count;
}
}
}
return numberOfCompetitorsWithSameRankInRound;
}
private int getNumberOfFleetsWithSameOrderingAsCompetitor(final RaceColumnInSeries raceColumn, Competitor competitor, Fleet fleetOfCompetitor) {
int result = 0;
final int orderingOfCompetitorsFleet = fleetOfCompetitor.getOrdering();
for (final Fleet fleet : raceColumn.getFleets()) {
if (fleet.getOrdering() == orderingOfCompetitorsFleet) {
result++;
}
}
return result;
}
private boolean isLastRoundInElimination(RaceColumnInSeries raceColumn, Iterator<? extends Series> seriesInRegattaIter) {
final boolean result;
if (raceColumn.getFleets().iterator().next().getOrdering() <= LOSERS_FINAL_FLEET_ORDERING) {
// the fleet ordering indicates that raceColumn represents a final round in an elimination
result = true;
} else {
// check if there is a next column
result = !seriesInRegattaIter.hasNext();
}
return result;
}
}
@@ -401,13 +401,13 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
public Double getUncorrectedScore() {
Double resultUncorrected = 0.0;
try {
resultUncorrected = scoringScheme.getScoreForRank(raceColumn, competitor,
trackedRankProvider.call(), new Callable<Integer>() {
resultUncorrected = scoringScheme.getScoreForRank(leaderboard, raceColumn,
competitor, trackedRankProvider.call(), new Callable<Integer>() {
@Override
public Integer call() {
return getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher);
}
}, numberOfCompetitorsInLeaderboardFetcher);
}, numberOfCompetitorsInLeaderboardFetcher, timePoint);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -448,13 +448,13 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
if (correctedNonMaxedScore == null || isCertainlyBeforeRaceFinish(timePoint, raceColumn, competitor)) {
try {
int trackedRank = trackedRankProvider.call();
result = scoringScheme.getScoreForRank(raceColumn, competitor, trackedRank,
new Callable<Integer>() {
result = scoringScheme.getScoreForRank(leaderboard, raceColumn, competitor,
trackedRank, new Callable<Integer>() {
@Override
public Integer call() {
return getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher);
}
}, numberOfCompetitorsInLeaderboardFetcher);
}, numberOfCompetitorsInLeaderboardFetcher, timePoint);
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while computing corrected non maxed score", e);
throw new RuntimeException(e);
@@ -82,7 +82,19 @@ public class CandidateChooserImpl implements CandidateChooser {
TreeSet<Candidate> fixedPasses = new TreeSet<Candidate>(new Comparator<Candidate>() {
@Override
public int compare(Candidate o1, Candidate o2) {
return o1.getOneBasedIndexOfWaypoint() - o2.getOneBasedIndexOfWaypoint();
final int result;
if (o1 == null) {
if (o2 == null) {
result = 0;
} else {
result = -1;
}
} else if (o2 == null) {
result = 1;
} else {
result = o1.getOneBasedIndexOfWaypoint() - o2.getOneBasedIndexOfWaypoint();
}
return result;
}
});
fixedPassings.put(c, fixedPasses);
@@ -133,14 +145,16 @@ public class CandidateChooserImpl implements CandidateChooser {
public void setFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint, TimePoint t) {
Candidate fixedCan = new CandidateImpl(zeroBasedIndexOfWaypoint + 1, t, 1, Util.get(race.getRace().getCourse().getWaypoints(), zeroBasedIndexOfWaypoint));
NavigableSet<Candidate> fixed = fixedPassings.get(c);
if (!fixed.add(fixedCan)) {
Candidate old = fixed.ceiling(fixedCan);
fixed.remove(old);
removeCandidates(c, Arrays.asList(old));
fixed.add(fixedCan);
if (fixed != null) { // can only set the mark passing if the competitor is still part of this race
if (!fixed.add(fixedCan)) {
Candidate old = fixed.ceiling(fixedCan);
fixed.remove(old);
removeCandidates(c, Arrays.asList(old));
fixed.add(fixedCan);
}
addCandidates(c, Arrays.asList(fixedCan));
findShortestPath(c);
}
addCandidates(c, Arrays.asList(fixedCan));
findShortestPath(c);
}
@Override
@@ -152,9 +166,11 @@ public class CandidateChooserImpl implements CandidateChooser {
break;
}
}
fixedPassings.get(c).remove(toRemove);
removeCandidates(c, Arrays.asList(toRemove));
findShortestPath(c);
if (toRemove != null) {
fixedPassings.get(c).remove(toRemove);
removeCandidates(c, Arrays.asList(toRemove));
findShortestPath(c);
}
}
@Override
@@ -294,30 +310,34 @@ public class CandidateChooserImpl implements CandidateChooser {
0, race.getRace().getCourse().getNumberOfWaypoints()), 0.0));
while (!endFound) {
Util.Pair<Edge, Double> cheapestEdgeWithCost = currentEdgesCheapestFirst.pollFirst();
Edge currentCheapestEdge = cheapestEdgeWithCost.getA();
Double currentCheapestCost = cheapestEdgeWithCost.getB();
// If the shortest path to this candidate is already known the new edge is not added.
if (!candidateWithParentAndSmallestTotalCost.containsKey(currentCheapestEdge.getEnd())) {
// The cheapest edge taking us to currentCheapestEdge.getEnd() is found. Remember it.
candidateWithParentAndSmallestTotalCost.put(currentCheapestEdge.getEnd(), new Util.Pair<Candidate, Double>(
currentCheapestEdge.getStart(), currentCheapestCost));
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Added "+ currentCheapestEdge + "as cheapest edge for " + c);
}
endFound = currentCheapestEdge.getEnd() == endOfFixedInterval;
if (!endFound) {
// the end of the segment was not yet found; add edges leading away from
// currentCheapestEdge.getEnd(), summing up their cost with the cost required
// to reach currentCheapestEdge.getEnd()
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentCheapestEdge.getEnd());
if (edgesForNewCandidate != null) {
for (Edge e : edgesForNewCandidate) {
int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint();
// only add edge if it stays within the current segment, not exceeding
// the next fixed mark passing
if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval
&& (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) {
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(e, currentCheapestCost + e.getCost()));
if (cheapestEdgeWithCost == null) {
endFound = true;
} else {
Edge currentCheapestEdge = cheapestEdgeWithCost.getA();
Double currentCheapestCost = cheapestEdgeWithCost.getB();
// If the shortest path to this candidate is already known the new edge is not added.
if (!candidateWithParentAndSmallestTotalCost.containsKey(currentCheapestEdge.getEnd())) {
// The cheapest edge taking us to currentCheapestEdge.getEnd() is found. Remember it.
candidateWithParentAndSmallestTotalCost.put(currentCheapestEdge.getEnd(), new Util.Pair<Candidate, Double>(
currentCheapestEdge.getStart(), currentCheapestCost));
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Added "+ currentCheapestEdge + "as cheapest edge for " + c);
}
endFound = currentCheapestEdge.getEnd() == endOfFixedInterval;
if (!endFound) {
// the end of the segment was not yet found; add edges leading away from
// currentCheapestEdge.getEnd(), summing up their cost with the cost required
// to reach currentCheapestEdge.getEnd()
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentCheapestEdge.getEnd());
if (edgesForNewCandidate != null) {
for (Edge e : edgesForNewCandidate) {
int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint();
// only add edge if it stays within the current segment, not exceeding
// the next fixed mark passing
if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval
&& (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) {
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(e, currentCheapestCost + e.getCost()));
}
}
}
}
@@ -448,7 +468,7 @@ public class CandidateChooserImpl implements CandidateChooser {
private void removeCandidates(Competitor c, Iterable<Candidate> wrongCandidates) {
for (Candidate can : wrongCandidates) {
logger.finest("Removing all edges containing " + can.toString() + "of "+ c);
logger.finest("Removing all edges containing " + can + "of "+ c);
candidates.get(c).remove(can);
Map<Candidate, Set<Edge>> edges = allEdges.get(c);
edges.remove(can);
@@ -40,6 +40,7 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.CANOE_TAIFUN.getDisplayName(), imageResources.CanoeTaifunIcon());
boatClassIconsMap.put(BoatClassMasterdata.CONTENDER.getDisplayName(), imageResources.ContenderIcon());
boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon());
boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon());
boatClassIconsMap.put(BoatClassMasterdata.EUROPE_INT.getDisplayName(), imageResources.EuropeIcon());
boatClassIconsMap.put(BoatClassMasterdata.EXTREME_40.getDisplayName(), imageResources.Extreme40Icon());
boatClassIconsMap.put(BoatClassMasterdata.D_ONE.getDisplayName(), imageResources.DOneIcon());
@@ -55,6 +56,7 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.HOBIE_16.getDisplayName(), imageResources.HobieIcon());
boatClassIconsMap.put(BoatClassMasterdata.HOBIE_TIGER.getDisplayName(), imageResources.HobieIcon());
boatClassIconsMap.put(BoatClassMasterdata.HOBIE_WILD_CAT.getDisplayName(), imageResources.HobieIcon());
boatClassIconsMap.put(BoatClassMasterdata.INTERNATIONAL_14.getDisplayName(), imageResources.International14Icon());
boatClassIconsMap.put(BoatClassMasterdata.J22.getDisplayName(), imageResources.J22Icon());
boatClassIconsMap.put(BoatClassMasterdata.J24.getDisplayName(), imageResources.J24Icon());
boatClassIconsMap.put(BoatClassMasterdata.J70.getDisplayName(), imageResources.J70Icon());
@@ -72,9 +74,12 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.NACRA_17.getDisplayName(), imageResources.Nacra17Icon());
boatClassIconsMap.put(BoatClassMasterdata.O_JOLLE.getDisplayName(), imageResources.OJolleIcon());
boatClassIconsMap.put(BoatClassMasterdata.OK.getDisplayName(), imageResources.OKIcon());
boatClassIconsMap.put(BoatClassMasterdata.OPEN_BIC.getDisplayName(), imageResources.OpenBicIcon());
boatClassIconsMap.put(BoatClassMasterdata.OPTIMIST.getDisplayName(), imageResources.OptimistIcon());
boatClassIconsMap.put(BoatClassMasterdata.PIRATE.getDisplayName(), imageResources.PiratIcon());
boatClassIconsMap.put(BoatClassMasterdata.PLATU_25.getDisplayName(), imageResources.Platu25Icon());
boatClassIconsMap.put(BoatClassMasterdata.PWA.getDisplayName(), imageResources.PWAIcon());
boatClassIconsMap.put(BoatClassMasterdata.RS800.getDisplayName(), imageResources.RS800Icon());
boatClassIconsMap.put(BoatClassMasterdata.RS_X.getDisplayName(), imageResources.RSXIcon());
boatClassIconsMap.put(BoatClassMasterdata.RS_FEVA.getDisplayName(), imageResources.RSFevaIcon());
boatClassIconsMap.put(BoatClassMasterdata.SONAR.getDisplayName(), imageResources.SonarIcon());
@@ -85,10 +90,6 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.TORNADO.getDisplayName(), imageResources.TornadoIcon());
boatClassIconsMap.put(BoatClassMasterdata.TRIAS.getDisplayName(), imageResources.TriasIcon());
boatClassIconsMap.put(BoatClassMasterdata.X_99.getDisplayName(), imageResources.X99Icon());
boatClassIconsMap.put(BoatClassMasterdata.INTERNATIONAL_14.getDisplayName(), imageResources.International14Icon());
boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon());
boatClassIconsMap.put(BoatClassMasterdata.OPEN_BIC.getDisplayName(), imageResources.OpenBicIcon());
boatClassIconsMap.put(BoatClassMasterdata.RS800.getDisplayName(), imageResources.RS800Icon());
}
public static ImageResource getBoatClassIconResource(String displayName) {
@@ -196,4 +196,7 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/D_ONE.png")
ImageResource DOneIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/PWA.png")
ImageResource PWAIcon();
}
@@ -212,7 +212,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement
raceSelectionProvider = new RaceSelectionModel();
trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher,
raceSelectionProvider, stringMessages, /* multiselection */false, /* actionButtonsEnabled */ false);
raceSelectionProvider, stringMessages, /* multiselection */false, isActionButtonsEnabled());
trackedRacesListComposite.ensureDebugId("TrackedRacesListComposite");
trackedRacesPanel.add(trackedRacesListComposite);
trackedRacesListComposite.addTrackedRaceChangeListener(this);
@@ -253,6 +253,10 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement
addSelectedLeaderboardRacesControls(selectedLeaderboardRaceButtonPanel);
}
protected boolean isActionButtonsEnabled() {
return /* actionButtonsEnabled */ false;
}
protected abstract void addLeaderboardControls(Panel controlsPanel);
protected abstract void addSelectedLeaderboardRacesControls(Panel racesPanel);
@@ -70,6 +70,13 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
importContent.add(deviceIdentifierTable);
}
/**
* When doing race log tracking, the Remove and Stop Tracking buttons are required.
*/
protected boolean isActionButtonsEnabled() {
return /* actionButtonsEnabled */ true;
}
@Override
protected void addColumnsToLeaderboardTableAndSetSelectionModel(CellTable<StrippedLeaderboardDTO> leaderboardTable, AdminConsoleTableResources tableResources) {
ListHandler<StrippedLeaderboardDTO> leaderboardColumnListHandler = new ListHandler<StrippedLeaderboardDTO>(
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.google.gwt.cell.client.AbstractCell;
@@ -317,23 +318,32 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer
}
}));
for (SeriesDTO series : editedRegatta.series) {
sailingService.updateSeries(regattaName, series.getName(), series.getName(), series.isMedal(),
series.getDiscardThresholds(), series.isStartsWithZeroScore(),
series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring(),
series.getFleets(), new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to update regatta " + editedRegatta.getName()
+ ": " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
regattaRefresher.fillRegattas();
}
}));
}
final Iterator<SeriesDTO> seriesIter = editedRegatta.series.iterator();
final Runnable r = new Runnable() {
@Override
public void run() {
if (seriesIter.hasNext()) {
final SeriesDTO series = seriesIter.next();
sailingService.updateSeries(regattaName, series.getName(), series.getName(), series.isMedal(),
series.getDiscardThresholds(), series.isStartsWithZeroScore(),
series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring(),
series.getFleets(), new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to update regatta " + editedRegatta.getName()
+ ": " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
regattaRefresher.fillRegattas();
run(); // update next series if iterator has next element
}
}));
}
}
};
r.run();
}
protected List<RegattaDTO> getSelectedRegattas() {
@@ -410,6 +410,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String scoringSchemeWinnerGetsSix();
String scoringSchemeWinnerGetsEight();
String scoringSchemeWinnerGetsEightAndInterpolation();
String scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07();
String scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07Description();
String scoringSystem();
String createFlexibleLeaderboard();
String createRegattaLeaderboard();
@@ -422,6 +422,8 @@ scoringSchemeWinnerGetsFive=High Point System, Winner Gets 5 Points
scoringSchemeWinnerGetsSix=High Point System, Winner Gets 6 Points
scoringSchemeWinnerGetsEight=High Point System, Winner Gets 8 Points
scoringSchemeWinnerGetsEightAndInterpolation=High Point System, Winner 8 Points, Interpolated
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07=Low Point with Eliminations and Rounds, Winner Gets 0.7 Points
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07Description=A variant of the low-point scoring scheme, using eliminations consisting of rounds.\nEach round shall be modeled as a series in the regatta, and the fleet ordering counts backwards\nstarting with the highest number for the first round.\nIf a final and losers final take place, use 1 and 2 as their ordering. Start with 3 for the semi-final round.
scoringSystem=Scoring system
createFlexibleLeaderboard=Create flexible leaderboard
createRegattaLeaderboard=Create regatta leaderboard
@@ -1353,4 +1355,4 @@ photosCount={0,number} photos
photosCount[one]={0,number} photo
eventsHaveTakenPlace={0} events have taken place
eventsHaveTakenPlace[one]= One event has taken place
raceOffice=Race Office
raceOffice=Race Office
@@ -423,6 +423,8 @@ scoringSchemeWinnerGetsFive=High Point System, Gewinner erhält 5 Punkte
scoringSchemeWinnerGetsSix=High Point System, Gewinner erhält 6 Punkte
scoringSchemeWinnerGetsEight=High Point System, Gewinner erhält 8 Punkte
scoringSchemeWinnerGetsEightAndInterpolation=High Point System, Gewinner erhält 8 Punkte, interpoliert
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07=Low Point System mit K.O.-Runden, Gewinner erhält 0,7 Punkte
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07Description=Eine Variante des Low Point Systems mit mehreren K.O.-Wertungen.\nJede Runde einer Wertung wird als "Serie" in der Regatta modelliert, und die Gruppen-Ordnung wird rückwärts zählend verwendet, mit der größten Zahl für die erste Runde.\nWenn die Final-Runde in großes und kleines Finale unterteilt ist, soll das große Finale mit Ordnungszahl 1 und das kleine mit Ordnungszahl 2 modelliert werden. Das Halbfinale verwendet dann 3, usw.
scoringSystem=Bepunktungssystem
createFlexibleLeaderboard=Flexible Rangliste anlegen
createRegattaLeaderboard=Rangliste für Regatta anlegen
@@ -1342,4 +1344,4 @@ photosCount={0,number} Bilder
photosCount[one]={0,number} Bild
eventsHaveTakenPlace={0} Veranstaltungen haben stattgefunden
eventsHaveTakenPlace[one]= Eine Veranstaltung hat stattgefunden
raceOffice=Wettfahrtleitung
raceOffice=Wettfahrtleitung
@@ -408,6 +408,8 @@ scoringSchemeWinnerGetsFive=Высокобальная система подсч
scoringSchemeWinnerGetsSix=Высокобальная система подсчета, победитель получает 6 очков
scoringSchemeWinnerGetsEight=Высокобальная система подсчета, победитель получает 8 очков
scoringSchemeWinnerGetsEightAndInterpolation=Высокобальная система подсчета, победитель получает 8 очков, Interpolated
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07=Low Point with Eliminations and Rounds, Winner Gets 0.7 Points
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07Description=A variant of the low-point scoring scheme, using eliminations consisting of rounds.\nEach round shall be modeled as a series in the regatta, and the fleet ordering counts backwards\nstarting with the highest number for the first round.\nIf a final and losers final take place, use 1 and 2 as their ordering. Start with 3 for the semi-final round.
scoringSystem=Система подсчета очков
createFlexibleLeaderboard=Создание адаптивной таблицы лидеров
createRegattaLeaderboard=Создание таблицы лидеров регаты
@@ -1292,4 +1294,4 @@ photosCount={0,number} photos
photosCount[one]={0,number} photo
eventsHaveTakenPlace={0} events have taken place
eventsHaveTakenPlace[one]= One event has taken place
raceOffice=Race Office
raceOffice=Race Office
@@ -523,6 +523,8 @@ imageURL=Image URL
mimeType=MIME Type
hideMap=隐藏地图
scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07=Low Point with Eliminations and Rounds, Winner Gets 0.7 Points
scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07Description=A variant of the low-point scoring scheme, using eliminations consisting of rounds.\nEach round shall be modeled as a series in the regatta, and the fleet ordering counts backwards\nstarting with the highest number for the first round.\nIf a final and losers final take place, use 1 and 2 as their ordering. Start with 3 for the semi-final round.
averageAbsoluteCrossTrackErrorInMeters=u2205 XTE
showOnlyRacesOfSameDay=Show only races of same day
waypoints=航路点
@@ -1328,4 +1330,4 @@ photosCount={0,number} photos
photosCount[one]={0,number} photo
eventsHaveTakenPlace={0} events have taken place
eventsHaveTakenPlace[one]= One event has taken place
raceOffice=Race Office
raceOffice=Race Office
@@ -43,15 +43,12 @@ import com.sap.sse.common.filter.Filter;
import com.sap.sse.common.filter.FilterSet;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.AsyncAction;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
public class EditMarkPassingsPanel extends AbsolutePanel implements Component<AbstractSettings>, RaceSelectionChangeListener,
CompetitorSelectionChangeListener {
private static class AnchorCell extends AbstractCell<SafeHtml> {
@Override
public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) {
@@ -61,7 +58,6 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
private final SailingServiceAsync sailingService;
private RegattaAndRaceIdentifier raceIdentifier;
private final AsyncActionsExecutor asyncExecutor;
private final ErrorReporter errorReporter;
private final StringMessages stringMessages;
private final CompetitorSelectionProvider competitorSelectionModel;
@@ -91,13 +87,11 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
private final Button removeSuppressedPassingButton;
private Label selectCompetitorLabel = new Label();
public EditMarkPassingsPanel(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
final RegattaAndRaceIdentifier raceIdentifier, final StringMessages stringMessages,
final CompetitorSelectionProvider competitorSelectionModel, final ErrorReporter errorReporter,
final Timer timer) {
public EditMarkPassingsPanel(final SailingServiceAsync sailingService, final RegattaAndRaceIdentifier raceIdentifier,
final StringMessages stringMessages, final CompetitorSelectionProvider competitorSelectionModel,
final ErrorReporter errorReporter, final Timer timer) {
this.sailingService = sailingService;
this.raceIdentifier = raceIdentifier;
this.asyncExecutor = asyncActionsExecutor;
this.errorReporter = errorReporter;
this.competitorSelectionModel = competitorSelectionModel;
this.stringMessages = stringMessages;
@@ -172,13 +166,8 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
removeFixedMarkPassingsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
asyncExecutor.execute(new AsyncAction<Void>() {
@Override
public void execute(AsyncCallback<Void> callback) {
sailingService.updateFixedMarkPassing(leaderboardName, column, column.getFleet(raceIdentifier),
waypointSelectionModel.getSelectedObject().getA(), null, competitor, callback);
}
}, new AsyncCallback<Void>() {
sailingService.updateFixedMarkPassing(leaderboardName, column, column.getFleet(raceIdentifier),
waypointSelectionModel.getSelectedObject().getA(), null, competitor, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorRemovingFixedPassing(caught.getMessage()));
@@ -195,14 +184,9 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
setTimeAsMarkPassingsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
asyncExecutor.execute(new AsyncAction<Void>() {
@Override
public void execute(AsyncCallback<Void> callback) {
sailingService.updateFixedMarkPassing(leaderboardName, column, column.getFleet(raceIdentifier),
sailingService.updateFixedMarkPassing(leaderboardName, column, column.getFleet(raceIdentifier),
waypointSelectionModel.getSelectedObject().getA(), timer.getTime(), competitor,
callback);
}
}, new AsyncCallback<Void>() {
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorSettingFixedPassing(caught.getMessage()));
@@ -221,14 +205,9 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
suppressPassingsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
asyncExecutor.execute(new AsyncAction<Void>() {
@Override
public void execute(AsyncCallback<Void> callback) {
sailingService.updateSuppressedMarkPassings(leaderboardName, column,
sailingService.updateSuppressedMarkPassings(leaderboardName, column,
column.getFleet(raceIdentifier), waypointSelectionModel.getSelectedObject().getA(),
competitor, callback);
}
}, new AsyncCallback<Void>() {
competitor, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorSuppressingPassing(caught.getMessage()));
@@ -246,13 +225,9 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
removeSuppressedPassingButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
asyncExecutor.execute(new AsyncAction<Void>() {
@Override
public void execute(AsyncCallback<Void> callback) {
sailingService.updateSuppressedMarkPassings(leaderboardName, column,
column.getFleet(raceIdentifier), null, competitor, callback);
}
}, new AsyncCallback<Void>() {
sailingService.updateSuppressedMarkPassings(leaderboardName, column,
column.getFleet(raceIdentifier), null, competitor,
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorRemovingSupressedPassing(caught.getMessage()));
@@ -301,14 +276,14 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
}
private void processCompetitorSelectionChange(boolean visible) {
waypointSelectionModel.clear();
if (visible && Util.size(competitorSelectionModel.getSelectedCompetitors()) == 1) {
selectCompetitorLabel.setText("");
refillList();
} else {
disableEditing();
selectCompetitorLabel.setText(stringMessages.selectCompetitor());
}
waypointSelectionModel.clear();
if (visible && Util.size(competitorSelectionModel.getSelectedCompetitors()) == 1) {
selectCompetitorLabel.setText("");
refillList();
} else {
disableEditing();
selectCompetitorLabel.setText(stringMessages.selectCompetitor());
}
}
private void disableEditing() {
@@ -316,17 +291,11 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
clearInfo();
}
private void refillList() {
clearInfo();
competitor = competitorSelectionModel.getSelectedCompetitors().iterator().next();
// Get current mark passings
asyncExecutor.execute(new AsyncAction<Map<Integer, Date>>() {
@Override
public void execute(AsyncCallback<Map<Integer, Date>> callback) {
sailingService.getCompetitorMarkPassings(raceIdentifier, competitor, callback);
}
}, new AsyncCallback<Map<Integer, Date>>() {
sailingService.getCompetitorMarkPassings(raceIdentifier, competitor, new AsyncCallback<Map<Integer, Date>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorTryingToObtainMarkPassing(caught.getMessage()));
@@ -341,32 +310,27 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
}
waypointList.getList().clear();
waypointList.getList().addAll(newMarkPassings);
}
});
// Get current edits
asyncExecutor.execute(new AsyncAction<Map<Integer, Date>>() {
@Override
public void execute(AsyncCallback<Map<Integer, Date>> callback) {
// Get current edits
sailingService.getCompetitorRaceLogMarkPassingData(leaderboardName, column,
column.getFleet(raceIdentifier), competitor, callback);
}
}, new AsyncCallback<Map<Integer, Date>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorTryingToObtainRaceLogMarkPassingData(caught.getMessage()));
}
@Override
public void onSuccess(Map<Integer, Date> result) {
for (Entry<Integer, Date> data : result.entrySet()) {
if (data.getValue() == null) {
zeroBasedIndexOfFirstSuppressedWaypoint = data.getKey();
} else {
currentCompetitorEdits.put(data.getKey(), data.getValue());
column.getFleet(raceIdentifier), competitor, new AsyncCallback<Map<Integer, Date>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorTryingToObtainRaceLogMarkPassingData(caught.getMessage()));
}
}
enableButtons();
wayPointSelectionTable.redraw();
@Override
public void onSuccess(Map<Integer, Date> result) {
for (Entry<Integer, Date> data : result.entrySet()) {
if (data.getValue() == null) {
zeroBasedIndexOfFirstSuppressedWaypoint = data.getKey();
} else {
currentCompetitorEdits.put(data.getKey(), data.getValue());
}
}
enableButtons();
wayPointSelectionTable.redraw();
}
});
}
});
}
@@ -382,12 +346,7 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
}
private void refreshWaypoints() {
asyncExecutor.execute(new AsyncAction<RaceCourseDTO>() {
@Override
public void execute(AsyncCallback<RaceCourseDTO> callback) {
sailingService.getRaceCourse(raceIdentifier, new Date(), callback);
}
}, new AsyncCallback<RaceCourseDTO>() {
sailingService.getRaceCourse(raceIdentifier, new Date(), new AsyncCallback<RaceCourseDTO>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorTryingToObtainRaceCourse(caught.getMessage()));
@@ -27,7 +27,7 @@ public class BoatClassVectorGraphicsResolver {
BoatClassMasterdata.LASER_RADIAL, BoatClassMasterdata.LASER_4_7, BoatClassMasterdata.LASER_2,
BoatClassMasterdata.CONTENDER,
BoatClassMasterdata.FINN, BoatClassMasterdata.MUSTO_SKIFF, BoatClassMasterdata.OPEN_BIC,
BoatClassMasterdata.OPTIMIST, BoatClassMasterdata.RS_X, BoatClassMasterdata.STAR);
BoatClassMasterdata.OPTIMIST, BoatClassMasterdata.PWA, BoatClassMasterdata.RS_X, BoatClassMasterdata.STAR);
BoatClassVectorGraphics _49er = new _49erVectorGraphics(BoatClassMasterdata._49ER, BoatClassMasterdata._49ERFX,
BoatClassMasterdata._29ER, BoatClassMasterdata._18Footer);
BoatClassVectorGraphics extreme40 = new Extreme40VectorGraphics(BoatClassMasterdata.EXTREME_40,
@@ -30,6 +30,8 @@ public class ScoringSchemeTypeFormatter {
return stringMessages.scoringSchemeWinnerGetsEightAndInterpolation();
case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT:
return stringMessages.scoringSchemeHighPointFirstGetsTenOrEight();
case LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07:
return stringMessages.scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07();
}
return null;
}
@@ -40,6 +42,8 @@ public class ScoringSchemeTypeFormatter {
return stringMessages.scoringSchemeHighPointFirstGetsTenOrEightDescription();
case HIGH_POINT_ESS_OVERALL:
return stringMessages.scoringSchemeHighPointEssOverallDescription();
case LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07:
return stringMessages.scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07Description();
default:
return format(scoringSchemeType, stringMessages);
}
@@ -214,8 +214,8 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi
windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
windChart.getEntryWidget().setTitle(stringMessages.windChart());
components.add(windChart);
editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, asyncActionsExecutor, selectedRaceIdentifier,
stringMessages, competitorSelectionModel, errorReporter, timer);
editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier, stringMessages,
competitorSelectionModel, errorReporter, timer);
editMarkPassingPanel.setLeaderboardNameAndColumn(leaderboardPanel.getLeaderboard());
editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings());
components.add(editMarkPassingPanel);
@@ -28,8 +28,7 @@
<!-- Specify the paths for translatable code -->
<extend-property name="locale" values="de"/>
<extend-property name="locale" values="ru"/>
<inherits name='com.sap.sailing.gwt.ui.SourceCommon' />
<inherits name='com.sap.sailing.gwt.ui.SourceClient' />
<inherits name='com.sap.sailing.gwt.ui.SourceShared' />
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -61,7 +61,7 @@ public abstract class BaseRaceLogEventDeserializer implements JsonDeserializer<R
Helpers.tryUuidConversion(id),
new MillisecondsTimePoint(createdAt.longValue()),
author,
new MillisecondsTimePoint(timeStamp.longValue()),
timeStamp == null ? null : new MillisecondsTimePoint(timeStamp.longValue()),
passId.intValue(), competitors);
}
@@ -13,6 +13,13 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEventRestoreFactory;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogEndOfTrackingEventDeserializer;
import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogStartOfTrackingEventDeserializer;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.BoatClassJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.BoatJsonSerializer;
@@ -34,12 +41,42 @@ public class TrackingTimesEventSerializerTest {
return new RaceLogEndOfTrackingEventSerializer(createCompetitorSerializer());
}
private JsonDeserializer<RaceLogEvent> createStartOfTrackingEventDeserializer() {
return new RaceLogStartOfTrackingEventDeserializer(createCompetitorDeserializer());
}
private JsonDeserializer<Competitor> createCompetitorDeserializer() {
return new CompetitorJsonDeserializer(DomainFactory.INSTANCE.getCompetitorStore());
}
private JsonDeserializer<RaceLogEvent> createEndOfTrackingEventDeserializer() {
return new RaceLogEndOfTrackingEventDeserializer(createCompetitorDeserializer());
}
private CompetitorJsonSerializer createCompetitorSerializer() {
return new CompetitorJsonSerializer(new TeamJsonSerializer(
new PersonJsonSerializer(new NationalityJsonSerializer())), new BoatJsonSerializer(
new BoatClassJsonSerializer()));
}
@Test
public void testNullStartOfTrackingTimeAttributeDeserialization() throws JsonDeserializationException {
final RaceLogStartOfTrackingEvent event = RaceLogEventRestoreFactory.INSTANCE.createStartOfTrackingEvent(/* startOfTracking */null,
new LogEventAuthorImpl("Axel", 0), UUID.randomUUID(), /* competitors */null, /* passId */1);
JSONObject json = createStartOfTrackingEventSerializer().serialize(event);
RaceLogEvent deserialized = createStartOfTrackingEventDeserializer().deserialize(json);
assertNull(deserialized.getLogicalTimePoint());
}
@Test
public void testNullEndOfTrackingTimeAttributeDeserialization() throws JsonDeserializationException {
final RaceLogEndOfTrackingEvent event = RaceLogEventRestoreFactory.INSTANCE.createEndOfTrackingEvent(/* endOfTracking */null,
new LogEventAuthorImpl("Axel", 0), UUID.randomUUID(), /* competitors */null, /* passId */1);
JSONObject json = createStartOfTrackingEventSerializer().serialize(event);
RaceLogEvent deserialized = createEndOfTrackingEventDeserializer().deserialize(json);
assertNull(deserialized.getLogicalTimePoint());
}
@Test
public void testNullStartOfTrackingTimeAttribute() {
final RaceLogStartOfTrackingEvent event = RaceLogEventRestoreFactory.INSTANCE.createStartOfTrackingEvent(/* startOfTracking */null,
@@ -176,13 +176,13 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
final int numberOfCompetitorsInRace,
Competitor competitor, int rankByRaceCommittee) throws NoWindException {
boolean scoreHasBeenCorrected = false;
Double scoreByRaceCommittee = leaderboard.getScoringScheme().getScoreForRank(raceColumn, competitor, rankByRaceCommittee,
new Callable<Integer>() {
Double scoreByRaceCommittee = leaderboard.getScoringScheme().getScoreForRank(leaderboard, raceColumn, competitor,
rankByRaceCommittee, new Callable<Integer>() {
@Override
public Integer call() {
return numberOfCompetitorsInRace;
}
}, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher());
}, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), timePoint);
// Do ALWAYS apply score corrections from race committee
applyScoreCorrectionOperation(leaderboard, raceColumn, competitor, scoreByRaceCommittee, timePoint);
@@ -25,6 +25,20 @@
<div id="mainContent">
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
<div class="innerContent">
<h2 class="articleSubheadline">September 2015</h2>
<ul class="bulletList">
<li>A new scoring scheme for elimination rounds has been added. It will first be used
at a major wind surfing event.
</li>
<li>The "Device Configuration" tab has been moved into a new top-level tab
"Race Committee App".
</li>
<li>Included magnetic declination data for 2016 already.
</li>
<li>Bug fix: in rare cases, adding several series to a regatta in one go swapped the order
of the series to be created. This has now been fixed.
</li>
</ul>
<h2 class="articleSubheadline">August 2015</h2>
<ul class="bulletList">
<li>Race log tracking now allows administrators to set the start and end of tracking for a race.
@@ -37,6 +51,8 @@
created by the server if not known yet. Note that for such boat classes no boat class icon
will be present when showing the regatta on the web page.
</li>
<li>Added boat classes Soling, Lago 26, Kielzugvogel, PWA (Professional Windsurfers Association)
</li>
</ul>
<h2 class="articleSubheadline">June 2015</h2>
<ul class="bulletList">
@@ -44,7 +44,7 @@ public class AsyncActionsExecutor {
@Override
public void onSuccess(T result) {
try {
GWT.log("Execution success for action of type: " + getType());
GWT.log("Execution success for action of type " + getType() + ", category "+getCategory());
this.callback.onSuccess(result);
} finally {
AsyncActionsExecutor.this.callCompleted(this);
@@ -54,7 +54,7 @@ public class AsyncActionsExecutor {
@Override
public void onFailure(Throwable caught) {
try {
GWT.log("Execution failure for action of type: " + getType());
GWT.log("Execution failure for action of type " + getType() + ", category "+getCategory());
this.callback.onFailure(caught);
} finally {
AsyncActionsExecutor.this.callCompleted(this);
@@ -62,7 +62,7 @@ public class AsyncActionsExecutor {
}
}
private static final int DEFAULT_MAX_PENDING_CALLS = 6;
private static final int DEFAULT_MAX_PENDING_CALLS = 10;
private static final int DEFAULT_MAX_PENDING_CALLS_PER_TYPE = 4;
/**
@@ -145,7 +145,7 @@ public class AsyncActionsExecutor {
}
numActionsOfType = 0;
} else {
GWT.log("Dropping action of type: " + job.getType());
GWT.log("Dropping action of type " + job.getType() + ", category "+job.getCategory());
/* don't put the call into the execution queue, but save it as the last one of each type
* after each successful execution of a job checkForEmptyCallQueue will check if there
* are other jobs of that type that need execution and execute the last one thus
@@ -9,9 +9,6 @@ import android.os.AsyncTask;
import android.support.v4.content.LocalBroadcastManager;
public class BroadcastManager {
private final static String TAG = BroadcastManager.class.getName();
private static BroadcastManager mInstance;
private final Context mContext;
private Queue<Intent> mQueue;