Merge branch 'master' into bug5440

This commit is contained in:
Axel Uhl
2022-11-02 18:14:17 +01:00
148 changed files with 2366 additions and 552 deletions
+7 -7
View File
@@ -11,7 +11,7 @@
"@sap/hana-client": "2.7.26",
"@sap/hdi": "4.0.1",
"@sap/xsenv": "3.1.0",
"async": "3.1.0",
"async": "3.2.2",
"handlebars": "4.7.7"
},
"dependencies": {
@@ -43,7 +43,7 @@
"resolved": "http://nginx-redirector.repo-cache.svc.cluster.local/repository/appstudio-npm-group/@sap/hdi/-/hdi-4.0.1.tgz",
"integrity": "sha512-f7QdRgFc/amQTgTepgMFpwNMmCvvo7o/sElaZhY/2/j/wYbEukpxvplzWpgAUyPJXe9+WL8BemrEwws0TOTNWQ==",
"requires": {
"async": "3.1.0"
"async": "3.2.2"
}
},
"@sap/xsenv": {
@@ -76,8 +76,8 @@
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": {
"version": "3.1.0",
"resolved": "http://nginx-redirector.repo-cache.svc.cluster.local/repository/appstudio-npm-group/async/-/async-3.1.0.tgz",
"version": "3.2.2",
"resolved": "http://nginx-redirector.repo-cache.svc.cluster.local/repository/appstudio-npm-group/async/-/async-3.2.2.tgz",
"integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ=="
},
"core-util-is": {
@@ -95,7 +95,7 @@
"resolved": "http://nginx-redirector.repo-cache.svc.cluster.local/repository/appstudio-npm-group/handlebars/-/handlebars-4.7.7.tgz",
"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
"requires": {
"minimist": "^1.2.5",
"minimist": "^1.2.6",
"neo-async": "^2.6.0",
"source-map": "^0.6.1",
"uglify-js": "^3.1.4",
@@ -103,8 +103,8 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "http://nginx-redirector.repo-cache.svc.cluster.local/repository/appstudio-npm-group/minimist/-/minimist-1.2.5.tgz",
"version": "1.2.6",
"resolved": "http://nginx-redirector.repo-cache.svc.cluster.local/repository/appstudio-npm-group/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"neo-async": {
@@ -2,21 +2,21 @@
<feature
id="com.amazon.aws.aws-java-api"
label="AWS API"
version="2.17.124"
version="2.18.8"
provider-name="Amazon">
<plugin
id="com.amazon.aws.aws-java-api"
download-size="0"
install-size="0"
version="2.17.124"
version="2.18.8"
unpack="false"/>
<plugin
id="com.amazon.aws.aws-java-api.source"
download-size="0"
install-size="0"
version="2.17.124"
version="2.18.8"
unpack="false"/>
</feature>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<site>
<feature url="features/aws-sdk/com.amazon.aws.aws-java-api_2.17.124.jar" id="com.amazon.aws.aws-java-api" version="2.17.124">
<feature url="features/aws-sdk/com.amazon.aws.aws-java-api_2.18.8.jar" id="com.amazon.aws.aws-java-api" version="2.18.8">
<category name="aws-java-api"/>
</feature>
<category-def name="com.amazon.aws.aws-java-api" label="aws-java-api"/>
@@ -5,7 +5,7 @@ repositories {
}
dependencies {
implementation platform('software.amazon.awssdk:bom:2.17.124')
implementation platform('software.amazon.awssdk:bom:2.18.8')
implementation 'software.amazon.awssdk:s3'
implementation 'software.amazon.awssdk:lambda'
implementation 'software.amazon.awssdk:ec2'
@@ -88,7 +88,7 @@ echo "Building the wrapper bundle..."
cd ..
echo "NOT USING ${extra} arguments for Maven to avoid using local target definition already for building local repo"
echo "In folder $(pwd) using: mvn clean install"
mvn clean install
mvn --batch-mode clean install
mkdir -p ${UPDATE_SITE_PROJECT}/plugins/aws-sdk
rm -rf ${UPDATE_SITE_PROJECT}/plugins/aws-sdk/*
# Note: the JAR ends up in target/ because the SDK project does not use any parent pom, so
+1 -1
View File
@@ -16,7 +16,7 @@
</properties>
<groupId>com.amazon.aws</groupId>
<artifactId>com.amazon.aws.aws-java-api</artifactId>
<version>2.17.124</version>
<version>2.18.8</version>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
@@ -30,6 +30,8 @@ public enum BoatClassMasterdata {
BAVARIA_CRUISER_45 ("Bavaria Cruiser 45", true, 14.27, 4.35, BoatHullType.MONOHULL, true, "B45", "B 45", "BAVARIACRUISER45"),
BAVARIA_CRUISER_46 ("Bavaria Cruiser 46", true, 14.27, 4.35, BoatHullType.MONOHULL, true, "B46", "B 46", "BAVARIACRUISER46"),
BB10M ("BB 10m", true, 10.00, 2.30, BoatHullType.MONOHULL, true, "Dansk BB10M klub"),
BENETEAU_FIRST_35 ("Benetau First 35", true, 10.66, 3.636, BoatHullType.MONOHULL, true, "First 35"),
BENETEAU_FIRST_45 ("Benetau First 45", true, 13.68, 4.202, BoatHullType.MONOHULL, true, "First 45"),
BRASSFAHRT_I ("Brassfahrt I", true, 12.00, 3.50, BoatHullType.MONOHULL, true, "Brassfahrt 1"),
BRASSFAHRT_II ("Brassfahrt II", true, 12.00, 3.50, BoatHullType.MONOHULL, true, "Brassfahrt 2"),
BRASSFAHRT_III ("Brassfahrt III", true, 12.00, 3.50, BoatHullType.MONOHULL, true, "Brassfahrt 3"),
@@ -70,7 +72,7 @@ public enum BoatClassMasterdata {
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"),
IQFOIL_MEN("iQFOil Men", true, 2.20, 0.95, BoatHullType.SURFERBOARD, false, "iQFOil 95 Men"),
IQFOIL_MEN("iQFOil Men", true, 2.20, 0.95, BoatHullType.SURFERBOARD, false, "iQFOil 95 Men", "iQF95"),
IQFOIL_WOMEN("iQFOil Women", true, 2.20, 0.95, BoatHullType.SURFERBOARD, false, "iQFOil 95 Women"),
IQFOIL_YOUTH("iQFOil Youth", true, 2.15, 0.85, BoatHullType.SURFERBOARD, false, "iQFOil 85 Youth"),
IRC("IRC", true, 15.00, 4.00, BoatHullType.MONOHULL, false),
@@ -85,7 +87,7 @@ public enum BoatClassMasterdata {
J111 ("J/111", true, 11.1, 3.29, BoatHullType.MONOHULL, true, "J111", "J-111"),
JK_20 ("20qm Jollenkreuzer", true, 7.75, 2.50, BoatHullType.MONOHULL, true, "JK 20", "JK20", "20er"),
KIELZUGVOGEL ("Kielzugvogel", true, 5.80, 1.88, BoatHullType.MONOHULL, false, "KZV"),
FORMULA_KITE ("Kite", true, 3.35, 1.52, BoatHullType.MONOHULL, false, "Formula Kite"),
FORMULA_KITE ("Kite", true, 3.35, 1.52, BoatHullType.MONOHULL, false, "Formula Kite", "FK"),
LASER_2 ("Laser 2", true, 4.39, 1.42, BoatHullType.MONOHULL, false, "Laser II", "Laser2", "Laser-2", "Laser-II"),
LASER_4_7 ("Laser 4.7", true, 4.20, 1.39, BoatHullType.MONOHULL, false, "L4.7", "ILCA 4", "ILCA4"),
LASER_RADIAL ("Laser Radial", true, 4.19, 1.41, BoatHullType.MONOHULL, false, "LAR", "Laser RAD", "RAD", "Radial", "ILCA 6", "ILCA6"),
@@ -1,9 +1,12 @@
package com.sap.sailing.domain.common;
public enum LeaderboardType {
RegattaLeaderboard(false, true), RegattaMetaLeaderboard(true, true),
RegattaLeaderboard(false, true),
RegattaMetaLeaderboard(true, true),
RegattaLeaderboardWithEliminations(false, true),
FlexibleLeaderboard(false, false), FlexibleMetaLeaderboard(true, false);
RegattaLeaderboardWithOtherTieBreakingLeaderboard(false, true),
FlexibleLeaderboard(false, false),
FlexibleMetaLeaderboard(true, false);
boolean isRegattaLeaderboard;
boolean isMetaLeaderboard;
@@ -5,5 +5,14 @@ public enum ScoringSchemeType {
LOW_POINT, LOW_POINT_WITH_AUTOMATIC_RDG, HIGH_POINT, HIGH_POINT_ESS_OVERALL,HIGH_POINT_ESS_OVERALL_12, 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_FIRST_GETS_TWELVE_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT_2017, LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07,
LOW_POINT_LEAGUE_OVERALL, HIGH_POINT_MATCH_RACING, LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY, LOW_POINT_FIRST_TO_WIN_TWO_RACES, LOW_POINT_FIRST_TO_WIN_THREE_RACES
LOW_POINT_LEAGUE_OVERALL, HIGH_POINT_MATCH_RACING, LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY, LOW_POINT_FIRST_TO_WIN_TWO_RACES, LOW_POINT_FIRST_TO_WIN_THREE_RACES,
HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD;
public static double getScaledScore(double columnFactor, double unscaledScore, boolean oneAlwaysStaysOne) {
return unscaledScore * columnFactor - (oneAlwaysStaysOne ? columnFactor-1 : 0);
}
public static double getUnscaledScore(double columnFactor, double scaledScore, boolean oneAlwaysStaysOne) {
return (scaledScore + (oneAlwaysStaysOne ? columnFactor - 1 : 0)) / columnFactor;
}
}
@@ -36,6 +36,10 @@ public abstract class AbstractLeaderboardDTO extends NamedDTO implements Secured
* Set to the non-<code>null</code> regatta name if this DTO represents a <code>RegattaLeaderboard</code>.
*/
public String regattaName;
/**
* Set if this DTO represents a {@code RegattaLeaderboardWithOtherTieBreakingLeaderboard}
*/
private String otherTieBreakingLeaderboardName;
public String displayName;
public List<CourseAreaDTO> courseAreas;
public ScoringSchemeType scoringScheme;
@@ -111,6 +115,14 @@ public abstract class AbstractLeaderboardDTO extends NamedDTO implements Secured
return competitorDisplayNames.get(competitor);
}
}
public String getOtherTieBreakingLeaderboardName() {
return otherTieBreakingLeaderboardName;
}
public void setOtherTieBreakingLeaderboardName(String otherTieBreakingLeaderboardName) {
this.otherTieBreakingLeaderboardName = otherTieBreakingLeaderboardName;
}
/**
* If the race column whose name is specified in <code>raceColumnName</code> has at least one competitor who has valid
@@ -196,7 +208,6 @@ public abstract class AbstractLeaderboardDTO extends NamedDTO implements Secured
* ensures that a fleet named <code>fleetName</code> is present. If it's not present yet, it's added to the race
* column's fleet name list. The <code>trackedRaceIdentifier</code> and <code>race</code> are associated with the
* column for the fleet identified by <code>fleetName</code>.
*
* @param explicitFactor
* factor by which to multiply the race column's points for the overall score; if <code>null</code>, the
* default will be determined by whether or not the column is marked as medal race
@@ -212,15 +223,16 @@ public abstract class AbstractLeaderboardDTO extends NamedDTO implements Secured
* {@link RaceColumnInSeriesDTO}
* @param fleetDTO
* must not be null
* @param oneAlwaysStaysOne TODO
*/
public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor,
String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace,
RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race, boolean isMetaLeaderboardColumn) {
RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race, boolean isMetaLeaderboardColumn, boolean oneAlwaysStaysOne) {
assert fleetDTO != null;
RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName);
if (raceColumnDTO == null) {
raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace,
explicitFactor, regattaName, seriesName, isMetaLeaderboardColumn);
explicitFactor, regattaName, seriesName, isMetaLeaderboardColumn, oneAlwaysStaysOne);
races.add(raceColumnDTO);
}
raceColumnDTO.setEffectiveFactor(effectiveFactor);
@@ -240,9 +252,9 @@ public abstract class AbstractLeaderboardDTO extends NamedDTO implements Secured
}
public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName,
String seriesName, boolean isMetaLeaderboardColumn) {
String seriesName, boolean isMetaLeaderboardColumn, boolean oneAlwaysStaysOne) {
final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName,
medalRace, /* explicit factor */ null, regattaName, seriesName, isMetaLeaderboardColumn);
medalRace, /* explicit factor */ null, regattaName, seriesName, isMetaLeaderboardColumn, oneAlwaysStaysOne);
races.add(raceColumn);
return raceColumn;
}
@@ -437,6 +449,11 @@ public abstract class AbstractLeaderboardDTO extends NamedDTO implements Secured
return false;
} else if (!getName().equals(other.getName()))
return false;
if (getOtherTieBreakingLeaderboardName() == null) {
if (other.getOtherTieBreakingLeaderboardName() != null)
return false;
} else if (!getOtherTieBreakingLeaderboardName().equals(other.getOtherTieBreakingLeaderboardName()))
return false;
if (races == null) {
if (other.races != null)
return false;
@@ -7,7 +7,7 @@ public class MetaLeaderboardRaceColumnDTO extends RaceColumnDTO {
MetaLeaderboardRaceColumnDTO() {} // for GWT RPC serialization only
public MetaLeaderboardRaceColumnDTO(String name) {
super(name);
super(name, /* oneAlwaysStaysOne */ false);
}
private static final long serialVersionUID = 6152752963316150432L;
@@ -23,6 +23,7 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
private Map<FleetDTO, RaceDTO> racesPerFleet;
private Double explicitFactor;
private double effectiveFactor;
private boolean oneAlwaysStaysOne;
private Map<FleetDTO, RaceLogTrackingInfoDTO> raceLogTrackingInfos;
public enum RaceColumnLiveState { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE };
@@ -30,12 +31,18 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
@Deprecated
RaceColumnDTO() {} // for GWT RPC serialization only
public RaceColumnDTO(String name) {
/**
* @param oneAlwaysStaysOne
* tells whether when scaling results with the {@link #getEffectiveFactor() effective column factor}, the
* 1.0 score shall always remain 1.0.
*/
public RaceColumnDTO(String name, boolean oneAlwaysStaysOne) {
super(name);
trackedRaceIdentifiersPerFleet = new HashMap<FleetDTO, RegattaAndRaceIdentifier>();
raceLogTrackingInfos = new HashMap<FleetDTO, RaceLogTrackingInfoDTO>();
racesPerFleet = new HashMap<FleetDTO, RaceDTO>();
fleets = new ArrayList<FleetDTO>();
this.oneAlwaysStaysOne = oneAlwaysStaysOne;
}
public RaceColumnLiveState getLiveState(long serverTimePointAsMillis) {
@@ -59,6 +66,10 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
public void setExplicitFactor(Double explicitFactor) {
this.explicitFactor = explicitFactor;
}
public boolean isOneAlwaysStaysOne() {
return oneAlwaysStaysOne;
}
public boolean hasTrackedRace(RaceIdentifier raceIdentifier) {
return trackedRaceIdentifiersPerFleet.values().contains(raceIdentifier);
@@ -10,14 +10,21 @@ public class RaceColumnDTOFactory {
* when <code>null</code>, a regular {@link RaceColumnDTO} or {@link MetaLeaderboardRaceColumnDTO} will
* be produced (depending on <code>isMetaLeaderboardColumn</code>) and initialized from the properties
* passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created.
* @param oneAlwaysStaysOne
* When scores in this column are scaled by some factor, either based on the {@link #getExplicitFactor()
* explicit factor} set for this column, or implicitly, e.g., because the {@link ScoringScheme} mandates
* the doubling of medal race scores and this column {@link #isMedalRace() represents a medal race}, then
* some configurations still want the 1.0 score still to be 1.0. For example, with a column factor of 2.0
* scores 1, 2, 3 would end up as 1, 3, 5; or with a column factor of 3.0 scores 1, 2, 3 would end up as
* 1, 4, 7. This method tells whether this column shall apply such a scheme.
*/
public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor,
String regattaName, String seriesName, boolean isMetaLeaderboardColumn) {
String regattaName, String seriesName, boolean isMetaLeaderboardColumn, boolean oneAlwaysStaysOne) {
final RaceColumnDTO raceColumnDTO;
if (seriesName != null) {
raceColumnDTO = new RaceColumnInSeriesDTO(columnName, seriesName, regattaName);
raceColumnDTO = new RaceColumnInSeriesDTO(columnName, seriesName, regattaName, oneAlwaysStaysOne);
} else {
raceColumnDTO = isMetaLeaderboardColumn ? new MetaLeaderboardRaceColumnDTO(columnName) : new RaceColumnDTO(columnName);
raceColumnDTO = isMetaLeaderboardColumn ? new MetaLeaderboardRaceColumnDTO(columnName) : new RaceColumnDTO(columnName, oneAlwaysStaysOne);
}
fillRaceColumnDTO(raceColumnDTO, isMedal, explicitFactor);
return raceColumnDTO;
@@ -8,8 +8,8 @@ public class RaceColumnInSeriesDTO extends RaceColumnDTO {
@Deprecated
RaceColumnInSeriesDTO() {} // for GWT serialization
public RaceColumnInSeriesDTO(String name, String seriesName, String regattaName) {
super(name);
public RaceColumnInSeriesDTO(String name, String seriesName, String regattaName, boolean oneAlwaysStaysOne) {
super(name, oneAlwaysStaysOne);
this.seriesName = seriesName;
this.regattaName = regattaName;
}
@@ -22,10 +22,12 @@ public class SeriesCreationParametersDTO implements Serializable {
private Integer maximumNumberOfDiscards;
private boolean oneAlwaysStaysOne;
SeriesCreationParametersDTO() {}
public SeriesCreationParametersDTO(List<FleetDTO> fleets, boolean isMedal, boolean isFleetsCanRunInParallel, boolean isStartsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward,
int[] discardingThresholds, boolean hasSplitFleetContiguousScoring, Integer maximumNumberOfDiscards) {
int[] discardingThresholds, boolean hasSplitFleetContiguousScoring, Integer maximumNumberOfDiscards, boolean oneAlwaysStaysOne) {
super();
this.fleets = fleets;
this.isMedal = isMedal;
@@ -35,6 +37,7 @@ public class SeriesCreationParametersDTO implements Serializable {
this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward;
this.discardingThresholds = discardingThresholds;
this.maximumNumberOfDiscards = maximumNumberOfDiscards;
this.oneAlwaysStaysOne = oneAlwaysStaysOne;
}
public List<FleetDTO> getFleets() {
@@ -68,4 +71,8 @@ public class SeriesCreationParametersDTO implements Serializable {
public Integer getMaximumNumberOfDiscards() {
return maximumNumberOfDiscards;
}
public boolean isOneAlwaysStaysOne() {
return oneAlwaysStaysOne;
}
}
@@ -22,6 +22,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaRegistry;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.base.SailingServerConfiguration;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.base.impl.DynamicBoat;
import com.sap.sailing.domain.base.impl.DynamicCompetitor;
@@ -35,8 +36,10 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -223,4 +226,12 @@ public interface DomainObjectFactory {
TypeBasedServiceFinder<RaceTrackingConnectivityParametersHandler> getRaceTrackingConnectivityParamsServiceFinder();
Wind loadWind(Document windDocument);
Map<RaceIdentifier, MarkPassingRaceFingerprint> loadFingerprintsForMarkPassingHashes();
/**
* For races that have a {@link MarkPassingRaceFingerprint} stored in the database (see {@link #loadFingerprintsForMarkPassingHashes()})
* a caller can load the corresponding mark passings with this method.
*/
Map<Competitor, Map<Waypoint, MarkPassing>> loadMarkPassings(RaceIdentifier raceIdentifier);
}
@@ -49,6 +49,7 @@ public enum FieldNames {
SERIES_STARTS_WITH_ZERO_SCORE,
SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING,
SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD,
SERIES_ONE_ALWAYS_STAYS_ONE,
SERIES_FLEETS,
SERIES_RACE_COLUMNS,
SERIES_DISCARDING_THRESHOLDS,
@@ -87,7 +88,7 @@ public enum FieldNames {
LEADERBOARD_SCORE_CORRECTIONS, LEADERBOARD_DISCARDING_THRESHOLDS,
LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON, LEADERBOARD_CORRECTED_SCORE, LEADERBOARD_SCORE_CORRECTION_TIMESTAMP,
LEADERBOARD_RANK, LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_SCORE_CORRECTION_MERGE_STATE,
LEADERBOARD_COLUMN_FACTORS, WRAPPED_REGATTA_LEADERBOARD_NAME,
LEADERBOARD_COLUMN_FACTORS, WRAPPED_REGATTA_LEADERBOARD_NAME, OTHER_TIEBREAKING_LEADERBOARD_NAME,
ELMINATED_COMPETITORS,
// leaderboard group
@@ -187,5 +188,8 @@ public enum FieldNames {
ORC_CERTIFICATE_TWS_14KT, ORC_CERTIFICATE_TWS_16KT, ORC_CERTIFICATE_TWS_20KT, ORC_CERTIFICATE_R52_PREDICTION, ORC_CERTIFICATE_R60_PREDICTION,
ORC_CERTIFICATE_R75_PREDICTION, ORC_CERTIFICATE_R90_PREDICTION, ORC_CERTIFICATE_R110_PREDICTION, ORC_CERTIFICATE_R120_PREDICTION,
ORC_CERTIFICATE_R135_PREDICTION, ORC_CERTIFICATE_R150_PREDICTION, ORC_CERTIFICATE_BOATCLASS, ORC_CERTIFICATE_LENGTH, ORC_CERTIFICATE_TWA_SPEED_PREDICTIONS,
ORC_CERTIFICATE_CIRCULAR_RANDOM_SPEED_PREDICTIONS, ORC_CERTIFICATE_NON_SPINNAKER_SPEED_PREDICTIONS, ORC_OTHER_RACE_IDENTIFIER, ORC_IMPLIED_WIND_SOURCE;
ORC_CERTIFICATE_CIRCULAR_RANDOM_SPEED_PREDICTIONS, ORC_CERTIFICATE_NON_SPINNAKER_SPEED_PREDICTIONS, ORC_OTHER_RACE_IDENTIFIER, ORC_IMPLIED_WIND_SOURCE,
// MARKPASSINGS collection
MARK_PASSINGS_FINGERPRINT, MARK_PASSINGS;
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.persistence;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -20,14 +21,18 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.base.SailingServerConfiguration;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.dto.AnniversaryType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
@@ -228,4 +233,14 @@ public interface MongoObjectFactory {
void storeAnniversaryData(ConcurrentHashMap<Integer, Pair<DetailedRaceInfo, AnniversaryType>> knownAnniversaries);
Document storeWind(Wind wind);
/**
* Stores a race's mark passings persistently.
*
* @param raceIdentifier
* identifies the race to which the mark passings belong
* @param fingerprint
* a composite fingerprint of the race in the state at which the mark passings were captured
*/
void storeMarkPassings(RaceIdentifier raceIdentifier, MarkPassingRaceFingerprint fingerprint, Map<Competitor, Map<Waypoint, MarkPassing>> markPassings);
}
@@ -120,6 +120,11 @@ public enum CollectionNames {
/**
* Contains the known anniversaries
*/
ANNIVERSARIES;
ANNIVERSARIES,
/**
* Contains the Hashes for the {@link MarkPassingHashFingerprint}
*/
MARKPASSINGS;
}
@@ -233,8 +233,11 @@ import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithC
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardWithOtherTieBreakingLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.markpassinghash.impl.MarkPassingRaceFingerprintImpl;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.FieldNames;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
@@ -246,10 +249,12 @@ import com.sap.sailing.domain.ranking.RankingMetricConstructor;
import com.sap.sailing.domain.ranking.RankingMetricsFactory;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
import com.sap.sailing.server.gateway.deserialization.impl.BoatJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.CompetitorWithBoatRefJsonDeserializer;
@@ -394,6 +399,14 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
return result;
}
/**
* @see #loadRaceIdentifier(Document)
*/
private void addRaceIdentifierToQuery(Document query, RaceIdentifier raceIdentifier) {
query.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName());
query.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName());
}
static void ensureIndicesOnWindTracks(MongoCollection<Document> windTracks) {
windTracks.createIndex(new Document(FieldNames.RACE_ID.name(), 1), new IndexOptions().name("windbyrace").background(false)); // for new programmatic access
@@ -491,7 +504,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
result = leaderboardRegistry.getLeaderboardByName(leaderboardName);
}
if (result == null) {
String wrappedRegattaLeaderboardName = (String) dbLeaderboard
final String wrappedRegattaLeaderboardName = (String) dbLeaderboard
.get(FieldNames.WRAPPED_REGATTA_LEADERBOARD_NAME.name());
if (wrappedRegattaLeaderboardName != null) {
result = loadRegattaLeaderboardWithEliminations(dbLeaderboard, leaderboardName,
@@ -510,7 +523,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
result = loadFlexibleLeaderboard(dbLeaderboard, resultDiscardingRule);
} else {
result = loadRegattaLeaderboard(leaderboardName, regattaName, dbLeaderboard,
resultDiscardingRule, regattaRegistry);
resultDiscardingRule, regattaRegistry, leaderboardRegistry);
}
}
if (result != null) {
@@ -590,14 +603,21 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
*/
private RegattaLeaderboard loadRegattaLeaderboard(String leaderboardName, String regattaName,
Document dbLeaderboard, ThresholdBasedResultDiscardingRule resultDiscardingRule,
RegattaRegistry regattaRegistry) {
RegattaLeaderboard result = null;
RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) {
final RegattaLeaderboard result;
Regatta regatta = regattaRegistry.getRegatta(new RegattaName(regattaName));
if (regatta == null) {
logger.info("Couldn't find regatta " + regattaName
+ " for corresponding regatta leaderboard. Not loading regatta leaderboard.");
result = null;
} else {
result = new RegattaLeaderboardImpl(regatta, resultDiscardingRule);
final String otherTieBreakingLeaderboardName = (String) dbLeaderboard.get(FieldNames.OTHER_TIEBREAKING_LEADERBOARD_NAME.name());
if (otherTieBreakingLeaderboardName == null) {
result = new RegattaLeaderboardImpl(regatta, resultDiscardingRule);
} else {
result = new RegattaLeaderboardWithOtherTieBreakingLeaderboardImpl(regatta, resultDiscardingRule,
() -> (RegattaLeaderboard) leaderboardRegistry.getLeaderboardByName(otherTieBreakingLeaderboardName));
}
}
return result;
}
@@ -1426,11 +1446,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
final Number maximumNumberOfDiscardsAsObject = (Number) dbSeries
.get(FieldNames.SERIES_MAXIMUM_NUMBER_OF_DISCARDS.name());
final Integer maximumNumberOfDiscards = maximumNumberOfDiscardsAsObject == null ? null : maximumNumberOfDiscardsAsObject.intValue();
Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name());
Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries
final Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name());
final Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries
.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name());
Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries
final Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries
.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name());
final Boolean oneAlwaysStaysOne = (Boolean) dbSeries.get(FieldNames.SERIES_ONE_ALWAYS_STAYS_ONE.name());
@SuppressWarnings("unchecked")
final Iterable<Document> dbFleets = (Iterable<Document>) dbSeries.get(FieldNames.SERIES_FLEETS.name());
List<Fleet> fleets = loadFleets(dbFleets);
@@ -1454,6 +1475,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
if (firstColumnIsNonDiscardableCarryForward != null) {
series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForward);
}
if (oneAlwaysStaysOne != null) {
series.setOneAlwaysStaysOne(oneAlwaysStaysOne);
}
loadRaceColumnRaceLinks(dbRaceColumns, series);
return series;
}
@@ -3146,5 +3170,70 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
public TypeBasedServiceFinder<RaceTrackingConnectivityParametersHandler> getRaceTrackingConnectivityParamsServiceFinder() {
return raceTrackingConnectivityParamsServiceFinder;
}
@Override
public Map<RaceIdentifier, MarkPassingRaceFingerprint> loadFingerprintsForMarkPassingHashes() {
final MongoCollection<Document> markPassingsCollection = database.getCollection(CollectionNames.MARKPASSINGS.name());
markPassingsCollection.createIndex(new Document()
.append(FieldNames.EVENT_NAME.name(), 1)
.append(FieldNames.RACE_NAME.name(), 1),
new IndexOptions()
.unique(true)
.name("markpassingsbyeventandrace")
.background(false));
final Map<RaceIdentifier, MarkPassingRaceFingerprint> fingerprintHashMap = new HashMap<>();
for (final Document currentDocument : markPassingsCollection.find()) {
final Pair<RaceIdentifier, MarkPassingRaceFingerprint> fingerprint = loadMarkPassingsFingerprint(currentDocument);
if (fingerprint != null && fingerprint.getB() != null) {
fingerprintHashMap.put(fingerprint.getA(), fingerprint.getB());
}
}
return fingerprintHashMap;
}
private Pair<RaceIdentifier, MarkPassingRaceFingerprint> loadMarkPassingsFingerprint(final Document currentDocument) {
final RaceIdentifier raceIdentifier = loadRaceIdentifier(currentDocument);
MarkPassingRaceFingerprint fingerprint;
try {
final JSONObject json = Helpers.toJSONObjectSafe(
new JSONParser().parse(((Document) currentDocument.get(FieldNames.MARK_PASSINGS_FINGERPRINT.name())).toJson()));
fingerprint = new MarkPassingRaceFingerprintImpl(json);
} catch (JsonDeserializationException | ParseException e) {
logger.log(Level.WARNING, "Problem de-serializing mark passings from document; ignoring", e);
fingerprint = null;
}
return new Pair<>(raceIdentifier, fingerprint);
}
@Override
public Map<Competitor, Map<Waypoint, MarkPassing>> loadMarkPassings(RaceIdentifier raceIdentifier) {
final Map<Competitor, Map<Waypoint, MarkPassing>> result;
final Document query = new Document();
addRaceIdentifierToQuery(query, raceIdentifier);
final MongoCollection<Document> markPassingsCollection = database.getCollection(CollectionNames.MARKPASSINGS.name());
final Document doc = markPassingsCollection.find(query).first();
if (doc != null) {
result = new HashMap<>();
final List<Document> markPassingsDoc = doc.getList(FieldNames.MARK_PASSINGS.name(), Document.class);
for (final Document markPassingsForOneCompetitorDoc : markPassingsDoc) {
final Serializable competitorId = markPassingsForOneCompetitorDoc.get(FieldNames.COMPETITOR_ID.name(), Serializable.class);
final Competitor competitor = baseDomainFactory.getExistingCompetitorById(competitorId);
for (final Document markPassingForWaypoint : markPassingsForOneCompetitorDoc.getList(FieldNames.MARK_PASSINGS.name(), Document.class)) {
final Pair<Waypoint, MarkPassing> waypointAndMarkPassing = loadWaypointAndMarkPassing(competitor, markPassingForWaypoint);
result.computeIfAbsent(competitor, c->new HashMap<>()).put(waypointAndMarkPassing.getA(), waypointAndMarkPassing.getB());
}
}
} else {
result = null;
}
return result;
}
private Pair<Waypoint, MarkPassing> loadWaypointAndMarkPassing(Competitor competitor, Document markPassingForWaypoint) {
final Serializable waypointId = markPassingForWaypoint.get(FieldNames.WAYPOINT_ID.name(), Serializable.class);
final Waypoint waypoint = baseDomainFactory.getExistingWaypointById(waypointId);
final TimePoint timePoint = TimePoint.of(markPassingForWaypoint.getLong(FieldNames.TIME_AS_MILLIS.name()));
final MarkPassing markPassing = new MarkPassingImpl(timePoint, waypoint, competitor);
return new Pair<>(waypoint, markPassing);
}
}
@@ -122,13 +122,16 @@ import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithOtherTieBreakingLeaderboard;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.persistence.FieldNames;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -399,6 +402,10 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, Document dbLeaderboard) {
dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
if (leaderboard instanceof RegattaLeaderboardWithOtherTieBreakingLeaderboard) {
dbLeaderboard.put(FieldNames.OTHER_TIEBREAKING_LEADERBOARD_NAME.name(),
((RegattaLeaderboardWithOtherTieBreakingLeaderboard) leaderboard).getOtherTieBreakingLeaderboard().getName());
}
}
private void storeFlexibleLeaderboard(FlexibleLeaderboard leaderboard, Document dbLeaderboard) {
@@ -798,6 +805,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring());
dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore());
dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward());
dbSeries.put(FieldNames.SERIES_ONE_ALWAYS_STAYS_ONE.name(), s.isOneAlwaysStaysOne());
BasicDBList dbFleets = new BasicDBList();
for (Fleet fleet : s.getFleets()) {
dbFleets.add(storeFleet(fleet));
@@ -1958,5 +1966,34 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
document.append(FieldNames.ORC_CERTIFICATE.name(), createORCCertificateObject(event.getCertificate()));
return storeRegattaLogEvent(regattaLikeIdentifier, document);
}
private List<Document> storeMarkPassings(Map<Competitor, Map<Waypoint, MarkPassing>> markPassings) {
final List<Document> result = new ArrayList<>();
for (final Entry<Competitor, Map<Waypoint, MarkPassing>> e : markPassings.entrySet()) {
final Document competitorMarkPassings = new Document();
competitorMarkPassings.put(FieldNames.COMPETITOR_ID.name(), e.getKey().getId());
final List<Document> markPassingsList = new ArrayList<>();
for (final Entry<Waypoint, MarkPassing> f : e.getValue().entrySet()) {
final Document markPassingDoc = new Document();
markPassingDoc.put(FieldNames.WAYPOINT_ID.name(), f.getKey().getId());
markPassingDoc.put(FieldNames.TIME_AS_MILLIS.name(), f.getValue().getTimePoint().asMillis());
markPassingsList.add(markPassingDoc);
}
competitorMarkPassings.put(FieldNames.MARK_PASSINGS.name(), markPassingsList);
result.add(competitorMarkPassings);
}
return result;
}
public void storeMarkPassings(RaceIdentifier raceIdentification, MarkPassingRaceFingerprint fingerprint, Map<Competitor, Map<Waypoint, MarkPassing>> markPassings) {
MongoCollection<Document> markPassingCollection = database.getCollection(CollectionNames.MARKPASSINGS.name());
JSONObject fingerprintjson = fingerprint.toJson();
final Document result = new Document();
final Document fingerprintDoc = Document.parse(fingerprintjson.toString());
result.put(FieldNames.MARK_PASSINGS_FINGERPRINT.name(), fingerprintDoc);
storeRaceIdentifier(result, raceIdentification);
final List<Document> markPassingsDoc = storeMarkPassings(markPassings);
result.put(FieldNames.MARK_PASSINGS.name(), markPassingsDoc);
markPassingCollection.insertOne(result);
}
}
@@ -70,7 +70,7 @@ public interface SharedDomainFactory<RLR extends RaceLogResolver> extends Compet
*/
Waypoint createWaypoint(ControlPoint controlPoint, PassingInstruction passingInstruction);
Waypoint getExistingWaypointById(Waypoint waypointPrototype);
Waypoint getExistingWaypointById(Serializable waypointId);
/**
* Atomically checks if a waypoint by an equal {@link Waypoint#getId()} as <code>waypoint</code> exists in this domain factory's
@@ -271,16 +271,16 @@ public class SharedDomainFactoryImpl<RLR extends RaceLogResolver> implements Sha
}
@Override
public Waypoint getExistingWaypointById(Waypoint waypointPrototype) {
public Waypoint getExistingWaypointById(Serializable waypointId) {
synchronized (waypointCache) {
expungeStaleWaypointCacheEntries();
Waypoint result = null;
Reference<Waypoint> ref = waypointCache.get(waypointPrototype.getId());
Reference<Waypoint> ref = waypointCache.get(waypointId);
if (ref != null) {
result = ref.get();
if (result == null) {
// waypoint was finalized; remove entry from cache
waypointCache.remove(waypointPrototype.getId());
waypointCache.remove(waypointId);
}
}
return result;
@@ -410,7 +410,7 @@ public class LeaderboardDTODiffingTest {
public void testPartialRaceColumnDTOCompaction() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
// create a modified R9 RaceDTO clone in newVersion to make sure that even changing a property in the RaceDTO will keep the RaceColumnDTO from being omitted
RaceColumnDTO r9 = newVersion.getRaceColumnByName("R9");
RaceColumnDTO clonedR9 = new RaceColumnDTO("R9");
RaceColumnDTO clonedR9 = new RaceColumnDTO("R9", /* oneAlwaysStaysOne */ false);
cloner.clone(r9, clonedR9);
// also clone the racesPerFleet map, or else we'd be modifying the previous version's one too
final Field racesPerFleetField = clonedR9.getClass().getDeclaredField("racesPerFleet");
@@ -0,0 +1,49 @@
package com.sap.sailing.domain.common.test;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.sap.sailing.domain.common.ScoringSchemeType;
public class ScoringSchemeTypeScoreScalingTest {
private static final double DELTA = 0.0000001;
@Test
public void testScalingThreeWithThree() {
testScalingAndUnscaling(/* unscaled */ 3, /* factor */ 3, /* scaled */ 9, /* oneAlwaysStaysOne */ false);
}
@Test
public void testScalingOneWithThree() {
testScalingAndUnscaling(/* unscaled */ 1, /* factor */ 3, /* scaled */ 3, /* oneAlwaysStaysOne */ false);
}
@Test
public void testScalingThreeWithTwo() {
testScalingAndUnscaling(/* unscaled */ 3, /* factor */ 2, /* scaled */ 6, /* oneAlwaysStaysOne */ false);
}
@Test
public void testScalingOneWithTwo() {
testScalingAndUnscaling(/* unscaled */ 1, /* factor */ 2, /* scaled */ 2, /* oneAlwaysStaysOne */ false);
}
@Test
public void testScalingWithThreeWithOneStaysOne() {
testScalingAndUnscaling(/* unscaled */ 3, /* factor */ 3, /* scaled */ 7, /* oneAlwaysStaysOne */ true);
testScalingAndUnscaling(/* unscaled */ 1, /* factor */ 3, /* scaled */ 1, /* oneAlwaysStaysOne */ true);
}
@Test
public void testScalingWithTwoWithOneStaysOne() {
testScalingAndUnscaling(/* unscaled */ 3, /* factor */ 2, /* scaled */ 5, /* oneAlwaysStaysOne */ true);
testScalingAndUnscaling(/* unscaled */ 1, /* factor */ 2, /* scaled */ 1, /* oneAlwaysStaysOne */ true);
}
private void testScalingAndUnscaling(final double unscaled, final double factor, final double scaled,
final boolean oneAlwaysStaysOne) {
assertEquals(scaled, ScoringSchemeType.getScaledScore(factor, unscaled, oneAlwaysStaysOne), DELTA);
assertEquals(unscaled, ScoringSchemeType.getUnscaledScore(factor, scaled, oneAlwaysStaysOne), DELTA);
}
}
@@ -29,6 +29,7 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.DynamicCompetitorWithBoat;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
@@ -83,10 +84,14 @@ public class TestFarthestAheadWithSkippedMarkPassing {
private Waypoint windward;
private Waypoint finish;
private void setUp(TimeOnTimeFactorMapping timeOnTimeFactors, Function<Competitor, Double> timeOnDistanceFactors) {
CompetitorWithBoat fastCompetitor = TrackBasedTest.createCompetitorWithBoat("FastBoat");
private void setUp(TimeOnTimeFactorMapping timeOnTimeFactors, Function<Competitor, Duration> timeOnDistanceAllowances) {
DynamicCompetitorWithBoat fastCompetitor = (DynamicCompetitorWithBoat) TrackBasedTest.createCompetitorWithBoat("FastBoat");
fastCompetitor.setTimeOnTimeFactor(timeOnTimeFactors.apply(fastCompetitor));
fastCompetitor.setTimeOnDistanceAllowancePerNauticalMile(timeOnDistanceAllowances.apply(fastCompetitor));
c1 = fastCompetitor;
CompetitorWithBoat slowCompetitor = TrackBasedTest.createCompetitorWithBoat("SlowBoat");
DynamicCompetitorWithBoat slowCompetitor = (DynamicCompetitorWithBoat) TrackBasedTest.createCompetitorWithBoat("SlowBoat");
slowCompetitor.setTimeOnTimeFactor(timeOnTimeFactors.apply(slowCompetitor));
slowCompetitor.setTimeOnDistanceAllowancePerNauticalMile(timeOnDistanceAllowances.apply(slowCompetitor));
c2 = slowCompetitor;
trackedRace = createTrackedRace(TrackBasedTest.createCompetitorAndBoatsMap(fastCompetitor, slowCompetitor));
rankingMetric = trackedRace.getRankingMetric();
@@ -141,7 +146,7 @@ public class TestFarthestAheadWithSkippedMarkPassing {
*/
@Test
public void testWithOneMarkPassingOnly() {
setUp(c -> c==c1 ? 2.0 : 1.0, c -> 0.0);
setUp(c -> c==c1 ? 2.0 : 1.0, c -> Duration.NULL);
final TimePoint startOfRace = MillisecondsTimePoint.now();
final TimePoint middleOfFirstLeg = startOfRace.plus(Duration.ONE_HOUR.times(3));
trackedRace.updateMarkPassings(
@@ -174,7 +179,7 @@ public class TestFarthestAheadWithSkippedMarkPassing {
*/
@Test
public void testWithTwoMarkPassingsForC1AndSkippedStartForC2() {
setUp(c -> c==c1 ? 2.0 : 1.0, c -> 0.0);
setUp(c -> c==c1 ? 2.0 : 1.0, c -> Duration.NULL);
final TimePoint startOfRace = MillisecondsTimePoint.now();
final TimePoint endOfFirstLeg = startOfRace.plus(Duration.ONE_HOUR.times(3));
final TimePoint middleOfSecondLeg = endOfFirstLeg.plus(Duration.ONE_MINUTE.times(10));
@@ -57,7 +57,6 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
@@ -85,11 +84,6 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRankingTestBase {
protected DelegatingRegattaLeaderboardWithCompetitorElimination createDelegatingRegattaLeaderboardWithCompetitorElimination(
Regatta regatta, String leaderboardName, int[] discardingThresholds) {
return new DelegatingRegattaLeaderboardWithCompetitorElimination(()->createLeaderboard(regatta, discardingThresholds), leaderboardName);
}
@Test
public void testOneStartedRaceWithDifferentScores() throws NoWindException {
List<Competitor> competitors = createCompetitors(10);
@@ -23,7 +23,6 @@ import com.sap.sailing.domain.common.CompetitorRegistrationType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
import com.sap.sailing.domain.leaderboard.impl.LowPointFirstToWinTwoRaces;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
@@ -42,12 +41,6 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardScoringAndRankingTestForLowPoints extends LeaderboardScoringAndRankingTestBase {
private static final double EPSILON = 0.000001;
protected DelegatingRegattaLeaderboardWithCompetitorElimination createDelegatingRegattaLeaderboardWithCompetitorElimination(
Regatta regatta, String leaderboardName, int[] discardingThresholds) {
return new DelegatingRegattaLeaderboardWithCompetitorElimination(
() -> createLeaderboard(regatta, discardingThresholds), leaderboardName);
}
private void executePreSeries(List<Competitor> yellow, List<Competitor> blue, TimePoint now) {
RaceColumn qColumn = series.get(0).getRaceColumnByName("Q");
TrackedRace qYellow = new MockedTrackedRaceWithStartTimeAndRanks(now, yellow);
@@ -0,0 +1,179 @@
package com.sap.sailing.domain.test.markpassinghash;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import org.json.simple.JSONObject;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintFactory;
import com.sap.sailing.domain.test.OnlineTracTracBasedTest;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class MarkPassingRaceFingerprintJsonSerializationTest extends OnlineTracTracBasedTest {
DynamicTrackedRaceImpl trackedRace1;
DynamicTrackedRaceImpl trackedRace2;
MarkPassingCalculator calculator1;
MarkPassingCalculator calculator2;
public MarkPassingRaceFingerprintJsonSerializationTest() throws MalformedURLException, URISyntaxException {
super();
}
@Override
protected String getExpectedEventName() {
return "Academy Tracking 2011";
}
@Before
public void setUp() throws Exception {
super.setUp("event_20110505_SailingTea", // Semifinale
/* raceId */ "01ea3604-02ef-11e1-9efc-406186cbf87c", /* liveUri */ null, /* storedUri */ null,
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACECOURSE,
ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS, ReceiverType.SENSORDATA });
trackedRace1 = getTrackedRace();
super.setUp();
super.setUp("event_20110505_SailingTea", // Semifinale
/* raceId */ "01ea3604-02ef-11e1-9efc-406186cbf87c", /* liveUri */ null, /* storedUri */ null,
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACECOURSE,
ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS, ReceiverType.SENSORDATA });
trackedRace2 = getTrackedRace();
calculator1 = new MarkPassingCalculator(trackedRace1, false, false);
calculator2 = new MarkPassingCalculator(trackedRace2, false, false);
}
@Test
public void testJsonSerialization() {
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
MarkPassingRaceFingerprint output1 = factory.fromJson(json1);
assertTrue("Original and de-serialized copy are equal", output1.matches(trackedRace1));
assertEquals(fingerprint1, output1);
assertEquals(fingerprint1.hashCode(), output1.hashCode());
}
@Test
public void testJsonSerializationWithChangesInMarkFixes() {
DynamicTrackedRaceImpl testRace = trackedRace2;
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
// Change of the race should result in a different hash
TimePoint epoch = new MillisecondsTimePoint(0l);
TimePoint now = MillisecondsTimePoint.now();
Map<String, Position> markPositions = new HashMap<String, Position>();
markPositions.put("CR Start (1)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("CR Start (2)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("Leeward mark", new DegreePosition(53.562145000000015, 10.009252));
markPositions.put("Luvtonne", new DegreePosition(53.560581899999995, 10.005657));
for (Waypoint w : trackedRace2.getRace().getCourse().getWaypoints()) {
for (Mark mark : w.getMarks()) {
assert markPositions.containsKey(mark.getName());
testRace.getOrCreateTrack(mark).addGPSFix(new GPSFixImpl(markPositions.get(mark.getName()), epoch));
testRace.getOrCreateTrack(mark).addGPSFix(new GPSFixImpl(markPositions.get(mark.getName()), now));
}
}
assertFalse(fingerprint1.matches(trackedRace2));
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(testRace);
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
@Test
public void testWaypointChangePassingInstruction() {
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
Waypoint wp = trackedRace2.getRace().getCourse().getFirstWaypoint();
ControlPoint cP = wp.getControlPoint();
WaypointImpl wpNew = new WaypointImpl(cP, PassingInstruction.Gate);
trackedRace2.getRace().getCourse().removeWaypoint(0);
trackedRace2.getRace().getCourse().addWaypoint(0, wpNew);
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(trackedRace2);
assertFalse(fingerprint1.matches(trackedRace2));
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
@Test
public void testControlPointChange() {
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
Mark gate1 = new MarkImpl("Gate1");
Mark gate2 = new MarkImpl("Gate2");
ControlPointWithTwoMarks cp = new ControlPointWithTwoMarksImpl(gate1, gate2, "cp", "");
Waypoint wpNew = new WaypointImpl(cp, PassingInstruction.None);
trackedRace2.getRace().getCourse().removeWaypoint(0);
trackedRace2.getRace().getCourse().addWaypoint(0, wpNew);
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(trackedRace2);
assertFalse(fingerprint1.matches(trackedRace2));
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
@Test
public void testCompetitorFixChange() {
DynamicTrackedRaceImpl testRace = trackedRace2;
final DynamicTrackedRaceImpl secureRace = trackedRace2;
MarkPassingRaceFingerprintFactory factory = MarkPassingRaceFingerprintFactory.INSTANCE;
MarkPassingRaceFingerprint fingerprint1 = factory.createFingerprint(trackedRace1);
assertTrue(fingerprint1.matches(trackedRace2));
Competitor firstCompetitor = trackedRace2.getRace().getCompetitors().iterator().next();
secureRace.getTrack(firstCompetitor).lockForRead();
GPSFixMoving firstFix;
try {
firstFix = testRace.getTrack(firstCompetitor).getRawFixes().iterator().next();
} finally {
secureRace.getTrack(firstCompetitor).unlockAfterRead();
}
SpeedWithBearing speed = firstFix.getSpeed();
Position pos = firstFix.getPosition();
TimePoint tp = firstFix.getTimePoint();
DegreePosition degPos = new DegreePosition(pos.getLatDeg() + 0.05, pos.getLngDeg() + 0.05);
GPSFixMoving gpsM = new GPSFixMovingImpl(degPos, tp, speed);
testRace.getTrack(firstCompetitor).add(gpsM, true);
MarkPassingRaceFingerprint fingerprint2 = factory.createFingerprint(testRace);
assertFalse(fingerprint1.matches(testRace));
assertFalse(fingerprint2.matches(trackedRace1));
JSONObject json1 = fingerprint1.toJson();
JSONObject json2 = fingerprint2.toJson();
assertNotEquals("Json1 and Json2 are equal: " + json1 + " json2: " + json2, json1, json2);
}
}
@@ -18,6 +18,8 @@ Export-Package: com.sap.sailing.domain.base,
com.sap.sailing.domain.markpassingcalculation,
com.sap.sailing.domain.markpassingcalculation.impl,
com.sap.sailing.domain.markpassingcalculation.splining,
com.sap.sailing.domain.markpassinghash,
com.sap.sailing.domain.markpassinghash.impl;x-friends:="com.sap.sailing.domain.persistence",
com.sap.sailing.domain.masterdataimport,
com.sap.sailing.domain.orc,
com.sap.sailing.domain.orc.impl;x-friends:="com.sap.sailing.domain.test,com.sap.sailing.domain.test,com.sap.sailing.mongodb.test",
@@ -11,6 +11,7 @@ import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.racelog.tracking.CompetitorRegistrationOnRaceLogDisabledException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.MetaLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
@@ -195,10 +196,20 @@ public interface RaceColumn extends Named {
void setFactor(Double factor);
/**
* If <code>null</code>, the {@link #getFactor() factor} defaults to 1 for non-medal and {@link #DEFAULT_MEDAL_RACE_FACTOR} for
* medal races. Otherwise, the explicit factor is used.
* {@link ScoringScheme}s shall prefer this explicit factor, if not <code>null</code>, and otherwise determine a default
* based on the rules of the scoring scheme, e.g., defaulting to 1 for non-medal and {@link #DEFAULT_MEDAL_RACE_FACTOR} for
* medal races, or similar.
*/
Double getExplicitFactor();
/**
* When scores in this column are scaled by some factor, either based on the {@link #getExplicitFactor() explicit factor}
* set for this column, or implicitly, e.g., because the {@link ScoringScheme} mandates the doubling of medal race scores
* and this column {@link #isMedalRace() represents a medal race}, then some configurations still want the 1.0 score still
* to be 1.0. For example, with a column factor of 2.0 scores 1, 2, 3 would end up as 1, 3, 5; or with a column factor of
* 3.0 scores 1, 2, 3 would end up as 1, 4, 7. This method tells whether this column shall apply such a scheme.
*/
boolean isOneAlwaysStaysOne();
/**
* Returns the race log identifier associated with this fleet and race log
@@ -23,6 +23,8 @@ public interface RaceColumnListener extends Serializable {
void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring);
void oneAlwaysStaysOneChanged(RaceColumn raceColumn, boolean oneAlwaysStaysOne);
default boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return true;
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
@@ -111,6 +112,9 @@ public interface Series extends SeriesBase {
*/
boolean isStartsWithZeroScore();
/**
* @see #isStartsWithZeroScore()
*/
void setStartsWithZeroScore(boolean startsWithZeroScore);
boolean isFirstColumnIsNonDiscardableCarryForward();
@@ -125,6 +129,23 @@ public interface Series extends SeriesBase {
*/
boolean hasSplitFleetContiguousScoring();
/**
* @see #hasSplitFleetContiguousScoring()
*/
void setSplitFleetContiguousScoring(boolean hasSplitFleetScore);
/**
* When scores in this series are scaled by some factor, either based on an {@link RaceColumn#getExplicitFactor()
* explicit column factor}, or implicitly, e.g., because the {@link ScoringScheme} mandates the
* doubling of medal race scores and this series {@link #isMedal() represents a medal series}, then some
* configurations still want the 1.0 score still to be 1.0. For example, with a column factor of 2.0 scores 1, 2, 3
* would end up as 1, 3, 5; or with a column factor of 3.0 scores 1, 2, 3 would end up as 1, 4, 7. This method tells
* whether this column shall apply such a scheme.
*/
boolean isOneAlwaysStaysOne();
/**
* @see #isOneAlwaysStaysOne()
*/
void setOneAlwaysStaysOne(boolean oneAlwaysStaysOne);
}
@@ -42,6 +42,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointByWinsTiesLastlyBrokenByOtherLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall12PointsMax;
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie;
@@ -155,6 +156,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl<RaceLogAndTracked
return new LowPointFirstToWinTwoRaces();
case LOW_POINT_FIRST_TO_WIN_THREE_RACES:
return new LowPointFirstToWinThreeRaces();
case HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD:
return new HighPointByWinsTiesLastlyBrokenByOtherLeaderboard();
}
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
}
@@ -71,6 +71,11 @@ public class RaceColumnInSeriesImpl extends AbstractRaceColumn implements RaceCo
return series.isMedal();
}
@Override
public boolean isOneAlwaysStaysOne() {
return getSeries().isOneAlwaysStaysOne();
}
@Override
public Series getSeries() {
return series;
@@ -89,6 +89,11 @@ public interface RaceColumnListenerWithDefaultAction extends RaceColumnListener
defaultAction();
}
@Override
default void oneAlwaysStaysOneChanged(RaceColumn raceColumn, boolean oneAlwaysStaysOne) {
defaultAction();
}
@Override
default void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
defaultAction();
@@ -576,6 +576,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
hasSplitFleetContiguousScoring);
}
@Override
public void oneAlwaysStaysOneChanged(RaceColumn raceColumn, boolean oneAlwaysStaysOne) {
raceColumnListeners.notifyListenersAboutOneAlwaysStaysOneChanged(raceColumn, oneAlwaysStaysOne);
}
@Override
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return raceColumnListeners.canAddRaceColumnToContainer(raceColumn);
@@ -40,6 +40,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
private Regatta regatta;
private final RaceColumnListeners raceColumnListeners;
private ThresholdBasedResultDiscardingRule resultDiscardingRule;
private boolean oneAlwaysStaysOne;
/**
* If not {@code null}, defines an upper inclusive limit for the number of races that may be discarded from
@@ -320,6 +321,11 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
raceColumnListeners.notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
}
@Override
public void oneAlwaysStaysOneChanged(RaceColumn raceColumn, boolean oneAlwaysStaysOne) {
raceColumnListeners.notifyListenersAboutOneAlwaysStaysOneChanged(raceColumn, oneAlwaysStaysOne);
}
@Override
public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
raceColumnListeners.notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
@@ -401,7 +407,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
boolean oldStartsWithZeroScore = this.startsWithZeroScore;
if (oldStartsWithZeroScore != startsWithZeroScore) {
this.startsWithZeroScore = startsWithZeroScore;
RaceColumn firstRaceColumnInSeries = getFirstRaceColumn();
final RaceColumn firstRaceColumnInSeries = getFirstRaceColumn();
if (firstRaceColumnInSeries != null) {
raceColumnListeners.notifyListenersAboutIsStartsWithZeroScoreChanged(firstRaceColumnInSeries, startsWithZeroScore);
}
@@ -428,7 +434,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
boolean oldFirstColumnIsNonDiscardableCarryForward = this.firstColumnIsNonDiscardableCarryForward;
if (oldFirstColumnIsNonDiscardableCarryForward != firstColumnIsNonDiscardableCarryForward) {
this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward;
RaceColumn firstRaceColumnInSeries = getFirstRaceColumn();
final RaceColumn firstRaceColumnInSeries = getFirstRaceColumn();
if (firstRaceColumnInSeries != null) {
raceColumnListeners.notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(firstRaceColumnInSeries, firstColumnIsNonDiscardableCarryForward);
}
@@ -440,4 +446,20 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
public boolean hasSplitFleetContiguousScoring() {
return hasSplitFleetContiguousScoring;
}
@Override
public boolean isOneAlwaysStaysOne() {
return oneAlwaysStaysOne;
}
@Override
public void setOneAlwaysStaysOne(boolean oneAlwaysStaysOne) {
boolean oldOneAlwaysStaysOne = this.oneAlwaysStaysOne;
if (oldOneAlwaysStaysOne != oneAlwaysStaysOne) {
this.oneAlwaysStaysOne = oneAlwaysStaysOne;
for (RaceColumn raceColumn : getRaceColumns()) {
raceColumnListeners.notifyListenersAboutOneAlwaysStaysOneChanged(raceColumn, oneAlwaysStaysOne);
}
}
}
}
@@ -0,0 +1,44 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sse.common.TimePoint;
public abstract class AbstractScoreCorrectionListenerWithDefaultAction implements ScoreCorrectionListener {
@Override
public void correctedScoreChanged(Competitor competitor, RaceColumn raceColumn, Double oldCorrectedScore,
Double newCorrectedScore) {
defaultAction();
}
@Override
public void maxPointsReasonChanged(Competitor competitor, RaceColumn raceColumn, MaxPointsReason oldMaxPointsReason,
MaxPointsReason newMaxPointsReason) {
defaultAction();
}
@Override
public void carriedPointsChanged(Competitor competitor, Double oldCarriedPoints, Double newCarriedPoints) {
defaultAction();
}
@Override
public void isSuppressedChanged(Competitor competitor, boolean newIsSuppressed) {
defaultAction();
}
@Override
public void timePointOfLastCorrectionsValidityChanged(TimePoint oldTimePointOfLastCorrectionsValidity,
TimePoint newTimePointOfLastCorrectionsValidity) {
defaultAction();
}
@Override
public void commentChanged(String oldComment, String newComment) {
defaultAction();
}
abstract protected void defaultAction();
}
@@ -6,5 +6,14 @@ import com.sap.sse.common.Renamable;
public interface FlexibleRaceColumn extends RaceColumn, Renamable {
void setIsMedalRace(boolean isMedalRace);
void setRegattaLikeHelper(IsRegattaLike regattaLikeHelper);
/**
* Flexible race columns always apply scoring factors linearly and regularly.
*/
@Override
default boolean isOneAlwaysStaysOne() {
return false;
}
}
@@ -433,7 +433,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
* Returns the total rank of the given competitor or {@code 0} if no rank can be determined for
* the {@code competitor} in this leaderboard.
*/
int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException;
int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint);
/**
* Fetches all entries for all competitors of all races tracked by this leaderboard in one sweep. This saves some
@@ -20,21 +20,19 @@ import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.WithNationality;
import com.sap.sailing.domain.base.impl.RaceColumnListenerWithDefaultAction;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardCache;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.concurrent.ConcurrentWeakHashMap;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
/**
* Manages a {@link LeaderboardCache}. When a {@link Leaderboard} is {@link LeaderboardCache#add added} to the cache, it
* start observing the leaderboard for changes through the linked {@link TrackedRace}s as a {@link RaceChangeListener}
* starts observing the leaderboard for changes through the linked {@link TrackedRace}s as a {@link RaceChangeListener}
* and through the race columns as a {@link RaceColumnListener}. When changes affecting a leaderboard occur, the
* {@link LeaderboardCache#removeFromCache(Leaderboard)} method is called on the leaderboard cache.
*
@@ -200,7 +198,7 @@ public class LeaderboardCacheManager {
}
}
private class CacheInvalidationUponScoreCorrectionListener implements ScoreCorrectionListener {
private class CacheInvalidationUponScoreCorrectionListener extends AbstractScoreCorrectionListenerWithDefaultAction {
private final Leaderboard leaderboard;
public CacheInvalidationUponScoreCorrectionListener(Leaderboard leaderboard) {
@@ -208,34 +206,7 @@ public class LeaderboardCacheManager {
}
@Override
public void correctedScoreChanged(Competitor competitor, RaceColumn raceColumn, Double oldCorrectedScore, Double newCorrectedScore) {
removeFromCache(leaderboard);
}
@Override
public void maxPointsReasonChanged(Competitor competitor, RaceColumn raceColumn,
MaxPointsReason oldMaxPointsReason, MaxPointsReason newMaxPointsReason) {
removeFromCache(leaderboard);
}
@Override
public void carriedPointsChanged(Competitor competitor, Double oldCarriedPoints, Double newCarriedPoints) {
removeFromCache(leaderboard);
}
@Override
public void isSuppressedChanged(Competitor competitor, boolean newIsSuppressed) {
removeFromCache(leaderboard);
}
@Override
public void timePointOfLastCorrectionsValidityChanged(TimePoint oldTimePointOfLastCorrectionsValidity,
TimePoint newTimePointOfLastCorrectionsValidity) {
removeFromCache(leaderboard);
}
@Override
public void commentChanged(String oldComment, String newComment) {
public void defaultAction() {
removeFromCache(leaderboard);
}
}
@@ -374,12 +345,12 @@ public class LeaderboardCacheManager {
trackedRace.addListener(listener);
invalidationListeners = invalidationListenersPerLeaderboard.get(leaderboard);
if (invalidationListeners == null) {
invalidationListeners = new ConcurrentHashMap<TrackedRace, Set<CacheInvalidationListener>>();
invalidationListeners = new ConcurrentHashMap<>();
invalidationListenersPerLeaderboard.put(leaderboard, invalidationListeners);
}
Set<CacheInvalidationListener> listeners = invalidationListeners.get(trackedRace);
if (listeners == null) {
listeners = Collections.synchronizedSet(new HashSet<CacheInvalidationListener>());
listeners = Collections.synchronizedSet(new HashSet<>());
invalidationListeners.put(trackedRace, listeners);
}
listeners.add(listener);
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.leaderboard;
/**
* A regatta leaderboard that may have to resort to the competitor standings in another leaderboard
* for breaking specific ties. This {@link #getOtherTieBreakingLeaderboard() other leaderboard} can,
* e.g., be obtained by a {@link ScoringScheme} which then looks up scores or ranks in that leaderboard
* for tie-breaking purposes.<p>
*
* Changes in the tie-breaking leaderboard may lead to tie-breaking changes in this leaderboard. However,
* this leaderboard assumes that by and large the tie-breaking leaderboard is complete and has no running
* races anymore. An exception to this assumption are score corrections applied to the tie-breaking
* leaderboard, e.g., by importing official results after the races in this leaderboard have started
* already. Therefore, this leaderboard acts as a {@link ScoreCorrectionListener} on the
* tie-breaking leaderboard and will forward those changes.
*
* @author Axel Uhl (d043530)
*
*/
public interface RegattaLeaderboardWithOtherTieBreakingLeaderboard extends RegattaLeaderboard {
RegattaLeaderboard getOtherTieBreakingLeaderboard();
}
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -117,10 +118,12 @@ public interface ScoringScheme extends Serializable {
* <p>
*
* <em>"A8.2 If a tie remains between two or more boats, they shall be ranked in order of their scores in the last
* race. Any remaining ties shall be broken by using the tied boats scores in the next-to-last race and so on until
* race. Any remaining ties shall be broken by using the tied boats' scores in the next-to-last race and so on until
* all ties are broken. These scores shall be used even if some of them are excluded scores."</em>
* @param timePoint TODO
* @param cache TODO
*/
int compareByLastRace(List<Util.Pair<RaceColumn, Double>> o1Scores, List<Util.Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter, Competitor o1, Competitor o2);
int compareByLastRace(List<Util.Pair<RaceColumn, Double>> o1Scores, List<Util.Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter, Competitor o1, Competitor o2, TimePoint timePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache);
/**
* Under certain circumstances, a scoring scheme may decide that the scores of a column are not (yet) to be used
@@ -140,6 +143,8 @@ public interface ScoringScheme extends Serializable {
* @throws NoWindException
*/
int compareByLatestRegattaInMetaLeaderboard(Leaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint);
int compareByOtherTieBreakingLeaderboard(RegattaLeaderboardWithOtherTieBreakingLeaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint);
/**
* Returning {@code true} makes the number of wins in a medal series the primary ranking criteria.
@@ -187,4 +192,27 @@ public interface ScoringScheme extends Serializable {
}
return factor;
}
/**
* Computes a score corrected by a {@link #getScoreFactor(RaceColumn) column factor} and potentially other
* column-specific rules, such as that despite multiplying, the original score 1 is to map to 1 again.
* Respects {@link RaceColumn#isOneAlwaysStaysOne()} on the {@code raceColumn}.
*
* @see #getOriginalScoreFromScoreScaledByFactor(RaceColumn, double)
* @see ScoringSchemeType#getScaledScore(double, double, boolean)
*/
default double getScoreScaledByFactor(RaceColumn raceColumn, double originalScore) {
return ScoringSchemeType.getScaledScore(getScoreFactor(raceColumn), originalScore, raceColumn.isOneAlwaysStaysOne());
}
/**
* "Un-scales" a score; the inverse of {@link #getScoreScaledByFactor(RaceColumn, double)}. Respects
* {@link RaceColumn#isOneAlwaysStaysOne()} on the {@code raceColumn}.
*
* @see #getScoreScaledByFactor(RaceColumn, double)
* @see ScoringSchemeType#getUnscaledScore(double, double, boolean)
*/
default double getOriginalScoreFromScoreScaledByFactor(RaceColumn raceColumn, double scaledScore) {
return ScoringSchemeType.getUnscaledScore(getScoreFactor(raceColumn), scaledScore, raceColumn.isOneAlwaysStaysOne());
}
}
@@ -9,7 +9,6 @@ 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.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.MetaLeaderboard;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
@@ -74,7 +73,7 @@ public abstract class AbstractHighPointExtremeSailingSeriesOverall extends HighP
private int getWins(List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> scores) {
int wins = 0;
for (com.sap.sse.common.Util.Pair<RaceColumn, Double> score : scores) {
if (Math.abs(score.getB() - maxPoints * getScoreFactor(score.getA())) < 0.0000001) {
if (Math.abs(score.getB() - getScoreScaledByFactor(score.getA(), maxPoints)) < 0.0000001) {
wins++;
}
}
@@ -109,7 +108,6 @@ public abstract class AbstractHighPointExtremeSailingSeriesOverall extends HighP
/**
* Notice of Race (NOR) section 13.5 specifies for the series score: "If a tie still remains, it shall be broken in
* favor of the boat that had the better place at the last Regatta sailed."
* @throws NoWindException
*/
@Override
public int compareByLatestRegattaInMetaLeaderboard(Leaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint) {
@@ -48,8 +48,9 @@ public abstract class AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie extends
private double getHighestScore(List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> scores) {
double highestScore = 0;
for (com.sap.sse.common.Util.Pair<RaceColumn, Double> score : scores) {
if ((score.getB() * getScoreFactor(score.getA())) > highestScore) {
highestScore = score.getB() * getScoreFactor(score.getA());
final double scoreScaledByFactor = getScoreScaledByFactor(score.getA(), score.getB());
if (scoreScaledByFactor > highestScore) {
highestScore = scoreScaledByFactor;
}
}
return highestScore;
@@ -419,7 +419,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
isMetaLeaderboardColumn);
isMetaLeaderboardColumn, raceColumn.isOneAlwaysStaysOne());
if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) {
calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO, baseDomainFactory);
}
@@ -438,7 +438,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), getScoringScheme().getScoreFactor(raceColumn),
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn);
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn, raceColumn.isOneAlwaysStaysOne());
}
Future<List<CompetitorDTO>> task = executor.submit(
() -> baseDomainFactory.getCompetitorDTOList(AbstractLeaderboardWithCache.this.getCompetitorsFromBestToWorst(
@@ -845,7 +845,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
row.totalDurationFoiledInSeconds = totalDurationFoiled==null?null:totalDurationFoiled.asSeconds();
}
private LeaderboardDTOCache getLeaderboardDTOCache() {
protected LeaderboardDTOCache getLeaderboardDTOCache() {
LeaderboardDTOCache result = this.leaderboardDTOCache;
if (result == null) {
synchronized (this) {
@@ -1183,7 +1183,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
}
@Override
public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException {
public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) {
return getCompetitorsFromBestToWorst(timePoint).indexOf(competitor) + 1;
}
@@ -15,8 +15,10 @@ 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.RegattaLeaderboardWithOtherTieBreakingLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
@@ -151,7 +153,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
* Assuming both competitors scored in the same number of races, compares the sorted scores based on World Sailing's
* Racing Rules of Sailing (RRS) addendum A8.1:<p>
*
* <em>"A8.1 If there is a series-score tie between two or more boats, each boats race scores shall be listed in
* <em>"A8.1 If there is a series-score tie between two or more boats, each boat's race scores shall be listed in
* order of best to worst, and at the first point(s) where there is a difference the tie shall be broken in favour
* of the boat(s) with the best score(s). No excluded scores shall be used."</em>
*/
@@ -166,14 +168,14 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
final Set<RaceColumn> o1Discards = discardedRaceColumnsPerCompetitor.get(o1);
for (com.sap.sse.common.Util.Pair<RaceColumn, Double> o1ColumnAndScore : o1Scores) {
if (includeDiscardedResults || !o1Discards.contains(o1ColumnAndScore.getA())) {
o1NetScores.add(new Pair<>(o1ColumnAndScore.getA(), o1ColumnAndScore.getB() / getScoreFactor(o1ColumnAndScore.getA())));
o1NetScores.add(new Pair<>(o1ColumnAndScore.getA(), getOriginalScoreFromScoreScaledByFactor(o1ColumnAndScore.getA(), o1ColumnAndScore.getB())));
}
}
List<Pair<RaceColumn, Double>> o2NetScores = new ArrayList<>();
final Set<RaceColumn> o2Discards = discardedRaceColumnsPerCompetitor.get(o2);
for (com.sap.sse.common.Util.Pair<RaceColumn, Double> o2ColumnAndScore : o2Scores) {
if (includeDiscardedResults || !o2Discards.contains(o2ColumnAndScore.getA())) {
o2NetScores.add(new Pair<>(o2ColumnAndScore.getA(), o2ColumnAndScore.getB() / getScoreFactor(o2ColumnAndScore.getA())));
o2NetScores.add(new Pair<>(o2ColumnAndScore.getA(), getOriginalScoreFromScoreScaledByFactor(o2ColumnAndScore.getA(), o2ColumnAndScore.getB())));
}
}
Collections.sort(o1NetScores, ruleA8_1ScoreComparator);
@@ -228,9 +230,8 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
* <p>
*
* <em>"A8.2 If a tie remains between two or more boats, they shall be ranked in order of their scores in the last race.
* Any remaining ties shall be broken by using the tied boats scores in the next-to-last race and so on until all
* Any remaining ties shall be broken by using the tied boats' scores in the next-to-last race and so on until all
* ties are broken. These scores shall be used even if some of them are excluded scores."</em>
*
* @param o1ScoresIncludingDiscarded
* scores that include the points for those races that have been discarded (total points)
* @param o2ScoresIncludingDiscarded
@@ -238,7 +239,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
*/
@Override
public int compareByLastRace(List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> o1ScoresIncludingDiscarded,
List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> o2ScoresIncludingDiscarded, boolean nullScoresAreBetter, Competitor o1, Competitor o2) {
List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> o2ScoresIncludingDiscarded, boolean nullScoresAreBetter, Competitor o1, Competitor o2, TimePoint timePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
int result = 0;
final Comparator<Double> pureScoreComparator = getScoreComparator(nullScoresAreBetter);
ListIterator<Pair<RaceColumn, Double>> o1Iter = o1ScoresIncludingDiscarded.listIterator(o1ScoresIncludingDiscarded.size());
@@ -270,4 +271,9 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
public int compareByLatestRegattaInMetaLeaderboard(Leaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint) {
return 0;
}
@Override
public int compareByOtherTieBreakingLeaderboard(RegattaLeaderboardWithOtherTieBreakingLeaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint) {
return 0;
}
}
@@ -271,6 +271,11 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
hasSplitFleetContiguousScoring);
}
@Override
public void oneAlwaysStaysOneChanged(RaceColumn raceColumn, boolean oneAlwaysStaysOne) {
getRaceColumnListeners().notifyListenersAboutOneAlwaysStaysOneChanged(raceColumn, oneAlwaysStaysOne);
}
@Override
public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
@@ -490,7 +495,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
if (totalPoints == null) {
result = null;
} else {
result = getScoringScheme().getScoreFactor(raceColumn) * totalPoints;
result = getScoringScheme().getScoreScaledByFactor(raceColumn, totalPoints);
}
}
return result;
@@ -676,11 +681,10 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns);
final Double correctedScore = correctedResults.getCorrectedScore();
final Double correctedScoreScaledByColumnFactor = correctedScore == null ? null
: Double.valueOf((correctedScore * getScoringScheme().getScoreFactor(race)));
: Double.valueOf(getScoringScheme().getScoreScaledByFactor(race, correctedScore));
return new EntryImpl(trackedRankProvider, correctedScoreScaledByColumnFactor, () -> correctedResults.getUncorrectedScore(),
correctedResults.isCorrected(),
discarded ? DOUBLE_0
: correctedScore == null ? null : Double.valueOf(correctedScore * getScoringScheme().getScoreFactor(race)),
discarded ? DOUBLE_0 : correctedScoreScaledByColumnFactor,
correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor));
}
@@ -752,7 +756,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
final Double correctedScore = correctedResults.getCorrectedScore();
final Double correctedScoreScaledByColumnFactor = correctedScore == null ? null
: Double.valueOf((correctedScore * getScoringScheme().getScoreFactor(raceColumn)));
: Double.valueOf(getScoringScheme().getScoreScaledByFactor(raceColumn, correctedScore));
Entry entry = new EntryImpl(trackedRankProvider, correctedScoreScaledByColumnFactor,
() -> correctedResults.getUncorrectedScore(), correctedResults.isCorrected(),
discarded ? DOUBLE_0 : correctedScoreScaledByColumnFactor,
@@ -0,0 +1,66 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.io.Serializable;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
public class DelegateLeaderboard implements Serializable {
private static final long serialVersionUID = -1764036951368356044L;
private RegattaLeaderboard delegateLeaderboard;
private transient final Supplier<RegattaLeaderboard> delegateLeaderboardSupplier;
/**
* The particular use case for which this field is introduced is registering score correction
* listeners at a point in time when the full leaderboard hasn't been resolved yet. Instead of
* letting this listener registration attempt the resolution without success the request can
* be queued here, and each time the {@link #getDelegateLeaderboard()} successfully resolves a
* leaderboard, all consumers in this set will be triggered.
*/
private final ConcurrentHashMap<Consumer<RegattaLeaderboard>, Boolean> triggerWhenDelegateLeaderboardIsResolved;
public DelegateLeaderboard(Supplier<RegattaLeaderboard> delegateLeaderboardSupplier) {
this.delegateLeaderboardSupplier = delegateLeaderboardSupplier;
this.triggerWhenDelegateLeaderboardIsResolved = new ConcurrentHashMap<>();
}
public RegattaLeaderboard getDelegateLeaderboard() {
// TODO make thread-safe; if multiple threads call concurrently, triggers from triggerWhenFullLeaderboardIsResolved may fire several times
if (delegateLeaderboard == null) {
if (delegateLeaderboardSupplier == null) {
throw new NullPointerException("Internal error: Regatta leaderboard supplier is null; this can only happen upon premature serialization");
}
delegateLeaderboard = delegateLeaderboardSupplier.get();
if (delegateLeaderboard != null) {
for (Iterator<Consumer<RegattaLeaderboard>> i=triggerWhenDelegateLeaderboardIsResolved.keySet().iterator(); i.hasNext(); ) {
final Consumer<RegattaLeaderboard> toTrigger = i.next();
toTrigger.accept(delegateLeaderboard);
i.remove();
}
}
}
return delegateLeaderboard;
}
public void scheduleLeaderboardConsumer(final Consumer<RegattaLeaderboard> consumer) {
triggerWhenDelegateLeaderboardIsResolved.put(consumer, true);
}
/**
* If the {@link #getDelegateLeaderboard() delegate leaderboard} is already available, the {@code action} is
* run with it right away; otherwise it is scheduled and run the next time that {@link #getDelegateLeaderboard()}
* is invoked such that it returns a valid leaderboard.
*/
public void runOrSchedule(final Consumer<RegattaLeaderboard> action) {
if (getDelegateLeaderboard() != null) {
action.accept(getDelegateLeaderboard());
} else {
scheduleLeaderboardConsumer(action);
}
}
}
@@ -6,12 +6,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -66,18 +64,7 @@ import com.sap.sse.common.Util.Pair;
public class DelegatingRegattaLeaderboardWithCompetitorElimination extends AbstractLeaderboardWithCache implements RegattaLeaderboardWithEliminations {
private static final long serialVersionUID = 8331154893189722924L;
private final String name;
private RegattaLeaderboard fullLeaderboard;
private transient final Supplier<RegattaLeaderboard> fullLeaderboardSupplier;
/**
* The particular use case for which this field is introduced is registering score correction
* listeners at a point in time when the full leaderboard hasn't been resolved yet. Instead of
* letting this listener registration attempt the resolution without success the request can
* be queued here, and each time the {@link #getFullLeaderboard()} successfully resolves a
* leaderboard, all consumers in this set will be triggered.
*/
private final ConcurrentHashMap<Consumer<RegattaLeaderboard>, Boolean> triggerWhenFullLeaderboardIsResolved;
private final DelegateLeaderboard fullLeaderboard;
/**
* Competitors eliminated from this leaderboard for regatta ranking; those competitors are not part of
@@ -93,22 +80,24 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
/**
* The leaderboard wrapper starts out with an empty set of eliminated competitors
*/
public DelegatingRegattaLeaderboardWithCompetitorElimination(Supplier<RegattaLeaderboard> fullLeaderboardSupplier,
String name) {
public DelegatingRegattaLeaderboardWithCompetitorElimination(Supplier<RegattaLeaderboard> fullLeaderboardSupplier, String name) {
this.name = name;
this.fullLeaderboardSupplier = fullLeaderboardSupplier;
this.eliminatedCompetitors = new ConcurrentHashMap<>();
this.triggerWhenFullLeaderboardIsResolved = new ConcurrentHashMap<>();
fullLeaderboard = new DelegateLeaderboard(fullLeaderboardSupplier);
}
@Override
public String getName() {
return name;
}
private RegattaLeaderboard getDelegateLeaderboard() {
return fullLeaderboard.getDelegateLeaderboard();
}
@Override
public Iterable<Competitor> getCompetitors() {
return new ObscuringIterable<>(getFullLeaderboard().getCompetitors(), eliminatedCompetitors.keySet());
return new ObscuringIterable<>(getDelegateLeaderboard().getCompetitors(), eliminatedCompetitors.keySet());
}
@Override
@@ -118,6 +107,7 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
} else {
eliminatedCompetitors.remove(competitor);
}
getLeaderboardDTOCache().invalidate(this);
}
@Override
@@ -133,7 +123,7 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
@Override
public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) throws NoWindException {
Map<RaceColumn, List<Competitor>> preResult = getFullLeaderboard().getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(timePoint, cache);
Map<RaceColumn, List<Competitor>> preResult = getDelegateLeaderboard().getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(timePoint, cache);
for (final List<Competitor> e : preResult.values()) {
e.removeAll(eliminatedCompetitors.keySet());
}
@@ -143,7 +133,7 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
@Override
public Map<Competitor, Double> getCompetitorsForWhichThereAreCarriedPoints() {
final Map<Competitor, Double> result = new HashMap<>();
for (final java.util.Map.Entry<Competitor, Double> e : getFullLeaderboard().getCompetitorsForWhichThereAreCarriedPoints().entrySet()) {
for (final java.util.Map.Entry<Competitor, Double> e : getDelegateLeaderboard().getCompetitorsForWhichThereAreCarriedPoints().entrySet()) {
if (!isEliminated(e.getKey())) {
result.put(e.getKey(), e.getValue());
}
@@ -154,13 +144,13 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
@Override
public Iterable<Competitor> getCompetitorsFromBestToWorst(RaceColumn raceColumn, TimePoint timePoint,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) throws NoWindException {
return new ObscuringIterable<>(getFullLeaderboard().getCompetitorsFromBestToWorst(raceColumn, timePoint, cache), eliminatedCompetitors.keySet());
return new ObscuringIterable<>(getDelegateLeaderboard().getCompetitorsFromBestToWorst(raceColumn, timePoint, cache), eliminatedCompetitors.keySet());
}
@Override
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
final List<Competitor> result = new ArrayList<>();
for (final Competitor c : getFullLeaderboard().getCompetitorsFromBestToWorst(timePoint, cache)) {
for (final Competitor c : getDelegateLeaderboard().getCompetitorsFromBestToWorst(timePoint, cache)) {
if (!isEliminated(c)) {
result.add(c);
}
@@ -171,7 +161,7 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
@Override
public Map<Pair<Competitor, RaceColumn>, Entry> getContent(TimePoint timePoint) throws NoWindException {
final Map<Pair<Competitor, RaceColumn>, Entry> result = new HashMap<>();
for (final java.util.Map.Entry<Pair<Competitor, RaceColumn>, Entry> e : getFullLeaderboard().getContent(timePoint).entrySet()) {
for (final java.util.Map.Entry<Pair<Competitor, RaceColumn>, Entry> e : getDelegateLeaderboard().getContent(timePoint).entrySet()) {
if (!isEliminated(e.getKey().getA())) {
result.put(e.getKey(), e.getValue());
}
@@ -187,383 +177,357 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
// --------------------- Delegate Pattern Implementation ----------------------
@Override
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
return getFullLeaderboard().getOrCreateCompetitorsProvider();
return getDelegateLeaderboard().getOrCreateCompetitorsProvider();
}
@Override
public Regatta getRegatta() {
return getFullLeaderboard().getRegatta();
return getDelegateLeaderboard().getRegatta();
}
@Override
public Iterable<Competitor> getCompetitorsRegisteredInRegattaLog() {
return getFullLeaderboard().getCompetitorsRegisteredInRegattaLog();
return getDelegateLeaderboard().getCompetitorsRegisteredInRegattaLog();
}
@Override
public IsRegattaLike getRegattaLike() {
return getFullLeaderboard().getRegattaLike();
return getDelegateLeaderboard().getRegattaLike();
}
@Override
public RaceLog getRacelog(String raceColumnName, String fleetName) {
return getFullLeaderboard().getRacelog(raceColumnName, fleetName);
return getDelegateLeaderboard().getRacelog(raceColumnName, fleetName);
}
@Override
public void registerCompetitor(Competitor competitor) {
getFullLeaderboard().registerCompetitor(competitor);
getDelegateLeaderboard().registerCompetitor(competitor);
}
@Override
public void registerCompetitors(Iterable<Competitor> competitors) {
getFullLeaderboard().registerCompetitors(competitors);
getDelegateLeaderboard().registerCompetitors(competitors);
}
@Override
public void deregisterCompetitor(Competitor competitor) {
getFullLeaderboard().deregisterCompetitor(competitor);
getDelegateLeaderboard().deregisterCompetitor(competitor);
}
@Override
public void deregisterCompetitors(Iterable<Competitor> competitor) {
getFullLeaderboard().deregisterCompetitors(competitor);
getDelegateLeaderboard().deregisterCompetitors(competitor);
}
@Override
public Iterable<Competitor> getAllCompetitors() {
return getFullLeaderboard().getAllCompetitors();
return getDelegateLeaderboard().getAllCompetitors();
}
@Override
public Pair<Iterable<RaceDefinition>, Iterable<Competitor>> getAllCompetitorsWithRaceDefinitionsConsidered() {
return getFullLeaderboard().getAllCompetitorsWithRaceDefinitionsConsidered();
return getDelegateLeaderboard().getAllCompetitorsWithRaceDefinitionsConsidered();
}
@Override
public Iterable<Competitor> getAllCompetitors(RaceColumn raceColumn, Fleet fleet) {
return getFullLeaderboard().getAllCompetitors(raceColumn, fleet);
return getDelegateLeaderboard().getAllCompetitors(raceColumn, fleet);
}
@Override
public Iterable<Competitor> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
return getFullLeaderboard().getCompetitors(raceColumn, fleet);
return getDelegateLeaderboard().getCompetitors(raceColumn, fleet);
}
@Override
public Iterable<Competitor> getSuppressedCompetitors() {
return getFullLeaderboard().getSuppressedCompetitors();
return getDelegateLeaderboard().getSuppressedCompetitors();
}
@Override
public boolean isSuppressed(Competitor competitor) {
return getFullLeaderboard().isSuppressed(competitor);
return getDelegateLeaderboard().isSuppressed(competitor);
}
@Override
public void setSuppressed(Competitor competitor, boolean suppressed) {
getFullLeaderboard().setSuppressed(competitor, suppressed);
getDelegateLeaderboard().setSuppressed(competitor, suppressed);
}
@Override
public Fleet getFleet(String fleetName) {
return getFullLeaderboard().getFleet(fleetName);
return getDelegateLeaderboard().getFleet(fleetName);
}
@Override
public Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
return getFullLeaderboard().getEntry(competitor, race, timePoint);
return getDelegateLeaderboard().getEntry(competitor, race, timePoint);
}
@Override
public Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint,
Set<RaceColumn> discardedRaceColumns) throws NoWindException {
return getFullLeaderboard().getEntry(competitor, race, timePoint, discardedRaceColumns);
return getDelegateLeaderboard().getEntry(competitor, race, timePoint, discardedRaceColumns);
}
@Override
public Map<RaceColumn, Map<Competitor, Double>> getNetPointsSumAfterRaceColumn(TimePoint timePoint)
throws NoWindException {
return getFullLeaderboard().getNetPointsSumAfterRaceColumn(timePoint);
return getDelegateLeaderboard().getNetPointsSumAfterRaceColumn(timePoint);
}
@Override
public double getCarriedPoints(Competitor competitor) {
return getFullLeaderboard().getCarriedPoints(competitor);
return getDelegateLeaderboard().getCarriedPoints(competitor);
}
public int getTrackedRank(Competitor competitor, RaceColumn race, TimePoint timePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
return getFullLeaderboard().getTrackedRank(competitor, race, timePoint, cache);
return getDelegateLeaderboard().getTrackedRank(competitor, race, timePoint, cache);
}
@Override
public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
return getFullLeaderboard().getTotalPoints(competitor, raceColumn, timePoint, cache);
return getDelegateLeaderboard().getTotalPoints(competitor, raceColumn, timePoint, cache);
}
@Override
public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn race, TimePoint timePoint) {
return getFullLeaderboard().getMaxPointsReason(competitor, race, timePoint);
return getDelegateLeaderboard().getMaxPointsReason(competitor, race, timePoint);
}
@Override
public Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) {
return getFullLeaderboard().getNetPoints(competitor, race, timePoint);
return getDelegateLeaderboard().getNetPoints(competitor, race, timePoint);
}
@Override
public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return getFullLeaderboard().isDiscarded(competitor, raceColumn, timePoint);
return getDelegateLeaderboard().isDiscarded(competitor, raceColumn, timePoint);
}
@Override
public Double getNetPoints(Competitor competitor, TimePoint timePoint) {
return getFullLeaderboard().getNetPoints(competitor, timePoint);
return getDelegateLeaderboard().getNetPoints(competitor, timePoint);
}
@Override
public Double getNetPoints(Competitor competitor, Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint)
throws NoWindException {
return getFullLeaderboard().getNetPoints(competitor, raceColumnsToConsider, timePoint);
return getDelegateLeaderboard().getNetPoints(competitor, raceColumnsToConsider, timePoint);
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
final RegattaLeaderboard theFullLeaderboard = getFullLeaderboard();
final RegattaLeaderboard theFullLeaderboard = getDelegateLeaderboard();
return theFullLeaderboard == null ? Collections.emptySet() : theFullLeaderboard.getRaceColumns();
}
@Override
public RaceColumn getRaceColumnByName(String name) {
return getFullLeaderboard().getRaceColumnByName(name);
return getDelegateLeaderboard().getRaceColumnByName(name);
}
@Override
public void setCarriedPoints(Competitor competitor, double carriedPoints) {
getFullLeaderboard().setCarriedPoints(competitor, carriedPoints);
getDelegateLeaderboard().setCarriedPoints(competitor, carriedPoints);
}
@Override
public void unsetCarriedPoints(Competitor competitor) {
getFullLeaderboard().unsetCarriedPoints(competitor);
getDelegateLeaderboard().unsetCarriedPoints(competitor);
}
@Override
public boolean hasCarriedPoints() {
return getFullLeaderboard().hasCarriedPoints();
return getDelegateLeaderboard().hasCarriedPoints();
}
@Override
public boolean hasCarriedPoints(Competitor competitor) {
return getFullLeaderboard().hasCarriedPoints(competitor);
return getDelegateLeaderboard().hasCarriedPoints(competitor);
}
@Override
public SettableScoreCorrection getScoreCorrection() {
return getFullLeaderboard().getScoreCorrection();
return getDelegateLeaderboard().getScoreCorrection();
}
@Override
public void addScoreCorrectionListener(ScoreCorrectionListener listener) {
if (getFullLeaderboard() != null) {
getFullLeaderboard().addScoreCorrectionListener(listener);
} else {
triggerWhenFullLeaderboardIsResolved.put(leaderboard->leaderboard.addScoreCorrectionListener(listener), true);
}
fullLeaderboard.runOrSchedule(leaderboard->leaderboard.addScoreCorrectionListener(listener));
}
@Override
public void removeScoreCorrectionListener(ScoreCorrectionListener listener) {
if (getFullLeaderboard() != null) {
getFullLeaderboard().removeScoreCorrectionListener(listener);
} else {
triggerWhenFullLeaderboardIsResolved.put(leaderboard->leaderboard.removeScoreCorrectionListener(listener), true);
}
fullLeaderboard.runOrSchedule(leaderboard->leaderboard.removeScoreCorrectionListener(listener));
}
@Override
public Competitor getCompetitorByName(String competitorName) {
return getFullLeaderboard().getCompetitorByName(competitorName);
return getDelegateLeaderboard().getCompetitorByName(competitorName);
}
public void setDisplayName(Competitor competitor, String displayName) {
getFullLeaderboard().setDisplayName(competitor, displayName);
getDelegateLeaderboard().setDisplayName(competitor, displayName);
}
@Override
public String getDisplayName(Competitor competitor) {
return getFullLeaderboard().getDisplayName(competitor);
return getDelegateLeaderboard().getDisplayName(competitor);
}
@Override
public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn,
TimePoint timePoint) {
return getFullLeaderboard().countRaceForComparisonWithDiscardingThresholds(competitor, raceColumn, timePoint);
return getDelegateLeaderboard().countRaceForComparisonWithDiscardingThresholds(competitor, raceColumn, timePoint);
}
@Override
public ResultDiscardingRule getResultDiscardingRule() {
return getFullLeaderboard().getResultDiscardingRule();
return getDelegateLeaderboard().getResultDiscardingRule();
}
@Override
public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) {
getFullLeaderboard().setCrossLeaderboardResultDiscardingRule(discardingRule);
getDelegateLeaderboard().setCrossLeaderboardResultDiscardingRule(discardingRule);
}
@Override
public Competitor getCompetitorByIdAsString(String idAsString) {
return getFullLeaderboard().getCompetitorByIdAsString(idAsString);
return getDelegateLeaderboard().getCompetitorByIdAsString(idAsString);
}
@Override
public void addRaceColumnListener(RaceColumnListener listener) {
getFullLeaderboard().addRaceColumnListener(listener);
getDelegateLeaderboard().addRaceColumnListener(listener);
}
@Override
public void removeRaceColumnListener(RaceColumnListener listener) {
getFullLeaderboard().removeRaceColumnListener(listener);
getDelegateLeaderboard().removeRaceColumnListener(listener);
}
@Override
public Long getDelayToLiveInMillis() {
return getFullLeaderboard().getDelayToLiveInMillis();
return getDelegateLeaderboard().getDelayToLiveInMillis();
}
@Override
public Iterable<TrackedRace> getTrackedRaces() {
return getFullLeaderboard().getTrackedRaces();
return getDelegateLeaderboard().getTrackedRaces();
}
@Override
public ScoringScheme getScoringScheme() {
return getFullLeaderboard().getScoringScheme();
return getDelegateLeaderboard().getScoringScheme();
}
@Override
public TimePoint getTimePointOfLatestModification() {
return getFullLeaderboard().getTimePointOfLatestModification();
return getDelegateLeaderboard().getTimePointOfLatestModification();
}
@Override
public Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) {
return getFullLeaderboard().getMaximumSpeedOverGround(competitor, timePoint);
return getDelegateLeaderboard().getMaximumSpeedOverGround(competitor, timePoint);
}
@Override
public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
return getFullLeaderboard().getAverageSpeedOverGround(competitor, timePoint);
return getDelegateLeaderboard().getAverageSpeedOverGround(competitor, timePoint);
}
@Override
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, Iterable<RaceColumn> raceColumnsToConsider,
TimePoint timePoint) throws NoWindException {
return getFullLeaderboard().getNetPoints(competitor, raceColumn, raceColumnsToConsider, timePoint);
return getDelegateLeaderboard().getNetPoints(competitor, raceColumn, raceColumnsToConsider, timePoint);
}
@Override
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
Set<RaceColumn> discardedRaceColumns) {
return getFullLeaderboard().getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
return getDelegateLeaderboard().getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
}
@Override
public TimePoint getNowMinusDelay() {
return getFullLeaderboard().getNowMinusDelay();
return getDelegateLeaderboard().getNowMinusDelay();
}
@Override
public Iterable<CourseArea> getCourseAreas() {
return getFullLeaderboard().getCourseAreas();
return getDelegateLeaderboard().getCourseAreas();
}
@Override
public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() {
return getFullLeaderboard().getNumberOfCompetitorsInLeaderboardFetcher();
return getDelegateLeaderboard().getNumberOfCompetitorsInLeaderboardFetcher();
}
@Override
public Pair<RaceColumn, Fleet> getRaceColumnAndFleet(TrackedRace trackedRace) {
return getFullLeaderboard().getRaceColumnAndFleet(trackedRace);
return getDelegateLeaderboard().getRaceColumnAndFleet(trackedRace);
}
@Override
public BoatClass getBoatClass() {
return getFullLeaderboard().getBoatClass();
return getDelegateLeaderboard().getBoatClass();
}
@Override
public Boat getBoatOfCompetitor(Competitor competitor, RaceColumn raceColumn, Fleet fleet) {
return getFullLeaderboard().getBoatOfCompetitor(competitor, raceColumn, fleet);
return getDelegateLeaderboard().getBoatOfCompetitor(competitor, raceColumn, fleet);
}
private RegattaLeaderboard getFullLeaderboard() {
if (fullLeaderboard == null) {
if (fullLeaderboardSupplier == null) {
throw new NullPointerException("Internal error: Regatta leaderboard supplier is null; this can only happen upon premature serialization");
}
fullLeaderboard = fullLeaderboardSupplier.get();
if (fullLeaderboard != null) {
for (Iterator<Consumer<RegattaLeaderboard>> i=triggerWhenFullLeaderboardIsResolved.keySet().iterator(); i.hasNext(); ) {
final Consumer<RegattaLeaderboard> toTrigger = i.next();
toTrigger.accept(fullLeaderboard);
i.remove();
}
}
}
return fullLeaderboard;
}
/**
* Before being serialized, ensure that the leaderboard supplier has been used to tru
* Before being serialized, ensure that the leaderboard supplier has been used
* to resolve the leaderboard.
* @throws IOException
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
getFullLeaderboard();
getDelegateLeaderboard();
oos.defaultWriteObject();
}
@Override
public Iterable<Boat> getBoatsRegisteredInRegattaLog() {
return getFullLeaderboard().getBoatsRegisteredInRegattaLog();
return getDelegateLeaderboard().getBoatsRegisteredInRegattaLog();
}
@Override
public Iterable<Boat> getAllBoats() {
return getFullLeaderboard().getAllBoats();
return getDelegateLeaderboard().getAllBoats();
}
@Override
public void registerBoat(Boat boat) {
getFullLeaderboard().registerBoat(boat);
getDelegateLeaderboard().registerBoat(boat);
}
@Override
public void registerBoats(Iterable<Boat> boats) {
getFullLeaderboard().registerBoats(boats);
getDelegateLeaderboard().registerBoats(boats);
}
@Override
public void deregisterBoat(Boat boat) {
getFullLeaderboard().deregisterBoat(boat);
getDelegateLeaderboard().deregisterBoat(boat);
}
@Override
public void deregisterBoats(Iterable<Boat> boats) {
getFullLeaderboard().deregisterBoats(boats);
getDelegateLeaderboard().deregisterBoats(boats);
}
@Override
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
Set<RaceColumn> discardedRaceColumns, Supplier<Double> totalPointsProvider) {
return getFullLeaderboard().getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns,
return getDelegateLeaderboard().getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns,
totalPointsProvider);
}
@Override
public boolean isResultsAreOfficial(RaceColumn raceColumn, Fleet fleet) {
return getFullLeaderboard().isResultsAreOfficial(raceColumn, fleet);
return getDelegateLeaderboard().isResultsAreOfficial(raceColumn, fleet);
}
}
@@ -0,0 +1,125 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithOtherTieBreakingLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
* Race winners get scored one point, all others zero points, just like in {@link HighPointMatchRacing}. Additionally,
* when the number of wins compares equal, the competitors are compared by their "finishing order" in their last race.
* As this scoring scheme disregards numbers of races scored as a ranking criterion, two competitors tied on the number
* of wins may have different numbers of races scored; therefore, comparing their "last" race may compare races from
* different race columns.
* <p>
*
* The "finishing order" is determined by first checking for a penalty code ({@link MaxPointsReason}). If a
* non-{@code null}. non-{@link MaxPointsReason#NONE NONE} penalty code is found, the competitor is considered to not
* have finished. Otherwise, the finishing rank is determined by looking at the competitor's {@link TrackedRace} for the
* last race. If no tracked race is found for a competitor's last race, {@code null} is assumed for the finishing order,
* representing "not finished." The finishing ranks are then compared. If they compare equal, this scoring scheme
* assumes that the ranking comparator will eventually break the tie by calling
* {@link #compareByOtherTieBreakingLeaderboard(RegattaLeaderboardWithOtherTieBreakingLeaderboard, Competitor, Competitor, TimePoint)}.<p>
*
* Columns marked as "medal race" are <em>not</em> doubled automatically by this scoring scheme.
*
* @author Axel Uhl (d043530)
*
*/
public class HighPointByWinsTiesLastlyBrokenByOtherLeaderboard extends HighPointMatchRacing {
private static final long serialVersionUID = -2930982687072741643L;
@Override
public ScoringSchemeType getType() {
return ScoringSchemeType.HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD;
}
/**
* No A8.1 comparison based on sorted scores happens for this scoring scheme
*/
@Override
public int compareByBetterScore(Competitor o1, List<Pair<RaceColumn, Double>> o1Scores, Competitor o2,
List<Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint,
Leaderboard leaderboard, Map<Competitor, Set<RaceColumn>> discardedRaceColumnsPerCompetitor) {
return 0;
}
/**
* Comparing by "last race" here assumes explicitly that the competitors may have sailed a different number of
* races, e.g., in the 2024 Foiling Kite Olympic Semifinal format. The comparison by last race then may compare
* results from different races. Furthermore, the rules specify that not the score / points but the <em>finishing order</em>
* is to be compared. Should two boats not have finished their respective last race (which usually leads to a
* penalty such as {@link MaxPointsReason#DNF DNF}) or should they not have started ({@link MaxPointsReason#DNS
* DNS}) or received some other penalty, that penalty leads to a score of {@code 0.0} by default and will lead to
* such competitors to be considered "tied" based on their last race. In this, a penalty code that exists for a
* competitor in the last race takes precedence over a finish mark passing and the corresponding order implied
* by the tracked race's finishing ranks.
* <p>
*
* Should by some definition one penalty code be considered "better" than another, manual intervention based on
* 1/1000th of a point may be required, or the implementation of
* {@link ScoringScheme#getPenaltyScore(RaceColumn, Competitor, MaxPointsReason, Integer, com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher, TimePoint, com.sap.sailing.domain.leaderboard.Leaderboard)}
* must be overridden accordingly here to provide those 1/1000th of a point automatically.
*/
@Override
public int compareByLastRace(List<Pair<RaceColumn, Double>> o1ScoresIncludingDiscarded,
List<Pair<RaceColumn, Double>> o2ScoresIncludingDiscarded, boolean nullScoresAreBetter, Competitor o1,
Competitor o2, TimePoint timePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
int result = 0;
if (!o1ScoresIncludingDiscarded.isEmpty() && !o2ScoresIncludingDiscarded.isEmpty()) {
final Comparator<Double> doubleComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
final Double o1LastRaceScore = o1ScoresIncludingDiscarded.get(o1ScoresIncludingDiscarded.size() - 1).getB();
final Double o2LastRaceScore = o2ScoresIncludingDiscarded.get(o2ScoresIncludingDiscarded.size() - 1).getB();
result = doubleComparatorNullsLast.compare(o1LastRaceScore, o2LastRaceScore);
// the scores would differ only if one competitor won its last race and the other didn't
if (result == 0 && o1LastRaceScore != null && o1LastRaceScore != 1.0) {
final TrackedRace tr1 = o1ScoresIncludingDiscarded.get(o1ScoresIncludingDiscarded.size()-1).getA().getTrackedRace(o1);
final int rank1;
final Integer finishingRank1 = tr1 == null ? null : (rank1=tr1.getRank(o1, timePoint, cache))==0?null:rank1;
final TrackedRace tr2 = o2ScoresIncludingDiscarded.get(o2ScoresIncludingDiscarded.size()-1).getA().getTrackedRace(o2);
final int rank2;
final Integer finishingRank2 = tr2 == null ? null : (rank2=tr2.getRank(o2, timePoint, cache))==0?null:rank2;
final Comparator<Integer> intComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
result = intComparatorNullsLast.compare(finishingRank1, finishingRank2);
}
}
return result;
}
@Override
public double getScoreFactor(RaceColumn raceColumn) {
return 1.0;
}
@Override
public int compareByOtherTieBreakingLeaderboard(RegattaLeaderboardWithOtherTieBreakingLeaderboard leaderboard,
Competitor o1, Competitor o2, TimePoint timePoint) {
final int result;
final int o1RankInOtherTieBreakingLeaderboard = leaderboard.getOtherTieBreakingLeaderboard().getTotalRankOfCompetitor(o1, timePoint);
final int o2RankInOtherTieBreakingLeaderboard = leaderboard.getOtherTieBreakingLeaderboard().getTotalRankOfCompetitor(o2, timePoint);
if (o1RankInOtherTieBreakingLeaderboard == o2RankInOtherTieBreakingLeaderboard) {
result = 0;
} else {
if (o1RankInOtherTieBreakingLeaderboard == 0) {
result = 1; // o1 has no rank; this is worse ("greater") than any valid rank
} else if (o2RankInOtherTieBreakingLeaderboard == 0) {
result = -1;
} else {
result = Integer.compare(o1RankInOtherTieBreakingLeaderboard, o2RankInOtherTieBreakingLeaderboard);
}
}
return result;
}
}
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.common.MaxPointsReason;
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.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
@@ -146,7 +147,7 @@ public class HighPointFirstGets1LastBreaksTie extends HighPointFirstGetsFixedSco
@Override
public int compareByLastRace(List<Pair<RaceColumn, Double>> o1ScoresIncludingDiscarded,
List<Pair<RaceColumn, Double>> o2ScoresIncludingDiscarded, boolean nullScoresAreBetter, Competitor o1,
Competitor o2) {
Competitor o2, TimePoint timePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
return 0;
}
}
@@ -32,5 +32,4 @@ public class HighPointMatchRacing extends HighPoint {
Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
return rank == 0 ? null : rank == 1 ? 1.0 : 0.0;
}
}
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithOtherTieBreakingLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
@@ -59,6 +60,7 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
private final Map<Competitor, Set<RaceColumn>> discardedRaceColumnsPerCompetitor;
private final boolean nullScoresAreBetter;
private final TimePoint timePoint;
private final WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache;
/**
* Considers all of the leaderboard's columns in their state at <code>timePoint</code> for calculating the score and rank.
@@ -88,6 +90,7 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
this.timePoint = timePoint;
this.scoringScheme = scoringScheme;
this.nullScoresAreBetter = nullScoresAreBetter;
this.cache = cache;
netPointsCache = new HashMap<>();
totalPointsCache = new HashMap<>();
discardedRaceColumnsPerCompetitor = new HashMap<>();
@@ -251,11 +254,17 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
result = compareByBetterScore(o1, Collections.unmodifiableList(o1TotalPoints), o2, Collections.unmodifiableList(o2TotalPoints), timePoint);
if (result == 0) {
// compare by last race:
result = scoringScheme.compareByLastRace(o1TotalPoints, o2TotalPoints, nullScoresAreBetter, o1, o2);
result = scoringScheme.compareByLastRace(o1TotalPoints, o2TotalPoints, nullScoresAreBetter, o1, o2, timePoint, cache);
if (result == 0) {
result = scoringScheme.compareByLatestRegattaInMetaLeaderboard(getLeaderboard(), o1, o2, timePoint);
if (result == 0) {
result = compareByArbitraryButStableCriteria(o1, o2);
if (getLeaderboard() instanceof RegattaLeaderboardWithOtherTieBreakingLeaderboard) {
result = scoringScheme.compareByOtherTieBreakingLeaderboard(
(RegattaLeaderboardWithOtherTieBreakingLeaderboard) getLeaderboard(), o1, o2, timePoint);
}
if (result == 0) {
result = compareByArbitraryButStableCriteria(o1, o2);
}
}
}
}
@@ -0,0 +1,48 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.function.Supplier;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.leaderboard.AbstractScoreCorrectionListenerWithDefaultAction;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithOtherTieBreakingLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
/**
* Upon construction accepts a supplier for another {@link RegattaLeaderboard} and schedules the registration
* of a {@link ScoreCorrectionListener} on that leaderboard. When score corrections change on that other
* leaderboard, this leaderboard's cache contents are invalidated.
*
* @author Axel Uhl (d043530)
*
*/
public class RegattaLeaderboardWithOtherTieBreakingLeaderboardImpl extends RegattaLeaderboardImpl
implements RegattaLeaderboardWithOtherTieBreakingLeaderboard {
private static final long serialVersionUID = -4859656155952136210L;
private final DelegateLeaderboard otherTieBreakingLeaderboardProvider;
public RegattaLeaderboardWithOtherTieBreakingLeaderboardImpl(Regatta regatta,
ThresholdBasedResultDiscardingRule resultDiscardingRule, Supplier<RegattaLeaderboard> otherTieBreakingLeaderboardProvider) {
super(regatta, resultDiscardingRule);
this.otherTieBreakingLeaderboardProvider = new DelegateLeaderboard(otherTieBreakingLeaderboardProvider);
this.otherTieBreakingLeaderboardProvider.runOrSchedule(otherTieBreakingLeaderboard->otherTieBreakingLeaderboard.addScoreCorrectionListener(
new AbstractScoreCorrectionListenerWithDefaultAction() {
@Override
protected void defaultAction() {
getLeaderboardDTOCache().invalidate(RegattaLeaderboardWithOtherTieBreakingLeaderboardImpl.this);
}
}));
}
@Override
public RegattaLeaderboard getOtherTieBreakingLeaderboard() {
return otherTieBreakingLeaderboardProvider.getDelegateLeaderboard();
}
@Override
public LeaderboardType getLeaderboardType() {
return LeaderboardType.RegattaLeaderboardWithOtherTieBreakingLeaderboard;
}
}
@@ -258,9 +258,9 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
private boolean isCertainlyBeforeRaceFinish(TimePoint timePoint, RaceColumn raceColumn, Competitor competitor) {
final boolean result;
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
final TimePoint endOfRace;
final TimePoint endOfRace = trackedRace == null ? null : trackedRace.getFinishedTime() == null ? trackedRace.getEndOfRace() : trackedRace.getFinishedTime();
// endOfRace != null means we at least will have a fallback result even if there are no mark passings for the competitor
if (trackedRace != null && (endOfRace = trackedRace.getEndOfRace()) != null) {
if (endOfRace != null) {
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
final MarkPassing lastMarkPassing;
// count race as finished for the competitor if the finish mark passing exists or the time is after the end of race
@@ -100,7 +100,7 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes
sortedRaces.add(raceColumn);
final Double totalPoints = totalPointsSupplier.apply(raceColumn);
totalPointsForCompetitorPerColumn.put(raceColumn,
totalPoints == null ? null : (totalPoints * scoringScheme.getScoreFactor(raceColumn)));
totalPoints == null ? null : scoringScheme.getScoreScaledByFactor(raceColumn, totalPoints));
}
}
result = new HashSet<RaceColumn>();
@@ -109,6 +109,11 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
return false;
}
@Override
public boolean isOneAlwaysStaysOne() {
return false;
}
@Override
public void releaseTrackedRace(Fleet fleet) {
}
@@ -148,6 +153,11 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring);
}
@Override
public void oneAlwaysStaysOneChanged(RaceColumn raceColumn, boolean oneAlwaysStaysOne) {
getRaceColumnListeners().notifyListenersAboutOneAlwaysStaysOneChanged(raceColumn, oneAlwaysStaysOne);
}
@Override
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
return getRaceColumnListeners().canAddRaceColumnToContainer(raceColumn);
@@ -58,6 +58,11 @@ public class MarkPassingCalculator {
private final MarkPassingUpdateListener listener;
private final static ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor();
private final LinkedBlockingQueue<StorePositionUpdateStrategy> queue;
/**
* This CALCULATOR_VERSION variable indicates the Version of the {@link MarkPassingCalculator} and must be changed manually when changing the calculator.
* It should be changed by adding +1;
*/
private final int CALCULATOR_VERSION = 1;
/**
* An "end marker" that can be {@link #enqueueUpdate(StorePositionUpdateStrategy) enqueued} in order to tell the {@link Listen}
@@ -96,6 +101,9 @@ public class MarkPassingCalculator {
*/
private boolean listenerThreadStarted;
/**
* Remember to change the {@link CALCULATOR_VERSION} when changing the MarkPassingCalculator
*/
public MarkPassingCalculator(DynamicTrackedRace race, boolean doListen, boolean waitForInitialMarkPassingCalculation) {
if (doListen) {
queue = new LinkedBlockingQueue<>();
@@ -542,4 +550,8 @@ public class MarkPassingCalculator {
public String toString() {
return getClass().getName()+" for "+race+" with chooser "+chooser;
}
public int getCalculatorVersion() {
return CALCULATOR_VERSION;
}
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.markpassinghash;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* An instance of this class represents a composite fingerprint of those components of a {@link TrackedRace} that are
* relevant for computing the
* {@link TrackedRace#getMarkPassing(com.sap.sailing.domain.base.Competitor, com.sap.sailing.domain.base.Waypoint) mark
* passings} in the {@link MarkPassingCalculator}. It can be {@link #matches(TrackedRace) matched} against a {@link TrackedRace}
* instance to see whether the {@link TrackedRace} will produce a set of {@link MarkPassing}s equal to that of the {@link TrackedRace}
* from which this fingerprint was produced.<p>
*
* To produce a fingerprint from a {@link TrackedRace} or from a JSON representation use {@link MarkPassingRaceFingerprintFactory}.<p>
*
* The {@link #equals(Object)} and {@link #hashCode()} methods are defined based on the contents of this fingerprint.
*
* @author Fabian Kallenbach (i550803)
* @author Axel Uhl (d043530)
*/
public interface MarkPassingRaceFingerprint {
/**
* Returns a {@link JSONObject} of the hash values.
*/
JSONObject toJson();
/**
* Incrementally computes the composite fingerprint of the {@code trackedRace} and compares to this fingerprint
* component by component. The fingerprint components are computed in ascending order of computational complexity,
* trying to fail early / fast.
*
* @return {@code true} if the {@code trackedRace} produces a fingerprint equal to this one if passed to
* {@link MarkPassingRaceFingerprintFactory#createFingerprint(TrackedRace)}
*/
boolean matches(TrackedRace trackedRace);
}
@@ -0,0 +1,25 @@
package com.sap.sailing.domain.markpassinghash;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.markpassinghash.impl.MarkPassingRaceFingerprintFactoryImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* Factory for the creation of a {@link MarkPassingRaceFingerprint}.
*
* @author Fabian Kallenbach (i550803)
*/
public interface MarkPassingRaceFingerprintFactory {
MarkPassingRaceFingerprintFactory INSTANCE = new MarkPassingRaceFingerprintFactoryImpl();
/**
* Creates a {@link MarkPassingRaceFingerprint} out of a given {@link TrackedRace}.
*/
MarkPassingRaceFingerprint createFingerprint(TrackedRace trackedRace);
/**
* Creates a {@link MarkPassingRaceFingerprint} out of a given {@link JSONObject}.
*/
MarkPassingRaceFingerprint fromJson(JSONObject json);
}
@@ -0,0 +1,19 @@
package com.sap.sailing.domain.markpassinghash.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintFactory;
import com.sap.sailing.domain.tracking.TrackedRace;
public class MarkPassingRaceFingerprintFactoryImpl implements MarkPassingRaceFingerprintFactory {
@Override
public MarkPassingRaceFingerprint createFingerprint(TrackedRace trackedRace) {
return new MarkPassingRaceFingerprintImpl(trackedRace);
}
@Override
public MarkPassingRaceFingerprint fromJson(JSONObject json) {
return new MarkPassingRaceFingerprintImpl(json);
}
}
@@ -0,0 +1,256 @@
package com.sap.sailing.domain.markpassinghash.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
public class MarkPassingRaceFingerprintImpl implements MarkPassingRaceFingerprint {
private final int calculatorVersion;
private final int competitorHash;
private final TimePoint startOfTracking;
private final TimePoint startTimeReceived;
private final TimePoint endOfTracking;
private final TimePoint startTimeFromRaceLog;
private final TimePoint finishTimeFromRaceLog;
private final int waypointsHash;
private final int numberOfGPSFixes;
private final int gpsFixesHash;
private static enum JSON_FIELDS {
COMPETITOR_HASH, START_OF_TRACKING_AS_MILLIS, END_OF_TRACKING_AS_MILLIS, START_TIME_RECEIVED_AS_MILLIS, START_TIME_FROM_RACE_LOG_AS_MILLIS, FINISH_TIME_FROM_RACE_LOG_AS_MILLIS, WAYPOINTS_HASH, NUMBEROFGPSFIXES, GPSFIXES_HASH, RACE_ID, CALCULATOR_VERSION
};
public MarkPassingRaceFingerprintImpl(TrackedRace trackedRace) {
this.calculatorVersion = getCalculatorVersion(trackedRace);
this.competitorHash = calculateHashForCompetitors(trackedRace);
this.startOfTracking = trackedRace.getStartOfTracking();
this.endOfTracking = trackedRace.getEndOfTracking();
this.startTimeReceived = trackedRace.getStartTimeReceived();
final Pair<TimePoint, TimePoint> startAndFinishedTimeFromRaceLogs = trackedRace
.getStartAndFinishedTimeFromRaceLogs();
this.startTimeFromRaceLog = startAndFinishedTimeFromRaceLogs == null ? null
: startAndFinishedTimeFromRaceLogs.getA();
this.finishTimeFromRaceLog = startAndFinishedTimeFromRaceLogs == null ? null
: startAndFinishedTimeFromRaceLogs.getB();
this.waypointsHash = calculateHashForWaypoints(trackedRace);
this.numberOfGPSFixes = calculateHashForNumberOfGPSFixes(trackedRace);
this.gpsFixesHash = calculateHashForGPSFixes(trackedRace);
}
public MarkPassingRaceFingerprintImpl(JSONObject json) {
this.calculatorVersion = ((Number) json.get(JSON_FIELDS.CALCULATOR_VERSION.name())).intValue();
this.competitorHash = ((Number) json.get(JSON_FIELDS.COMPETITOR_HASH.name())).intValue();
this.startOfTracking = TimePoint.of((Long) json.get(JSON_FIELDS.START_OF_TRACKING_AS_MILLIS.name()));
this.endOfTracking = TimePoint.of((Long) json.get(JSON_FIELDS.END_OF_TRACKING_AS_MILLIS.name()));
this.startTimeReceived = TimePoint.of((Long) json.get(JSON_FIELDS.START_TIME_RECEIVED_AS_MILLIS.name()));
this.startTimeFromRaceLog = TimePoint
.of((Long) json.get(JSON_FIELDS.START_TIME_FROM_RACE_LOG_AS_MILLIS.name()));
this.finishTimeFromRaceLog = TimePoint
.of((Long) json.get(JSON_FIELDS.FINISH_TIME_FROM_RACE_LOG_AS_MILLIS.name()));
this.waypointsHash = ((Number) json.get(JSON_FIELDS.WAYPOINTS_HASH.name())).intValue();
this.numberOfGPSFixes = ((Number) json.get(JSON_FIELDS.NUMBEROFGPSFIXES.name())).intValue();
this.gpsFixesHash = ((Number) json.get(JSON_FIELDS.GPSFIXES_HASH.name())).intValue();
}
@Override
public JSONObject toJson() {
JSONObject result = new JSONObject();
result.put(JSON_FIELDS.CALCULATOR_VERSION.name(), calculatorVersion);
result.put(JSON_FIELDS.COMPETITOR_HASH.name(), competitorHash);
result.put(JSON_FIELDS.START_OF_TRACKING_AS_MILLIS.name(),
startOfTracking == null ? null : startOfTracking.asMillis());
result.put(JSON_FIELDS.END_OF_TRACKING_AS_MILLIS.name(),
endOfTracking == null ? null : endOfTracking.asMillis());
result.put(JSON_FIELDS.START_TIME_RECEIVED_AS_MILLIS.name(),
startTimeReceived == null ? null : startTimeReceived.asMillis());
result.put(JSON_FIELDS.START_TIME_FROM_RACE_LOG_AS_MILLIS.name(),
startTimeFromRaceLog == null ? null : startTimeFromRaceLog.asMillis());
result.put(JSON_FIELDS.FINISH_TIME_FROM_RACE_LOG_AS_MILLIS.name(),
finishTimeFromRaceLog == null ? null : finishTimeFromRaceLog.asMillis());
result.put(JSON_FIELDS.WAYPOINTS_HASH.name(), waypointsHash);
result.put(JSON_FIELDS.NUMBEROFGPSFIXES.name(), numberOfGPSFixes);
result.put(JSON_FIELDS.GPSFIXES_HASH.name(), gpsFixesHash);
return result;
}
@Override
public boolean matches(TrackedRace trackedRace) {
final boolean result;
if (!Util.equalsWithNull(calculatorVersion, getCalculatorVersion(trackedRace))) {
result = false;
} else if (!Util.equalsWithNull(startOfTracking, trackedRace.getStartOfTracking())) {
result = false;
} else if (!Util.equalsWithNull(endOfTracking, trackedRace.getEndOfTracking())) {
result = false;
} else if (!Util.equalsWithNull(startTimeReceived, trackedRace.getStartTimeReceived())) {
result = false;
} else {
final Pair<TimePoint, TimePoint> startAndFinishedTimeFromRaceLogs = trackedRace
.getStartAndFinishedTimeFromRaceLogs();
if (!Util.equalsWithNull(startTimeFromRaceLog,
startAndFinishedTimeFromRaceLogs == null ? null : startAndFinishedTimeFromRaceLogs.getA())) {
result = false;
} else if (!Util.equalsWithNull(finishTimeFromRaceLog,
startAndFinishedTimeFromRaceLogs == null ? null : startAndFinishedTimeFromRaceLogs.getB())) {
result = false;
} else if (waypointsHash != calculateHashForWaypoints(trackedRace)) {
result = false;
} else if (competitorHash != calculateHashForCompetitors(trackedRace)) {
result = false;
} else if (numberOfGPSFixes != calculateHashForNumberOfGPSFixes(trackedRace)) {
result = false;
} else if (gpsFixesHash != calculateHashForGPSFixes(trackedRace)) {
result = false;
} else {
result = true;
}
}
return result;
}
private int getCalculatorVersion(TrackedRace trackedRace) {
MarkPassingCalculator calculator = new MarkPassingCalculator((DynamicTrackedRace) trackedRace, false, false);
int result = calculator.getCalculatorVersion();
return result;
}
private int calculateHashForCompetitors(TrackedRace trackedRace) {
int hashForCompetitors = 1023;
for (Competitor c : trackedRace.getRace().getCompetitors()) {
hashForCompetitors = hashForCompetitors ^ c.getId().hashCode();
}
return hashForCompetitors;
}
private int calculateHashForNumberOfGPSFixes(TrackedRace trackedRace) {
int count = 0;
for (Mark m : trackedRace.getMarks()) {
count += trackedRace.getTrack(m).size();
}
for (final Competitor competitor : trackedRace.getRace().getCompetitors()) {
count += trackedRace.getTrack(competitor).size();
}
return count;
}
private int calculateHashForGPSFixes(TrackedRace trackedRace) {
int res = 511;
for (Mark m : trackedRace.getMarks()) {
final GPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getTrack(m);
markTrack.lockForRead();
try {
for (GPSFix gf : markTrack.getFixes()) {
res = res ^ gf.getTimePoint().hashCode();
res = res ^ gf.getPosition().hashCode();
}
} finally {
markTrack.unlockAfterRead();
}
}
for (final Competitor competitor : trackedRace.getRace().getCompetitors()) {
final GPSFixTrack<Competitor, GPSFixMoving> competitorTrack = trackedRace.getTrack(competitor);
competitorTrack.lockForRead();
try {
for (GPSFixMoving gfm : competitorTrack.getFixes()) {
res = res ^ gfm.getTimePoint().hashCode();
res = res ^ gfm.getPosition().hashCode();
res = res ^ gfm.getSpeed().getBearing().hashCode();
res = res ^ Double.hashCode(gfm.getSpeed().getKnots());
}
} finally {
competitorTrack.unlockAfterRead();
}
}
return res;
}
private int calculateHashForWaypoints(TrackedRace trackedRace) {
Iterable<Waypoint> waypoints = trackedRace.getRace().getCourse().getWaypoints();
int res = 0;
for (Waypoint p : waypoints) {
Iterable<Mark> marks = p.getMarks();
for (Mark m : marks) {
res = res ^ m.getId().hashCode();
}
res = res ^ p.getPassingInstructions().hashCode();
res = (res << 5) - res; // we want to detect changes in order
}
return res;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + calculatorVersion;
result = prime * result + competitorHash;
result = prime * result + ((endOfTracking == null) ? 0 : endOfTracking.hashCode());
result = prime * result + ((finishTimeFromRaceLog == null) ? 0 : finishTimeFromRaceLog.hashCode());
result = prime * result + gpsFixesHash;
result = prime * result + numberOfGPSFixes;
result = prime * result + ((startOfTracking == null) ? 0 : startOfTracking.hashCode());
result = prime * result + ((startTimeFromRaceLog == null) ? 0 : startTimeFromRaceLog.hashCode());
result = prime * result + ((startTimeReceived == null) ? 0 : startTimeReceived.hashCode());
result = prime * result + waypointsHash;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MarkPassingRaceFingerprintImpl other = (MarkPassingRaceFingerprintImpl) obj;
if (calculatorVersion != other.calculatorVersion)
return false;
if (competitorHash != other.competitorHash)
return false;
if (endOfTracking == null) {
if (other.endOfTracking != null)
return false;
} else if (!endOfTracking.equals(other.endOfTracking))
return false;
if (finishTimeFromRaceLog == null) {
if (other.finishTimeFromRaceLog != null)
return false;
} else if (!finishTimeFromRaceLog.equals(other.finishTimeFromRaceLog))
return false;
if (gpsFixesHash != other.gpsFixesHash)
return false;
if (numberOfGPSFixes != other.numberOfGPSFixes)
return false;
if (startOfTracking == null) {
if (other.startOfTracking != null)
return false;
} else if (!startOfTracking.equals(other.startOfTracking))
return false;
if (startTimeFromRaceLog == null) {
if (other.startTimeFromRaceLog != null)
return false;
} else if (!startTimeFromRaceLog.equals(other.startTimeFromRaceLog))
return false;
if (startTimeReceived == null) {
if (other.startTimeReceived != null)
return false;
} else if (!startTimeReceived.equals(other.startTimeReceived))
return false;
if (waypointsHash != other.waypointsHash)
return false;
return true;
}
}
@@ -4,6 +4,13 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sse.common.Timed;
/**
* Mark passings have an {@link #equals(Object)} and {@link #hashCode()} implementation based
* on their content, not their identity.
*
* @author Axel Uhl (d043530)
*
*/
public interface MarkPassing extends Timed {
Waypoint getWaypoint();
@@ -1,16 +0,0 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.common.PassingInstruction;
/**
* The Waypoint before an Offset Mark (see {@link PassingInstruction}) contains a MarkPassing for this mark, which does
* not start an own leg but can be interesting for analysis.
*
* @author Nicolas Klose
*
*/
public interface MarkPassingForOffsetWaypoint extends MarkPassing {
MarkPassing getOffsetPassing();
}
@@ -1,7 +1,10 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedMap;
@@ -484,6 +487,21 @@ public interface TrackedRace
* {@link MarkPassingCalculator }to block the thread until all calculations will be finished.
*/
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor, boolean waitForLatestUpdates);
/**
* Fetches all mark passings for all competitors from this tracked race.
*
* @see #getMarkPassings(Competitor, boolean)
*/
default Map<Competitor, Map<Waypoint, MarkPassing>> getMarkPassings(boolean waitForLatestUpdates) {
final Map<Competitor, Map<Waypoint, MarkPassing>> result = new HashMap<>();
for (final Competitor competitor : getRace().getCompetitors()) {
for (final MarkPassing markPassing : getMarkPassings(competitor, waitForLatestUpdates)) {
result.computeIfAbsent(competitor, k->new HashMap<>()).put(markPassing.getWaypoint(), markPassing);
}
}
return result;
}
/**
* This obtains the course's read lock before asking for the read lock for the <code>markPassings</code> structure.
@@ -1,28 +0,0 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.MarkPassingForOffsetWaypoint;
import com.sap.sse.common.TimePoint;
public class MarkPassingForOffsetImpl extends MarkPassingImpl implements MarkPassingForOffsetWaypoint {
private static final long serialVersionUID = -2168312586388285697L;
private final MarkPassing markpassingforOffset;
public MarkPassingForOffsetImpl(TimePoint timePoint, Waypoint waypoint, Competitor competitor, MarkPassing markPassingforOffset) {
super(timePoint, waypoint, competitor);
this.markpassingforOffset = markPassingforOffset;
}
@Override
public MarkPassing getOffsetPassing() {
return markpassingforOffset;
}
}
@@ -43,4 +43,41 @@ public class MarkPassingImpl implements MarkPassing {
return this;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((competitor == null) ? 0 : competitor.hashCode());
result = prime * result + ((timePoint == null) ? 0 : timePoint.hashCode());
result = prime * result + ((waypoint == null) ? 0 : waypoint.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MarkPassingImpl other = (MarkPassingImpl) obj;
if (competitor == null) {
if (other.competitor != null)
return false;
} else if (!competitor.equals(other.competitor))
return false;
if (timePoint == null) {
if (other.timePoint != null)
return false;
} else if (!timePoint.equals(other.timePoint))
return false;
if (waypoint == null) {
if (other.waypoint != null)
return false;
} else if (!waypoint.equals(other.waypoint))
return false;
return true;
}
}
@@ -1134,6 +1134,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
protected void setFinishingTime(final TimePoint newFinishingTime) {
finishingTime = newFinishingTime;
updated(newFinishingTime);
}
@Override
@@ -1143,6 +1144,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
protected void setFinishedTime(final TimePoint newFinishedTime) {
finishedTime = newFinishedTime;
updated(newFinishedTime);
}
private TimePoint getLastPassingOfFinishLine() {
@@ -181,4 +181,10 @@ public class RaceColumnListeners implements Serializable {
listener.regattaLogEventAdded(event);
}
}
public void notifyListenersAboutOneAlwaysStaysOneChanged(RaceColumn raceColumn, boolean oneAlwaysStaysOne) {
for (RaceColumnListener listener : getRaceColumnListeners()) {
listener.oneAlwaysStaysOneChanged(raceColumn, oneAlwaysStaysOne);
}
}
}
@@ -14,6 +14,8 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@@ -44,10 +46,12 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
private final int XKM_RADIUS = 5;
private final int ROWS_PER_XKM_RADIUS = 15;
private final int MAX_ROW_NUMBER = 1000;
private final int MAX_ROW_NUMBER = 500;
private final int MAX_RADIUS = 300;
private QuadTree<Util.Triple<Position, Double, List<Placemark>>> cache = new QuadTree<Util.Triple<Position,Double,List<Placemark>>>();;
private static final Logger logger = Logger.getLogger(ReverseGeocoderImpl.class.getName());
@Override
public Placemark getPlacemarkNearest(Position position) throws IOException, ParseException {
@@ -71,14 +75,13 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
@Override
public List<Placemark> getPlacemarksNear(Position position, double radius) throws IOException, ParseException {
List<Placemark> placemarks = null;
Util.Triple<Position, Double, List<Placemark>> cachedPlacemarks = checkCache(position);
final List<Placemark> placemarks;
final Util.Triple<Position, Double, List<Placemark>> cachedPlacemarks = checkCache(position);
// Calculating the search radius and the maximum number of returning Placemarks
double limitedRadius = Math.min(radius, MAX_RADIUS);
int radiusInt = (int) limitedRadius;
int xKmRadius = radiusInt / XKM_RADIUS;
int maxRows = (int) (ROWS_PER_XKM_RADIUS * Math.pow(2, xKmRadius));
maxRows = Math.min(maxRows, MAX_ROW_NUMBER);
final int radiusInt = (int) limitedRadius;
final int xKmRadius = radiusInt / XKM_RADIUS;
final int maxRows = (int) Math.min(ROWS_PER_XKM_RADIUS * Math.pow(2, xKmRadius), MAX_ROW_NUMBER);
if (cachedPlacemarks != null && cachedPlacemarks.getB() >= limitedRadius) {
if (cachedPlacemarks.getC().size() > maxRows) {
placemarks = cachedPlacemarks.getC().subList(0, maxRows);
@@ -95,23 +98,29 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
} else {
searchPosition = position;
}
JSONArray geonames = callNearbyService(searchPosition, limitedRadius, maxRows);
final JSONArray geonames = callNearbyService(searchPosition, limitedRadius, maxRows);
if (geonames != null) {
Iterator<Object> iterator = geonames.iterator();
placemarks = iterator.hasNext() ? new ArrayList<Placemark>() : null;
while (iterator.hasNext()) {
JSONObject object = (JSONObject) iterator.next();
Placemark place = jsonToPlacemark(object);
if (place != null) {
placemarks.add(jsonToPlacemark(object));
if (iterator.hasNext()) {
placemarks = new ArrayList<Placemark>();
while (iterator.hasNext()) {
JSONObject object = (JSONObject) iterator.next();
Placemark place = jsonToPlacemark(object);
if (place != null) {
placemarks.add(jsonToPlacemark(object));
}
}
// If there are no cached placemarks for the requested Position just cache them, otherwise update the cache
if (cachedPlacemarks == null) {
cachePlacemarks(searchPosition, limitedRadius, placemarks);
} else {
updateCachedPlacemarks(searchPosition, limitedRadius, placemarks);
}
} else {
placemarks = null;
}
}
// If there are no cached placemarks for the requested Position just cache them, otherwise update the cache
if (cachedPlacemarks == null && placemarks != null) {
cachePlacemarks(searchPosition, limitedRadius, placemarks);
} else if (placemarks != null) {
updateCachedPlacemarks(searchPosition, limitedRadius, placemarks);
} else {
placemarks = null;
}
}
return placemarks;
@@ -135,11 +144,7 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
public Placemark getPlacemark(String name, Comparator<Placemark> comp) throws IOException, ParseException {
StringBuilder url = new StringBuilder(SEARCH_BY_NAME_SERVICE);
url.append("name=" + URLEncoder.encode(name, "UTF-8"));
URLConnection connection = addUsernameParameterAndConnect(url);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));
final JSONParser parser = new JSONParser();
final JSONObject obj = (JSONObject) parser.parse(in);
final JSONArray geonames = (JSONArray) obj.get("geonames");
final JSONArray geonames = submitGeonamesRequestForJSONArrayResult(url);
return geonames.stream().map(o->jsonToPlacemark((JSONObject) o)).sorted(comp).findFirst().orElse(null);
}
@@ -242,14 +247,8 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
}
private JSONArray callNearestService(Position position) throws MalformedURLException, IOException, ParseException {
StringBuilder url = new StringBuilder(NEARBY_PLACE_SERVICE);
url.append("lat=" + Double.toString(position.getLatDeg()));
url.append("&lng=" + Double.toString(position.getLngDeg()));
URLConnection connection = addUsernameParameterAndConnect(url);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));
JSONParser parser = new JSONParser();
JSONObject obj = (JSONObject) parser.parse(in);
JSONArray geonames = (JSONArray) obj.get("geonames");
StringBuilder url = generateRequestUrlNearbyPlaceService(position);
JSONArray geonames = submitGeonamesRequestForJSONArrayResult(url);
return geonames;
}
@@ -261,16 +260,34 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
private JSONArray callNearbyService(Position position, double radius, int maxRows) throws MalformedURLException,
IOException, ParseException {
StringBuilder url = generateRequestUrlNearbyPlaceService(position, radius, maxRows);
JSONArray geonames = submitGeonamesRequestForJSONArrayResult(url);
return geonames;
}
private StringBuilder generateRequestUrlNearbyPlaceService(Position position) {
StringBuilder url = new StringBuilder(NEARBY_PLACE_SERVICE);
url.append("lat=" + Double.toString(position.getLatDeg()));
url.append("&lng=" + Double.toString(position.getLngDeg()));
return url;
}
private StringBuilder generateRequestUrlNearbyPlaceService(Position position, double radius, int maxRows) {
StringBuilder url = generateRequestUrlNearbyPlaceService(position);
url.append("&radius=" + Double.toString(radius));
url.append("&maxRows=" + Integer.toString(maxRows));
return url;
}
private JSONArray submitGeonamesRequestForJSONArrayResult(StringBuilder url) throws MalformedURLException, IOException, ParseException {
URLConnection connection = addUsernameParameterAndConnect(url);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));
JSONParser parser = new JSONParser();
JSONObject obj = (JSONObject) parser.parse(in);
JSONArray geonames = (JSONArray) obj.get("geonames");
if (geonames == null) {
logger.log(Level.WARNING, "Returning null value for geonames object: " + obj.toJSONString());
}
return geonames;
}
@@ -40,10 +40,12 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.ALBIN_EXPRESS.getDisplayName(), imageResources.AlbinExpressIcon());
boatClassIconsMap.put(BoatClassMasterdata.ALBIN_BALLAD.getDisplayName(), imageResources.AlbinBalladIcon());
boatClassIconsMap.put(BoatClassMasterdata.B_ONE.getDisplayName(), imageResources.BOneIcon());
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_41S.getDisplayName(), imageResources.BavariaCruiser41S());
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_45.getDisplayName(), imageResources.BavariaCruiser45());
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_46.getDisplayName(), imageResources.BavariaCruiser46());
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_41S.getDisplayName(), imageResources.BavariaCruiser41SIcon());
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_45.getDisplayName(), imageResources.BavariaCruiser45Icon());
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_46.getDisplayName(), imageResources.BavariaCruiser46Icon());
boatClassIconsMap.put(BoatClassMasterdata.BB10M.getDisplayName(), imageResources.BB10MIcon());
boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_35.getDisplayName(), imageResources.BeneteauFirst35Icon());
boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_45.getDisplayName(), imageResources.BeneteauFirst45Icon());
boatClassIconsMap.put(BoatClassMasterdata.BRASSFAHRT_I.getDisplayName(), imageResources.Brassfahrt1Icon());
boatClassIconsMap.put(BoatClassMasterdata.BRASSFAHRT_II.getDisplayName(), imageResources.Brassfahrt2Icon());
boatClassIconsMap.put(BoatClassMasterdata.BRASSFAHRT_III.getDisplayName(), imageResources.Brassfahrt3Icon());
@@ -78,20 +78,28 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BAVARIA_CRUISER_41S.png")
@ImageOptions(preventInlining = true)
ImageResource BavariaCruiser41S();
ImageResource BavariaCruiser41SIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BAVARIA_CRUISER_45.png")
@ImageOptions(preventInlining = true)
ImageResource BavariaCruiser45();
ImageResource BavariaCruiser45Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BAVARIA_CRUISER_46.png")
@ImageOptions(preventInlining = true)
ImageResource BavariaCruiser46();
ImageResource BavariaCruiser46Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BB10M.png")
@ImageOptions(preventInlining = true)
ImageResource BB10MIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_35.png")
@ImageOptions(preventInlining = true)
ImageResource BeneteauFirst35Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_45.png")
@ImageOptions(preventInlining = true)
ImageResource BeneteauFirst45Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/Brassfahrt1.png")
@ImageOptions(preventInlining = true)
ImageResource Brassfahrt1Icon();
@@ -9,7 +9,6 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.gwt.home.communication.SailingAction;
@@ -43,13 +42,10 @@ public class GetEventsForSailorProfileAction implements SailingAction<SailorProf
@Override
@GwtIncompatible
public SailorProfileEventsDTO execute(SailingDispatchContext ctx) throws DispatchException {
CompetitorAndBoatStore store = ctx.getRacingEventService().getCompetitorAndBoatStore();
SailorProfilePreferences prefs = ctx.getPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME);
SailorProfilePreference pref = findSailorProfile(store, prefs);
Collection<ParticipatedEventDTO> participatedEvents = new ArrayList<>();
final CompetitorAndBoatStore store = ctx.getRacingEventService().getCompetitorAndBoatStore();
final SailorProfilePreferences prefs = ctx.getPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME);
final SailorProfilePreference pref = findSailorProfile(store, prefs);
final Collection<ParticipatedEventDTO> participatedEvents = new ArrayList<>();
// iterate over all existing events
for (Event event : ctx.getRacingEventService().getAllEvents()) {
Collection<ParticipatedRegattaDTO> participatedRegattas = new ArrayList<>();
@@ -57,57 +53,41 @@ public class GetEventsForSailorProfileAction implements SailingAction<SailorProf
for (LeaderboardGroup leaderboardGroup : event.getLeaderboardGroups()) {
if (leaderboardGroup.hasOverallLeaderboard()) {
leaderboardGroup.getOverallLeaderboard();
}
// iterate over the leaderboards in each leaderboard group
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
// check if this leaderboard contains at least one of the selected competitors
Collection<Competitor> containedCompetitors = new ArrayList<>();
final Collection<Competitor> containedCompetitors = new ArrayList<>();
for (Competitor competitor : pref.getCompetitors()) {
if (leaderboard.getCompetitors() != null
&& Util.contains(leaderboard.getCompetitors(), competitor)) {
containedCompetitors.add(competitor);
}
}
// skip if none of the selected competitors is in this leaderboard
if (containedCompetitors.size() == 0) {
continue;
}
// create and add ParticipatedRegattaDTO for each of the selected competitors who was in this
// leaderboard
for (Competitor competitor : containedCompetitors) {
int rank = 0;
try {
rank = leaderboard.getTotalRankOfCompetitor(competitor, MillisecondsTimePoint.now());
} catch (NoWindException e1) {
// ignore
}
int rank = leaderboard.getTotalRankOfCompetitor(competitor, MillisecondsTimePoint.now());
// final String leaderboardName = leaderboard.getDisplayName();
final double points = leaderboard.getNetPoints(competitor, MillisecondsTimePoint.now());
// regatta name is equal to the leaderboard name
String regattaName = leaderboard.getName();
Regatta regatta = ctx.getRacingEventService().getRegattaByName(regattaName);
final String regattaName = leaderboard.getName();
final Regatta regatta = ctx.getRacingEventService().getRegattaByName(regattaName);
if (regatta == null) {
continue;
}
// skip, if the regatta is not part of this event (e.g. shared leaderboard group)
if (!leaderboard.isPartOfEvent(event)) {
// skip, if overall leaderboard is not part of this event, don't skip if this is a regatta
// during an event which is not part of an overall leaderboard
if (leaderboardGroup.hasOverallLeaderboard()
&& !leaderboardGroup.getOverallLeaderboard().isPartOfEvent(event))
continue;
}
participatedRegattas.add(
new ParticipatedRegattaDTO(regattaName, rank, new SimpleCompetitorWithIdDTO(competitor),
"" + regatta.getId(), "" + event.getId(), points));
@@ -119,7 +99,6 @@ public class GetEventsForSailorProfileAction implements SailingAction<SailorProf
.add(new ParticipatedEventDTO(event.getName(), event.getId().toString(), participatedRegattas));
}
}
return new SailorProfileEventsDTO(participatedEvents);
}
@@ -1,11 +1,17 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public abstract class AbstractLeaderboardDialog<LD extends LeaderboardDescriptor> extends DataEntryDialog<LD> {
@@ -17,7 +23,7 @@ public abstract class AbstractLeaderboardDialog<LD extends LeaderboardDescriptor
protected DiscardThresholdBoxes discardThresholdBoxes;
public AbstractLeaderboardDialog(String title, LD leaderboardDescriptor, StringMessages stringMessages,
Validator<LD> validator, DialogCallback<LD> callback) {
Validator<LD> validator, DialogCallback<LD> callback) {
super(title, null, stringMessages.ok(), stringMessages.cancel(), validator, callback);
nameTextBox = createTextBox(leaderboardDescriptor.getName());
nameTextBox.setVisibleLength(50);
@@ -63,4 +69,26 @@ public abstract class AbstractLeaderboardDialog<LD extends LeaderboardDescriptor
}
return result;
}
protected ListBox createSortedRegattaLeaderboardsListBox(Collection<StrippedLeaderboardDTO> existingLeaderboards, String preSelectedRegattaName) {
ListBox result = createListBox(false);
// sort the regatta names
List<StrippedLeaderboardDTO> sortedRegattaLeaderboards = new ArrayList<>();
for (StrippedLeaderboardDTO leaderboard : existingLeaderboards) {
sortedRegattaLeaderboards.add(leaderboard);
}
Collections.sort(sortedRegattaLeaderboards, (rl1, rl2) -> rl1.getName().compareTo(rl2.getName()));
result.addItem(stringMessages.pleaseSelectARegatta());
int i=1;
for (StrippedLeaderboardDTO leaderboard : sortedRegattaLeaderboards) {
if (leaderboard.type.isRegattaLeaderboard()) {
result.addItem(leaderboard.getName(), leaderboard.getName());
if (preSelectedRegattaName != null && leaderboard.getName().equals(preSelectedRegattaName)) {
result.setSelectedIndex(i);
}
i++;
}
}
return result;
}
}
@@ -159,8 +159,7 @@ public class CompetitorRegistrationsPanel extends FlowPanel implements BusyDispl
stringMessages, errorReporter, /* multiSelection */ true, /* enablePager */ true,
/* filterCompetitorWithBoat */ false, /* filterCompetitorsWithoutBoat */ !canBoatsOfCompetitorsChangePerRace);
registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingServiceWrite, userService,
/* competitorsRefresher not needed; filled based on registrations */ null,
/* boatsRefresher not needed */ null, stringMessages,
competitorsRefresher, boatsRefresher, stringMessages,
errorReporter, /* multiSelection */ true, /* enablePager */ false, /* filterCompetitorWithBoat */ false,
/* filterCompetitorsWithoutBoat */ false);
registeredCompetitorsTable.getSelectionModel().addSelectionChangeHandler(event -> validateAndUpdate());
@@ -57,7 +57,7 @@ public class CreateRegattaCallback implements DialogCallback<RegattaDTO>{
SeriesCreationParametersDTO seriesPair = new SeriesCreationParametersDTO(seriesDTO.getFleets(),
seriesDTO.isMedal(), seriesDTO.isFleetsCanRunInParallel(), seriesDTO.isStartsWithZeroScore(),
seriesDTO.isFirstColumnIsNonDiscardableCarryForward(), seriesDTO.getDiscardThresholds(),
seriesDTO.hasSplitFleetContiguousScoring(), seriesDTO.getMaximumNumberOfDiscards());
seriesDTO.hasSplitFleetContiguousScoring(), seriesDTO.getMaximumNumberOfDiscards(), seriesDTO.isOneAlwaysStaysOne());
seriesStructure.put(seriesDTO.getName(), seriesPair);
}
sailingServiceWrite.createRegatta(newRegatta.getName(),
@@ -163,6 +163,10 @@ public class LeaderboardConfigPanel extends AbstractLeaderboardConfigPanel
stringMessages.createRegattaLeaderboardWithEliminations() + " ...",
leaderboardCreateAndRegattaReadPermission, this::createRegattaLeaderboardWithEliminations);
createRegattaLeaderboardWithEliminationsBtn.ensureDebugId("CreateRegattaLeaderboardWithEliminationsButton");
final Button createRegattaLeaderboardWithOtherTieBreakingLeaderboardBtn = buttonPanel.addAction(
stringMessages.createRegattaLeaderboardWithOtherTieBreakingLeaderboard() + " ...",
leaderboardCreateAndRegattaReadPermission, this::createRegattaLeaderboardWithOtherTieBreakingLeaderboard);
createRegattaLeaderboardWithOtherTieBreakingLeaderboardBtn.ensureDebugId("CreateRegattaLeaderboardWithOtherTieBreakingLeaderboardButton");
leaderboardRemoveButton = buttonPanel.addRemoveAction(stringMessages.remove(), leaderboardSelectionModel, true,
() -> removeLeaderboards(leaderboardSelectionModel.getSelectedSet()));
@@ -402,7 +406,7 @@ public class LeaderboardConfigPanel extends AbstractLeaderboardConfigPanel
AbstractLeaderboardDialog<?> dialog;
switch (leaderboardDTO.type) {
case RegattaLeaderboard:
dialog = new RegattaLeaderboardEditDialog(Collections.unmodifiableCollection(otherExistingLeaderboard),
dialog = new RegattaLeaderboardEditDialog<>(Collections.unmodifiableCollection(otherExistingLeaderboard),
Collections.unmodifiableCollection(allRegattas),
createLeaderboardDescriptor(leaderboardDTO,
/* scoring scheme is provided by regatta, not leaderboard */ null),
@@ -418,6 +422,25 @@ public class LeaderboardConfigPanel extends AbstractLeaderboardConfigPanel
});
dialog.show();
break;
case RegattaLeaderboardWithOtherTieBreakingLeaderboard:
dialog = new RegattaLeaderboardWithOtherTieBreakingLeaderboardEditDialog(Collections.unmodifiableCollection(otherExistingLeaderboard),
Collections.unmodifiableCollection(allRegattas),
new LeaderboardDescriptorWithOtherTieBreakingLeaderboard(
leaderboardDTO.getName(), leaderboardDTO.displayName, /* scoringScheme is provided by regatta, not leaderboard */ null,
leaderboardDTO.discardThresholds, leaderboardDTO.regattaName, Util.mapToArrayList(leaderboardDTO.courseAreas, CourseAreaDTO::getId),
leaderboardDTO.getOtherTieBreakingLeaderboardName()),
stringMessages, errorReporter, new DialogCallback<LeaderboardDescriptorWithOtherTieBreakingLeaderboard>() {
@Override
public void cancel() {
}
@Override
public void ok(LeaderboardDescriptorWithOtherTieBreakingLeaderboard result) {
updateLeaderboard(oldLeaderboardName, result);
}
});
dialog.show();
break;
case RegattaLeaderboardWithEliminations:
dialog = new RegattaLeaderboardWithEliminationsEditDialog(sailingServiceWrite, userService,
Collections.unmodifiableCollection(otherExistingLeaderboard),
@@ -930,10 +953,10 @@ public class LeaderboardConfigPanel extends AbstractLeaderboardConfigPanel
}
private void createRegattaLeaderboard() {
RegattaLeaderboardCreateDialog dialog = new RegattaLeaderboardCreateDialog(
RegattaLeaderboardCreateDialog<LeaderboardDescriptor> dialog = new RegattaLeaderboardCreateDialog<LeaderboardDescriptor>(
Collections.unmodifiableCollection(availableLeaderboardList),
Collections.unmodifiableCollection(allRegattas), stringMessages, errorReporter,
new DialogCallback<LeaderboardDescriptor>() {
Collections.unmodifiableCollection(allRegattas), new LeaderboardDescriptor(), stringMessages,
errorReporter, new DialogCallback<LeaderboardDescriptor>() {
@Override
public void cancel() {
}
@@ -1008,6 +1031,39 @@ public class LeaderboardConfigPanel extends AbstractLeaderboardConfigPanel
dialog.show();
}
private void createRegattaLeaderboardWithOtherTieBreakingLeaderboard() {
RegattaLeaderboardWithOtherTieBreakingLeaderboardCreateDialog dialog = new RegattaLeaderboardWithOtherTieBreakingLeaderboardCreateDialog(
Collections.unmodifiableCollection(availableLeaderboardList),
Collections.unmodifiableCollection(allRegattas), stringMessages, errorReporter,
new DialogCallback<LeaderboardDescriptorWithOtherTieBreakingLeaderboard>() {
@Override
public void cancel() {
}
@Override
public void ok(final LeaderboardDescriptorWithOtherTieBreakingLeaderboard newLeaderboard) {
final RegattaName regattaIdentifier = new RegattaName(newLeaderboard.getRegattaName());
sailingServiceWrite.createRegattaLeaderboardWithOtherTieBreakingLeaderboard(
regattaIdentifier, newLeaderboard.getDisplayName(),
newLeaderboard.getDiscardThresholds(), newLeaderboard.getOtherTieBreakingLeaderboardName(),
new AsyncCallback<StrippedLeaderboardDTO>() {
@Override
public void onFailure(Throwable t) {
errorReporter.reportError("Error trying to create the new regatta leaderboard "
+ newLeaderboard.getName() + ": " + t.getMessage());
}
@Override
public void onSuccess(StrippedLeaderboardDTO result) {
addLeaderboard(result);
}
});
}
});
dialog.ensureDebugId("RegattaLeaderboardWithOtherTieBreakingLeaderboardCreateDialog");
dialog.show();
}
private void addLeaderboard(StrippedLeaderboardDTO result) {
filteredLeaderboardList.getList().add(result);
availableLeaderboardList.add(result);
@@ -0,0 +1,23 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.UUID;
import com.sap.sailing.domain.common.ScoringSchemeType;
public class LeaderboardDescriptorWithOtherTieBreakingLeaderboard extends LeaderboardDescriptor {
private String otherTieBreakingLeaderboardName;
public LeaderboardDescriptorWithOtherTieBreakingLeaderboard() {
super();
}
public LeaderboardDescriptorWithOtherTieBreakingLeaderboard(String name, String displayName, ScoringSchemeType scoringScheme,
int[] discardThresholds, String regattaName, Iterable<UUID> courseAreaIds, String otherTieBreakingLeaderboardName) {
super(name, displayName, scoringScheme, discardThresholds, regattaName, courseAreaIds);
this.otherTieBreakingLeaderboardName = otherTieBreakingLeaderboardName;
}
public String getOtherTieBreakingLeaderboardName() {
return otherTieBreakingLeaderboardName;
}
}
@@ -6,8 +6,10 @@ import static com.sap.sse.security.shared.HasPermissions.DefaultActions.CHANGE_O
import java.util.function.Consumer;
import java.util.function.Predicate;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Grid;
@@ -17,9 +19,12 @@ import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.adminconsole.places.AdminConsoleView.Presenter;
import com.sap.sailing.gwt.ui.adminconsole.places.advanced.UserGroupManagementPlace;
import com.sap.sailing.gwt.ui.adminconsole.places.advanced.UserManagementPlace;
import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.ServerConfigurationDTO;
import com.sap.sse.gwt.adminconsole.AbstractFilterablePlace;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
@@ -41,7 +46,8 @@ public class LocalServerManagementPanel extends SimplePanel {
private final StringMessages stringMessages;
private final AccessControlledButtonPanel buttonPanel;
private Label serverNameInfo, buildVersionInfo, groupOwnerInfo, userOwnerInfo;
private Label serverNameInfo, buildVersionInfo;
private Anchor groupOwnerInfo, userOwnerInfo;
private CheckBox isStandaloneServerCheckbox, isPublicServerCheckbox, isSelfServiceServerCheckbox;
private ServerInfoDTO currentServerInfo;
@@ -101,8 +107,8 @@ public class LocalServerManagementPanel extends SimplePanel {
final ServerDataCaptionPanel captionPanel = new ServerDataCaptionPanel(stringMessages.serverInformation(), 4);
serverNameInfo = captionPanel.addInformation(stringMessages.name() + ":");
buildVersionInfo = captionPanel.addInformation(stringMessages.buildVersion() + ":");
groupOwnerInfo = captionPanel.addInformation(stringMessages.ownership() + " - " + stringMessages.group() + ":");
userOwnerInfo = captionPanel.addInformation(stringMessages.ownership() + " - " + stringMessages.user() + ":");
groupOwnerInfo = captionPanel.addAnchor(stringMessages.ownership() + " - " + stringMessages.group() + ":");
userOwnerInfo = captionPanel.addAnchor(stringMessages.ownership() + " - " + stringMessages.user() + ":");
return captionPanel;
}
@@ -157,7 +163,21 @@ public class LocalServerManagementPanel extends SimplePanel {
final boolean hasGroupOwner = ownership != null && ownership.getTenantOwner() != null;
final boolean hasUserOwner = ownership != null && ownership.getUserOwner() != null;
groupOwnerInfo.setText(hasGroupOwner ? ownership.getTenantOwner().getName() : "---");
if (hasGroupOwner) {
groupOwnerInfo.setHref(
UriUtils.fromString("#"+UserGroupManagementPlace.class.getSimpleName()+":"+AbstractFilterablePlace.FILTER_KEY+"="+ownership.getTenantOwner().getName()+
"&"+AbstractFilterablePlace.SELECT_EXACT_KEY+"="+ownership.getTenantOwner().getId().toString()));
} else {
groupOwnerInfo.setHref("javascript:;");
}
userOwnerInfo.setText(hasUserOwner ? ownership.getUserOwner().getName() : "---");
if (hasUserOwner) {
userOwnerInfo.setHref(
UriUtils.fromString("#"+UserManagementPlace.class.getSimpleName()+":"+AbstractFilterablePlace.FILTER_KEY+"="+ownership.getUserOwner().getName()+
"&"+AbstractFilterablePlace.SELECT_EXACT_KEY+"="+ownership.getUserOwner().getName()));
} else {
userOwnerInfo.setHref("javascript:;");
}
// Update changeability
isSelfServiceServerCheckbox.setEnabled(userService.hasServerPermission(DefaultActions.CHANGE_ACL));
// TODO update isPublicServerCheckbox -> default server tenant is currently not available in the UI
@@ -205,6 +225,10 @@ public class LocalServerManagementPanel extends SimplePanel {
private Label addInformation(final String labelText) {
return addWidget(labelText, new Label());
}
private Anchor addAnchor(final String labelText) {
return addWidget(labelText, new Anchor());
}
private CheckBox addChekBox(final String labelText, final Command callback) {
final CheckBox checkBox = new CheckBox();
@@ -110,7 +110,7 @@ public class RaceColumnsInLeaderboardDialog extends DataEntryDialog<List<RaceCol
int racesCount = raceNameEntryFields.size();
for(int i = 0; i < racesCount; i++) {
String raceColumnName = raceNameEntryFields.get(i).getValue();
RaceColumnDTO raceColumnDTO = new RaceColumnDTO(raceColumnName);
RaceColumnDTO raceColumnDTO = new RaceColumnDTO(raceColumnName, /* oneAlwaysStaysOne */ false);
raceColumnDTO.setMedalRace(isMedalRaceCheckboxes.get(i).getValue());
racesWithFleet.add(raceColumnDTO);
}
@@ -214,6 +214,12 @@ public class RegattaDetailsComposite extends Composite {
return series.isFleetsCanRunInParallel() ? stringMessages.yes() : stringMessages.no();
}
};
TextColumn<SeriesDTO> isOneAlwaysStaysOneColumn = new TextColumn<SeriesDTO>() {
@Override
public String getValue(SeriesDTO series) {
return series.isOneAlwaysStaysOne() ? stringMessages.yes() : stringMessages.no();
}
};
TextColumn<SeriesDTO> maximumNumberOfDiscardsColumn = new TextColumn<SeriesDTO>() {
@Override
public String getValue(SeriesDTO series) {
@@ -311,6 +317,7 @@ public class RegattaDetailsComposite extends Composite {
table.addColumn(startsWithZeroScoreColumn, stringMessages.startsWithZeroScore());
table.addColumn(hasSplitFleetContiguousScoringColumn, stringMessages.hasSplitFleetContiguousScoring());
table.addColumn(isFleetsCanRunInParallelColumn, stringMessages.canFleetsRunInParallel());
table.addColumn(isOneAlwaysStaysOneColumn, stringMessages.oneAlwaysStaysOne());
table.addColumn(maximumNumberOfDiscardsColumn, stringMessages.maximumNumberOfDiscards());
table.addColumn(actionsColumn, stringMessages.actions());
return table;
@@ -352,6 +359,7 @@ public class RegattaDetailsComposite extends Composite {
final boolean seriesResultDiscardingThresholdsChanged = !Arrays.equals(series.getDiscardThresholds(),
seriesDescriptor.getResultDiscardingThresholds());
final boolean maximumNumberOfDiscardsChanged = series.getMaximumNumberOfDiscards() != seriesDescriptor.getMaximumNumberOfDiscards();
final boolean oneAlwaysStaysOneChanged = series.isOneAlwaysStaysOne() != seriesDescriptor.isOneAlwaysStaysOne();
final boolean seriesNameChanged = !series.getName().equals(seriesDescriptor.getSeriesName());
final RegattaIdentifier regattaIdentifier = new RegattaName(regatta.getName());
List<RaceColumnDTO> existingRaceColumns = series.getRaceColumns();
@@ -405,13 +413,13 @@ public class RegattaDetailsComposite extends Composite {
public void onSuccess(List<RaceColumnInSeriesDTO> raceColumns) {
if (isMedalChanged || isFleetsCanRunInParallelChanged || seriesResultDiscardingThresholdsChanged || isStartsWithZeroScoreChanged
|| isFirstColumnIsNonDiscardableCarryForwardChanged || hasSplitFleetContiguousScoringChanged
|| seriesNameChanged || maximumNumberOfDiscardsChanged) {
|| seriesNameChanged || maximumNumberOfDiscardsChanged || oneAlwaysStaysOneChanged) {
sailingServiceWrite.updateSeries(regattaIdentifier, series.getName(), seriesDescriptor.getSeriesName(),
seriesDescriptor.isMedal(), seriesDescriptor.isFleetsCanRunInParallel(), seriesDescriptor.getResultDiscardingThresholds(),
seriesDescriptor.isStartsWithZeroScore(),
seriesDescriptor.isFirstColumnIsNonDiscardableCarryForward(),
seriesDescriptor.hasSplitFleetContiguousScoring(), seriesDescriptor.getMaximumNumberOfDiscards(),
series.getFleets(), new AsyncCallback<Void>() {
seriesDescriptor.isOneAlwaysStaysOne(), series.getFleets(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to update series " + series.getName() + ": "
@@ -9,13 +9,13 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.ErrorReporter;
public class RegattaLeaderboardCreateDialog extends RegattaLeaderboardDialog {
public class RegattaLeaderboardCreateDialog<LD extends LeaderboardDescriptor> extends RegattaLeaderboardDialog<LD> {
public RegattaLeaderboardCreateDialog(Collection<StrippedLeaderboardDTO> existingLeaderboards,
Collection<RegattaDTO> existingRegattas, StringMessages stringMessages, ErrorReporter errorReporter,
DialogCallback<LeaderboardDescriptor> callback) {
super(stringMessages.createRegattaLeaderboard(), new LeaderboardDescriptor(), existingRegattas, stringMessages,
errorReporter, new RegattaLeaderboardDialog.LeaderboardParameterValidator(stringMessages, existingLeaderboards),
Collection<RegattaDTO> existingRegattas, LD initialLeaderboardDescriptor, StringMessages stringMessages,
ErrorReporter errorReporter, DialogCallback<LD> callback) {
super(stringMessages.createRegattaLeaderboard(), initialLeaderboardDescriptor, existingRegattas, stringMessages,
errorReporter, new RegattaLeaderboardDialog.LeaderboardParameterValidator<LD>(stringMessages, existingLeaderboards),
callback);
displayNameTextBox = createTextBox(null);
displayNameTextBox.ensureDebugId("DisplayNameTextBox");
@@ -16,12 +16,12 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.ErrorReporter;
public abstract class RegattaLeaderboardDialog extends AbstractLeaderboardDialog<LeaderboardDescriptor> {
public abstract class RegattaLeaderboardDialog<LD extends LeaderboardDescriptor> extends AbstractLeaderboardDialog<LD> {
protected ListBox regattaListBox;
protected Collection<RegattaDTO> existingRegattas;
private Label regattaDefinesDiscardsLabel;
protected Label regattaDefinesDiscardsLabel;
protected static class LeaderboardParameterValidator implements Validator<LeaderboardDescriptor> {
protected static class LeaderboardParameterValidator<LD extends LeaderboardDescriptor> implements Validator<LD> {
protected final StringMessages stringMessages;
protected final Collection<StrippedLeaderboardDTO> existingLeaderboards;
@@ -32,7 +32,7 @@ public abstract class RegattaLeaderboardDialog extends AbstractLeaderboardDialog
}
@Override
public String getErrorMessage(LeaderboardDescriptor leaderboardToValidate) {
public String getErrorMessage(LD leaderboardToValidate) {
String errorMessage;
boolean unique = true;
for (StrippedLeaderboardDTO dao : existingLeaderboards) {
@@ -58,9 +58,9 @@ public abstract class RegattaLeaderboardDialog extends AbstractLeaderboardDialog
}
}
public RegattaLeaderboardDialog(String title, LeaderboardDescriptor leaderboardDTO, Collection<RegattaDTO> existingRegattas, StringMessages stringMessages,
ErrorReporter errorReporter, LeaderboardParameterValidator validator, DialogCallback<LeaderboardDescriptor> callback) {
super(title, leaderboardDTO, stringMessages, validator, callback);
public RegattaLeaderboardDialog(String title, LD leaderboardDescriptor, Collection<RegattaDTO> existingRegattas, StringMessages stringMessages,
ErrorReporter errorReporter, LeaderboardParameterValidator<LD> validator, DialogCallback<LD> callback) {
super(title, leaderboardDescriptor, stringMessages, validator, callback);
this.existingRegattas = existingRegattas;
regattaDefinesDiscardsLabel = new Label(stringMessages.regattaDefinesResultDiscardingRules());
}
@@ -100,8 +100,8 @@ public abstract class RegattaLeaderboardDialog extends AbstractLeaderboardDialog
}
@Override
protected LeaderboardDescriptor getResult() {
LeaderboardDescriptor leaderboard = super.getResult();
protected LD getResult() {
LD leaderboard = super.getResult();
leaderboard.setRegattaName(getSelectedRegatta() != null ? getSelectedRegatta().getName() : null);
return leaderboard;
}
@@ -109,13 +109,13 @@ public abstract class RegattaLeaderboardDialog extends AbstractLeaderboardDialog
@Override
protected Widget getAdditionalWidget() {
VerticalPanel mainPanel = new VerticalPanel();
Grid formGrid = new Grid(3,3);
Grid formGrid = new Grid(3, 3);
formGrid.setCellSpacing(3);
formGrid.setWidget(0, 0, createLabel(stringMessages.regatta()));
formGrid.setWidget(0, 1, regattaListBox);
formGrid.setWidget(1, 0, createLabel(stringMessages.name()));
formGrid.setWidget(1, 0, createLabel(stringMessages.name()));
formGrid.setWidget(1, 1, nameTextBox);
formGrid.setWidget(2, 0, createLabel(stringMessages.displayName()));
formGrid.setWidget(2, 0, createLabel(stringMessages.displayName()));
formGrid.setWidget(2, 1, displayNameTextBox);
mainPanel.add(formGrid);
mainPanel.add(regattaDefinesDiscardsLabel);
@@ -7,12 +7,12 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.ErrorReporter;
public class RegattaLeaderboardEditDialog extends RegattaLeaderboardDialog {
public class RegattaLeaderboardEditDialog<LD extends LeaderboardDescriptor> extends RegattaLeaderboardDialog<LD> {
public RegattaLeaderboardEditDialog(Collection<StrippedLeaderboardDTO> otherExistingLeaderboards, Collection<RegattaDTO> existingRegattas,
LeaderboardDescriptor leaderboardDescriptor, StringMessages stringConstants, ErrorReporter errorReporter,
DialogCallback<LeaderboardDescriptor> callback) {
LD leaderboardDescriptor, StringMessages stringConstants, ErrorReporter errorReporter,
DialogCallback<LD> callback) {
super(stringConstants.editRegattaLeaderboard(), leaderboardDescriptor, existingRegattas, stringConstants, errorReporter,
new RegattaLeaderboardDialog.LeaderboardParameterValidator(stringConstants, otherExistingLeaderboards), callback);
new RegattaLeaderboardDialog.LeaderboardParameterValidator<>(stringConstants, otherExistingLeaderboards), callback);
displayNameTextBox = createTextBox(leaderboardDescriptor.getDisplayName());
displayNameTextBox.setVisibleLength(50);
regattaListBox = createSortedRegattaListBox(existingRegattas, leaderboardDescriptor.getRegattaName());
@@ -1,10 +1,7 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;
import com.google.gwt.event.dom.client.ChangeEvent;
@@ -159,28 +156,6 @@ public abstract class RegattaLeaderboardWithEliminationsDialog extends AbstractL
return null;
}
protected ListBox createSortedRegattaLeaderboardsListBox(Collection<StrippedLeaderboardDTO> existingLeaderboards, String preSelectedRegattaName) {
ListBox result = createListBox(false);
// sort the regatta names
List<StrippedLeaderboardDTO> sortedRegattaLeaderboards = new ArrayList<>();
for (StrippedLeaderboardDTO leaderboard : existingLeaderboards) {
sortedRegattaLeaderboards.add(leaderboard);
}
Collections.sort(sortedRegattaLeaderboards, (rl1, rl2) -> rl1.getName().compareTo(rl2.getName()));
result.addItem(stringMessages.pleaseSelectARegatta());
int i=1;
for (StrippedLeaderboardDTO leaderboard : sortedRegattaLeaderboards) {
if (leaderboard.type.isRegattaLeaderboard()) {
result.addItem(leaderboard.getName(), leaderboard.getName());
if (preSelectedRegattaName != null && leaderboard.getName().equals(preSelectedRegattaName)) {
result.setSelectedIndex(i);
}
i++;
}
}
return result;
}
@Override
protected LeaderboardDescriptorWithEliminations getResult() {
LeaderboardDescriptor leaderboard = super.getResult();
@@ -30,7 +30,7 @@ public class RegattaLeaderboardWithEliminationsEditDialog extends RegattaLeaderb
Collection<StrippedLeaderboardDTO> otherExistingLeaderboards, Collection<RegattaDTO> existingRegattas,
LeaderboardDescriptorWithEliminations leaderboardDescriptor, StringMessages stringMessages, ErrorReporter errorReporter,
DialogCallback<LeaderboardDescriptorWithEliminations> callback) {
super(sailingServiceWrite, userService, stringMessages.editRegattaLeaderboard(), leaderboardDescriptor,
super(sailingServiceWrite, userService, stringMessages.editRegattaLeaderboardWithEliminations(), leaderboardDescriptor,
existingRegattas, otherExistingLeaderboards, stringMessages,
errorReporter,
new RegattaLeaderboardWithEliminationsDialog.LeaderboardParameterValidator(stringMessages,
@@ -0,0 +1,38 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.ErrorReporter;
public class RegattaLeaderboardWithOtherTieBreakingLeaderboardCreateDialog extends RegattaLeaderboardWithOtherTieBreakingLeaderboardDialog {
public RegattaLeaderboardWithOtherTieBreakingLeaderboardCreateDialog(
Collection<StrippedLeaderboardDTO> existingLeaderboards, Collection<RegattaDTO> existingRegattas,
StringMessages stringMessages, ErrorReporter errorReporter,
DialogCallback<LeaderboardDescriptorWithOtherTieBreakingLeaderboard> callback) {
super(stringMessages.createRegattaLeaderboardWithOtherTieBreakingLeaderboard(), new LeaderboardDescriptorWithOtherTieBreakingLeaderboard(),
existingLeaderboards, existingRegattas, stringMessages, errorReporter, callback);
displayNameTextBox = createTextBox(null);
displayNameTextBox.ensureDebugId("DisplayNameTextBox");
displayNameTextBox.setVisibleLength(50);
regattaListBox = createSortedRegattaListBox(existingRegattas, null);
regattaListBox.ensureDebugId("RegattaListBox");
regattaListBox.addChangeHandler(new ChangeHandler() {
public void onChange(ChangeEvent event) {
int selectedIndex = regattaListBox.getSelectedIndex();
if (selectedIndex > 0) {
nameTextBox.setText(regattaListBox.getValue(selectedIndex));
validateAndUpdate();
}
adjustVisibilityOfResultDiscardingRuleComponent();
}
});
otherTieBreakingLeaderboardsListBox = createSortedRegattaLeaderboardsListBox(existingLeaderboards, null);
otherTieBreakingLeaderboardsListBox.ensureDebugId("OtherTieBreakingLeaderboardsListBox");
discardThresholdBoxes = new DiscardThresholdBoxes(this, stringMessages);
}
}
@@ -0,0 +1,58 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.ErrorReporter;
public class RegattaLeaderboardWithOtherTieBreakingLeaderboardDialog extends RegattaLeaderboardDialog<LeaderboardDescriptorWithOtherTieBreakingLeaderboard> {
protected ListBox otherTieBreakingLeaderboardsListBox;
public RegattaLeaderboardWithOtherTieBreakingLeaderboardDialog(
String title, LeaderboardDescriptorWithOtherTieBreakingLeaderboard leaderboardDescriptor,
Collection<StrippedLeaderboardDTO> existingLeaderboards, Collection<RegattaDTO> existingRegattas,
StringMessages stringMessages, ErrorReporter errorReporter, DialogCallback<LeaderboardDescriptorWithOtherTieBreakingLeaderboard> callback) {
super(title, leaderboardDescriptor,
existingRegattas, stringMessages, errorReporter,
new RegattaLeaderboardDialog.LeaderboardParameterValidator<LeaderboardDescriptorWithOtherTieBreakingLeaderboard>(
stringMessages, existingLeaderboards),
callback);
}
@Override
protected Widget getAdditionalWidget() {
VerticalPanel mainPanel = new VerticalPanel();
Grid formGrid = new Grid(4, 3);
formGrid.setCellSpacing(3);
formGrid.setWidget(0, 0, createLabel(stringMessages.regatta()));
formGrid.setWidget(0, 1, regattaListBox);
formGrid.setWidget(1, 0, createLabel(stringMessages.otherTieBreakingLeaderboard()));
formGrid.setWidget(1, 1, otherTieBreakingLeaderboardsListBox);
formGrid.setWidget(2, 0, createLabel(stringMessages.name()));
formGrid.setWidget(2, 1, nameTextBox);
formGrid.setWidget(3, 0, createLabel(stringMessages.displayName()));
formGrid.setWidget(3, 1, displayNameTextBox);
mainPanel.add(formGrid);
mainPanel.add(regattaDefinesDiscardsLabel);
if (discardThresholdBoxes != null) {
mainPanel.add(discardThresholdBoxes.getWidget());
regattaDefinesDiscardsLabel.setVisible(false);
}
return mainPanel;
}
@Override
protected LeaderboardDescriptorWithOtherTieBreakingLeaderboard getResult() {
final LeaderboardDescriptor interimsResult = super.getResult();
return new LeaderboardDescriptorWithOtherTieBreakingLeaderboard(interimsResult.getName(),
interimsResult.getDisplayName(), interimsResult.getScoringScheme(),
interimsResult.getDiscardThresholds(), interimsResult.getRegattaName(),
interimsResult.getCourseAreaIds(), otherTieBreakingLeaderboardsListBox.getSelectedValue());
}
}
@@ -0,0 +1,27 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.ErrorReporter;
public class RegattaLeaderboardWithOtherTieBreakingLeaderboardEditDialog extends RegattaLeaderboardWithOtherTieBreakingLeaderboardDialog {
public RegattaLeaderboardWithOtherTieBreakingLeaderboardEditDialog(Collection<StrippedLeaderboardDTO> otherExistingLeaderboards, Collection<RegattaDTO> existingRegattas,
LeaderboardDescriptorWithOtherTieBreakingLeaderboard leaderboardDescriptor, StringMessages stringMessages, ErrorReporter errorReporter,
DialogCallback<LeaderboardDescriptorWithOtherTieBreakingLeaderboard> callback) {
super(stringMessages.editRegattaLeaderboardWithOtherTieBreakingLeaderboard(), leaderboardDescriptor, otherExistingLeaderboards, existingRegattas, stringMessages, errorReporter, callback);
displayNameTextBox = createTextBox(leaderboardDescriptor.getDisplayName());
displayNameTextBox.setVisibleLength(50);
regattaListBox = createSortedRegattaListBox(existingRegattas, leaderboardDescriptor.getRegattaName());
regattaListBox.setEnabled(false);
otherTieBreakingLeaderboardsListBox = createSortedRegattaLeaderboardsListBox(otherExistingLeaderboards, leaderboardDescriptor.getOtherTieBreakingLeaderboardName());
otherTieBreakingLeaderboardsListBox.ensureDebugId("OtherTieBreakingLeaderboardsListBox");
otherTieBreakingLeaderboardsListBox.setEnabled(false);
if (!getSelectedRegatta().definesSeriesDiscardThresholds()) {
discardThresholdBoxes = new DiscardThresholdBoxes(this, leaderboardDescriptor.getDiscardThresholds(), stringMessages);
} // else, the regatta leaderboard obtains its result discarding rule implicitly from the underlying regatta
adjustVisibilityOfResultDiscardingRuleComponent();
}
}
@@ -381,7 +381,8 @@ public class RegattaListComposite extends Composite {
sailingServiceWrite.updateSeries(regattaName, series.getName(), series.getName(), series.isMedal(),
series.isFleetsCanRunInParallel(), series.getDiscardThresholds(), series.isStartsWithZeroScore(),
series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring(),
series.getMaximumNumberOfDiscards(), series.getFleets(), new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
series.getMaximumNumberOfDiscards(), series.isOneAlwaysStaysOne(), series.getFleets(),
new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to update regatta " + editedRegatta.getName()
@@ -155,7 +155,7 @@ public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAnd
if (series.getName().equals(Series.DEFAULT_NAME)) {
List<RaceColumnDTO> races = new ArrayList<RaceColumnDTO>();
for (int i = 1; i <= 3; i++) {
RaceColumnDTO raceColumnDTO = new RaceColumnInSeriesDTO("R"+i, series.getName(), dto.getName());
RaceColumnDTO raceColumnDTO = new RaceColumnInSeriesDTO("R"+i, series.getName(), dto.getName(), series.isOneAlwaysStaysOne());
races.add(raceColumnDTO);
}
series.setRaceColumns(races);
@@ -23,10 +23,11 @@ public class SeriesDescriptor {
private final boolean firstColumnIsNonDiscardableCarryForward;
private final Integer maximumNumberOfDiscards;
private final int[] resultDiscardingThresholds;
private final boolean oneAlwaysStaysOne;
public SeriesDescriptor(SeriesDTO series, String seriesName, List<RaceColumnDTO> races, boolean isMedal, boolean isFleetsCanRunInParallel,
int[] resultDiscardingThresholds, boolean startsWithZeroScore,
boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, Integer maximumNumberOfDiscards) {
boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, Integer maximumNumberOfDiscards, boolean oneAlwaysStaysOne) {
this.seriesName = seriesName;
this.series = series;
this.races = races;
@@ -37,6 +38,7 @@ public class SeriesDescriptor {
this.resultDiscardingThresholds = resultDiscardingThresholds;
this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward;
this.maximumNumberOfDiscards = maximumNumberOfDiscards;
this.oneAlwaysStaysOne = oneAlwaysStaysOne;
}
public SeriesDTO getSeries() {
@@ -78,4 +80,8 @@ public class SeriesDescriptor {
public Integer getMaximumNumberOfDiscards() {
return maximumNumberOfDiscards;
}
public boolean isOneAlwaysStaysOne() {
return oneAlwaysStaysOne;
}
}
@@ -45,6 +45,7 @@ public class SeriesEditDialog extends DataEntryDialog<SeriesDescriptor> {
private CheckBox startWithZeroScoreCheckbox;
private CheckBox hasSplitFleetContiguousScoringCheckbox;
private CheckBox firstColumnIsNonDiscardableCarryForwardCheckbox;
private CheckBox oneAlwaysStaysOneCheckbox;
private IntegerBox maximumNumberOfDiscardsBox;
private CheckBox useSeriesResultDiscardingThresholdsCheckbox;
private final StringMessages stringMessages;
@@ -131,7 +132,7 @@ public class SeriesEditDialog extends DataEntryDialog<SeriesDescriptor> {
for (String name : raceNamesEditor.getValue()) {
RaceColumnDTO raceColumnDTO = findRaceColumnInSeriesByName(selectedSeries, name);
if (raceColumnDTO == null) {
raceColumnDTO = new RaceColumnInSeriesDTO(name, selectedSeries.getName(), regatta.getName());
raceColumnDTO = new RaceColumnInSeriesDTO(name, selectedSeries.getName(), regatta.getName(), selectedSeries.isOneAlwaysStaysOne());
}
races.add(raceColumnDTO);
}
@@ -140,7 +141,7 @@ public class SeriesEditDialog extends DataEntryDialog<SeriesDescriptor> {
useSeriesResultDiscardingThresholdsCheckbox.getValue() ? discardThresholdBoxes.getDiscardThresholds()
: null, startWithZeroScoreCheckbox.getValue(),
firstColumnIsNonDiscardableCarryForwardCheckbox.getValue(), hasSplitFleetContiguousScoringCheckbox.getValue(),
maximumNumberOfDiscardsBox.getValue());
maximumNumberOfDiscardsBox.getValue(), oneAlwaysStaysOneCheckbox.getValue());
}
private RaceColumnDTO findRaceColumnInSeriesByName(SeriesDTO series, String raceColumnName) {
@@ -196,6 +197,11 @@ public class SeriesEditDialog extends DataEntryDialog<SeriesDescriptor> {
firstColumnIsNonDiscardableCarryForwardCheckbox.setValue(selectedSeries.isFirstColumnIsNonDiscardableCarryForward());
additionalWidgetPanel.add(firstColumnIsNonDiscardableCarryForwardCheckbox);
oneAlwaysStaysOneCheckbox = createCheckbox(stringMessages.oneAlwaysStaysOne());
oneAlwaysStaysOneCheckbox.ensureDebugId("OneAlwaysStaysOneCheckbox");
oneAlwaysStaysOneCheckbox.setValue(selectedSeries.isOneAlwaysStaysOne());
additionalWidgetPanel.add(oneAlwaysStaysOneCheckbox);
final HorizontalPanel maximumNumberOfDiscardsPanel = new HorizontalPanel();
maximumNumberOfDiscardsPanel.add(new Label(stringMessages.maximumNumberOfDiscards()));
maximumNumberOfDiscardsBox = createIntegerBox(selectedSeries.getMaximumNumberOfDiscards(), /* visibleLength */ 3);
@@ -40,6 +40,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> {
protected CheckBox hasSplitFleetContiguousScoringCheckbox;
protected CheckBox firstColumnIsNonDiscardableCarryForwardCheckbox;
protected CheckBox useSeriesResultDiscardingThresholdsCheckbox;
protected CheckBox oneAlwaysStaysOneCheckbox;
protected IntegerBox maximumNumberOfDiscardsBox;
protected final DiscardThresholdBoxes discardThresholdBoxes;
protected ListEditorComposite<FleetDTO> fleetListComposite;
@@ -149,10 +150,11 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> {
SeriesWithFleetsCreateDialog.this.discardThresholdBoxes.getWidget().setVisible(event.getValue());
}
});
oneAlwaysStaysOneCheckbox = createCheckbox(stringMessages.oneAlwaysStaysOne());
oneAlwaysStaysOneCheckbox.ensureDebugId("OneAlwaysStaysOneCheckbox");
Widget widget = discardThresholdBoxes.getWidget();
widget.ensureDebugId("DiscardThresholdBoxes");
widget.setVisible(false);
initializeFleetListComposite(stringMessages);
}
@@ -177,6 +179,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> {
series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoringCheckbox.getValue());
series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForwardCheckbox.getValue());
series.setMaximumNumberOfDiscards(maximumNumberOfDiscardsBox.getValue());
series.setOneAlwaysStaysOne(oneAlwaysStaysOneCheckbox.getValue());
series.setFleets(fleetListComposite.getValue());
series.setDiscardThresholds(useSeriesResultDiscardingThresholdsCheckbox.getValue() ? discardThresholdBoxes.getDiscardThresholds() : null);
return series;
@@ -189,7 +192,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> {
if (additionalWidget != null) {
panel.add(additionalWidget);
}
Grid formGrid = new Grid(9, 2);
Grid formGrid = new Grid(10, 2);
panel.add(formGrid);
int row = 0;
formGrid.setWidget(row, 0, new Label(stringMessages.name() + ":"));
@@ -199,11 +202,11 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> {
formGrid.setWidget(row++, 1, startsWithZeroScoreCheckbox);
formGrid.setWidget(row++, 1, hasSplitFleetContiguousScoringCheckbox);
formGrid.setWidget(row++, 1, firstColumnIsNonDiscardableCarryForwardCheckbox);
formGrid.setWidget(row++, 1, oneAlwaysStaysOneCheckbox);
formGrid.setWidget(row, 0, new Label(stringMessages.maximumNumberOfDiscards()));
formGrid.setWidget(row++, 1, maximumNumberOfDiscardsBox);
formGrid.setWidget(row++, 1, useSeriesResultDiscardingThresholdsCheckbox);
formGrid.setWidget(row++, 1, discardThresholdBoxes.getWidget());
TabPanel tabPanel = new TabPanel();
tabPanel.setWidth("100%");
tabPanel.add(fleetListComposite, stringMessages.fleets());
@@ -271,7 +271,7 @@ public interface SailingServiceWrite extends FileStorageManagementGwtService, Sa
void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal,
boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore,
boolean firstRaceIsNonDiscardableCarryForward, boolean hasSplitFleetScore, Integer maximumNumberOfDiscards,
List<FleetDTO> fleets) throws UnauthorizedException;
boolean oneAlwaysStaysOne, List<FleetDTO> fleets) throws UnauthorizedException;
void updateRegatta(RegattaIdentifier regattaIdentifier, Date startDate, Date endDate, List<UUID> courseAreaUuids,
RegattaConfigurationDTO regattaConfiguration, Double buoyZoneRadiusInHullLengths,
@@ -378,6 +378,9 @@ public interface SailingServiceWrite extends FileStorageManagementGwtService, Sa
StrippedLeaderboardDTO createRegattaLeaderboardWithEliminations(String name, String displayName,
String regattaName) throws UnauthorizedException;
StrippedLeaderboardDTO createRegattaLeaderboardWithOtherTieBreakingLeaderboard(RegattaName regattaIdentifier,
String leaderboardDisplayName, int[] discardThresholds, String otherTieBreakingLeaderboardName);
StrippedLeaderboardDTO createRegattaLeaderboard(RegattaName regattaIdentifier,
String leaderboardDisplayName, int[] discardThresholds) throws UnauthorizedException;
@@ -356,8 +356,11 @@ public interface SailingServiceWriteAsync extends FileStorageManagementGwtServic
int[] discardThresholds, AsyncCallback<StrippedLeaderboardDTO> asyncCallback);
void createRegattaLeaderboardWithEliminations(String name, String displayName,
String regattaName,
AsyncCallback<StrippedLeaderboardDTO> asyncCallback);
String regattaName, AsyncCallback<StrippedLeaderboardDTO> asyncCallback);
void createRegattaLeaderboardWithOtherTieBreakingLeaderboard(RegattaName regattaIdentifier,
String leaderboardDisplayName, int[] discardThresholds, String otherTieBreakingLeaderboardName,
AsyncCallback<StrippedLeaderboardDTO> callback);
void removeLeaderboard(String leaderboardName, AsyncCallback<Void> asyncCallback);
@@ -480,7 +483,7 @@ public interface SailingServiceWriteAsync extends FileStorageManagementGwtServic
void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal,
boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore,
boolean firstRaceIsNonDiscardableCarryForward, boolean hasSplitFleetScore, Integer maximumNumberOfDiscards,
List<FleetDTO> fleets, AsyncCallback<Void> callback);
boolean oneAlwaysStaysOne, List<FleetDTO> fleets, AsyncCallback<Void> callback);
void removeRaceColumnsFromSeries(RegattaIdentifier regattaIdentifier, String seriesName, List<String> columnNames,
AsyncCallback<Void> callback);
@@ -463,8 +463,11 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String createFlexibleLeaderboard();
String createRegattaLeaderboard();
String createRegattaLeaderboardWithEliminations();
String createRegattaLeaderboardWithOtherTieBreakingLeaderboard();
String editFlexibleLeaderboard();
String editRegattaLeaderboard();
String editRegattaLeaderboardWithEliminations();
String editRegattaLeaderboardWithOtherTieBreakingLeaderboard();
String defaultRegatta();
String useOverallLeaderboard();
String overallStandings();
@@ -2410,4 +2413,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String goToSelfServicePortalDialogTitle();
String goToSelfServicePortalDialogText();
String failedFetchingSelfServicePortalSession();
String oneAlwaysStaysOne();
String otherTieBreakingLeaderboard();
String scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboard();
String scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboardDescription();
}

Some files were not shown because too many files have changed in this diff Show More