mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-06 19:12:23 +00:00
Merge remote-tracking branch 'origin/master' into media
This commit is contained in:
commit
f4b72a4873
586 files changed
+21759
-8138
No files matched your search
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
Exporting swisstiming 'Store and forward' messages
|
||||
------------------------------------------------------
|
||||
|
||||
27012: local MongoDB "winddb" instance
|
||||
|
||||
/opt/mongodb/bin/mongoexport --port 27017 -d winddb -c SWISSTIMING_CONFIGURATIONS > /tmp/swisstiming_configurations.json
|
||||
/opt/mongodb/bin/mongoexport --port 27017 -d winddb -c LAST_MESSAGE_COUNT > /tmp/swisstiming_last_message_count.json
|
||||
/opt/mongodb/bin/mongoexport --port 27017 -d winddb -c RACES_MASTERDATA > /tmp/swisstiming_races_masterdata.json
|
||||
/opt/mongodb/bin/mongoexport --port 27017 -d winddb -c RACES_MESSAGES > /tmp/swisstiming_races_massages.json
|
||||
/opt/mongodb/bin/mongoexport --port 27017 -d winddb -c COMMAND_MESSAGES > /tmp/swisstiming_command_messages.json
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -2,4 +2,5 @@
|
||||
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.4.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.4.0/distro-source/core/src/gwt-module.dtd">
|
||||
<module>
|
||||
<source path='common'/>
|
||||
<source path='common/impl'/>
|
||||
</module>
|
||||
+1
-1
@@ -31,7 +31,7 @@ public class Base64Utils {
|
||||
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
|
||||
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '$', '_'};
|
||||
'4', '5', '6', '7', '8', '9', '+', '/'};
|
||||
|
||||
/**
|
||||
* An array mapping legal base 64 characters [a-zA-Z0-9$_] to their associated
|
||||
|
||||
+5
-1
@@ -21,9 +21,13 @@ public enum DetailType implements Serializable {
|
||||
RACE_DISTANCE_TO_LEADER_IN_METERS(0, ASCENDING), RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING),
|
||||
WINDWARD_DISTANCE_TO_OVERALL_LEADER(0, ASCENDING),
|
||||
HEAD_UP(0, ASCENDING), BEAR_AWAY(0, ASCENDING),
|
||||
TACK(0, ASCENDING), JIBE(0, ASCENDING), PENALTY_CIRCLE(0, ASCENDING), MARK_PASSING(0, ASCENDING), CURRENT_LEG(0, ASCENDING),
|
||||
TACK(0, ASCENDING), JIBE(0, ASCENDING), PENALTY_CIRCLE(0, ASCENDING), AVERAGE_MANEUVER_LOSS_IN_METERS(1, ASCENDING),
|
||||
AVERAGE_TACK_LOSS_IN_METERS(1, ASCENDING), AVERAGE_JIBE_LOSS_IN_METERS(1, ASCENDING),
|
||||
MARK_PASSING(0, ASCENDING), CURRENT_LEG(0, ASCENDING),
|
||||
DISPLAY_LEGS(0, NONE), TIME_TRAVELED(0, ASCENDING),
|
||||
TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS(1, ASCENDING),
|
||||
TOTAL_TIME_SAILED_UPWIND_IN_SECONDS(1, ASCENDING),
|
||||
TOTAL_TIME_SAILED_REACHING_IN_SECONDS(1, ASCENDING),
|
||||
MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS(1, DESCENDING),
|
||||
TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING);
|
||||
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public enum PassingDirection { PORT, STARBOARD };
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public enum ScoringSchemeType {
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
package com.sap.sailing.domain.common.impl;
|
||||
|
||||
import com.sap.sailing.domain.common.AbstractDistance;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
+14
-3
@@ -39,8 +39,9 @@ public interface MongoObjectFactory {
|
||||
void renameLeaderboard(String oldName, String newName);
|
||||
|
||||
/**
|
||||
* Stores the group, if it doesn't exist or updates it.<br />
|
||||
* Leaderboards in the group, which aren't stored in the database, will be stored.
|
||||
* Stores the group, if it doesn't exist or updates it. Leaderboards in the group, which aren't stored in the
|
||||
* database, will be stored. If the leaderboard group has an {@link LeaderboardGroup#getOverallLeaderboard() overall
|
||||
* leaderboard}, it will be stored / updated as well.
|
||||
*/
|
||||
void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup);
|
||||
|
||||
@@ -60,7 +61,17 @@ public interface MongoObjectFactory {
|
||||
* by calls to {@link #storeRegatta} where a reference to their owning event is stored.
|
||||
*/
|
||||
void storeEvent(Event event);
|
||||
|
||||
|
||||
/**
|
||||
* Renames the event with the name <code>oldName</code>.
|
||||
*/
|
||||
void renameEvent(String oldName, String newName);
|
||||
|
||||
/**
|
||||
* Removes the event named <code>eventName</code> from the database.
|
||||
*/
|
||||
void removeEvent(String eventName);
|
||||
|
||||
/**
|
||||
* Stores the regatta together with its name, {@link Series} definitions and an optional link to the
|
||||
* {@link Event} to which the regatta belongs.
|
||||
|
||||
+93
-47
@@ -67,29 +67,35 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr
|
||||
@Override
|
||||
public void setCarriedPoints(String competitorName, double carriedPoints) {
|
||||
assertNoTrackedRaceAssociatedYet();
|
||||
carriedPointsByCompetitorName.put(competitorName, carriedPoints);
|
||||
synchronized (carriedPointsByCompetitorName) {
|
||||
carriedPointsByCompetitorName.put(competitorName, carriedPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxPointsReason(String competitorName, RaceColumn raceColumn, MaxPointsReason maxPointsReason) {
|
||||
assertNoTrackedRaceAssociatedYet();
|
||||
Map<RaceColumn, MaxPointsReason> map = maxPointsReasonsByCompetitorName.get(competitorName);
|
||||
if (map == null) {
|
||||
map = new HashMap<RaceColumn, MaxPointsReason>();
|
||||
maxPointsReasonsByCompetitorName.put(competitorName, map);
|
||||
synchronized (maxPointsReasonsByCompetitorName) {
|
||||
Map<RaceColumn, MaxPointsReason> map = maxPointsReasonsByCompetitorName.get(competitorName);
|
||||
if (map == null) {
|
||||
map = new HashMap<RaceColumn, MaxPointsReason>();
|
||||
maxPointsReasonsByCompetitorName.put(competitorName, map);
|
||||
}
|
||||
map.put(raceColumn, maxPointsReason);
|
||||
}
|
||||
map.put(raceColumn, maxPointsReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void correctScore(String competitorName, RaceColumn raceColumn, double correctedScore) {
|
||||
assertNoTrackedRaceAssociatedYet();
|
||||
Map<RaceColumn, Double> map = correctedScoresByCompetitorName.get(competitorName);
|
||||
if (map == null) {
|
||||
map = new HashMap<RaceColumn, Double>();
|
||||
correctedScoresByCompetitorName.put(competitorName, map);
|
||||
synchronized (correctedScoresByCompetitorName) {
|
||||
Map<RaceColumn, Double> map = correctedScoresByCompetitorName.get(competitorName);
|
||||
if (map == null) {
|
||||
map = new HashMap<RaceColumn, Double>();
|
||||
correctedScoresByCompetitorName.put(competitorName, map);
|
||||
}
|
||||
map.put(raceColumn, correctedScore);
|
||||
}
|
||||
map.put(raceColumn, correctedScore);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,48 +111,67 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr
|
||||
for (Competitor competitor : race.getRace().getCompetitors()) {
|
||||
competitorsByName.put(competitor.getName(), competitor);
|
||||
}
|
||||
for (Iterator<Map.Entry<String, Double>> carryEntryIter = carriedPointsByCompetitorName.entrySet().iterator(); carryEntryIter.hasNext(); ) {
|
||||
Map.Entry<String, Double> carryEntry = carryEntryIter.next();
|
||||
if (competitorsByName.containsKey(carryEntry.getKey())) {
|
||||
leaderboard.setCarriedPoints(competitorsByName.get(carryEntry.getKey()), carryEntry.getValue());
|
||||
carryEntryIter.remove();
|
||||
}
|
||||
}
|
||||
for (Iterator<java.util.Map.Entry<String, Map<RaceColumn, MaxPointsReason>>> maxPointsReasonsEntryIter =
|
||||
maxPointsReasonsByCompetitorName.entrySet().iterator(); maxPointsReasonsEntryIter.hasNext();) {
|
||||
java.util.Map.Entry<String, Map<RaceColumn, MaxPointsReason>> maxPointsReasonEntries = maxPointsReasonsEntryIter.next();
|
||||
if (competitorsByName.containsKey(maxPointsReasonEntries.getKey())) {
|
||||
for (Map.Entry<RaceColumn, MaxPointsReason> maxPointsReasonEntry : maxPointsReasonEntries.getValue().entrySet()) {
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitorsByName.get(maxPointsReasonEntries.getKey()),
|
||||
maxPointsReasonEntry.getKey(), maxPointsReasonEntry.getValue());
|
||||
synchronized (carriedPointsByCompetitorName) {
|
||||
for (Iterator<Map.Entry<String, Double>> carryEntryIter = carriedPointsByCompetitorName.entrySet()
|
||||
.iterator(); carryEntryIter.hasNext();) {
|
||||
Map.Entry<String, Double> carryEntry = carryEntryIter.next();
|
||||
if (competitorsByName.containsKey(carryEntry.getKey())) {
|
||||
leaderboard.setCarriedPoints(competitorsByName.get(carryEntry.getKey()), carryEntry.getValue());
|
||||
carryEntryIter.remove();
|
||||
}
|
||||
maxPointsReasonsEntryIter.remove();
|
||||
}
|
||||
}
|
||||
for (Iterator<java.util.Map.Entry<String, Map<RaceColumn, Double>>> correctedScoresEntryIter =
|
||||
correctedScoresByCompetitorName.entrySet().iterator(); correctedScoresEntryIter.hasNext();) {
|
||||
java.util.Map.Entry<String, Map<RaceColumn, Double>> correctedScoresEntries = correctedScoresEntryIter.next();
|
||||
if (competitorsByName.containsKey(correctedScoresEntries.getKey())) {
|
||||
for (java.util.Map.Entry<RaceColumn, Double> correctedScoreEntry : correctedScoresEntries.getValue().entrySet()) {
|
||||
leaderboard.getScoreCorrection().correctScore(competitorsByName.get(correctedScoresEntries.getKey()),
|
||||
correctedScoreEntry.getKey(), correctedScoreEntry.getValue());
|
||||
synchronized (maxPointsReasonsByCompetitorName) {
|
||||
for (Iterator<java.util.Map.Entry<String, Map<RaceColumn, MaxPointsReason>>> maxPointsReasonsEntryIter = maxPointsReasonsByCompetitorName
|
||||
.entrySet().iterator(); maxPointsReasonsEntryIter.hasNext();) {
|
||||
java.util.Map.Entry<String, Map<RaceColumn, MaxPointsReason>> maxPointsReasonEntries = maxPointsReasonsEntryIter
|
||||
.next();
|
||||
if (competitorsByName.containsKey(maxPointsReasonEntries.getKey())) {
|
||||
for (Map.Entry<RaceColumn, MaxPointsReason> maxPointsReasonEntry : maxPointsReasonEntries
|
||||
.getValue().entrySet()) {
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(
|
||||
competitorsByName.get(maxPointsReasonEntries.getKey()), maxPointsReasonEntry.getKey(),
|
||||
maxPointsReasonEntry.getValue());
|
||||
}
|
||||
maxPointsReasonsEntryIter.remove();
|
||||
}
|
||||
correctedScoresEntryIter.remove();
|
||||
}
|
||||
}
|
||||
for (Iterator<java.util.Map.Entry<String, String>> displayNamesEntryIter =
|
||||
displayNamesByCompetitorName.entrySet().iterator(); displayNamesEntryIter.hasNext();) {
|
||||
java.util.Map.Entry<String, String> displayNamesEntry = displayNamesEntryIter.next();
|
||||
if (competitorsByName.containsKey(displayNamesEntry.getKey())) {
|
||||
leaderboard.setDisplayName(competitorsByName.get(displayNamesEntry.getKey()), displayNamesEntry.getValue());
|
||||
displayNamesEntryIter.remove();
|
||||
synchronized (correctedScoresByCompetitorName) {
|
||||
for (Iterator<java.util.Map.Entry<String, Map<RaceColumn, Double>>> correctedScoresEntryIter = correctedScoresByCompetitorName
|
||||
.entrySet().iterator(); correctedScoresEntryIter.hasNext();) {
|
||||
java.util.Map.Entry<String, Map<RaceColumn, Double>> correctedScoresEntries = correctedScoresEntryIter
|
||||
.next();
|
||||
if (competitorsByName.containsKey(correctedScoresEntries.getKey())) {
|
||||
for (java.util.Map.Entry<RaceColumn, Double> correctedScoreEntry : correctedScoresEntries
|
||||
.getValue().entrySet()) {
|
||||
leaderboard.getScoreCorrection().correctScore(
|
||||
competitorsByName.get(correctedScoresEntries.getKey()), correctedScoreEntry.getKey(),
|
||||
correctedScoreEntry.getValue());
|
||||
}
|
||||
correctedScoresEntryIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Iterator<String> suppressedCompetitorNameIter=suppressedCompetitorNames.iterator(); suppressedCompetitorNameIter.hasNext(); ) {
|
||||
String next = suppressedCompetitorNameIter.next();
|
||||
if (competitorsByName.containsKey(next)) {
|
||||
leaderboard.setSuppressed(competitorsByName.get(next), true);
|
||||
suppressedCompetitorNameIter.remove();
|
||||
synchronized (displayNamesByCompetitorName) {
|
||||
for (Iterator<java.util.Map.Entry<String, String>> displayNamesEntryIter = displayNamesByCompetitorName
|
||||
.entrySet().iterator(); displayNamesEntryIter.hasNext();) {
|
||||
java.util.Map.Entry<String, String> displayNamesEntry = displayNamesEntryIter.next();
|
||||
if (competitorsByName.containsKey(displayNamesEntry.getKey())) {
|
||||
leaderboard.setDisplayName(competitorsByName.get(displayNamesEntry.getKey()),
|
||||
displayNamesEntry.getValue());
|
||||
displayNamesEntryIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (suppressedCompetitorNames) {
|
||||
for (Iterator<String> suppressedCompetitorNameIter = suppressedCompetitorNames.iterator(); suppressedCompetitorNameIter
|
||||
.hasNext();) {
|
||||
String next = suppressedCompetitorNameIter.next();
|
||||
if (competitorsByName.containsKey(next)) {
|
||||
leaderboard.setSuppressed(competitorsByName.get(next), true);
|
||||
suppressedCompetitorNameIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
removeAsListenerIfNoLeftOvers();
|
||||
@@ -163,13 +188,17 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr
|
||||
@Override
|
||||
public void setDisplayName(String competitorName, String displayName) {
|
||||
assertNoTrackedRaceAssociatedYet();
|
||||
displayNamesByCompetitorName.put(competitorName, displayName);
|
||||
synchronized (displayNamesByCompetitorName) {
|
||||
displayNamesByCompetitorName.put(competitorName, displayName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suppressCompetitor(String competitorName) {
|
||||
assertNoTrackedRaceAssociatedYet();
|
||||
suppressedCompetitorNames.add(competitorName);
|
||||
synchronized (suppressedCompetitorNames) {
|
||||
suppressedCompetitorNames.add(competitorName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,6 +214,11 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr
|
||||
public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
|
||||
}
|
||||
@@ -193,6 +227,18 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr
|
||||
public void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return false;
|
||||
|
||||
+23
-3
@@ -220,6 +220,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result);
|
||||
loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, scoreCorrection);
|
||||
loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections);
|
||||
loadColumnFactors(dbLeaderboard, result);
|
||||
// add the leaderboard to the registry
|
||||
if (leaderboardRegistry != null) {
|
||||
leaderboardRegistry.addLeaderboard(result);
|
||||
@@ -230,6 +231,17 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
return result;
|
||||
}
|
||||
|
||||
private void loadColumnFactors(DBObject dbLeaderboard, Leaderboard result) {
|
||||
DBObject dbColumnFactors = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMN_FACTORS.name());
|
||||
if (dbColumnFactors != null) {
|
||||
for (String encodedRaceColumnName : dbColumnFactors.keySet()) {
|
||||
double factor = ((Number) dbColumnFactors.get(encodedRaceColumnName)).doubleValue();
|
||||
String raceColumnName = MongoUtils.unescapeDollarAndDot(encodedRaceColumnName);
|
||||
result.getRaceColumnByName(raceColumnName).setFactor(factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSuppressedCompetitors(DBObject dbLeaderboard,
|
||||
DelayedLeaderboardCorrections loadedLeaderboardCorrections) {
|
||||
BasicDBList dbSuppressedCompetitorNames = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITORS.name());
|
||||
@@ -432,18 +444,24 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
||||
String name = (String) o.get(FieldNames.LEADERBOARD_GROUP_NAME.name());
|
||||
String description = (String) o.get(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name());
|
||||
boolean displayGroupsInReverseOrder = false; // default value
|
||||
Object displayGroupsInReverseOrderObj = o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name());
|
||||
if (displayGroupsInReverseOrderObj != null) {
|
||||
displayGroupsInReverseOrder = (Boolean) displayGroupsInReverseOrderObj;
|
||||
}
|
||||
ArrayList<Leaderboard> leaderboards = new ArrayList<Leaderboard>();
|
||||
BasicDBList dbLeaderboardIds = (BasicDBList) o.get(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name());
|
||||
for (Object object : dbLeaderboardIds) {
|
||||
ObjectId dbLeaderboardId = (ObjectId) object;
|
||||
DBObject dbLeaderboard = leaderboardCollection.findOne(dbLeaderboardId);
|
||||
final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ null);
|
||||
final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry,
|
||||
leaderboardRegistry, /* groupForMetaLeaderboard */ null);
|
||||
if (loadedLeaderboard != null) {
|
||||
leaderboards.add(loadedLeaderboard);
|
||||
}
|
||||
}
|
||||
logger.info("loaded leaderboard group "+name);
|
||||
LeaderboardGroupImpl result = new LeaderboardGroupImpl(name, description, leaderboards);
|
||||
LeaderboardGroupImpl result = new LeaderboardGroupImpl(name, description, displayGroupsInReverseOrder, leaderboards);
|
||||
Object overallLeaderboardIdOrName = o.get(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name());
|
||||
if (overallLeaderboardIdOrName != null) {
|
||||
final DBObject dbOverallLeaderboard;
|
||||
@@ -554,8 +572,10 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
*/
|
||||
private Event loadEvent(DBObject eventDBObject) {
|
||||
String name = (String) eventDBObject.get(FieldNames.EVENT_NAME.name());
|
||||
String publicationUrl = (String) eventDBObject.get(FieldNames.EVENT_PUBLICATION_URL.name());
|
||||
boolean isPublic = eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) != null ? (Boolean) eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) : false;
|
||||
Venue venue = loadVenue((DBObject) eventDBObject.get(FieldNames.VENUE.name()));
|
||||
Event result = new EventImpl(name, venue);
|
||||
Event result = new EventImpl(name, venue, publicationUrl, isPublic);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -10,6 +10,8 @@ public enum FieldNames {
|
||||
* TODO this will have to be renamed to REGATTA_NAME, but compatibility concerns keep us from doing so at the moment
|
||||
*/
|
||||
EVENT_NAME,
|
||||
EVENT_PUBLICATION_URL,
|
||||
EVENT_IS_PUBLIC,
|
||||
|
||||
REGATTA_NAME,
|
||||
REGATTA_BASE_NAME,
|
||||
@@ -45,10 +47,11 @@ public enum FieldNames {
|
||||
LEADERBOARD_NAME, LEADERBOARD_SUPPRESSED_COMPETITORS, LEADERBOARD_COLUMNS, LEADERBOARD_COLUMN_NAME, LEADERBOARD_COMPETITOR_DISPLAY_NAMES,
|
||||
LEADERBOARD_IS_MEDAL_RACE_COLUMN, LEADERBOARD_CARRIED_POINTS, LEADERBOARD_SCORE_CORRECTIONS, LEADERBOARD_DISCARDING_THRESHOLDS,
|
||||
LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON, LEADERBOARD_CORRECTED_SCORE, LEADERBOARD_SCORE_CORRECTION_TIMESTAMP,
|
||||
LEADERBOARD_SCORE_CORRECTION_COMMENT,
|
||||
LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_COLUMN_FACTORS,
|
||||
|
||||
// leaderboard group
|
||||
LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, LEADERBOARD_GROUP_LEADERBOARDS, LEADERBOARD_GROUP_OVERALL_LEADERBOARD,
|
||||
LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER,
|
||||
|
||||
// race ID to regatta name
|
||||
RACE_ID_AS_STRING;
|
||||
|
||||
+32
@@ -152,10 +152,22 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
// at least store the scoring scheme
|
||||
dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
|
||||
}
|
||||
storeColumnFactors(leaderboard, dbLeaderboard);
|
||||
storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
|
||||
leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false);
|
||||
}
|
||||
|
||||
private void storeColumnFactors(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
|
||||
DBObject raceColumnFactors = new BasicDBObject();
|
||||
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
||||
Double explicitFactor = raceColumn.getExplicitFactor();
|
||||
if (explicitFactor != null) {
|
||||
raceColumnFactors.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), explicitFactor);
|
||||
}
|
||||
}
|
||||
dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMN_FACTORS.name(), raceColumnFactors);
|
||||
}
|
||||
|
||||
private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) {
|
||||
dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
|
||||
}
|
||||
@@ -234,6 +246,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
}
|
||||
}
|
||||
if (!dbCorrectionForRace.isEmpty()) {
|
||||
// using the column name as the key for the score corrections requires re-writing the score corrections
|
||||
// of a meta-leaderboard if the name of one of its leaderboards changes
|
||||
dbScoreCorrections.put(raceColumn.getName(), dbCorrectionForRace);
|
||||
}
|
||||
}
|
||||
@@ -276,6 +290,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
BasicDBObject dbLeaderboardGroup = new BasicDBObject();
|
||||
dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
|
||||
dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription());
|
||||
dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder());
|
||||
final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard();
|
||||
if (overallLeaderboard != null) {
|
||||
BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName());
|
||||
@@ -325,11 +340,28 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
query.put(FieldNames.EVENT_NAME.name(), event.getName());
|
||||
DBObject eventDBObject = new BasicDBObject();
|
||||
eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName());
|
||||
eventDBObject.put(FieldNames.EVENT_PUBLICATION_URL.name(), event.getPublicationUrl());
|
||||
eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic());
|
||||
DBObject venueDBObject = getVenueAsDBObject(event.getVenue());
|
||||
eventDBObject.put(FieldNames.VENUE.name(), venueDBObject);
|
||||
eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renameEvent(String oldName, String newName) {
|
||||
DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
|
||||
BasicDBObject query = new BasicDBObject(FieldNames.EVENT_NAME.name(), oldName);
|
||||
BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.EVENT_NAME.name(), newName));
|
||||
eventCollection.update(query, renameUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEvent(String eventName) {
|
||||
DBCollection eventsCollection = database.getCollection(CollectionNames.EVENTS.name());
|
||||
BasicDBObject query = new BasicDBObject(FieldNames.EVENT_NAME.name(), eventName);
|
||||
eventsCollection.remove(query);
|
||||
}
|
||||
|
||||
private DBObject getVenueAsDBObject(Venue venue) {
|
||||
DBObject result = new BasicDBObject();
|
||||
result.put(FieldNames.VENUE_NAME.name(), venue.getName());
|
||||
|
||||
+5
-1
@@ -20,7 +20,11 @@ public interface MediaDB {
|
||||
|
||||
void deleteMediaTrack(String title);
|
||||
|
||||
void updateStartTime(String title, Date startTime);
|
||||
void updateTitle(String dbId, String title);
|
||||
|
||||
void updateUrl(String dbId, String url);
|
||||
|
||||
void updateStartTime(String dbId, Date startTime);
|
||||
|
||||
void updateDuration(String dbId, int durationInMillis);
|
||||
|
||||
|
||||
+22
@@ -103,6 +103,28 @@ public class MediaDBImpl implements MediaDB {
|
||||
getVideoCollection().remove(dbObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTitle(String dbId, String title) {
|
||||
BasicDBObject updateQuery = new BasicDBObject();
|
||||
updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId));
|
||||
|
||||
BasicDBObject update = new BasicDBObject();
|
||||
update.append("$set", new BasicDBObject(DbNames.Fields.MEDIA_TITLE.name(), title));
|
||||
|
||||
getVideoCollection().update(updateQuery, update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUrl(String dbId, String url) {
|
||||
BasicDBObject updateQuery = new BasicDBObject();
|
||||
updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId));
|
||||
|
||||
BasicDBObject update = new BasicDBObject();
|
||||
update.append("$set", new BasicDBObject(DbNames.Fields.MEDIA_URL.name(), url));
|
||||
|
||||
getVideoCollection().update(updateQuery, update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStartTime(String dbId, Date startTime) {
|
||||
BasicDBObject updateQuery = new BasicDBObject();
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.sap.sailing.domain.swisstimingadapter.persistence.StoreAndForward"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="3500 3501"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.swisstimingadapter.persistence"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties"/>
|
||||
</launchConfiguration>
|
||||
+34
-7
@@ -157,7 +157,7 @@ public class StoreAndForward implements Runnable {
|
||||
if (lastMessageCountRecord == null) {
|
||||
lastMessageCountCollection.insert(new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 0l));
|
||||
}
|
||||
return lastMessageCountRecord == null ? 0 : (Long) lastMessageCountRecord.get(FieldNames.LAST_MESSAGE_COUNT.name());
|
||||
return lastMessageCountRecord == null ? 0 : ((Number) lastMessageCountRecord.get(FieldNames.LAST_MESSAGE_COUNT.name())).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,13 +241,23 @@ public class StoreAndForward implements Runnable {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException, IOException {
|
||||
int listenPort = Integer.valueOf(args[0]);
|
||||
int clientPort = Integer.valueOf(args[1]);
|
||||
String hostname = null;
|
||||
int i=0;
|
||||
if (args[i].equals("-h")) {
|
||||
hostname = args[++i];
|
||||
i++;
|
||||
}
|
||||
int sailMasterPort = Integer.valueOf(args[i++]);
|
||||
int clientPort = Integer.valueOf(args[i++]);
|
||||
|
||||
MongoDBService mongoDBService = MongoDBService.INSTANCE;
|
||||
mongoDBService.setConfiguration(MongoDBConfiguration.getDefaultConfiguration());
|
||||
SwissTimingAdapterPersistence swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE;
|
||||
new StoreAndForward(listenPort, clientPort, SwissTimingFactory.INSTANCE, swissTimingAdapterPersistence, mongoDBService);
|
||||
if (hostname == null) {
|
||||
new StoreAndForward(sailMasterPort, clientPort, SwissTimingFactory.INSTANCE, swissTimingAdapterPersistence, mongoDBService);
|
||||
} else {
|
||||
new StoreAndForward(hostname, sailMasterPort, clientPort, SwissTimingFactory.INSTANCE, swissTimingAdapterPersistence, mongoDBService);
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -265,15 +275,32 @@ public class StoreAndForward implements Runnable {
|
||||
while (!stopped && messageAndOptionalSequenceNumber != null) {
|
||||
logger.fine("Received message: "+messageAndOptionalSequenceNumber.getA());
|
||||
DBObject newCountRecord = lastMessageCountCollection.findAndModify(emptyQuery, incrementLastMessageCountQuery);
|
||||
lastMessageCount = (Long) ((newCountRecord == null) ? 0 : newCountRecord.get(FieldNames.LAST_MESSAGE_COUNT.name()));
|
||||
lastMessageCount = ((newCountRecord == null) ? 0l :
|
||||
((Number) newCountRecord.get(FieldNames.LAST_MESSAGE_COUNT.name())).longValue());
|
||||
SailMasterMessage message = swissTimingFactory.createMessage(messageAndOptionalSequenceNumber.getA(), lastMessageCount);
|
||||
swissTimingAdapterPersistence.storeSailMasterMessage(message);
|
||||
synchronized (this) {
|
||||
for (OutputStream os : streamsToForwardTo) {
|
||||
for (OutputStream os : new ArrayList<OutputStream>(streamsToForwardTo)) {
|
||||
// write the sequence number of the message into the stream before actually writing the
|
||||
// SwissTiming message
|
||||
try {
|
||||
// TODO if forwarding to os doesn't work, e.g., because the socket was closed or the client died, remove os from streamsToForwardTo and the socket from socketsToForwardTo
|
||||
transceiver.sendMessage(message, os);
|
||||
transceiver.sendMessage(message, os);
|
||||
} catch (Throwable e) {
|
||||
int i=streamsToForwardTo.indexOf(os);
|
||||
try {
|
||||
os.close();
|
||||
} catch (Throwable t) {
|
||||
logger.throwing(StoreAndForward.class.getName(), "run", t);
|
||||
}
|
||||
streamsToForwardTo.remove(os);
|
||||
Socket s = socketsToForwardTo.remove(i);
|
||||
try {
|
||||
s.close();
|
||||
} catch (Throwable t) {
|
||||
logger.throwing(StoreAndForward.class.getName(), "run", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stopped) {
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
|
||||
List<SailMasterMessage> result = new ArrayList<SailMasterMessage>();
|
||||
for (DBObject o : results) {
|
||||
SailMasterMessage msg = swissTimingFactory.createMessage((String) o.get(FieldNames.MESSAGE_CONTENT.name()),
|
||||
(Long) o.get(FieldNames.MESSAGE_SEQUENCE_NUMBER.name()));
|
||||
((Number) o.get(FieldNames.MESSAGE_SEQUENCE_NUMBER.name())).longValue());
|
||||
result.add(msg);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
RAC|1|W4702;Women 470 Race 2STL|W4702|46|ARG 8;ARG;ARG 8|AUS 7;AUS;AUS 7|AUT 431;AUT;AUT 431|BRA 177;BRA;BRA 177|BRA 1187;BRA;BRA 1187|CAN 11;CAN;CAN 11|CAN 610;CAN;CAN 610|CHN 1211;CHN;CHN 1211|CHN 1261;CHN;CHN 1261|CHN 221;CHN;CHN 221|CHN 616;CHN;CHN 616|CRO 111;CRO;CRO 111|DEN 143;DEN;DEN 143|ESP 133;ESP;ESP 133|ESP 696;ESP;ESP 696|EST 20;EST;EST 20|FRA 12;FRA;FRA 12|FRA 4;FRA;FRA 4|FRA 9;FRA;FRA 9|GBR 812;GBR;GBR 812|GBR 831;GBR;GBR 831|GBR 841;GBR;GBR 841|GBR 847;GBR;GBR 847|GBR 850;GBR;GBR 850|GBR 855;GBR;GBR 855|GER 12;GER;GER 12|GER 21;GER;GER 21|GER 61;GER;GER 61|GER 66;GER;GER 66|GER 72;GER;GER 72|ISR 311;ISR;ISR 311|ITA 23;ITA;ITA 23|JPN 4151;JPN;JPN 4151|JPN 4321;JPN;JPN 4321|NED 11;NED;NED 11|NED 24;NED;NED 24|NZL 75;NZL;NZL 75|RUS 12;RUS;RUS 12|RUS 700;RUS;RUS 700|SLO 64;SLO;SLO 64|SUI 14;SUI;SUI 14|SWE 342;SWE;SWE 342|SWE 344;SWE;SWE 344|USA 1712;USA;USA 1712|USA 1736;USA;USA 1736|USA 1757;USA;USA 1757RPD|W4702|0|2011-06-06T17:54:19+02:00|||||61|SB;2;0;50.621933;-2.366140;0.2;;;90;;;;;|MB4;4;0;50.605008;-2.373398;0.2;;;17;;;;;|MB3;4;0;50.607335;-2.388345;0.8;;;69;;;;;|MB2;4;0;50.617526;-2.352416;0.2;;;220;;;;;|MB1;4;0;50.619644;-2.363547;0.4;;;81;;;;;|M7;5;0;50.621848;-2.366225;0.0;;;90;;;;;|M6;5;0;50.607561;-2.387959;0.8;;;108;;;;;|M5;5;0;50.605096;-2.373590;0.4;;;222;;;;;|M4;5;0;50.617094;-2.352122;1.0;;;32;;;;;|M3;5;0;50.620254;-2.366982;1.0;;;167;;;;;|M2;5;0;50.619848;-2.366420;0.6;;;311;;;;;|M1;5;0;50.617360;-2.352805;0.6;;;134;;;;;|FP;4;0;50.617521;-2.352463;0.2;;;180;;;;;|FB;4;0;50.619268;-2.354392;0.6;;;27;;;;;|SP;4;0;50.619847;-2.363828;0.4;;;322;;;;;|ARG 8;1;0;50.621380;-2.365032;0.8;;;225;;;;;|AUS 7;1;0;50.621263;-2.364862;0.8;;;243;;;;;|AUT 431;1;0;50.621965;-2.366063;2.1;;;90;;;;;|BRA 177;1;0;50.622014;-2.366027;0.4;;;180;;;;;|BRA 1187;1;0;50.621852;-2.365759;0.6;;;153;;;;;|CAN 11;1;0;50.621128;-2.364736;0.6;;;180;;;;;|CAN 610;1;0;50.621951;-2.365865;1.6;;;243;;;;;|CHN 1211;1;0;50.621690;-2.365462;0.8;;;225;;;;;|CHN 1261;1;0;50.621501;-2.365166;1.7;;;169;;;;;|CHN 221;1;0;50.620430;-2.363818;0.2;;;225;;;;;|CHN 616;1;0;50.620251;-2.363536;3.3;;;219;;;;;|CRO 111;1;0;50.621173;-2.364813;0.0;;;225;;;;;|DEN 143;1;0;50.620934;-2.364467;0.8;;;243;;;;;|ESP 133;1;0;50.620624;-2.364079;0.6;;;180;;;;;|ESP 696;1;0;50.621312;-2.364947;0.6;;;162;;;;;|EST 20;1;0;50.621785;-2.365646;1.6;;;108;;;;;|FRA 12;1;0;50.621659;-2.365364;1.9;;;180;;;;;|FRA 4;1;0;50.621713;-2.365547;0.8;;;225;;;;;|FRA 9;1;0;50.620723;-2.364178;0.8;;;225;;;;;|GBR 812;1;0;50.621868;-2.365812;0.6;;;132;;;;;|GBR 831;1;;;;;;;;;;;;|GBR 841;1;0;50.620964;-2.364544;1.7;;;174;;;;;|GBR 847;1;0;50.621773;-2.365637;3.5;;;191;;;;;|GBR 850;1;0;50.621475;-2.365135;2.3;;;273;;;;;|GBR 855;1;0;50.619879;-2.363592;1.0;;;296;;;;;|GER 12;1;0;50.621562;-2.365258;0.6;;;179;;;;;|GER 21;1;0;50.620871;-2.364329;1.4;;;193;;;;;|GER 61;1;0;50.620787;-2.364242;1.0;;;183;;;;;|GER 66;1;0;50.620484;-2.363859;1.7;;;200;;;;;|GER 72;1;0;50.621653;-2.365376;1.9;;;174;;;;;|ISR 311;1;0;50.622183;-2.366012;1.4;;;211;;;;;|ITA 23;1;0;50.620513;-2.363913;2.1;;;278;;;;;|JPN 4151;1;0;50.620337;-2.363733;1.9;;;208;;;;;|JPN 4321;1;0;50.621128;-2.364648;1.0;;;157;;;;;|NED 11;1;0;50.620606;-2.364038;0.4;;;272;;;;;|NED 24;1;;;;;;;;;;;;|NZL 75;1;0;50.621411;-2.365085;0.6;;;332;;;;;|RUS 12;1;0;50.622181;-2.366163;1.9;;;161;;;;;|RUS 700;1;0;50.622005;-2.365919;0.8;;;217;;;;;|SLO 64;1;0;50.621017;-2.364512;3.9;;;259;;;;;|SUI 14;1;0;50.621708;-2.365600;0.2;;;270;;;;;|SWE 342;1;0;50.620647;-2.364130;0.6;;;274;;;;;|SWE 344;1;0;50.621326;-2.364849;5.6;;;142;;;;;|USA 1712;1;0;50.621901;-2.365836;0.0;;;155;;;;;|USA 1736;1;0;50.620603;-2.363976;0.6;;;207;;;;;|USA 1757;1;0;50.621044;-2.364583;1.6;;;196;;;;;RPD|W4702|0|2011-06-06T17:54:20+02:00|||||61|SB;2;0;50.621933;-2.366140;0.2;;;90;;;;;|MB4;4;0;50.605010;-2.373399;0.2;;;0;;;;;|MB3;4;0;50.607337;-2.388341;0.8;;;63;;;;;|MB2;4;0;50.617524;-2.352420;0.2;;;225;;;;;|MB1;4;0;50.619644;-2.363544;0.4;;;90;;;;;|M7;5;0;50.621848;-2.366225;0.0;;;90;;;;;|M6;5;0;50.607561;-2.387960;0.8;;;90;;;;;|M5;5;0;50.605096;-2.373589;0.4;;;90;;;;;|M4;5;0;50.617096;-2.352124;1.0;;;0;;;;;|M3;5;0;50.620253;-2.366984;1.0;;;180;;;;;|M2;5;0;50.619848;-2.366419;0.6;;;90;;;;;|M1;5;0;50.617358;-2.352802;0.6;;;135;;;;;|FP;4;0;50.617519;-2.352463;0.2;;;180;;;;;|FB;4;0;50.619273;-2.354388;0.6;;;27;;;;;|SP;4;0;50.619846;-2.363826;0.4;;;90;;;;;|ARG 8;1;0;50.621375;-2.365036;0.8;;;213;;;;;|AUS 7;1;0;50.621261;-2.364871;0.6;;;250;;;;;|AUT 431;1;0;50.621959;-2.366061;1.0;;;170;;;;;|BRA 177;1;0;50.622012;-2.366027;0.6;;;181;;;;;|BRA 1187;1;0;50.621850;-2.365762;1.0;;;220;;;;;|CAN 11;1;0;50.621125;-2.364739;0.8;;;219;;;;;|CAN 610;1;0;50.621948;-2.365866;1.4;;;194;;;;;|CHN 1211;1;0;50.621688;-2.365462;0.6;;;175;;;;;|CHN 1261;1;0;50.621496;-2.365174;1.7;;;228;;;;;|CHN 221;1;0;50.620427;-2.363819;0.4;;;191;;;;;|CHN 616;1;0;50.620246;-2.363549;2.1;;;239;;;;;|CRO 111;1;0;50.621171;-2.364817;0.0;;;225;;;;;|DEN 143;1;0;50.620934;-2.364478;0.8;;;268;;;;;|ESP 133;1;0;50.62062Line truncated
|
||||
+4650
File diff suppressed because it is too large.
Load diff
+3
@@ -0,0 +1,3 @@
|
||||
RAC|1|W4702;Women 470 Race 2
|
||||
STL|W4702|46|ARG 8;ARG;ARG 8|AUS 7;AUS;AUS 7|AUT 431;AUT;AUT 431|BRA 177;BRA;BRA 177|BRA 1187;BRA;BRA 1187|CAN 11;CAN;CAN 11|CAN 610;CAN;CAN 610|CHN 1211;CHN;CHN 1211|CHN 1261;CHN;CHN 1261|CHN 221;CHN;CHN 221|CHN 616;CHN;CHN 616|CRO 111;CRO;CRO 111|DEN 143;DEN;DEN 143|ESP 133;ESP;ESP 133|ESP 696;ESP;ESP 696|EST 20;EST;EST 20|FRA 12;FRA;FRA 12|FRA 4;FRA;FRA 4|FRA 9;FRA;FRA 9|GBR 812;GBR;GBR 812|GBR 831;GBR;GBR 831|GBR 841;GBR;GBR 841|GBR 847;GBR;GBR 847|GBR 850;GBR;GBR 850|GBR 855;GBR;GBR 855|GER 12;GER;GER 12|GER 21;GER;GER 21|GER 61;GER;GER 61|GER 66;GER;GER 66|GER 72;GER;GER 72|ISR 311;ISR;ISR 311|ITA 23;ITA;ITA 23|JPN 4151;JPN;JPN 4151|JPN 4321;JPN;JPN 4321|NED 11;NED;NED 11|NED 24;NED;NED 24|NZL 75;NZL;NZL 75|RUS 12;RUS;RUS 12|RUS 700;RUS;RUS 700|SLO 64;SLO;SLO 64|SUI 14;SUI;SUI 14|SWE 342;SWE;SWE 342|SWE 344;SWE;SWE 344|USA 1712;USA;USA 1712|USA 1736;USA;USA 1736|USA 1757;USA;USA 1757
|
||||
CCG|W4702|7|0;StartGate;M1;M7|1;Buoy6;M6|2;Buoy5;M5|3;LeeGate;M2;M3|4;Buoy6;M6|5;Buoy5;M5|6;FinishGate;M1;M4
|
||||
+16
-3
@@ -17,14 +17,15 @@ import java.util.List;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.base.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Competitor;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Mark;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Race;
|
||||
import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Mark.MarkType;
|
||||
import com.sap.sailing.domain.swisstimingadapter.classes.messages.CAMMessage;
|
||||
import com.sap.sailing.domain.swisstimingadapter.classes.messages.CCGMessage;
|
||||
import com.sap.sailing.domain.swisstimingadapter.classes.messages.ClockAtMarkElement;
|
||||
@@ -193,8 +194,20 @@ public class MessageFileServiceImpl implements MessageFileService {
|
||||
List<Mark> marks = new ArrayList<Mark>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String[] markDetails = message.getSections()[3 + i].split(";");
|
||||
marks.add(new MarkImpl(markDetails[1], Integer.valueOf(markDetails[0]), Arrays.asList(markDetails).subList(
|
||||
2, markDetails.length)));
|
||||
MarkType markType = null;
|
||||
final int devicesNamesStartIndex;
|
||||
if (message.getSections()[3+i].split(";", -1).length == 5) {
|
||||
// this is the SailMaster protocol version 1.0 (May 2012) or later (see bug 1000), containing
|
||||
// a MarkType specification before the two tracker IDs:
|
||||
int markTypeIndex = Integer.valueOf(markDetails[2]);
|
||||
markType = MarkType.values()[markTypeIndex];
|
||||
devicesNamesStartIndex = 3;
|
||||
} else {
|
||||
devicesNamesStartIndex = 2;
|
||||
}
|
||||
marks.add(new MarkImpl(markDetails[1], Integer.valueOf(markDetails[0]),
|
||||
Arrays.asList(markDetails).subList(devicesNamesStartIndex, markDetails.length),
|
||||
markType));
|
||||
}
|
||||
return new CCGMessage(raceId, marks);
|
||||
}
|
||||
|
||||
+18
-4
@@ -89,9 +89,10 @@ public class EndToEndListeningStoreAndFowardTest {
|
||||
new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 0l),
|
||||
/* upsert */true, /* multi */false);
|
||||
// important: construct a new domain factory each time to make sure the competitor cache starts out empty
|
||||
final com.sap.sailing.domain.base.impl.DomainFactoryImpl baseDomainFactory = new com.sap.sailing.domain.base.impl.DomainFactoryImpl();
|
||||
racingEventService = new RacingEventServiceImpl(mongoDBService, SwissTimingFactory.INSTANCE,
|
||||
new DomainFactoryImpl(new com.sap.sailing.domain.base.impl.DomainFactoryImpl()),
|
||||
com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE);
|
||||
new DomainFactoryImpl(baseDomainFactory),
|
||||
new com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl(baseDomainFactory));
|
||||
raceHandles = new ArrayList<RacesHandle>();
|
||||
}
|
||||
|
||||
@@ -140,7 +141,19 @@ public class EndToEndListeningStoreAndFowardTest {
|
||||
String scriptName1 = "/SailMasterDataInterfaceRACandSTL.txt";
|
||||
String scriptName2 = "/SailMasterDataInterface-ExampleAsText.txt";
|
||||
setUpUsingScript(racesToTrack, scriptName1, scriptName2);
|
||||
coreOfTestLongRaceLog();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongRaceLogNewVersion() throws Exception {
|
||||
String[] racesToTrack = new String[] { "W4702" };
|
||||
String scriptName1 = "/W4702RACandSTLandCCG.txt";
|
||||
String scriptName2 = "/W4702AsText.txt";
|
||||
setUpUsingScript(racesToTrack, scriptName1, scriptName2);
|
||||
coreOfTestLongRaceLog();
|
||||
}
|
||||
|
||||
private void coreOfTestLongRaceLog() {
|
||||
Set<TrackedRace> allTrackedRaces = new HashSet<TrackedRace>();
|
||||
Iterable<Regatta> allRegattas = racingEventService.getAllRegattas();
|
||||
for (Regatta regatta : allRegattas) {
|
||||
@@ -163,7 +176,8 @@ public class EndToEndListeningStoreAndFowardTest {
|
||||
assertEquals(7, Util.size(race.getCourse().getWaypoints()));
|
||||
assertEquals(6, Util.size(race.getCourse().getLegs()));
|
||||
for (Competitor competitor : race.getCompetitors()) {
|
||||
if (!competitor.getName().equals("Competitor 35") && !competitor.getName().equals("Competitor 20")) {
|
||||
if (!competitor.getName().equals("Competitor 35") && !competitor.getName().equals("Competitor 20")
|
||||
&& !competitor.getName().equals("GBR 831") && !competitor.getName().equals("NED 24")) {
|
||||
final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
|
||||
track.lockForRead();
|
||||
try {
|
||||
@@ -227,7 +241,7 @@ public class EndToEndListeningStoreAndFowardTest {
|
||||
assertEquals(2, Util.size(race.getCourse().getLegs()));
|
||||
for (Competitor competitor : race.getCompetitors()) {
|
||||
if (!competitor.getName().equals("NED 24") && !competitor.getName().equals("Competitor 35")
|
||||
&& !competitor.getName().equals("Competitor 20")) {
|
||||
&& !competitor.getName().equals("Competitor 20") && !competitor.getName().equals("GBR 831")) {
|
||||
final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
|
||||
track.lockForRead();
|
||||
try {
|
||||
|
||||
+62
-4
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.swisstimingadapter.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -14,6 +15,7 @@ import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.RegattaImpl;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Competitor;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Course;
|
||||
@@ -30,6 +32,62 @@ import com.sap.sailing.server.impl.RacingEventServiceImpl;
|
||||
import difflib.PatchFailedException;
|
||||
|
||||
public class SimpleDomainFactoryTest {
|
||||
/**
|
||||
* Tests that an unknown boat class encoded in a regular "SA" format race ID is returned as the unknown boat class
|
||||
* instead of null.
|
||||
*/
|
||||
@Test
|
||||
public void testUnknownBoatClassInValidRaceID() {
|
||||
DomainFactory domainFactory = DomainFactory.INSTANCE;
|
||||
final String raceID = "SAX920103";
|
||||
RaceDefinition raceDefinition = domainFactory.createRaceDefinition(domainFactory.getOrCreateRegatta(raceID,
|
||||
new RacingEventServiceImpl()),
|
||||
new Race() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRaceID() {
|
||||
return raceID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getStartTime() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartTime(TimePoint timePoint) {
|
||||
}
|
||||
|
||||
}, new StartList() {
|
||||
@Override
|
||||
public String getRaceID() {
|
||||
return raceID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Competitor> getCompetitors() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}, new Course() {
|
||||
@Override
|
||||
public String getRaceID() {
|
||||
return raceID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Mark> getMarks() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
});
|
||||
assertNotNull(raceDefinition.getBoatClass());
|
||||
assertEquals("Unknown", raceDefinition.getBoatClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCourseConfigForBuoy() throws PatchFailedException {
|
||||
DomainFactory domainFactory = DomainFactory.INSTANCE;
|
||||
@@ -38,8 +96,8 @@ public class SimpleDomainFactoryTest {
|
||||
Race race = new RaceImpl("1234", "Race 1234");
|
||||
Iterable<Competitor> competitors = Collections.emptyList();
|
||||
StartList startList = new StartListImpl("1234", competitors);
|
||||
Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"));
|
||||
Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4"));
|
||||
Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"), /* markType */ null);
|
||||
Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4"), /* markType */ null);
|
||||
List<Mark> marks = Arrays.asList(mark1, mark2);
|
||||
Course course = new CourseImpl("1234", marks);
|
||||
RaceDefinition raceDefinition = domainFactory.createRaceDefinition(regatta, race, startList, course);
|
||||
@@ -63,8 +121,8 @@ public class SimpleDomainFactoryTest {
|
||||
Race race = new RaceImpl("1234", "Race 1234");
|
||||
Iterable<Competitor> competitors = Collections.emptyList();
|
||||
StartList startList = new StartListImpl("1234", competitors);
|
||||
Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"));
|
||||
Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4"));
|
||||
Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"), /* markType */ null);
|
||||
Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4"), /* markType */ null);
|
||||
List<Mark> marks = Arrays.asList(mark1, mark2);
|
||||
Course course = new CourseImpl("1234", marks);
|
||||
RaceDefinition raceDefinition = domainFactory.createRaceDefinition(regatta, race, startList, course);
|
||||
|
||||
+2
-2
@@ -269,8 +269,8 @@ public class EditCCG extends javax.swing.JDialog {
|
||||
|
||||
private class MarkWithDesc extends MarkImpl {
|
||||
|
||||
public MarkWithDesc(String description, int index, Iterable<String> devices ){
|
||||
super(description, index, devices);
|
||||
public MarkWithDesc(String description, int index, Iterable<String> devices) {
|
||||
super(description, index, devices, /* markType */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
@@ -34,4 +34,10 @@ public interface Fix {
|
||||
Distance getDistanceToLeader();
|
||||
|
||||
Distance getDistanceToNextMark();
|
||||
|
||||
/**
|
||||
* Obtains a disqualification reason which is one of { None, BFD, DNS, DNF, DNC, DSQ, OCS, RAF, SCP, ZFP }. May be
|
||||
* <code>null</code>.
|
||||
*/
|
||||
String getBoatIRM();
|
||||
}
|
||||
+4
@@ -1,7 +1,11 @@
|
||||
package com.sap.sailing.domain.swisstimingadapter;
|
||||
|
||||
public interface Mark extends WithDescription {
|
||||
public enum MarkType { MANUAL, LINE_CROSSING, BUOY_ROUNDING };
|
||||
|
||||
int getIndex();
|
||||
|
||||
Iterable<String> getDevices();
|
||||
|
||||
MarkType getMarkType();
|
||||
}
|
||||
+2
-2
@@ -54,12 +54,12 @@ public enum MessageType {
|
||||
/**
|
||||
* Weather Information
|
||||
*/
|
||||
WEA(/* isRaceSpecific */ false),
|
||||
WEA(/* isRaceSpecific */ true),
|
||||
|
||||
/**
|
||||
* Wind Data
|
||||
*/
|
||||
WND(/* isRaceSpecific */ false),
|
||||
WND(/* isRaceSpecific */ true),
|
||||
|
||||
/**
|
||||
* Version
|
||||
|
||||
+39
-29
@@ -70,15 +70,15 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
controlPointCache = new HashMap<Iterable<String>, ControlPoint>();
|
||||
olympicClassesByID = new HashMap<String, BoatClass>();
|
||||
/*
|
||||
SAM102000 Men's Windsurfer = Windsufer Männer RS:X
|
||||
SAM102000 Men's Windsurfer = Windsufer M�nner RS:X
|
||||
SAW102000 Women's Windsurfer = Windsurfer Damen RS:X
|
||||
SAM004000 Men's One Person Dinghy = Laser Männer
|
||||
SAM004000 Men's One Person Dinghy = Laser M�nner
|
||||
SAW103000 Women's One Person Dinghy = Laser Damen Laser Radial
|
||||
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy Männer
|
||||
SAM005000 Men's Two Person Dinghy = 470er Männer
|
||||
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy M�nner
|
||||
SAM005000 Men's Two Person Dinghy = 470er M�nner
|
||||
SAW005000 Women's Two Person Dinghy = 470er Damen
|
||||
SAM009000 Men's Skiff = 49er Männer
|
||||
SAM007000 Men's Keelboat = Starboot Männer
|
||||
SAM009000 Men's Skiff = 49er M�nner
|
||||
SAM007000 Men's Keelboat = Starboot M�nner
|
||||
SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified)
|
||||
*/
|
||||
olympicClassesByID.put("102", baseDomainFactory.getOrCreateBoatClass("RS:X", /* typicallyStartsUpwind */ true));
|
||||
@@ -104,7 +104,7 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
result = raceIDToRegattaCache.get(raceID);
|
||||
}
|
||||
if (result == null) {
|
||||
result = new RegattaImpl(raceID, null, trackedRegattaRegistry,
|
||||
result = new RegattaImpl(raceID, getOrCreateBoatClassFromRaceID(raceID), trackedRegattaRegistry,
|
||||
com.sap.sailing.domain.base.DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
logger.info("Created regatta "+result.getName()+" ("+result.hashCode()+")");
|
||||
raceIDToRegattaCache.put(raceID, result);
|
||||
@@ -128,7 +128,7 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
/* dateOfBirth */ null, teamMemberName.trim()));
|
||||
}
|
||||
Team team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null);
|
||||
result = baseDomainFactory.createCompetitor(competitor.getBoatID(), competitor.getName(), team, boat);
|
||||
result = baseDomainFactory.getOrCreateCompetitor(competitor.getBoatID(), competitor.getName(), team, boat);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -148,20 +148,24 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
private BoatClass getOrCreateBoatClassFromRaceID(String raceID) {
|
||||
BoatClass result;
|
||||
/*
|
||||
SAM102000 Men's Windsurfer = Windsufer Männer RS:X
|
||||
SAM102000 Men's Windsurfer = Windsufer M�nner RS:X
|
||||
SAW102000 Women's Windsurfer = Windsurfer Damen RS:X
|
||||
SAM004000 Men's One Person Dinghy = Laser Männer
|
||||
SAM004000 Men's One Person Dinghy = Laser M�nner
|
||||
SAW103000 Women's One Person Dinghy = Laser Damen Laser Radial
|
||||
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy Männer
|
||||
SAM005000 Men's Two Person Dinghy = 470er Männer
|
||||
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy M�nner
|
||||
SAM005000 Men's Two Person Dinghy = 470er M�nner
|
||||
SAW005000 Women's Two Person Dinghy = 470er Damen
|
||||
SAM009000 Men's Skiff = 49er Männer
|
||||
SAM007000 Men's Keelboat = Starboot Männer
|
||||
SAM009000 Men's Skiff = 49er M�nner
|
||||
SAM007000 Men's Keelboat = Starboot M�nner
|
||||
SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified)
|
||||
*/
|
||||
if (raceID.startsWith("SA") && raceID.length() == 9) {
|
||||
String classID = raceID.substring(3, 6);
|
||||
result = olympicClassesByID.get(classID);
|
||||
if (result == null) {
|
||||
// unknown code
|
||||
result = unknownBoatClass;
|
||||
}
|
||||
} else {
|
||||
result = unknownBoatClass;
|
||||
}
|
||||
@@ -189,22 +193,27 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
}
|
||||
|
||||
private ControlPoint getOrCreateControlPoint(Iterable<String> devices) {
|
||||
ControlPoint result = controlPointCache.get(devices);
|
||||
if (result == null) {
|
||||
switch (Util.size(devices)) {
|
||||
case 1:
|
||||
result = getOrCreateBuoy(devices.iterator().next());
|
||||
break;
|
||||
case 2:
|
||||
Iterator<String> buoyNameIter = devices.iterator();
|
||||
String left = buoyNameIter.next();
|
||||
String right = buoyNameIter.next();
|
||||
result = baseDomainFactory.createGate(getOrCreateBuoy(left), getOrCreateBuoy(right), left+"/"+right);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Don't know how to handle control points with number of devices neither 1 nor 2. Was "+Util.size(devices));
|
||||
ControlPoint result;
|
||||
synchronized (controlPointCache) {
|
||||
result = controlPointCache.get(devices);
|
||||
if (result == null) {
|
||||
switch (Util.size(devices)) {
|
||||
case 1:
|
||||
result = getOrCreateBuoy(devices.iterator().next());
|
||||
break;
|
||||
case 2:
|
||||
Iterator<String> buoyNameIter = devices.iterator();
|
||||
String left = buoyNameIter.next();
|
||||
String right = buoyNameIter.next();
|
||||
result = baseDomainFactory.createGate(getOrCreateBuoy(left), getOrCreateBuoy(right), left + "/" + right);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException(
|
||||
"Don't know how to handle control points with number of devices neither 1 nor 2. Was "
|
||||
+ Util.size(devices));
|
||||
}
|
||||
controlPointCache.put(devices, result);
|
||||
}
|
||||
controlPointCache.put(devices, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -230,6 +239,7 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
public void updateCourseWaypoints(com.sap.sailing.domain.base.Course courseToUpdate, Iterable<Mark> marks) throws PatchFailedException {
|
||||
List<com.sap.sailing.domain.base.ControlPoint> newDomainControlPoints = new ArrayList<ControlPoint>();
|
||||
for (Mark mark : marks) {
|
||||
// TODO bug 1043: propagate the mark names to the waypoint names
|
||||
com.sap.sailing.domain.base.ControlPoint domainControlPoint = getOrCreateControlPoint(mark.getDevices());
|
||||
newDomainControlPoints.add(domainControlPoint);
|
||||
}
|
||||
|
||||
+12
-2
@@ -19,11 +19,15 @@ public class FixImpl implements Fix {
|
||||
private final Speed velocityMadeGood;
|
||||
private final Distance distanceToLeader;
|
||||
private final Distance distanceToNextMark;
|
||||
|
||||
|
||||
/**
|
||||
* A disqualification reason, such as "DNC" or "DNS". May be <code>null</code>.
|
||||
*/
|
||||
private final String boatIRM;
|
||||
|
||||
public FixImpl(String boatID, TrackerType trackerType, Long ageOfDataInMilliseconds, Position position,
|
||||
SpeedWithBearing speed, Integer nextMarkIndex, Integer rank, Speed averageSpeedOverGroundPerLeg,
|
||||
Speed velocityMadeGood, Distance distanceToLeader, Distance distanceToNextMark) {
|
||||
Speed velocityMadeGood, Distance distanceToLeader, Distance distanceToNextMark, String boatIRM) {
|
||||
super();
|
||||
this.boatID = boatID;
|
||||
this.trackerType = trackerType;
|
||||
@@ -36,6 +40,12 @@ public class FixImpl implements Fix {
|
||||
this.velocityMadeGood = velocityMadeGood;
|
||||
this.distanceToLeader = distanceToLeader;
|
||||
this.distanceToNextMark = distanceToNextMark;
|
||||
this.boatIRM = boatIRM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBoatIRM() {
|
||||
return boatIRM;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+8
-2
@@ -8,12 +8,13 @@ public class MarkImpl implements Mark {
|
||||
private final String description;
|
||||
private final int index;
|
||||
private final Iterable<String> devices;
|
||||
private final MarkType markType;
|
||||
|
||||
|
||||
public MarkImpl(String description, int index, Iterable<String> devices) {
|
||||
public MarkImpl(String description, int index, Iterable<String> devices, MarkType markType) {
|
||||
super();
|
||||
this.description = description;
|
||||
this.index = index;
|
||||
this.markType = markType;
|
||||
ArrayList<String> l = new ArrayList<String>();
|
||||
for (String device : devices) {
|
||||
l.add(device);
|
||||
@@ -21,6 +22,11 @@ public class MarkImpl implements Mark {
|
||||
this.devices = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarkType getMarkType() {
|
||||
return markType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
|
||||
+58
-80
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.base.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
@@ -36,12 +35,14 @@ import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.common.impl.Util.Triple;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Competitor;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Course;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Fix;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Mark;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Mark.MarkType;
|
||||
import com.sap.sailing.domain.swisstimingadapter.MessageType;
|
||||
import com.sap.sailing.domain.swisstimingadapter.Race;
|
||||
import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader;
|
||||
@@ -336,7 +337,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
parseHHMMSSToMilliseconds(details[2]);
|
||||
markIndicesRanksAndTimesSinceStartInMilliseconds.add(new Triple<Integer, Integer, Long>(markIndex, rank, timeSinceStartInMilliseconds));
|
||||
}
|
||||
for (SailMasterListener listener : listeners) {
|
||||
for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
|
||||
try {
|
||||
listener.receivedTimingData(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds);
|
||||
} catch (Throwable t) {
|
||||
@@ -344,22 +345,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersTMD", t);
|
||||
}
|
||||
}
|
||||
Set<SailMasterListener> raceSpecificListenerSet = raceSpecificListeners.get(message.getRaceID());
|
||||
if (raceSpecificListenerSet != null) {
|
||||
for (SailMasterListener listener : raceSpecificListenerSet) {
|
||||
try {
|
||||
listener.receivedTimingData(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds);
|
||||
} catch (Throwable t) {
|
||||
logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+t.getMessage());
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersTMD", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListenersCAM(SailMasterMessage message) throws ParseException {
|
||||
List<Triple<Integer, TimePoint, String>> clockAtMarkResults = parseClockAtMarkMessage(message);
|
||||
for (SailMasterListener listener : listeners) {
|
||||
for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
|
||||
try {
|
||||
listener.receivedClockAtMark(message.getSections()[1], clockAtMarkResults);
|
||||
} catch (Throwable t) {
|
||||
@@ -367,22 +357,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCAM", t);
|
||||
}
|
||||
}
|
||||
Set<SailMasterListener> raceSpecificListenerSet = raceSpecificListeners.get(message.getRaceID());
|
||||
if (raceSpecificListenerSet != null) {
|
||||
for (SailMasterListener listener : raceSpecificListenerSet) {
|
||||
try {
|
||||
listener.receivedClockAtMark(message.getSections()[1], clockAtMarkResults);
|
||||
} catch (Throwable t) {
|
||||
logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+t.getMessage());
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCAM", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListenersSTL(SailMasterMessage message) {
|
||||
StartList startListMessage = parseStartListMessage(message);
|
||||
for (SailMasterListener listener : listeners) {
|
||||
for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
|
||||
try {
|
||||
listener.receivedStartList(message.getSections()[1], startListMessage);
|
||||
} catch (Throwable t) {
|
||||
@@ -390,22 +369,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersSTL", t);
|
||||
}
|
||||
}
|
||||
Set<SailMasterListener> raceSpecificListenerSet = raceSpecificListeners.get(message.getRaceID());
|
||||
if (raceSpecificListenerSet != null) {
|
||||
for (SailMasterListener listener : raceSpecificListenerSet) {
|
||||
try {
|
||||
listener.receivedStartList(message.getSections()[1], startListMessage);
|
||||
} catch (Throwable t) {
|
||||
logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+t.getMessage());
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersSTL", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListenersCCG(SailMasterMessage message) {
|
||||
Course course = parseCourseConfigurationMessage(message);
|
||||
for (SailMasterListener listener : listeners) {
|
||||
for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
|
||||
try {
|
||||
listener.receivedCourseConfiguration(message.getSections()[1], course);
|
||||
} catch (Throwable t) {
|
||||
@@ -413,16 +381,6 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCCG", t);
|
||||
}
|
||||
}
|
||||
Set<SailMasterListener> raceSpecificListenerSet = raceSpecificListeners.get(message.getRaceID());
|
||||
if (raceSpecificListenerSet != null) {
|
||||
for (SailMasterListener listener : raceSpecificListenerSet) {
|
||||
try {
|
||||
listener.receivedCourseConfiguration(message.getSections()[1], course);
|
||||
} catch (Throwable t) {
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCCG", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListenersRAC(SailMasterMessage message) {
|
||||
@@ -458,42 +416,52 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
Collection<Fix> fixes = new ArrayList<Fix>();
|
||||
for (int i=0; i<count; i++) {
|
||||
int fixDetailIndex = 0;
|
||||
String[] fixSections = sections[9+i].split(";");
|
||||
final String[] fixSections = sections[9+i].split(";");
|
||||
final boolean postVersion1_0 = sections[9+i].split(";", -1).length >= 14;
|
||||
if (fixSections.length > 2) {
|
||||
String boatID = fixSections[fixDetailIndex++];
|
||||
TrackerType trackerType = TrackerType.values()[Integer.valueOf(fixSections[fixDetailIndex++])];
|
||||
Long ageOfDataInMilliseconds = 1000l * Long.valueOf(fixSections[fixDetailIndex++]);
|
||||
Position position = new DegreePosition(Double.valueOf(fixSections[fixDetailIndex++]),
|
||||
final String boatID = fixSections[fixDetailIndex++];
|
||||
final TrackerType trackerType = TrackerType.values()[Integer.valueOf(fixSections[fixDetailIndex++])];
|
||||
final Long ageOfDataInMilliseconds = 1000l * Long.valueOf(fixSections[fixDetailIndex++]);
|
||||
final Position position = new DegreePosition(Double.valueOf(fixSections[fixDetailIndex++]),
|
||||
Double.valueOf(fixSections[fixDetailIndex++]));
|
||||
Double speedOverGroundInKnots = Double.valueOf(fixSections[fixDetailIndex++]);
|
||||
Speed averageSpeedOverGround = fixSections[fixDetailIndex].trim().length() == 0 ? null
|
||||
: new KnotSpeedImpl(Double.valueOf(fixSections[fixDetailIndex]));
|
||||
fixDetailIndex++;
|
||||
Speed velocityMadeGood = fixSections[fixDetailIndex].trim().length() == 0 ? null : new KnotSpeedImpl(
|
||||
Double.valueOf(fixSections[fixDetailIndex]));
|
||||
fixDetailIndex++;
|
||||
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(speedOverGroundInKnots, new DegreeBearingImpl(
|
||||
Double.valueOf(fixSections[fixDetailIndex++])));
|
||||
Integer nextMarkIndex = fixSections.length <= fixDetailIndex
|
||||
final Double speedOverGroundInKnots = Double.valueOf(fixSections[fixDetailIndex++]);
|
||||
final int alsIndex = postVersion1_0 ? fixDetailIndex+1 : fixDetailIndex;
|
||||
final int vmgIndex = postVersion1_0 ? fixDetailIndex : fixDetailIndex+1;
|
||||
final Speed averageSpeedOverGround = fixSections[alsIndex].trim().length() == 0 ? null
|
||||
: new KnotSpeedImpl(Double.valueOf(fixSections[alsIndex]));
|
||||
final Speed velocityMadeGood = fixSections[vmgIndex].trim().length() == 0 ? null : new KnotSpeedImpl(
|
||||
Double.valueOf(fixSections[vmgIndex]));
|
||||
fixDetailIndex += 2;
|
||||
final DegreeBearingImpl cog = new DegreeBearingImpl(
|
||||
Double.valueOf(fixSections[fixDetailIndex++]));
|
||||
final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(speedOverGroundInKnots, cog);
|
||||
final Integer nextMarkIndex = fixSections.length <= fixDetailIndex
|
||||
|| fixSections[fixDetailIndex].trim().length() == 0 ? null : Integer
|
||||
.valueOf(fixSections[fixDetailIndex]);
|
||||
fixDetailIndex++;
|
||||
Integer rank = fixSections.length <= fixDetailIndex || fixSections[fixDetailIndex].trim().length() == 0 ? null
|
||||
final Integer rank = fixSections.length <= fixDetailIndex || fixSections[fixDetailIndex].trim().length() == 0 ? null
|
||||
: Integer.valueOf(fixSections[fixDetailIndex]);
|
||||
fixDetailIndex++;
|
||||
Distance distanceToLeader = fixSections.length <= fixDetailIndex
|
||||
final Distance distanceToLeader = fixSections.length <= fixDetailIndex
|
||||
|| fixSections[fixDetailIndex].trim().length() == 0 ? null : new MeterDistance(
|
||||
Double.valueOf(fixSections[fixDetailIndex]));
|
||||
fixDetailIndex++;
|
||||
Distance distanceToNextMark = fixSections.length <= fixDetailIndex
|
||||
final Distance distanceToNextMark = fixSections.length <= fixDetailIndex
|
||||
|| fixSections[fixDetailIndex].trim().length() == 0 ? null : new MeterDistance(
|
||||
Double.valueOf(fixSections[fixDetailIndex]));
|
||||
fixDetailIndex++;
|
||||
final String boatIRM; // the "disqualification" or "MaxPointReason"
|
||||
if (postVersion1_0 && fixSections.length > fixDetailIndex) {
|
||||
boatIRM = fixSections[fixDetailIndex++];
|
||||
} else {
|
||||
boatIRM = null;
|
||||
}
|
||||
fixes.add(new FixImpl(boatID, trackerType, ageOfDataInMilliseconds, position, speed, nextMarkIndex,
|
||||
rank, averageSpeedOverGround, velocityMadeGood, distanceToLeader, distanceToNextMark));
|
||||
rank, averageSpeedOverGround, velocityMadeGood, distanceToLeader, distanceToNextMark, boatIRM));
|
||||
}
|
||||
}
|
||||
for (SailMasterListener listener : listeners) {
|
||||
Set<SailMasterListener> allListeners = getGeneralAndRaceSpecificListeners(message.getRaceID());
|
||||
for (SailMasterListener listener : allListeners) {
|
||||
try {
|
||||
listener.receivedRacePositionData(raceID, status, timePoint, startTimeEstimatedStartTime, millisecondsSinceRaceStart,
|
||||
nextMarkIndexForLeader, distanceToNextMarkForLeader, fixes);
|
||||
@@ -502,18 +470,15 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersRPD", t);
|
||||
}
|
||||
}
|
||||
Set<SailMasterListener> raceSpecificListenerSet = raceSpecificListeners.get(message.getRaceID());
|
||||
if (raceSpecificListenerSet != null) {
|
||||
for (SailMasterListener listener : raceSpecificListenerSet) {
|
||||
try {
|
||||
listener.receivedRacePositionData(raceID, status, timePoint, startTimeEstimatedStartTime, millisecondsSinceRaceStart,
|
||||
nextMarkIndexForLeader, distanceToNextMarkForLeader, fixes);
|
||||
} catch (Throwable t) {
|
||||
logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+t.getMessage());
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersRPD", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<SailMasterListener> getGeneralAndRaceSpecificListeners(String raceID) {
|
||||
Set<SailMasterListener> allListeners = new HashSet<SailMasterListener>(listeners);
|
||||
Set<SailMasterListener> listenersForThisRace = raceSpecificListeners.get(raceID);
|
||||
if (listenersForThisRace != null) {
|
||||
allListeners.addAll(listenersForThisRace);
|
||||
}
|
||||
return allListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -630,7 +595,20 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
List<Mark> marks = new ArrayList<Mark>();
|
||||
for (int i=0; i<count; i++) {
|
||||
String[] markDetails = courseConfigurationMessage.getSections()[3+i].split(";");
|
||||
marks.add(new MarkImpl(markDetails[1], Integer.valueOf(markDetails[0]), Arrays.asList(markDetails).subList(2, markDetails.length)));
|
||||
MarkType markType = null;
|
||||
final int devicesNamesStartIndex;
|
||||
if (courseConfigurationMessage.getSections()[3+i].split(";", -1).length == 5) {
|
||||
// this is the SailMaster protocol version 1.0 (May 2012) or later (see bug 1000), containing
|
||||
// a MarkType specification before the two tracker IDs:
|
||||
int markTypeIndex = Integer.valueOf(markDetails[2]);
|
||||
markType = MarkType.values()[markTypeIndex];
|
||||
devicesNamesStartIndex = 3;
|
||||
} else {
|
||||
devicesNamesStartIndex = 2;
|
||||
}
|
||||
marks.add(new MarkImpl(markDetails[1], Integer.valueOf(markDetails[0]),
|
||||
Arrays.asList(markDetails).subList(devicesNamesStartIndex, markDetails.length),
|
||||
markType));
|
||||
}
|
||||
return new CourseImpl(courseConfigurationMessage.getSections()[1], marks);
|
||||
}
|
||||
|
||||
-2
@@ -15,9 +15,7 @@ public class SailMasterLiveSimulatorConnectorImpl extends SailMasterConnectorImp
|
||||
|
||||
public SailMasterLiveSimulatorConnectorImpl(String host, int port, RaceSpecificMessageLoader messageLoader, boolean canSendRequests) throws InterruptedException {
|
||||
super(host, port, messageLoader, canSendRequests);
|
||||
|
||||
bufferedMessageList = Collections.synchronizedList(new ArrayList<SailMasterMessage>());
|
||||
|
||||
Thread messageDeliveryThread = new Thread("SailMasterLiveSimulatorConnector") {
|
||||
public void run() {
|
||||
while(true) {
|
||||
|
||||
+5
-3
@@ -247,13 +247,14 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
public void receivedClockAtMark(String raceID,
|
||||
List<Triple<Integer, TimePoint, String>> markIndicesTimePointsAndBoatIDs) {
|
||||
// Ignored because it's covered by TMD. Mail from Kai Hahndorf of 2011-11-15T12:42:00Z:
|
||||
// "Die TMD werden immer gesendet. Das CAM Protokoll ist nur für unsere TV-Grafik wichtig, da damit die Rückstandsuhr gestartet wird."
|
||||
// "Die TMD werden immer gesendet. Das CAM Protokoll ist nur fuer unsere TV-Grafik wichtig, da damit die Rueckstandsuhr gestartet wird."
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedStartList(String raceID, StartList startList) {
|
||||
StartList oldStartList = this.startList;
|
||||
this.startList = startList;
|
||||
if (course != null) {
|
||||
if (oldStartList == null && course != null) {
|
||||
createRaceDefinition(raceID);
|
||||
}
|
||||
}
|
||||
@@ -291,9 +292,10 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
|
||||
@Override
|
||||
public void receivedCourseConfiguration(String raceID, Course course) {
|
||||
Course oldCourse = this.course;
|
||||
this.course = course;
|
||||
if (trackedRace == null) {
|
||||
if (startList != null) {
|
||||
if (oldCourse == null && startList != null) {
|
||||
createRaceDefinition(raceID);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@ Bundle-Version: 1.0.0.qualifier
|
||||
Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||
Require-Bundle: com.sap.sailing.domain,
|
||||
com.tractrac.clientmodule,
|
||||
com.tractrac.clientmodule;bundle-version="2.0.0",
|
||||
com.sap.sailing.domain.tractracadapter,
|
||||
com.googlecode.java-diff-utils;bundle-version="1.3.0",
|
||||
com.sap.sailing.domain.common
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.test"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=10.18.206.73 -ea -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=10.18.22.156 -ea -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties"/>
|
||||
</launchConfiguration>
|
||||
+1
-1
@@ -17,10 +17,10 @@ import org.junit.Test;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.impl.DouglasPeucker;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.base.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
|
||||
+14
-10
@@ -22,11 +22,12 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.ControlPoint;
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.base.CourseListener;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.BuoyImpl;
|
||||
import com.sap.sailing.domain.base.impl.CourseImpl;
|
||||
import com.sap.sailing.domain.base.impl.WaypointImpl;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.common.impl.Util.Triple;
|
||||
@@ -215,9 +216,8 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
|
||||
@Test
|
||||
public void testWaypointAddedAtEnd() throws PatchFailedException, InterruptedException {
|
||||
final boolean[] result = new boolean[1];
|
||||
final com.tractrac.clientmodule.ControlPoint cp1 = new com.tractrac.clientmodule.ControlPoint(
|
||||
UUID.randomUUID(), "CP1", /* hasTwo */false) {
|
||||
};
|
||||
final com.tractrac.clientmodule.Event event = new com.tractrac.clientmodule.Event(UUID.randomUUID(), "Event1");
|
||||
final com.tractrac.clientmodule.ControlPoint cp1 = event.addControlPoint(UUID.randomUUID(), "CP1", /* hasTwo */false);
|
||||
waitForRouteData();
|
||||
final List<com.tractrac.clientmodule.ControlPoint> controlPoints = new ArrayList<com.tractrac.clientmodule.ControlPoint>(
|
||||
routeData[0].getPoints());
|
||||
@@ -235,17 +235,21 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
|
||||
System.out.println("waypointRemoved " + zeroBasedIndex + " / " + waypointThatGotRemoved);
|
||||
}
|
||||
});
|
||||
domainFactory.updateCourseWaypoints(course, controlPoints);
|
||||
assertTrue(result[0]);
|
||||
testLegStructure(3);
|
||||
((CourseImpl) course).lockForWrite(); // without the lock it's possible that another race course update removes the additional waypoint again
|
||||
try {
|
||||
domainFactory.updateCourseWaypoints(course, controlPoints);
|
||||
assertTrue(result[0]);
|
||||
testLegStructure(3);
|
||||
} finally {
|
||||
((CourseImpl) course).unlockAfterWrite();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrackedRacesTrackedLegsUpdatedProperly() throws InterruptedException, PatchFailedException {
|
||||
final boolean[] result = new boolean[1];
|
||||
final com.tractrac.clientmodule.ControlPoint cp1 = new com.tractrac.clientmodule.ControlPoint(
|
||||
UUID.randomUUID(), "CP1", /* hasTwo */false) {
|
||||
};
|
||||
final com.tractrac.clientmodule.Event event = new com.tractrac.clientmodule.Event(UUID.randomUUID(), "Event1");
|
||||
final com.tractrac.clientmodule.ControlPoint cp1 = event.addControlPoint(UUID.randomUUID(), "CP1", /* hasTwo */false);
|
||||
waitForRouteData();
|
||||
final List<com.tractrac.clientmodule.ControlPoint> controlPoints = new ArrayList<com.tractrac.clientmodule.ControlPoint>(
|
||||
routeData[0].getPoints());
|
||||
|
||||
+8
-5
@@ -250,16 +250,19 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
}
|
||||
List<Integer> ranksOfNonMedalStartedRaces = new ArrayList<Integer>();
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
int numberOfRacesToCompareToDiscardThresholds = 0;
|
||||
for (RaceColumn column : raceColumnsInLeaderboard.values()) {
|
||||
if (!column.isMedalRace() && column.getTrackedRace(defaultFleet) != null && column.getTrackedRace(defaultFleet).hasStarted(now)) {
|
||||
ranksOfNonMedalStartedRaces.add(column.getTrackedRace(defaultFleet).getRank(competitor, now));
|
||||
if (column.getTrackedRace(defaultFleet) != null && column.getTrackedRace(defaultFleet).hasStarted(now)) {
|
||||
numberOfRacesToCompareToDiscardThresholds++;
|
||||
if (!column.isMedalRace()) {
|
||||
ranksOfNonMedalStartedRaces.add(column.getTrackedRace(defaultFleet).getRank(competitor, now));
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(ranksOfNonMedalStartedRaces);
|
||||
int carryInt = (carry == null ? 0 : carry);
|
||||
int totalPoints = carryInt;
|
||||
int medalRacePoints = getMedalRacePoints(competitor, now, defaultFleet);
|
||||
int numberOfRacesFromWhichToDiscard = ranksOfNonMedalStartedRaces.size();
|
||||
for (TrackedRace race : testRaces) {
|
||||
RaceColumn raceColumn = raceColumnsInLeaderboard.get(race);
|
||||
Pair<Competitor, RaceColumn> key = new Pair<Competitor, RaceColumn>(competitor, raceColumn);
|
||||
@@ -274,8 +277,8 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
// One race is discarded because four races were started, and for [3-6) one race can be discarded.
|
||||
// The discarded race is the worst of those started, so the one with rank 4.
|
||||
int expectedNumberOfDiscardedRaces =
|
||||
numberOfRacesFromWhichToDiscard < firstDiscardingThreshold ? 0 :
|
||||
numberOfRacesFromWhichToDiscard < secondDiscardingThreshold ? 1 : 2;
|
||||
numberOfRacesToCompareToDiscardThresholds < firstDiscardingThreshold ? 0 :
|
||||
numberOfRacesToCompareToDiscardThresholds < secondDiscardingThreshold ? 1 : 2;
|
||||
boolean discarded = ranksOfNonMedalStartedRaces.indexOf(rank) >= ranksOfNonMedalStartedRaces.size()-expectedNumberOfDiscardedRaces;
|
||||
int expected = discarded ? 0 : rank==medalRacePoints?2*rank:rank;
|
||||
assertEquals(expected, leaderboard.getTotalPoints(competitor, raceColumn, now), 0.000000001);
|
||||
|
||||
+315
-4
@@ -7,8 +7,12 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -19,10 +23,14 @@ import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.BuoyImpl;
|
||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
||||
import com.sap.sailing.domain.base.impl.GateImpl;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.base.impl.RegattaImpl;
|
||||
import com.sap.sailing.domain.base.impl.SeriesImpl;
|
||||
import com.sap.sailing.domain.base.impl.WaypointImpl;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
@@ -40,7 +48,11 @@ import com.sap.sailing.domain.leaderboard.impl.ResultDiscardingRuleImpl;
|
||||
import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
|
||||
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
|
||||
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.TimedComparator;
|
||||
import com.sap.sailing.util.impl.ArrayListNavigableSet;
|
||||
|
||||
public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
private ArrayList<Series> series;
|
||||
@@ -107,6 +119,209 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column doesn't
|
||||
* count for neither the total points sum nor the competitor ordering.
|
||||
*/
|
||||
@Test
|
||||
public void testUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
||||
List<Competitor> competitors = createCompetitors(10);
|
||||
Regatta regatta = createRegatta(/* qualifying */ 2, new String[] { "Yellow", "Blue" }, /* final */0,
|
||||
new String[] { "Default" },
|
||||
/* medal */false, "testUnorderedGroupsWithOneGroupNotHavingRacedInAcolumn",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
|
||||
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
|
||||
Series qualificationSeries;
|
||||
Iterator<? extends Series> seriesIter = regatta.getSeries().iterator();
|
||||
qualificationSeries = seriesIter.next();
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
TrackedRace q1Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5));
|
||||
TrackedRace q1Blue = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(5, 10));
|
||||
RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1");
|
||||
q1Column.setTrackedRace(q1Column.getFleetByName("Yellow"), q1Yellow);
|
||||
q1Column.setTrackedRace(q1Column.getFleetByName("Blue"), q1Blue);
|
||||
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||
for (int i=0; i<5; i++) {
|
||||
assertTrue(competitors.get(i) == rankedCompetitors.get(2*i) || competitors.get(i) == rankedCompetitors.get(2*i+1));
|
||||
assertTrue(competitors.get(i+5) == rankedCompetitors.get(2*i) || competitors.get(i+5) == rankedCompetitors.get(2*i+1));
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
||||
}
|
||||
// now add one race for yellow fleet and test that it doesn't count because blue fleet is still missing its race for Q2
|
||||
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
||||
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
||||
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||
for (int i=0; i<5; i++) {
|
||||
assertTrue(competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertTrue(competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
||||
}
|
||||
// now add a tracked race for the blue fleet for Q2 and assert that the Q2 scores count for the total points sum
|
||||
TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(now,
|
||||
Arrays.asList(new Competitor[] { competitors.get(0), competitors.get(1), competitors.get(2),
|
||||
competitors.get(8), competitors.get(9) }));
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue);
|
||||
// the new order in Q2 expected to be { (0, 3), (1, 4), (2, 5), (8, 6), (9, 7) }
|
||||
// therefore the new total points, in ascending order, are expected to be:
|
||||
// { 0: 1+1=2; 5: 1+3=4; 1: 2+2=4; 3: 4+1=5; 2: 3+3=6; 6: 2+4=6; 4: 5+2=7; 7: 3+5=8; 8: 4+4=8; 9: 5+5=10 }
|
||||
double[] expectedTotalPoints = new double[] { 2, 4, 6, 5, 7, 4, 6, 8, 8, 10 };
|
||||
int[] expectedOrderAfterTwoFullRacesPlusMinusOne = new int[] { 0, 5, 1, 3, 2, 6, 4, 7, 8, 9 };
|
||||
List<Competitor> rankedCompetitorsWithAllRacesInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||
for (int i=0; i<5; i++) {
|
||||
assertTrue(competitors.get(expectedOrderAfterTwoFullRacesPlusMinusOne[2*i]) == rankedCompetitorsWithAllRacesInQ2.get(2*i) ||
|
||||
competitors.get(expectedOrderAfterTwoFullRacesPlusMinusOne[2*i]) == rankedCompetitorsWithAllRacesInQ2.get(2*i+1));
|
||||
assertTrue(competitors.get(expectedOrderAfterTwoFullRacesPlusMinusOne[2*i+1]) == rankedCompetitorsWithAllRacesInQ2.get(2*i) ||
|
||||
competitors.get(expectedOrderAfterTwoFullRacesPlusMinusOne[2*i+1]) == rankedCompetitorsWithAllRacesInQ2.get(2*i+1));
|
||||
assertEquals(expectedTotalPoints[2*i], leaderboard.getTotalPoints(competitors.get(2*i), later), 0.000000001);
|
||||
assertEquals(expectedTotalPoints[2*i+1], leaderboard.getTotalPoints(competitors.get(2*i+1), later), 0.000000001);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Then add the second tracked
|
||||
* race for the second column but such that it hasn't started yet at the time point of the query. Then add a score correction,
|
||||
* simulating the use of a proxy race (that doesn't ever start) and a manual score entry.
|
||||
*/
|
||||
@Test
|
||||
public void testUnorderedGroupsWithOneGroupNotHavingStartedRacedInAcolumnAndThenCorrectingScore() throws NoWindException {
|
||||
List<Competitor> competitors = createCompetitors(10);
|
||||
Regatta regatta = createRegatta(/* qualifying */ 2, new String[] { "Yellow", "Blue" }, /* final */0,
|
||||
new String[] { "Default" },
|
||||
/* medal */false, "testUnorderedGroupsWithOneGroupNotHavingRacedInAcolumn",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
|
||||
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
|
||||
Series qualificationSeries;
|
||||
Iterator<? extends Series> seriesIter = regatta.getSeries().iterator();
|
||||
qualificationSeries = seriesIter.next();
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
TrackedRace q1Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5));
|
||||
TrackedRace q1Blue = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(5, 10));
|
||||
RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1");
|
||||
q1Column.setTrackedRace(q1Column.getFleetByName("Yellow"), q1Yellow);
|
||||
q1Column.setTrackedRace(q1Column.getFleetByName("Blue"), q1Blue);
|
||||
// now add one race for yellow fleet and test that it doesn't count because blue fleet is still missing its race for Q2
|
||||
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
||||
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
||||
// now add a tracked race for the blue fleet for Q2 that hasn't started yet and assert that the Q2 scores still don't count for the total points sum
|
||||
TimePoint muchLater = later.plus(10000000000l);
|
||||
TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(muchLater,
|
||||
Arrays.asList(new Competitor[] { competitors.get(0), competitors.get(1), competitors.get(2),
|
||||
competitors.get(8), competitors.get(9) }));
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue);
|
||||
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||
for (int i=0; i<5; i++) {
|
||||
assertTrue(competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertTrue(competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
||||
}
|
||||
assertFalse(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
|
||||
// now add a score correction for Q2/Blue to make it count:
|
||||
leaderboard.getScoreCorrection().correctScore(competitors.get(9), q2Column, 42.);
|
||||
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
|
||||
// the new order in Q2 expected to be { (9, 3), ... } (we don't know about any competitor in q2Blue but #9
|
||||
// therefore the new total points for #9 are
|
||||
// { 9: 5+42=47 }
|
||||
assertEquals(47.0, leaderboard.getTotalPoints(competitors.get(9), later), 0.00000001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column doesn't
|
||||
* count for computing the number of discards.
|
||||
*/
|
||||
@Test
|
||||
public void testDiscardsForUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
||||
List<Competitor> competitors = createCompetitors(10);
|
||||
Regatta regatta = createRegatta(/* qualifying */ 2, new String[] { "Yellow", "Blue" }, /* final */0,
|
||||
new String[] { "Default" },
|
||||
/* medal */false, "testDiscardsForUnorderedGroupsWithOneGroupNotHavingRacedInAColumn",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
|
||||
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[]{2}); // one discard for two or more races
|
||||
Series qualificationSeries;
|
||||
Iterator<? extends Series> seriesIter = regatta.getSeries().iterator();
|
||||
qualificationSeries = seriesIter.next();
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
TrackedRace q1Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5));
|
||||
TrackedRace q1Blue = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(5, 10));
|
||||
RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1");
|
||||
q1Column.setTrackedRace(q1Column.getFleetByName("Yellow"), q1Yellow);
|
||||
q1Column.setTrackedRace(q1Column.getFleetByName("Blue"), q1Blue);
|
||||
// now add one race for yellow fleet and test that there are no discards still because blue fleet is still missing its race for Q2
|
||||
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
||||
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
||||
for (Competitor competitor : competitors) {
|
||||
assertFalse("Competitor "+competitor+" has a discard in Q1 but shouldn't", leaderboard.isDiscarded(competitor, q1Column, later));
|
||||
assertFalse("Competitor "+competitor+" has a discard in Q2 but shouldn't", leaderboard.isDiscarded(competitor, q2Column, later));
|
||||
}
|
||||
// now add a tracked race for the blue fleet for Q2 and assert that all competitors have one discard
|
||||
TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(now,
|
||||
Arrays.asList(new Competitor[] { competitors.get(0), competitors.get(1), competitors.get(2),
|
||||
competitors.get(8), competitors.get(9) }));
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue);
|
||||
for (Competitor competitor : competitors) {
|
||||
assertTrue("Competitor "+competitor+" has no discard but should",
|
||||
leaderboard.isDiscarded(competitor, q1Column, later) || leaderboard.isDiscarded(competitor, q2Column, later));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regarding bug 961, test scoring in a leaderboard that has a qualification series with two ordered groups where for one
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the competitors in
|
||||
* that column get their discard.
|
||||
*/
|
||||
@Test
|
||||
public void testDiscardsForOrderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
||||
List<Competitor> competitors = createCompetitors(10);
|
||||
Regatta regatta = createRegatta(/* qualifying */ 0, new String[] { "Default" }, /* final */2,
|
||||
new String[] { "Gold", "Silver" },
|
||||
/* medal */false, "testDiscardsForOrderedGroupsWithOneGroupNotHavingRacedInAColumn",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true),
|
||||
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[]{2}); // one discard for two or more races
|
||||
Series finalSeries;
|
||||
Iterator<? extends Series> seriesIter = regatta.getSeries().iterator();
|
||||
seriesIter.next();
|
||||
finalSeries = seriesIter.next();
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
TrackedRace f1Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5));
|
||||
TrackedRace f1Silver = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(5, 10));
|
||||
RaceColumn f1Column = finalSeries.getRaceColumnByName("F1");
|
||||
f1Column.setTrackedRace(f1Column.getFleetByName("Gold"), f1Gold);
|
||||
f1Column.setTrackedRace(f1Column.getFleetByName("Silver"), f1Silver);
|
||||
// now add one race for yellow fleet and test that there are no discards still because blue fleet is still missing its race for Q2
|
||||
TrackedRace f2Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5));
|
||||
RaceColumn f2Column = finalSeries.getRaceColumnByName("F2");
|
||||
f2Column.setTrackedRace(f2Column.getFleetByName("Gold"), f2Gold);
|
||||
for (int i=0; i<5; i++) {
|
||||
assertTrue("Competitor "+competitors.get(i)+" has no discard in F1 or F2 but should",
|
||||
leaderboard.isDiscarded(competitors.get(i), f1Column, later) ||
|
||||
leaderboard.isDiscarded(competitors.get(i), f2Column, later));
|
||||
}
|
||||
for (int i=5; i<10; i++) {
|
||||
assertFalse("Competitor "+competitors.get(i)+" has a discard in F1 or F2 but shouldn't",
|
||||
leaderboard.isDiscarded(competitors.get(i), f1Column, later) ||
|
||||
leaderboard.isDiscarded(competitors.get(i), f2Column, later));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the competitors ranking worse than the disqualified competitor advance by one in the
|
||||
@@ -118,7 +333,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
List<Competitor> competitors = createCompetitors(10);
|
||||
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1,
|
||||
new String[] { "Default" },
|
||||
/* medal */false, "testOneStartedRaceWithDifferentScores",
|
||||
/* medal */false, "testOneStartedRaceWithDifferentScoresAndDisqualification",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
|
||||
Series finalSeries;
|
||||
@@ -157,7 +372,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
}
|
||||
Regatta regatta = createRegatta(/* qualifying */1, new String[] { "Yellow", "Blue" }, /* final */0,
|
||||
new String[] { "Default" },
|
||||
/* medal */false, "testAllTrackedAndStartedWithDifferentScores",
|
||||
/* medal */false, "testDistributionAcrossQualifyingFleetsWithDifferentScores",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
@@ -280,6 +495,76 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLastRaceTakesPrecedenceWithHighPointLastBreaksTieScheme() throws NoWindException {
|
||||
List<Competitor> competitors = createCompetitors(20);
|
||||
Regatta regatta = createRegatta(/* qualifying */ 0, new String[] { "Default" }, /* final */ 2, new String[] { "Default" },
|
||||
/* medal */ false, "testLastRaceTakesPrecedenceWithHighPointLastBreaksTieScheme",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */ true),
|
||||
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_LAST_BREAKS_TIE));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
|
||||
TrackedRace f1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors);
|
||||
f1Column.setTrackedRace(f1Column.getFleetByName("Default"), f1Default);
|
||||
List<Competitor> reversedCompetitors = new ArrayList<Competitor>(competitors);
|
||||
Collections.reverse(reversedCompetitors);
|
||||
RaceColumn f2Column = series.get(1).getRaceColumnByName("F2");
|
||||
TrackedRace f2Default = new MockedTrackedRaceWithStartTimeAndRanks(now, reversedCompetitors);
|
||||
f2Column.setTrackedRace(f2Column.getFleetByName("Default"), f2Default);
|
||||
|
||||
// assert that all competitors have equal points now
|
||||
Competitor firstCompetitor = competitors.iterator().next();
|
||||
for (Competitor competitor : competitors) {
|
||||
assertEquals(leaderboard.getTotalPoints(firstCompetitor, later), leaderboard.getTotalPoints(competitor, later));
|
||||
}
|
||||
// assert that the ordering of competitors equals that of the last race
|
||||
assertEquals(reversedCompetitors, leaderboard.getCompetitorsFromBestToWorst(later));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTotalTimeNotCountedForRacesStartedLaterThanTimePointRequested() {
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint earlier = now.minus(1000000);
|
||||
TimePoint later = now.plus(1000000); // first race from "earlier" to "now", second from "now" to "later", third from "later" to "finish"
|
||||
TimePoint finish = later.plus(1000000);
|
||||
Competitor[] c = createCompetitors(3).toArray(new Competitor[0]);
|
||||
Competitor[] f1 = new Competitor[] { c[0], c[1], c[2] };
|
||||
Competitor[] f2 = new Competitor[] { c[0], c[1], c[2] };
|
||||
Competitor[] f3 = new Competitor[] { c[1], c[2], c[0] };
|
||||
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */3, new String[] { "Default" },
|
||||
/* medal */ false, "testTotalTimeNotCountedForRacesStartedLaterThanTimePointReqeusted",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Competitor, TimePoint>[] lastMarkPassingTimesForCompetitors = (Map<Competitor, TimePoint>[]) new HashMap<?, ?>[3];
|
||||
lastMarkPassingTimesForCompetitors[0] = new HashMap<>();
|
||||
lastMarkPassingTimesForCompetitors[0].put(c[0], now);
|
||||
lastMarkPassingTimesForCompetitors[0].put(c[1], now);
|
||||
lastMarkPassingTimesForCompetitors[0].put(c[2], now);
|
||||
lastMarkPassingTimesForCompetitors[1] = new HashMap<>();
|
||||
lastMarkPassingTimesForCompetitors[1].put(c[0], later);
|
||||
lastMarkPassingTimesForCompetitors[1].put(c[1], later);
|
||||
lastMarkPassingTimesForCompetitors[1].put(c[2], later);
|
||||
lastMarkPassingTimesForCompetitors[2] = new HashMap<>();
|
||||
lastMarkPassingTimesForCompetitors[2].put(c[0], finish);
|
||||
lastMarkPassingTimesForCompetitors[2].put(c[1], finish);
|
||||
lastMarkPassingTimesForCompetitors[2].put(c[2], finish);
|
||||
createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes(series.get(1), "Default",
|
||||
new Competitor[][] { f1, f2, f3 }, new TimePoint[] { earlier, now, later }, lastMarkPassingTimesForCompetitors);
|
||||
long totalTimeSailedC0_InRace1 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], earlier.plus(1000));
|
||||
assertEquals(1000l, totalTimeSailedC0_InRace1);
|
||||
long totalTimeSailedC0_InRace2 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], now.plus(1000));
|
||||
assertEquals(now.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace2);
|
||||
long totalTimeSailedC0_InRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], later.plus(1000));
|
||||
assertEquals(later.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace3);
|
||||
long totalTimeSailedC0_AtEndOfRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], finish);
|
||||
assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AtEndOfRace3);
|
||||
long totalTimeSailedC0_AfterRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], finish.plus(1000));
|
||||
assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AfterRace3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTieBreakWithTwoVersusOneWins() throws NoWindException {
|
||||
Competitor[] c = createCompetitors(3).toArray(new Competitor[0]);
|
||||
@@ -412,8 +697,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
leaderboard1.getFleet(null));
|
||||
assertTrue(leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[3]) <
|
||||
leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[0])); // c3 better than c0; won last race
|
||||
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", Arrays.asList(leaderboard1,
|
||||
leaderboard2, leaderboard3));
|
||||
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", false, Arrays.asList(leaderboard1,
|
||||
leaderboard2, leaderboard3));
|
||||
leaderboardGroup.setOverallLeaderboard(new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(),
|
||||
new ResultDiscardingRuleImpl(new int[0])));
|
||||
leaderboardGroup.getOverallLeaderboard().setSuppressed(c[1], true);
|
||||
@@ -438,6 +723,32 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
return later;
|
||||
}
|
||||
|
||||
private void createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes(
|
||||
Series theSeries, String fleetName, Competitor[][] competitorLists, TimePoint[] startTimes,
|
||||
Map<Competitor, TimePoint>[] lastMarkPassingTimesForCompetitors) {
|
||||
Iterator<? extends RaceColumn> columnIter = theSeries.getRaceColumns().iterator();
|
||||
int i=0;
|
||||
for (Competitor[] competitorList : competitorLists) {
|
||||
RaceColumn raceColumn = columnIter.next();
|
||||
final Map<Competitor, TimePoint> lastMarkPassingTimes = lastMarkPassingTimesForCompetitors[i];
|
||||
final Waypoint start = new WaypointImpl(new GateImpl(new BuoyImpl("Left StartBuoy"), new BuoyImpl("Right StartBuoy"), "Start"));
|
||||
final Waypoint finish = new WaypointImpl(new BuoyImpl("FinishBuoy"));
|
||||
TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(startTimes[i], Arrays.asList(competitorList)) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public NavigableSet<MarkPassing> getMarkPassings(Competitor competitor) {
|
||||
ArrayListNavigableSet<MarkPassing> result = new ArrayListNavigableSet<>(new TimedComparator());
|
||||
result.add(new MarkPassingImpl(lastMarkPassingTimes.get(competitor), finish, competitor));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
trackedRace.getRace().getCourse().addWaypoint(0, start);
|
||||
trackedRace.getRace().getCourse().addWaypoint(1, finish);
|
||||
raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Competitor> createCompetitors(int numberOfCompetitorsToCreate) {
|
||||
List<Competitor> result = new ArrayList<Competitor>();
|
||||
for (int i=1; i<=numberOfCompetitorsToCreate; i++) {
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
|
||||
|
||||
public class LockTraceTest {
|
||||
private static class LockingThread extends Thread {
|
||||
private boolean ownsLock;
|
||||
|
||||
public LockingThread(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public synchronized boolean ownsLock() {
|
||||
return ownsLock;
|
||||
}
|
||||
|
||||
public synchronized void obtainLockAndWait(Lock lock) throws InterruptedException {
|
||||
lock.lock();
|
||||
ownsLock = true;
|
||||
notifyAll();
|
||||
wait();
|
||||
}
|
||||
|
||||
public synchronized void waitUntilLockIsObtained() throws InterruptedException {
|
||||
while (!ownsLock()) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testLockTraceForMultipleReaders() throws InterruptedException {
|
||||
NamedReentrantReadWriteLock lock1 = new NamedReentrantReadWriteLock("Lock1", /* fair */ true);
|
||||
lock1.readLock().lock();
|
||||
Object o = createAndStartLockingThreadReturningObjectToNotifyInOrderToReleaseLockAndTerminateThread(lock1.readLock());
|
||||
lock1.writeLock().lock();
|
||||
boolean itWorked = lock1.writeLock().tryLock(1000000000000000000l, TimeUnit.MILLISECONDS);
|
||||
System.out.println(itWorked);
|
||||
lock1.readLock().unlock();
|
||||
synchronized (o) {
|
||||
o.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testDeadlockDetectionWithJMX() throws InterruptedException {
|
||||
NamedReentrantReadWriteLock lock1 = new NamedReentrantReadWriteLock("Lock1", /* fair */ true);
|
||||
NamedReentrantReadWriteLock lock2 = new NamedReentrantReadWriteLock("Lock2", /* fair */ true);
|
||||
lock1.readLock().lock();
|
||||
// let other thread get write lock on lock2
|
||||
LockingThread o = createAndStartLockingThreadReturningObjectToNotifyInOrderToCauseDeadlock(lock2.writeLock(), lock1.writeLock());
|
||||
o.waitUntilLockIsObtained();
|
||||
// now let other thread continue so it tries to obtain the write lock on lock1 which it can't get because we own the read lock
|
||||
synchronized (o) {
|
||||
o.notifyAll();
|
||||
}
|
||||
// and complete the deadlock by asking lock2's read lock
|
||||
lock2.readLock().lock();
|
||||
lock1.readLock().unlock();
|
||||
lock2.readLock().unlock();
|
||||
}
|
||||
|
||||
private Object createAndStartLockingThreadReturningObjectToNotifyInOrderToReleaseLockAndTerminateThread(final Lock lock) {
|
||||
final Thread thread = new Thread("Thread to lock "+lock) {
|
||||
public void run() {
|
||||
lock.lock();
|
||||
synchronized (Thread.currentThread()) {
|
||||
try {
|
||||
Thread.currentThread().wait();
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
}
|
||||
};
|
||||
thread.start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
private LockingThread createAndStartLockingThreadReturningObjectToNotifyInOrderToCauseDeadlock(final Lock lock1, final Lock lock2) {
|
||||
final LockingThread thread = new LockingThread("Thread to lock "+lock1+" and then "+lock2) {
|
||||
public void run() {
|
||||
try {
|
||||
obtainLockAndWait(lock1);
|
||||
} catch (InterruptedException e1) {
|
||||
throw new RuntimeException(e1);
|
||||
}
|
||||
lock2.lock();
|
||||
}
|
||||
};
|
||||
thread.start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
new LockTraceTest().testDeadlockDetectionWithJMX();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,6 @@ import org.junit.Test;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.impl.DouglasPeucker;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.base.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
@@ -28,6 +27,7 @@ import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -482,7 +483,8 @@ public class TrackTest {
|
||||
GPSFix outlier = new GPSFixImpl(outlierPosition, timePointForOutlier);
|
||||
track.addGPSFix(outlier);
|
||||
assertEquals(1, invalidationCalls.size());
|
||||
assertEquals(timePointForOutlier, invalidationCalls.iterator().next()); // outlier doesn't turn its preceding element into an outlier
|
||||
TimePoint timePointOfLastFixBeforeOutlier = track.getLastFixBefore(timePointForOutlier).getTimePoint();
|
||||
assertTrue(invalidationCalls.iterator().next().after(timePointOfLastFixBeforeOutlier)); // outlier doesn't turn its preceding element into an outlier
|
||||
assertNull(distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start)); // no more entry for "to"-value start in cache
|
||||
invalidationCalls.clear();
|
||||
final TimePoint timePointOfLastOriginalFix = track.getLastRawFix().getTimePoint();
|
||||
@@ -498,7 +500,8 @@ public class TrackTest {
|
||||
GPSFix lateOutlier = new GPSFixImpl(lateOutlierPosition, timePointForLateOutlier);
|
||||
track.addGPSFix(lateOutlier);
|
||||
assertEquals(1, invalidationCalls.size());
|
||||
assertEquals(timePointForLateOutlier, invalidationCalls.iterator().next());
|
||||
TimePoint timePointOfLastFixBeforeLateOutlier = track.getLastFixBefore(timePointForLateOutlier).getTimePoint();
|
||||
assertTrue(invalidationCalls.iterator().next().after(timePointOfLastFixBeforeLateOutlier));
|
||||
invalidationCalls.clear();
|
||||
// expect the invalidation to have started after the single cache entry, so the cache entry still has to be there:
|
||||
final Pair<TimePoint, Pair<TimePoint, Distance>> stillPresentFullIntervalCacheEntry = distanceCache
|
||||
@@ -539,6 +542,57 @@ public class TrackTest {
|
||||
assertEquals(speed.getMetersPerSecond()*steps, track.getDistanceTraveled(now, start).getMeters(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* A test case for bug 968. If distances are requested for time intervals ending after the last GPS fix, the position
|
||||
* is estimated to be at the last fix known. When another fix arrives, the positions for these time points can now be
|
||||
* interpolated, leading to different results and hence to a cache inconsistency in case the cache isn't flushed for the
|
||||
* interval between the last and the newest fix. This test case adds a few fixes, then asks a distance traveled for an
|
||||
* interval ending after the last fix, then adds another fix at a later time point and asks the distance again.
|
||||
*/
|
||||
@Test
|
||||
public void testMoreFrequentDistanceComputationThanGPSFixReception() {
|
||||
DynamicGPSFixTrack<Object, GPSFix> track = new DynamicGPSFixTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l);
|
||||
final int timeBetweenFixesInMillis = 1000;
|
||||
Bearing bearing = new DegreeBearingImpl(123);
|
||||
Speed speed = new KnotSpeedImpl(7);
|
||||
Position p = new DegreePosition(0, 0);
|
||||
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint start = now;
|
||||
final int steps = 10;
|
||||
TimePoint next = null;
|
||||
GPSFix fix = null;
|
||||
for (int i=0; i<steps; i++) {
|
||||
fix = new GPSFixImpl(p, start);
|
||||
track.addGPSFix(fix);
|
||||
next = start.plus(timeBetweenFixesInMillis);
|
||||
p = p.translateGreatCircle(bearing, speed.travel(start, next));
|
||||
start = next;
|
||||
bearing = new DegreeBearingImpl(bearing.getDegrees() + 1);
|
||||
}
|
||||
Distance distance1 = track.getDistanceTraveled(now, next.minus(timeBetweenFixesInMillis));
|
||||
Distance distance2 = track.getDistanceTraveled(now, next.minus(2*timeBetweenFixesInMillis/3));
|
||||
Distance distance3 = track.getDistanceTraveled(now, next.minus(timeBetweenFixesInMillis/3));
|
||||
Distance distance4 = track.getDistanceTraveled(now, next);
|
||||
assertEquals(distance1, distance2); // no progress after time point "next" because no further fixes are known
|
||||
assertEquals(distance1, distance3); // no progress after time point "next" because no further fixes are known
|
||||
assertEquals(distance1, distance4); // no progress after time point "next" because no further fixes are known
|
||||
// now add one more fix
|
||||
fix = new GPSFixImpl(p, start);
|
||||
track.addGPSFix(fix);
|
||||
Distance distance1_new = track.getDistanceTraveled(now, next.minus(timeBetweenFixesInMillis));
|
||||
Distance distance2_new = track.getDistanceTraveled(now, next.minus(2*timeBetweenFixesInMillis/3));
|
||||
Distance distance3_new = track.getDistanceTraveled(now, next.minus(timeBetweenFixesInMillis/3));
|
||||
Distance distance4_new = track.getDistanceTraveled(now, next);
|
||||
assertEquals(distance1, distance1_new);
|
||||
assertFalse(distance2.equals(distance2_new));
|
||||
assertFalse(distance3.equals(distance3_new));
|
||||
assertFalse(distance4.equals(distance4_new));
|
||||
Distance toReachInOneThirdOfTimeBetweenFixesInMillis = speed.travel(next, next.plus(timeBetweenFixesInMillis/3));
|
||||
assertEquals(toReachInOneThirdOfTimeBetweenFixesInMillis.getMeters(), distance2_new.getMeters()-distance1_new.getMeters(), 0.01);
|
||||
assertEquals(toReachInOneThirdOfTimeBetweenFixesInMillis.getMeters(), distance3_new.getMeters()-distance2_new.getMeters(), 0.01);
|
||||
assertEquals(toReachInOneThirdOfTimeBetweenFixesInMillis.getMeters(), distance4_new.getMeters()-distance3_new.getMeters(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistanceCacheAccessForPartialStrip() {
|
||||
final Set<TimePoint> invalidationCalls = new HashSet<TimePoint>();
|
||||
|
||||
@@ -1,24 +1,49 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Buoy;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.ControlPoint;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.BoatClassImpl;
|
||||
import com.sap.sailing.domain.base.impl.BoatImpl;
|
||||
import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
||||
import com.sap.sailing.domain.base.impl.CourseImpl;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.base.impl.NationalityImpl;
|
||||
import com.sap.sailing.domain.base.impl.PersonImpl;
|
||||
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
|
||||
import com.sap.sailing.domain.base.impl.RegattaImpl;
|
||||
import com.sap.sailing.domain.base.impl.TeamImpl;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.domain.tracking.WindTrack;
|
||||
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.WindImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
|
||||
|
||||
@@ -213,4 +238,46 @@ public class WindTest {
|
||||
// TODO create a tracked race with corresponding WEB wind source; put leader in downwind, trailer in upwind and
|
||||
// TODO test that windward distance includes the full reaching leg's length
|
||||
}
|
||||
|
||||
/**
|
||||
* See bug 943. Wind estimation should return <code>null</code> instead of causing exceptions in case there
|
||||
* is no wind data to bootstrap the estimator with.
|
||||
*/
|
||||
@Test
|
||||
public void testWindEstimationReturnsNullIfNoUpwindStartAndNoOtherWindDataAvailable() {
|
||||
DomainFactory domainFactory = DomainFactory.INSTANCE;
|
||||
Buoy startFinishLeft = domainFactory.getOrCreateBuoy("Start/Finish left");
|
||||
Buoy startFinishRight = domainFactory.getOrCreateBuoy("Start/Finish right");
|
||||
ControlPoint startFinish = domainFactory.createGate(startFinishLeft, startFinishRight, "Start/Finish");
|
||||
ControlPoint top = domainFactory.getOrCreateBuoy("Top");
|
||||
Waypoint w1 = domainFactory.createWaypoint(startFinish);
|
||||
Waypoint w2 = domainFactory.createWaypoint(top);
|
||||
Waypoint w3 = domainFactory.createWaypoint(startFinish);
|
||||
Competitor competitor = new CompetitorImpl(123, "Test Competitor", new TeamImpl("STG", Collections.singleton(
|
||||
new PersonImpl("Test Competitor", new NationalityImpl("GER"),
|
||||
/* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas",
|
||||
new NationalityImpl("NED"),
|
||||
/* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Test Competitor" + "'s boat",
|
||||
new BoatClassImpl("505", /* typicallyStartsUpwind */true), null));
|
||||
final BoatClass boatClass = domainFactory.getOrCreateBoatClass("ESS40");
|
||||
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl(
|
||||
new RegattaImpl("Test Regatta", boatClass,
|
||||
/* trackedRegattaRegistry */ null, domainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT))),
|
||||
new RaceDefinitionImpl("Test Race",
|
||||
new CourseImpl("Test Course", Arrays.asList(new Waypoint[] { w1, w2, w3 })),
|
||||
boatClass, Collections.singleton(competitor)),
|
||||
EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 1000,
|
||||
/* millisecondsOverWhichToAverageWind */ 30000,
|
||||
/* millisecondsOverWhichToAverageSpeed */ 30000);
|
||||
TimePoint start = MillisecondsTimePoint.now();
|
||||
TimePoint topMarkRounding = start.plus(30000);
|
||||
TimePoint finish = topMarkRounding.plus(30000);
|
||||
trackedRace.updateMarkPassings(competitor, Arrays.asList(new MarkPassing[] {
|
||||
new MarkPassingImpl(start, w1, competitor),
|
||||
new MarkPassingImpl(topMarkRounding, w2, competitor),
|
||||
new MarkPassingImpl(finish, w3, competitor)
|
||||
}));
|
||||
assertFalse(boatClass.typicallyStartsUpwind());
|
||||
assertNull(trackedRace.getEstimatedWindDirection(new DegreePosition(0, 0), MillisecondsTimePoint.now()));
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -78,8 +78,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
|
||||
|
||||
@Override
|
||||
public TimePoint getStartOfRace() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,11 +6,11 @@ Bundle-Version: 1.0.0.qualifier
|
||||
Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||
Require-Bundle: com.sap.sailing.domain,
|
||||
com.tractrac.clientmodule,
|
||||
com.tractrac.clientmodule;bundle-version="2.0.0",
|
||||
com.googlecode.java-diff-utils;bundle-version="1.3.0",
|
||||
com.sap.sailing.domain.common
|
||||
Export-Package: com.sap.sailing.domain.tractracadapter,
|
||||
com.sap.sailing.domain.tractracadapter.impl;x-friends:="com.sap.sailing.domain.test"
|
||||
com.sap.sailing.domain.tractracadapter.impl;x-friends:="com.sap.sailing.domain.test,com.sap.sailing.domain.swisstimingadapter.test"
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Import-Package: org.json.simple,
|
||||
org.json.simple.parser
|
||||
+31
-3
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.tractracadapter.impl;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.StringReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -14,6 +15,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.Boat;
|
||||
@@ -66,6 +68,7 @@ import com.tractrac.clientmodule.ControlPoint;
|
||||
import com.tractrac.clientmodule.Race;
|
||||
import com.tractrac.clientmodule.RaceCompetitor;
|
||||
import com.tractrac.clientmodule.data.ControlPointPositionData;
|
||||
import com.tractrac.clientmodule.metadata.IMetadata;
|
||||
|
||||
import difflib.PatchFailedException;
|
||||
|
||||
@@ -139,12 +142,19 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
synchronized (controlPointCache) {
|
||||
com.sap.sailing.domain.base.ControlPoint domainControlPoint = controlPointCache.get(controlPoint);
|
||||
if (domainControlPoint == null) {
|
||||
String controlPointName = controlPoint.getName();
|
||||
Map<String, String> controlPointMetadata = parseControlPointMetadata(controlPoint);
|
||||
String buoyColor = controlPointMetadata.get("Color");
|
||||
String buoyShape = controlPointMetadata.get("Shape");
|
||||
String buoyPattern = controlPointMetadata.get("Pattern");
|
||||
if (controlPoint.getHasTwoPoints()) {
|
||||
// it's a gate
|
||||
domainControlPoint = baseDomainFactory.createGate(baseDomainFactory.getOrCreateBuoy(controlPoint.getName() + " (left)"),
|
||||
baseDomainFactory.getOrCreateBuoy(controlPoint.getName() + " (right)"), controlPoint.getName());
|
||||
Buoy leftBuoy = baseDomainFactory.getOrCreateBuoy(controlPointName + " (left)", buoyColor, buoyShape, buoyPattern);
|
||||
Buoy rightBuoy = baseDomainFactory.getOrCreateBuoy(controlPointName + " (right)", buoyColor, buoyShape, buoyPattern);
|
||||
domainControlPoint = baseDomainFactory.createGate(leftBuoy, rightBuoy, controlPointName);
|
||||
} else {
|
||||
domainControlPoint = baseDomainFactory.getOrCreateBuoy(controlPoint.getName());
|
||||
Buoy buoy = baseDomainFactory.getOrCreateBuoy(controlPointName, buoyColor, buoyShape, buoyPattern);
|
||||
domainControlPoint = buoy;
|
||||
}
|
||||
controlPointCache.put(controlPoint, domainControlPoint);
|
||||
}
|
||||
@@ -152,6 +162,24 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Map<String, String> parseControlPointMetadata(ControlPoint controlPoint) {
|
||||
Map<String, String> metadataMap = new HashMap<String, String>();
|
||||
IMetadata metadata = controlPoint.getMetadata();
|
||||
if(metadata != null && !metadata.isEmpty()) {
|
||||
// we assume the format of the metadata is like in a java .properties file
|
||||
String text = metadata.getText();
|
||||
try {
|
||||
Properties p = new Properties();
|
||||
p.load(new StringReader(text));
|
||||
metadataMap = new HashMap<String, String>((Map) p);
|
||||
} catch (IOException e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
return metadataMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Course createCourse(String name, Iterable<ControlPoint> controlPoints) {
|
||||
List<Waypoint> waypointList = new ArrayList<Waypoint>();
|
||||
|
||||
+2
-1
@@ -83,7 +83,8 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
|
||||
Course course = getDomainFactory().createCourse(event.getA().getName(), event.getB().getPoints());
|
||||
RaceDefinition existingRaceDefinitionForRace = getDomainFactory().getExistingRaceDefinitionForRace(event.getC());
|
||||
if (existingRaceDefinitionForRace != null) {
|
||||
logger.log(Level.INFO, "Received course update for existing race "+event.getC().getName());
|
||||
logger.log(Level.INFO, "Received course update for existing race "+event.getC().getName()+": "+
|
||||
event.getB().getPoints());
|
||||
// race already exists; this means that we obviously found a course re-definition (yuck...)
|
||||
// Therefore, don't create TrackedRace again because it already exists.
|
||||
try {
|
||||
|
||||
@@ -9,5 +9,7 @@ package com.sap.sailing.domain.base;
|
||||
*
|
||||
*/
|
||||
public interface Buoy extends WithID, ControlPoint, IsManagedByDomainFactory {
|
||||
public String getDisplayColor();
|
||||
public String getColor();
|
||||
public String getShape();
|
||||
public String getPattern();
|
||||
}
|
||||
@@ -20,6 +20,13 @@ public interface DomainFactory {
|
||||
Nationality getOrCreateNationality(String threeLetterIOCCode);
|
||||
|
||||
Buoy getOrCreateBuoy(String id);
|
||||
|
||||
/**
|
||||
* If the buoy with ID <code>id</code> already exists, it is returned. Its color may differ from <code>color</code>
|
||||
* in that case. Otherwise, a new {@link Buoy} is created with <code>color</code> as its {@link Buoy#getColor()}
|
||||
* and <code>shape</code> as its {@link Buoy#getShape()}.
|
||||
*/
|
||||
Buoy getOrCreateBuoy(String id, String color, String shape, String pattern);
|
||||
|
||||
Gate createGate(Buoy left, Buoy right, String name);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import com.sap.sailing.domain.common.Named;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
|
||||
/**
|
||||
* An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For
|
||||
@@ -10,11 +11,19 @@ import com.sap.sailing.domain.common.Named;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public interface Event extends Named {
|
||||
public interface Event extends Named, Renamable {
|
||||
/**
|
||||
* @return a non-<code>null</code> venue for this event
|
||||
*/
|
||||
Venue getVenue();
|
||||
|
||||
String getPublicationUrl();
|
||||
|
||||
void setPublicationUrl(String publicationUrl);
|
||||
|
||||
boolean isPublic();
|
||||
|
||||
void setPublic(boolean isPublic);
|
||||
|
||||
Iterable<Regatta> getRegattas();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import com.sap.sailing.domain.common.Named;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
@@ -25,7 +25,7 @@ import com.sap.sailing.util.impl.RaceColumnListeners;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public interface RaceColumn extends Renamable {
|
||||
public interface RaceColumn extends Named {
|
||||
/**
|
||||
* @return the fleets for each of which this column has a single race and therefore optionally a {@link TrackedRace}, in
|
||||
* ascending order; best fleets first
|
||||
@@ -65,6 +65,12 @@ public interface RaceColumn extends Renamable {
|
||||
*/
|
||||
TrackedRace getTrackedRace(Fleet fleet);
|
||||
|
||||
/**
|
||||
* If a race is associated with this column for the <code>fleet</code>, the respective {@link RaceDefinition} is returned.
|
||||
* Otherwise, <code>null</code> is returned.
|
||||
*/
|
||||
RaceDefinition getRaceDefinition(Fleet fleet);
|
||||
|
||||
/**
|
||||
* Tries to find a tracked race whose {@link RaceDefinition#getCompetitors() competitors} contain <code>competitor</code>. If
|
||||
* no such {@link TrackedRace} is currently associated with this race column, <code>null</code> is returned. No two
|
||||
@@ -102,11 +108,11 @@ public interface RaceColumn extends Renamable {
|
||||
*/
|
||||
Pair<Competitor, RaceColumn> getKey(Competitor competitor);
|
||||
|
||||
public abstract RaceColumnListeners getRaceColumnListeners();
|
||||
RaceColumnListeners getRaceColumnListeners();
|
||||
|
||||
public abstract void removeRaceColumnListener(RaceColumnListener listener);
|
||||
void removeRaceColumnListener(RaceColumnListener listener);
|
||||
|
||||
public abstract void addRaceColumnListener(RaceColumnListener listener);
|
||||
void addRaceColumnListener(RaceColumnListener listener);
|
||||
|
||||
/**
|
||||
* Releases the {@link TrackedRace} previously set by {@link #setTrackedRace(Fleet, TrackedRace)} but leaves the
|
||||
@@ -117,4 +123,24 @@ public interface RaceColumn extends Renamable {
|
||||
* the fleet for which to release its tracked race
|
||||
*/
|
||||
void releaseTrackedRace(Fleet fleet);
|
||||
|
||||
/**
|
||||
* Usually, the scores in each leaderboard column count as they are for the overall score. However, if a column is
|
||||
* a medal race column it usually counts double. Under certain circumstances, columns may also count with factors different
|
||||
* from 1 or 2. For example, we've seen cases in the Extreme Sailing Series where the race committee defined that in the
|
||||
* overall series leaderboard the last two columns each count 1.5 times their scores.
|
||||
*/
|
||||
double getFactor();
|
||||
|
||||
/**
|
||||
* @param factor if <code>null</code>, {@link #getFactor()} will again compute a default value; otherwise, {@link #getFactor()} will
|
||||
* then return the double value of <code>factor</code>.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
Double getExplicitFactor();
|
||||
}
|
||||
@@ -11,10 +11,18 @@ public interface RaceColumnListener extends Serializable {
|
||||
|
||||
void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace);
|
||||
|
||||
boolean canAddRaceColumnToContainer(RaceColumn raceColumn);
|
||||
|
||||
void raceColumnAddedToContainer(RaceColumn raceColumn);
|
||||
|
||||
void raceColumnRemovedFromContainer(RaceColumn raceColumn);
|
||||
|
||||
void raceColumnMoved(RaceColumn raceColumn, int newIndex);
|
||||
|
||||
void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor);
|
||||
|
||||
void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName);
|
||||
|
||||
/**
|
||||
* A listener can use this to specify that it must not be serialized together with other listeners.
|
||||
* Background: the collections holding race column listeners are usually non-transient. This will cause
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import com.sap.sailing.domain.common.Named;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
|
||||
/**
|
||||
* Describes the place where a sailing event with one or more regattas takes place.
|
||||
@@ -8,7 +9,7 @@ import com.sap.sailing.domain.common.Named;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public interface Venue extends Named {
|
||||
public interface Venue extends Named, Renamable {
|
||||
Iterable<CourseArea> getCourseAreas();
|
||||
|
||||
void addCourseArea(CourseArea courseArea);
|
||||
|
||||
+1
-13
@@ -14,10 +14,8 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
|
||||
private static final long serialVersionUID = -7801617988982540470L;
|
||||
|
||||
private final Map<Fleet, TrackedRace> trackedRaces;
|
||||
private String name;
|
||||
private final Map<Fleet, RaceIdentifier> raceIdentifiers;
|
||||
public AbstractRaceColumn(String name) {
|
||||
this.name = name;
|
||||
public AbstractRaceColumn() {
|
||||
this.trackedRaces = new HashMap<Fleet, TrackedRace>();
|
||||
this.raceIdentifiers = new HashMap<Fleet, RaceIdentifier>();
|
||||
}
|
||||
@@ -49,16 +47,6 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String newName) {
|
||||
this.name = newName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceIdentifier getRaceIdentifier(Fleet fleet) {
|
||||
return raceIdentifiers.get(fleet);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.base.impl;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
|
||||
public class BoatClassImpl extends NamedImpl implements BoatClass {
|
||||
|
||||
@@ -10,16 +10,36 @@ import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
public class BuoyImpl extends NamedImpl implements Buoy {
|
||||
private static final long serialVersionUID = 1900673146064411979L;
|
||||
|
||||
private String displayColor;
|
||||
|
||||
private final String color;
|
||||
private final String shape;
|
||||
private final String pattern;
|
||||
|
||||
public BuoyImpl(String name) {
|
||||
super(name);
|
||||
displayColor = null;
|
||||
color = null;
|
||||
shape = null;
|
||||
pattern = null;
|
||||
}
|
||||
|
||||
public BuoyImpl(String name, String color) {
|
||||
super(name);
|
||||
this.displayColor = color;
|
||||
this.color = color;
|
||||
shape = null;
|
||||
pattern = null;
|
||||
}
|
||||
|
||||
public BuoyImpl(String name, String color, String shape) {
|
||||
super(name);
|
||||
this.color = color;
|
||||
this.shape = shape;
|
||||
pattern = null;
|
||||
}
|
||||
|
||||
public BuoyImpl(String name, String color, String shape, String pattern) {
|
||||
super(name);
|
||||
this.color = color;
|
||||
this.shape = shape;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,11 +56,22 @@ public class BuoyImpl extends NamedImpl implements Buoy {
|
||||
|
||||
@Override
|
||||
public Buoy resolve(DomainFactory domainFactory) {
|
||||
Buoy result = domainFactory.getOrCreateBuoy(getName());
|
||||
Buoy result = domainFactory.getOrCreateBuoy(getName(), color, shape, pattern);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getDisplayColor() {
|
||||
return displayColor;
|
||||
@Override
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,14 @@ public class CourseImpl extends NamedImpl implements Course {
|
||||
public void unlockAfterRead() {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
}
|
||||
|
||||
public void lockForWrite() {
|
||||
LockUtil.lockForWrite(lock);
|
||||
}
|
||||
|
||||
public void unlockAfterWrite() {
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
ois.defaultReadObject();
|
||||
@@ -368,7 +376,8 @@ public class CourseImpl extends NamedImpl implements Course {
|
||||
// new waypoint list; since several waypoints can have the same control point, the map goes from
|
||||
// control point to List<Waypoint>. The waypoints in the lists are held in the order of their
|
||||
// occurrence in courseToUpdate.getWaypoints().
|
||||
Map<com.sap.sailing.domain.base.ControlPoint, List<Waypoint>> existingWaypointsByControlPoint = new HashMap<com.sap.sailing.domain.base.ControlPoint, List<Waypoint>>();
|
||||
Map<com.sap.sailing.domain.base.ControlPoint, List<Waypoint>> existingWaypointsByControlPoint =
|
||||
new HashMap<com.sap.sailing.domain.base.ControlPoint, List<Waypoint>>();
|
||||
for (Waypoint waypoint : courseWaypoints) {
|
||||
List<Waypoint> wpl = existingWaypointsByControlPoint.get(waypoint.getControlPoint());
|
||||
if (wpl == null) {
|
||||
|
||||
+18
-16
@@ -30,6 +30,7 @@ import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
||||
@@ -105,26 +106,25 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* the ID which is probably also used as the "device name" and the "sail number" in case of an
|
||||
* {@link MessageType#RPD RPD} message
|
||||
* the ID which is probably also used as the "device name" and the "sail number" in case of an
|
||||
* {@link MessageType#RPD RPD} message
|
||||
*/
|
||||
@Override
|
||||
public Buoy getOrCreateBuoy(String id) {
|
||||
String parsedId = id;
|
||||
String color = null;
|
||||
|
||||
// This is a special hack for the extreme sailing series event where we encoded the buoy color directly into the buoy name
|
||||
String colorTag = "_COLOR:";
|
||||
if(id.contains(colorTag)) {
|
||||
int index = id.indexOf(colorTag);
|
||||
color = id.substring(index + colorTag.length(), id.length());
|
||||
parsedId = id.substring(0, index);
|
||||
}
|
||||
|
||||
Buoy result = buoyCache.get(parsedId);
|
||||
Buoy result = buoyCache.get(id);
|
||||
if (result == null) {
|
||||
result = new BuoyImpl(parsedId, color);
|
||||
buoyCache.put(parsedId, result);
|
||||
result = new BuoyImpl(id);
|
||||
buoyCache.put(id, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Buoy getOrCreateBuoy(String id, String color, String shape, String pattern) {
|
||||
Buoy result = buoyCache.get(id);
|
||||
if (result == null) {
|
||||
result = new BuoyImpl(id, color, shape, pattern);
|
||||
buoyCache.put(id, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -248,6 +248,8 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
return new HighPoint();
|
||||
case HIGH_POINT_ESS_OVERALL:
|
||||
return new HighPointExtremeSailingSeriesOverall();
|
||||
case HIGH_POINT_LAST_BREAKS_TIE:
|
||||
return new HighPointLastBreaksTie();
|
||||
default:
|
||||
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
|
||||
}
|
||||
|
||||
@@ -7,26 +7,29 @@ import java.util.Set;
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Venue;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
|
||||
public class EventImpl extends NamedImpl implements Event {
|
||||
public class EventImpl implements Event {
|
||||
private static final long serialVersionUID = 855135446595485715L;
|
||||
|
||||
private final Set<Regatta> regattas;
|
||||
|
||||
private String name;
|
||||
private String publicationUrl;
|
||||
private final Venue venue;
|
||||
private boolean isPublic;
|
||||
|
||||
public EventImpl(String name, String venueName) {
|
||||
this(name, new VenueImpl(venueName));
|
||||
public EventImpl(String name, String venueName, String publicationUrl, boolean isPublic) {
|
||||
this(name, new VenueImpl(venueName), publicationUrl, isPublic);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param venue must not be <code>null</code>
|
||||
*/
|
||||
public EventImpl(String name, Venue venue) {
|
||||
super(name);
|
||||
public EventImpl(String name, Venue venue, String publicationUrl, boolean isPublic) {
|
||||
assert venue != null;
|
||||
this.name = name;
|
||||
this.venue = venue;
|
||||
this.publicationUrl = publicationUrl;
|
||||
this.isPublic = isPublic;
|
||||
this.regattas = new HashSet<Regatta>();
|
||||
}
|
||||
|
||||
@@ -49,5 +52,37 @@ public class EventImpl extends NamedImpl implements Event {
|
||||
public Venue getVenue() {
|
||||
return venue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getPublicationUrl() {
|
||||
return publicationUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicationUrl(String publicationUrl) {
|
||||
this.publicationUrl = publicationUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newName must not be <code>null</code>
|
||||
*/
|
||||
public void setName(String newName) {
|
||||
if (newName == null) {
|
||||
throw new IllegalArgumentException("An event name must not be null");
|
||||
}
|
||||
this.name = newName;
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
public void setPublic(boolean isPublic) {
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -18,6 +18,7 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
public class RaceColumnInSeriesImpl extends AbstractRaceColumn implements RaceColumnInSeries {
|
||||
private static final long serialVersionUID = -2199678838624406645L;
|
||||
private final Series series;
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Making this transient will leave it <code>null</code> on a replica. This, however, is OK because
|
||||
@@ -34,11 +35,16 @@ public class RaceColumnInSeriesImpl extends AbstractRaceColumn implements RaceCo
|
||||
* carried out.
|
||||
*/
|
||||
public RaceColumnInSeriesImpl(String name, Series series, TrackedRegattaRegistry trackedRegattaRegistry) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
this.series = series;
|
||||
this.trackedRegattaRegistry = trackedRegattaRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Regatta getRegatta() {
|
||||
return getSeries().getRegatta();
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.util.impl.RaceColumnListeners;
|
||||
|
||||
@@ -48,6 +49,13 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
/**
|
||||
* Constructs a regatta with a single default series with empty race column list, and a single default fleet which
|
||||
* is not {@link #isPersistent() marked for persistence}.
|
||||
*
|
||||
* @param trackedRegattaRegistry
|
||||
* used to find the {@link TrackedRegatta} for this column's series' {@link Series#getRegatta() regatta}
|
||||
* in order to re-associate a {@link TrackedRace} passed to {@link #setTrackedRace(Fleet, TrackedRace)}
|
||||
* with this column's series' {@link TrackedRegatta}, and the tracked race's {@link RaceDefinition} with
|
||||
* this column's series {@link Regatta}, respectively. If <code>null</code>, the re-association won't be
|
||||
* carried out.
|
||||
*/
|
||||
public RegattaImpl(String baseName, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme) {
|
||||
this(baseName, boatClass, Collections.singletonList(new SeriesImpl("Default", /* isMedal */false, Collections
|
||||
@@ -207,6 +215,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
raceColumnListeners.notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
|
||||
return raceColumnListeners.canAddRaceColumnToContainer(raceColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnAddedToContainer(raceColumn);
|
||||
@@ -217,6 +230,21 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
|
||||
raceColumnListeners.notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
|
||||
raceColumnListeners.notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return false;
|
||||
|
||||
@@ -6,15 +6,18 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnInSeries;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.util.impl.RaceColumnListeners;
|
||||
|
||||
@@ -28,7 +31,14 @@ public class SeriesImpl extends NamedImpl implements Series, RaceColumnListener
|
||||
private final RaceColumnListeners raceColumnListeners;
|
||||
|
||||
/**
|
||||
* @param fleets must be non-empty
|
||||
* @param fleets
|
||||
* must be non-empty
|
||||
* @param trackedRegattaRegistry
|
||||
* used to find the {@link TrackedRegatta} for this column's series' {@link Series#getRegatta() regatta}
|
||||
* in order to re-associate a {@link TrackedRace} passed to {@link #setTrackedRace(Fleet, TrackedRace)}
|
||||
* with this column's series' {@link TrackedRegatta}, and the tracked race's {@link RaceDefinition} with
|
||||
* this column's series {@link Regatta}, respectively. If <code>null</code>, the re-association won't be
|
||||
* carried out.
|
||||
*/
|
||||
public SeriesImpl(String name, boolean isMedal, Iterable<? extends Fleet> fleets, Iterable<String> raceColumnNames,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry) {
|
||||
@@ -84,16 +94,36 @@ public class SeriesImpl extends NamedImpl implements Series, RaceColumnListener
|
||||
public Iterable<? extends RaceColumnInSeries> getRaceColumns() {
|
||||
return raceColumns;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param trackedRegattaRegistry
|
||||
* used to find the {@link TrackedRegatta} for this column's series' {@link Series#getRegatta() regatta}
|
||||
* in order to re-associate a {@link TrackedRace} passed to {@link #setTrackedRace(Fleet, TrackedRace)}
|
||||
* with this column's series' {@link TrackedRegatta}, and the tracked race's {@link RaceDefinition} with
|
||||
* this column's series {@link Regatta}, respectively. If <code>null</code>, the re-association won't be
|
||||
* carried out.
|
||||
*/
|
||||
@Override
|
||||
public RaceColumnInSeries addRaceColumn(String raceColumnName, TrackedRegattaRegistry trackedRegattaRegistry) {
|
||||
final RaceColumnInSeriesImpl result = createRaceColumn(raceColumnName, trackedRegattaRegistry);
|
||||
result.addRaceColumnListener(this);
|
||||
raceColumns.add(result);
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnAddedToContainer(result);
|
||||
RaceColumnInSeriesImpl result = createRaceColumn(raceColumnName, trackedRegattaRegistry);
|
||||
if (raceColumnListeners.canAddRaceColumnToContainer(result)) {
|
||||
result.addRaceColumnListener(this);
|
||||
raceColumns.add(result);
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnAddedToContainer(result);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trackedRegattaRegistry
|
||||
* used to find the {@link TrackedRegatta} for this column's series' {@link Series#getRegatta() regatta}
|
||||
* in order to re-associate a {@link TrackedRace} passed to {@link #setTrackedRace(Fleet, TrackedRace)}
|
||||
* with this column's series' {@link TrackedRegatta}, and the tracked race's {@link RaceDefinition} with
|
||||
* this column's series {@link Regatta}, respectively. If <code>null</code>, the re-association won't be
|
||||
* carried out.
|
||||
*/
|
||||
private RaceColumnInSeriesImpl createRaceColumn(String raceColumnName, TrackedRegattaRegistry trackedRegattaRegistry) {
|
||||
RaceColumnInSeriesImpl result = new RaceColumnInSeriesImpl(raceColumnName, this, trackedRegattaRegistry);
|
||||
return result;
|
||||
@@ -174,6 +204,16 @@ public class SeriesImpl extends NamedImpl implements Series, RaceColumnListener
|
||||
raceColumnListeners.notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
|
||||
}
|
||||
|
||||
/**
|
||||
* A series listens on its columns; individual columns, however, don't ask whether they can be added; the series itself does.
|
||||
*
|
||||
* @see #addRaceColumn(String, TrackedRegattaRegistry)
|
||||
*/
|
||||
@Override
|
||||
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnAddedToContainer(raceColumn);
|
||||
@@ -184,6 +224,21 @@ public class SeriesImpl extends NamedImpl implements Series, RaceColumnListener
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
|
||||
raceColumnListeners.notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
|
||||
raceColumnListeners.notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
|
||||
raceColumnListeners.notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return false;
|
||||
|
||||
+43
@@ -1,14 +1,30 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.util.impl.RaceColumnListeners;
|
||||
|
||||
public abstract class SimpleAbstractRaceColumn implements RaceColumn {
|
||||
private static final long serialVersionUID = -3590156714385187908L;
|
||||
private final RaceColumnListeners raceColumnListeners;
|
||||
|
||||
/**
|
||||
* The factor by which a medal race score is multiplied by default in the overall point scheme.
|
||||
*
|
||||
* @see #getFactor()
|
||||
*/
|
||||
private static final double DEFAULT_MEDAL_RACE_FACTOR = 2.0;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private Double explicitFactor;
|
||||
|
||||
public SimpleAbstractRaceColumn() {
|
||||
raceColumnListeners = new RaceColumnListeners();
|
||||
@@ -32,4 +48,31 @@ public abstract class SimpleAbstractRaceColumn implements RaceColumn {
|
||||
public void removeRaceColumnListener(RaceColumnListener listener) {
|
||||
getRaceColumnListeners().removeRaceColumnListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceDefinition getRaceDefinition(Fleet fleet) {
|
||||
TrackedRace trackedRace = getTrackedRace(fleet);
|
||||
RaceDefinition result = null;
|
||||
if (trackedRace != null) {
|
||||
result = trackedRace.getRace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getFactor() {
|
||||
return explicitFactor == null ? (isMedalRace() ? DEFAULT_MEDAL_RACE_FACTOR : 1.) : explicitFactor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFactor(Double factor) {
|
||||
Double oldExplicitFactor = getExplicitFactor();
|
||||
explicitFactor = factor;
|
||||
raceColumnListeners.notifyListenersAboutFactorChanged(this, oldExplicitFactor, factor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getExplicitFactor() {
|
||||
return explicitFactor;
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.CourseArea;
|
||||
import com.sap.sailing.domain.base.Venue;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
|
||||
public class VenueImpl extends NamedImpl implements Venue {
|
||||
public class VenueImpl implements Venue {
|
||||
private static final long serialVersionUID = 6854152040737643290L;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The course areas are ordered because they typically follow an ordered naming pattern borrowed from the
|
||||
@@ -18,7 +18,7 @@ public class VenueImpl extends NamedImpl implements Venue {
|
||||
private final List<CourseArea> courseAreas;
|
||||
|
||||
public VenueImpl(String name) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
courseAreas = new ArrayList<CourseArea>();
|
||||
}
|
||||
|
||||
@@ -37,4 +37,18 @@ public class VenueImpl extends NamedImpl implements Venue {
|
||||
courseAreas.remove(courseArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newName must not be <code>null</code>
|
||||
*/
|
||||
public void setName(String newName) {
|
||||
if (newName == null) {
|
||||
throw new IllegalArgumentException("An venue name must not be null");
|
||||
}
|
||||
this.name = newName;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.sap.sailing.domain.leaderboard;
|
||||
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
|
||||
/**
|
||||
* A leaderboard group which is connected to a sailing event
|
||||
* @author Frank Mittag (c5163874)
|
||||
*/
|
||||
public interface EventLeaderboardGroup extends LeaderboardGroup {
|
||||
Event getEvent();
|
||||
}
|
||||
+1
@@ -3,5 +3,6 @@ package com.sap.sailing.domain.leaderboard;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
|
||||
public interface FlexibleRaceColumn extends RaceColumn {
|
||||
void setName(String newName);
|
||||
void setIsMedalRace(boolean isMedalRace);
|
||||
}
|
||||
@@ -261,11 +261,13 @@ public interface Leaderboard extends Named {
|
||||
* Tells if the column represented by <code>raceColumn</code> shall be considered when counting the number of "races
|
||||
* so far" for discarding. Although medal races are never discarded themselves, they still count in determining the
|
||||
* number of "races so far" which is then the basis for deciding how many races may be discarded. If a leaderboard
|
||||
* has corrections for a column then that column shall be considered for discarding and counts for determining the
|
||||
* number of races so far. Also, if a tracked race is connected to the column and has started already, the column is
|
||||
* to be considered for discarding.
|
||||
* has corrections for a column for the <code>competitor</code> then that column shall be considered for discarding
|
||||
* and counts for determining the number of races so far. Also, if a tracked race is connected to the column and has
|
||||
* started already, the column is to be considered for discarding unless the column has several unordered fleets and
|
||||
* not all fleets have started their race yet (see
|
||||
* {@link ScoringScheme#isValidInTotalScore(Leaderboard, RaceColumn, TimePoint)}).
|
||||
*/
|
||||
boolean considerForDiscarding(RaceColumn raceColumn, TimePoint timePoint);
|
||||
boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint);
|
||||
|
||||
public void setResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule);
|
||||
|
||||
|
||||
+6
@@ -4,6 +4,10 @@ import java.util.Collection;
|
||||
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
|
||||
/**
|
||||
* A leaderboard group is used to group one or more {@link Leaderboard}s.
|
||||
* @author Frank Mittag (c5163874)
|
||||
*/
|
||||
public interface LeaderboardGroup extends Renamable {
|
||||
void addLeaderboardGroupListener(LeaderboardGroupListener listener);
|
||||
void removeLeaderboardGroupListener(LeaderboardGroupListener listener);
|
||||
@@ -11,6 +15,8 @@ public interface LeaderboardGroup extends Renamable {
|
||||
String getDescription();
|
||||
void setDescriptiom(String description);
|
||||
|
||||
boolean isDisplayGroupsInReverseOrder();
|
||||
|
||||
Iterable<Leaderboard> getLeaderboards();
|
||||
int getIndexOf(Leaderboard leaderboard);
|
||||
void addLeaderboard(Leaderboard leaderboard);
|
||||
|
||||
+10
-1
@@ -9,6 +9,7 @@ import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
|
||||
/**
|
||||
* A leaderboard has a scoring scheme that decides how race ranks map to scores, how penalties are to be scored,
|
||||
@@ -79,7 +80,15 @@ public interface ScoringScheme extends Serializable {
|
||||
|
||||
/**
|
||||
* Usually, when all other sorting criteria end up in a tie, the last race sailed is used to decide.
|
||||
* @param nullScoresAreBetter TODO
|
||||
*/
|
||||
int compareByLastRace(List<Double> o1Scores, List<Double> o2Scores, boolean nullScoresAreBetter);
|
||||
|
||||
/**
|
||||
* Under certain circumstances, a scoring scheme may decide that the scores of a column are not (yet) to be used
|
||||
* for the leaderboard's total scores. This may, e.g., be the case if a column is split into more than one fleet and
|
||||
* those fleets are unordered. In that case, scores need to be available for all fleets before the column counts
|
||||
* for the total scores.
|
||||
* @param at TODO
|
||||
*/
|
||||
boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at);
|
||||
}
|
||||
+62
-1
@@ -4,10 +4,20 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
|
||||
public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
||||
private static final long serialVersionUID = 6830414905539642446L;
|
||||
@@ -58,6 +68,53 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
||||
public Comparator<Double> getScoreComparator(boolean nullScoresAreBetter) {
|
||||
return new ScoreComparator(nullScoresAreBetter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
|
||||
boolean result;
|
||||
Iterable<? extends Fleet> fleets = raceColumn.getFleets();
|
||||
if (Util.size(fleets) <= 1 || allFleetsOrdered(fleets)) {
|
||||
result = true;
|
||||
} else {
|
||||
// multiple unordered fleets; ensure that the leaderboard has results for all of them
|
||||
result = leaderboardHasResultsForAllFleets(leaderboard, raceColumn, at);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean leaderboardHasResultsForAllFleets(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
|
||||
Set<Fleet> fleetsForWhichNoScoreWasFound = new HashSet<Fleet>();
|
||||
for (Fleet fleet : raceColumn.getFleets()) {
|
||||
final TrackedRace trackedRaceForFleet = raceColumn.getTrackedRace(fleet);
|
||||
if (trackedRaceForFleet == null || !trackedRaceForFleet.hasStarted(at)) {
|
||||
fleetsForWhichNoScoreWasFound.add(fleet);
|
||||
}
|
||||
}
|
||||
for (Competitor competitor : leaderboard.getCompetitors()) {
|
||||
Fleet fleet = raceColumn.getFleetOfCompetitor(competitor);
|
||||
if (fleetsForWhichNoScoreWasFound.contains(fleet)) {
|
||||
try {
|
||||
if (leaderboard.getNetPoints(competitor, raceColumn, at) != null) {
|
||||
fleetsForWhichNoScoreWasFound.remove(fleet);
|
||||
}
|
||||
} catch (NoWindException nwe) {
|
||||
// can't occur here because no started tracked race exists yet for the competitor
|
||||
}
|
||||
}
|
||||
}
|
||||
return fleetsForWhichNoScoreWasFound.isEmpty();
|
||||
}
|
||||
|
||||
private boolean allFleetsOrdered(Iterable<? extends Fleet> fleets) {
|
||||
boolean allOrdered = true;
|
||||
for (Fleet fleet : fleets) {
|
||||
if (fleet.getOrdering() == 0) {
|
||||
allOrdered = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return allOrdered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming both competitors scored in the same number of races, compares the sorted scores.
|
||||
@@ -91,6 +148,10 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
||||
|
||||
@Override
|
||||
public int compareByLastRace(List<Double> o1Scores, List<Double> o2Scores, boolean nullScoresAreBetter) {
|
||||
return getScoreComparator(nullScoresAreBetter).compare(o1Scores.get(o1Scores.size()-1), o2Scores.get(o2Scores.size()-1));
|
||||
int result = 0;
|
||||
if (!o1Scores.isEmpty() && !o2Scores.isEmpty()) {
|
||||
result = getScoreComparator(nullScoresAreBetter).compare(o1Scores.get(o1Scores.size()-1), o2Scores.get(o2Scores.size()-1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+67
-39
@@ -47,11 +47,6 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
static final Double DOUBLE_0 = new Double(0);
|
||||
|
||||
/**
|
||||
* The factor by which a medal race score is multiplied in the overall point scheme
|
||||
*/
|
||||
static final double MEDAL_RACE_FACTOR = 2.0;
|
||||
|
||||
private final SettableScoreCorrection scoreCorrection;
|
||||
|
||||
private ThresholdBasedResultDiscardingRule resultDiscardingRule;
|
||||
@@ -191,7 +186,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
@Override
|
||||
public void setDisplayName(Competitor competitor, String displayName) {
|
||||
String oldDisplayName = displayNames.get(competitor);
|
||||
displayNames.put(competitor, displayName);
|
||||
getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -232,7 +229,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
if (netPoints == null) {
|
||||
result = null;
|
||||
} else {
|
||||
result = (raceColumn.isMedalRace() ? MEDAL_RACE_FACTOR : 1.0) * netPoints;
|
||||
result = raceColumn.getFactor() * netPoints;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -242,9 +239,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
public Double getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException {
|
||||
double result = getCarriedPoints(competitor);
|
||||
for (RaceColumn r : getRaceColumns()) {
|
||||
final Double totalPoints = getTotalPoints(competitor, r, timePoint);
|
||||
if (totalPoints != null) {
|
||||
result += totalPoints;
|
||||
if (getScoringScheme().isValidInTotalScore(this, r, timePoint)) {
|
||||
final Double totalPoints = getTotalPoints(competitor, r, timePoint);
|
||||
if (totalPoints != null) {
|
||||
result += totalPoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -334,18 +333,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean considerForDiscarding(RaceColumn raceColumn, TimePoint timePoint) {
|
||||
boolean result = getScoreCorrection().hasCorrectionFor(raceColumn);
|
||||
if (!result && !raceColumn.isMedalRace()) {
|
||||
for (Fleet fleet : raceColumn.getFleets()) {
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
||||
if (trackedRace != null && trackedRace.hasStarted(timePoint)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
|
||||
TrackedRace trackedRaceForCompetitorInColumn;
|
||||
return getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint) &&
|
||||
(getScoreCorrection().isScoreCorrected(competitor, raceColumn) ||
|
||||
((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null &&
|
||||
trackedRaceForCompetitorInColumn.hasStarted(timePoint)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -371,7 +364,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
final Double correctedScore = correctedResults.getCorrectedScore();
|
||||
return new EntryImpl(trackedRankProvider, correctedScore, correctedResults.isCorrected(),
|
||||
discarded ? DOUBLE_0
|
||||
: correctedScore == null ? null : Double.valueOf(correctedScore * (race.isMedalRace() ? MEDAL_RACE_FACTOR : 1.0)),
|
||||
: correctedScore == null ? null : Double.valueOf(correctedScore * race.getFactor()),
|
||||
correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor));
|
||||
}
|
||||
|
||||
@@ -398,7 +391,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
final Double correctedScore = correctedResults.getCorrectedScore();
|
||||
Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
|
||||
correctedResults.isCorrected(), discarded ? DOUBLE_0 : (correctedScore==null?null:
|
||||
Double.valueOf((correctedScore * (raceColumn.isMedalRace() ? MEDAL_RACE_FACTOR : 1.0)))),
|
||||
Double.valueOf((correctedScore * raceColumn.getFactor()))),
|
||||
correctedResults.getMaxPointsReason(), discarded,
|
||||
raceColumn.getFleetOfCompetitor(competitor));
|
||||
result.put(new Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
|
||||
@@ -421,6 +414,31 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
|
||||
getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
|
||||
getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard.
|
||||
*/
|
||||
@Override
|
||||
public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) {
|
||||
boolean result = true;
|
||||
for (RaceColumn raceColumn : getRaceColumns()) {
|
||||
if (raceColumn.getName().equals(newRaceColumn.getName())) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
|
||||
@@ -432,6 +450,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
|
||||
getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return false;
|
||||
@@ -555,24 +578,29 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
if (!markPassings.isEmpty()) {
|
||||
TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line
|
||||
TimePoint to;
|
||||
if (timePoint.after(markPassings.last().getTimePoint()) &&
|
||||
markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
|
||||
// stop counting when competitor finished the race
|
||||
to = markPassings.last().getTimePoint();
|
||||
} else {
|
||||
if (trackedRace.getEndOfTracking() != null && timePoint.after(trackedRace.getEndOfTracking())) {
|
||||
result = null; // race not finished until end of tracking; no reasonable value can be computed for competitor
|
||||
break;
|
||||
if (!timePoint.before(from)) { // but only if the race started after timePoint
|
||||
TimePoint to;
|
||||
if (timePoint.after(markPassings.last().getTimePoint())
|
||||
&& markPassings.last().getWaypoint() == trackedRace.getRace().getCourse()
|
||||
.getLastWaypoint()) {
|
||||
// stop counting when competitor finished the race
|
||||
to = markPassings.last().getTimePoint();
|
||||
} else {
|
||||
to = timePoint;
|
||||
if (trackedRace.getEndOfTracking() != null
|
||||
&& timePoint.after(trackedRace.getEndOfTracking())) {
|
||||
result = null; // race not finished until end of tracking; no reasonable value can be
|
||||
// computed for competitor
|
||||
break;
|
||||
} else {
|
||||
to = timePoint;
|
||||
}
|
||||
}
|
||||
long timeSpent = to.asMillis() - from.asMillis();
|
||||
if (result == null) {
|
||||
result = timeSpent;
|
||||
} else {
|
||||
result += timeSpent;
|
||||
}
|
||||
}
|
||||
long timeSpent = to.asMillis() - from.asMillis();
|
||||
if (result == null) {
|
||||
result = timeSpent;
|
||||
} else {
|
||||
result += timeSpent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
import com.sap.sailing.domain.leaderboard.EventLeaderboardGroup;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
|
||||
public class EventLeaderboardImpl extends LeaderboardGroupImpl implements EventLeaderboardGroup {
|
||||
private static final long serialVersionUID = -8933204023833087837L;
|
||||
private Event event;
|
||||
|
||||
public EventLeaderboardImpl(Event event, String name, String description, boolean displayGroupsInReverseOrder, List<Leaderboard> leaderboards) {
|
||||
super(name, description, displayGroupsInReverseOrder, leaderboards);
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event getEvent() {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
+20
-21
@@ -140,15 +140,15 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
|
||||
race = r;
|
||||
}
|
||||
}
|
||||
if (race == null) {
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
index = races.lastIndexOf(race);
|
||||
index--;
|
||||
if (index >= 0) {
|
||||
races.remove(race);
|
||||
races.add(index, race);
|
||||
if (race != null) {
|
||||
int index = 0;
|
||||
index = races.lastIndexOf(race);
|
||||
index--;
|
||||
if (index >= 0) {
|
||||
races.remove(race);
|
||||
races.add(index, race);
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,18 +160,17 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
|
||||
race = r;
|
||||
}
|
||||
}
|
||||
if (race == null) {
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
index = races.lastIndexOf(race);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
if (index < races.size()) {
|
||||
races.remove(race);
|
||||
races.add(index, race);
|
||||
if (race != null) {
|
||||
int index = 0;
|
||||
index = races.lastIndexOf(race);
|
||||
if (index != -1) {
|
||||
index++;
|
||||
if (index < races.size()) {
|
||||
races.remove(race);
|
||||
races.add(index, race);
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
|
||||
/**
|
||||
* In this variant of the {@link HighPoint} scoring scheme, scoring by "better race" (sometimes referred to as a
|
||||
* "back run" where the races are sorted with best races first and the first difference is counted) is disabled, such
|
||||
* that even if the last race is not a medeal race, the last race score decides the ordering.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class HighPointLastBreaksTie extends HighPoint {
|
||||
private static final long serialVersionUID = -5338636946886101669L;
|
||||
|
||||
@Override
|
||||
public int compareByBetterScore(List<Double> o1Scores, List<Double> o2Scores, boolean nullScoresAreBetter) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.HIGH_POINT_LAST_BREAKS_TIE;
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -15,6 +15,7 @@ public class LeaderboardGroupImpl implements LeaderboardGroup {
|
||||
private static final long serialVersionUID = 2035927369446736934L;
|
||||
private String name;
|
||||
private String description;
|
||||
private boolean displayGroupsInReverseOrder;
|
||||
private final List<Leaderboard> leaderboards;
|
||||
private final Set<LeaderboardGroupListener> listeners;
|
||||
|
||||
@@ -25,9 +26,10 @@ public class LeaderboardGroupImpl implements LeaderboardGroup {
|
||||
*/
|
||||
private Leaderboard overallLeaderboard;
|
||||
|
||||
public LeaderboardGroupImpl(String name, String description, List<? extends Leaderboard> leaderboards) {
|
||||
public LeaderboardGroupImpl(String name, String description, boolean displayGroupsInReverseOrder, List<? extends Leaderboard> leaderboards) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.displayGroupsInReverseOrder = displayGroupsInReverseOrder;
|
||||
this.leaderboards = new ArrayList<Leaderboard>(leaderboards);
|
||||
this.listeners = new HashSet<LeaderboardGroupListener>();
|
||||
}
|
||||
@@ -140,4 +142,8 @@ public class LeaderboardGroupImpl implements LeaderboardGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayGroupsInReverseOrder() {
|
||||
return displayGroupsInReverseOrder;
|
||||
}
|
||||
}
|
||||
+27
-23
@@ -50,11 +50,13 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
|
||||
private final ScoringScheme scoringScheme;
|
||||
private final Map<Pair<Competitor, RaceColumn>, Double> totalPointsCache;
|
||||
private final boolean nullScoresAreBetter;
|
||||
private final TimePoint timePoint;
|
||||
|
||||
public LeaderboardTotalRankComparator(Leaderboard leaderboard, TimePoint timePoint,
|
||||
ScoringScheme scoringScheme, boolean nullScoresAreBetter) throws NoWindException {
|
||||
super();
|
||||
this.leaderboard = leaderboard;
|
||||
this.timePoint = timePoint;
|
||||
this.scoringScheme = scoringScheme;
|
||||
this.nullScoresAreBetter = nullScoresAreBetter;
|
||||
totalPointsCache = new HashMap<Pair<Competitor, RaceColumn>, Double>();
|
||||
@@ -79,29 +81,31 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
|
||||
Double o1MedalRaceScore = 0.0;
|
||||
Double o2MedalRaceScore = 0.0;
|
||||
for (RaceColumn raceColumn : getLeaderboard().getRaceColumns()) {
|
||||
int preemptiveColumnResult = 0;
|
||||
final Double o1Score = totalPointsCache.get(new Pair<Competitor, RaceColumn>(o1, raceColumn));
|
||||
if (o1Score != null) {
|
||||
o1Scores.add(o1Score);
|
||||
o1ScoreSum += o1Score;
|
||||
}
|
||||
final Double o2Score = totalPointsCache.get(new Pair<Competitor, RaceColumn>(o2, raceColumn));
|
||||
if (o2Score != null) {
|
||||
o2Scores.add(o2Score);
|
||||
o2ScoreSum += o2Score;
|
||||
}
|
||||
if (raceColumn.isMedalRace()) {
|
||||
o1MedalRaceScore = o1Score;
|
||||
o2MedalRaceScore = o2Score;
|
||||
// similar to compareByFleet, however, tracking is not required; having medal race column points
|
||||
// (tracked or manual) is sufficient
|
||||
preemptiveColumnResult = compareByMedalRaceParticipation(o1Score, o2Score);
|
||||
}
|
||||
if (preemptiveColumnResult == 0) {
|
||||
preemptiveColumnResult = compareByFleet(raceColumn, o1, o2);
|
||||
}
|
||||
if (preemptiveColumnResult != 0) {
|
||||
return preemptiveColumnResult;
|
||||
if (getLeaderboard().getScoringScheme().isValidInTotalScore(getLeaderboard(), raceColumn, timePoint)) {
|
||||
int preemptiveColumnResult = 0;
|
||||
final Double o1Score = totalPointsCache.get(new Pair<Competitor, RaceColumn>(o1, raceColumn));
|
||||
if (o1Score != null) {
|
||||
o1Scores.add(o1Score);
|
||||
o1ScoreSum += o1Score;
|
||||
}
|
||||
final Double o2Score = totalPointsCache.get(new Pair<Competitor, RaceColumn>(o2, raceColumn));
|
||||
if (o2Score != null) {
|
||||
o2Scores.add(o2Score);
|
||||
o2ScoreSum += o2Score;
|
||||
}
|
||||
if (raceColumn.isMedalRace()) {
|
||||
o1MedalRaceScore = o1Score;
|
||||
o2MedalRaceScore = o2Score;
|
||||
// similar to compareByFleet, however, tracking is not required; having medal race column points
|
||||
// (tracked or manual) is sufficient
|
||||
preemptiveColumnResult = compareByMedalRaceParticipation(o1Score, o2Score);
|
||||
}
|
||||
if (preemptiveColumnResult == 0) {
|
||||
preemptiveColumnResult = compareByFleet(raceColumn, o1, o2);
|
||||
}
|
||||
if (preemptiveColumnResult != 0) {
|
||||
return preemptiveColumnResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
// now count the races in which they scored; if they scored in a different number of races, prefer the
|
||||
|
||||
+14
-2
@@ -10,7 +10,8 @@ import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
|
||||
|
||||
public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceColumn {
|
||||
private static final long serialVersionUID = -7801617988982540470L;
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* All fleets for which this column can contain a race. This is the maximum set of keys possible for
|
||||
* {@link #trackedRaces} and {@link #raceIdentifiers}.
|
||||
@@ -19,7 +20,8 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo
|
||||
private boolean medalRace;
|
||||
|
||||
public RaceColumnImpl(String name, boolean medalRace, Iterable<Fleet> fleets) {
|
||||
super(name);
|
||||
super();
|
||||
this.name = name;
|
||||
this.medalRace = medalRace;
|
||||
List<Fleet> myFleets = new ArrayList<Fleet>();
|
||||
for (Fleet fleet : fleets) {
|
||||
@@ -29,6 +31,16 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo
|
||||
this.fleets = myFleets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String newName) {
|
||||
this.name = newName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMedalRace() {
|
||||
return medalRace;
|
||||
|
||||
+3
-3
@@ -51,7 +51,7 @@ public class ResultDiscardingRuleImpl implements ThresholdBasedResultDiscardingR
|
||||
|
||||
@Override
|
||||
public Set<RaceColumn> getDiscardedRaceColumns(final Competitor competitor, final Leaderboard leaderboard, final TimePoint timePoint) {
|
||||
int resultsToDiscard = getNumberOfResultsToDiscard(leaderboard.getRaceColumns(), leaderboard, timePoint);
|
||||
int resultsToDiscard = getNumberOfResultsToDiscard(competitor, leaderboard.getRaceColumns(), leaderboard, timePoint);
|
||||
Set<RaceColumn> result;
|
||||
if (resultsToDiscard > 0) {
|
||||
result = new HashSet<RaceColumn>();
|
||||
@@ -94,11 +94,11 @@ public class ResultDiscardingRuleImpl implements ThresholdBasedResultDiscardingR
|
||||
return result;
|
||||
}
|
||||
|
||||
private int getNumberOfResultsToDiscard(Iterable<RaceColumn> raceColumns, Leaderboard leaderboard, TimePoint timePoint) {
|
||||
private int getNumberOfResultsToDiscard(Competitor competitor, Iterable<RaceColumn> raceColumns, Leaderboard leaderboard, TimePoint timePoint) {
|
||||
int numberOfResultsToDiscard;
|
||||
int numberOfStartedRaces = 0;
|
||||
for (RaceColumn raceInLeaderboard : raceColumns) {
|
||||
if (leaderboard.considerForDiscarding(raceInLeaderboard, timePoint)) {
|
||||
if (leaderboard.countRaceForComparisonWithDiscardingThresholds(competitor, raceInLeaderboard, timePoint)) {
|
||||
numberOfStartedRaces++;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -36,7 +36,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
* Instances of subclasses need to register a {@link ScoreCorrectionChangeForwarder} as listener on each leaderboard
|
||||
* referenced by this meta leaderboard to ensure that score corrections applied to the "inner" leaderboard are signaled
|
||||
* to {@link ScoreCorrectionListeners} on this meta leaderboard's score correction. See
|
||||
* {@link #registerScoreCorrectionChangeForwarder(Leaderboard)} and
|
||||
* {@link #registerScoreCorrectionAndRaceColumnChangeForwarder(Leaderboard)} and
|
||||
* {@link #unregisterScoreCorrectionChangeForwarder(Leaderboard)}.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
@@ -130,12 +130,14 @@ public abstract class AbstractMetaLeaderboard extends AbstractSimpleLeaderboardI
|
||||
RaceColumn result = columnsForLeaderboards.get(leaderboard);
|
||||
if (result == null) {
|
||||
result = new MetaLeaderboardColumn(leaderboard, metaFleet);
|
||||
result.addRaceColumnListener(this);
|
||||
columnsForLeaderboards.put(leaderboard, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void registerScoreCorrectionChangeForwarder(Leaderboard leaderboard) {
|
||||
protected void registerScoreCorrectionAndRaceColumnChangeForwarder(Leaderboard leaderboard) {
|
||||
leaderboard.addRaceColumnListener(this);
|
||||
final ScoreCorrectionChangeForwarder listener = new ScoreCorrectionChangeForwarder();
|
||||
scoreCorrectionChangeForwardersByLeaderboard.put(leaderboard, listener);
|
||||
leaderboard.getScoreCorrection().addScoreCorrectionListener(listener);
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
|
||||
LockUtil.lockForWrite(leaderboardsLock);
|
||||
try {
|
||||
leaderboards.add(leaderboard);
|
||||
registerScoreCorrectionChangeForwarder(leaderboard);
|
||||
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(leaderboardsLock);
|
||||
|
||||
+6
-8
@@ -20,9 +20,9 @@ import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
* <p>
|
||||
*
|
||||
* After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard
|
||||
* group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderOnAllLeaderboards()} must be called to
|
||||
* group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to
|
||||
* ensure that score corrections are propagated as desired. This is because score correction listeners are "transient"
|
||||
* and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderOnAllLeaderboards()}
|
||||
* and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()}
|
||||
* during <code>readObject</code> or <code>readResolve</code> is not possible because the object graph hasn't been fully
|
||||
* initialized yet when those methods are called. Hence, the structures for registering the score correction listeners
|
||||
* are not yet in place at that time.
|
||||
@@ -40,13 +40,12 @@ public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard imp
|
||||
super(leaderboardGroup.getName()+" "+LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule);
|
||||
this.leaderboardGroup = leaderboardGroup;
|
||||
leaderboardGroup.addLeaderboardGroupListener(this);
|
||||
registerAsScoreCorrectionChangeForwarderOnAllLeaderboards();
|
||||
registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
|
||||
}
|
||||
|
||||
public void registerAsScoreCorrectionChangeForwarderOnAllLeaderboards() {
|
||||
public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() {
|
||||
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
|
||||
leaderboard.addRaceColumnListener(this);
|
||||
registerScoreCorrectionChangeForwarder(leaderboard);
|
||||
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +56,7 @@ public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard imp
|
||||
|
||||
@Override
|
||||
public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) {
|
||||
leaderboard.addRaceColumnListener(this);
|
||||
registerScoreCorrectionChangeForwarder(leaderboard);
|
||||
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
|
||||
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
||||
for (Fleet fleet : raceColumn.getFleets()) {
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
||||
|
||||
+20
-12
@@ -39,14 +39,6 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
|
||||
return leaderboard.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRaceColumnListener(RaceColumnListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRaceColumnListener(RaceColumnListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends Fleet> getFleets() {
|
||||
return Collections.singleton(metaFleet);
|
||||
@@ -95,10 +87,6 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String newName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseTrackedRace(Fleet fleet) {
|
||||
}
|
||||
@@ -118,6 +106,11 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
|
||||
getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
|
||||
return getRaceColumnListeners().canAddRaceColumnToContainer(raceColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn);
|
||||
@@ -128,6 +121,21 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
|
||||
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
|
||||
getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
|
||||
getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.tracking;
|
||||
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
|
||||
@@ -14,5 +15,7 @@ public interface Maneuver extends GPSFix {
|
||||
SpeedWithBearing getSpeedWithBearingAfter();
|
||||
|
||||
double getDirectionChangeInDegrees();
|
||||
|
||||
Distance getManeuverLoss();
|
||||
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@ public interface RaceChangeListener {
|
||||
|
||||
/**
|
||||
* Invoked after the mark passings have been updated in the {@link TrackedRace}.
|
||||
* @param competitor TODO
|
||||
*
|
||||
* @param oldMarkPassings
|
||||
* the mark passings replaced by <code>markPassings</code>, keyed by the waypoints
|
||||
*/
|
||||
|
||||
+22
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
@@ -13,6 +14,7 @@ import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.tracking.impl.TrackedLegImpl;
|
||||
|
||||
public interface TrackedLegOfCompetitor extends Serializable {
|
||||
Leg getLeg();
|
||||
@@ -169,6 +171,26 @@ public interface TrackedLegOfCompetitor extends Serializable {
|
||||
|
||||
Distance getAverageCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException;
|
||||
|
||||
/**
|
||||
* If the current {@link #getLeg() leg} is +/- {@link TrackedLegImpl#UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees
|
||||
* co-linear with the wind's bearing, the competitor's position is projected onto the line crossing
|
||||
* <code>buoy</code> in the wind's bearing, and the distance from the projection to the <code>buoy</code> is
|
||||
* returned. Otherwise, it is assumed that the leg is neither an upwind nor a downwind leg, and hence the along-track
|
||||
* distance to <code>buoy</code> is returned.
|
||||
*
|
||||
* @param at
|
||||
* the wind estimation is performed for this point in time
|
||||
*/
|
||||
Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at) throws NoWindException;
|
||||
|
||||
/**
|
||||
* Computes the maneuver loss as the "windward distance" that the boat lost compared to not having maneuvered.
|
||||
* As defined by {@link TrackedLegOfCompetitor#getWindwardDistance(Position, Position, TimePoint)}, distances for
|
||||
* reaching legs are computed as the along-track distance. For upwind/downwind legs it's taken to be the
|
||||
* along-wind projection. With this distance measure, the competitors speed and bearing before the maneuver,
|
||||
* as defined by <code>timePointBeforeManeuver</code> is extrapolated until <code>timePointAfterManeuver</code>,
|
||||
* and the resulting extrapolated position's "windward distance" is computed to the competitor's actual position
|
||||
* at that time. This distance is returned as the result of this method.
|
||||
*/
|
||||
Distance getManeuverLoss(MillisecondsTimePoint timePointBeforeManeuver, MillisecondsTimePoint timePointAfterManeuver) throws NoWindException;
|
||||
}
|
||||
@@ -346,7 +346,6 @@ public interface TrackedRace extends Serializable {
|
||||
List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to);
|
||||
|
||||
/**
|
||||
* @param waitForLatest TODO
|
||||
* @return a non-<code>null</code> but perhaps empty list of the maneuvers that <code>competitor</code> performed in
|
||||
* this race between <code>from</code> and <code>to</code>.
|
||||
*/
|
||||
|
||||
+6
-4
@@ -41,12 +41,14 @@ public class CombinedWindAsNavigableSet extends VirtualWindFixesAsNavigableSet {
|
||||
/**
|
||||
* Time point up to and including which the GPS fixes are considered in the race's tracks. Returns the value of
|
||||
* {@link #to} unless it is <code>null</code>. In this case, the time point of the
|
||||
* {@link TrackedRace#getEndOfRace() assumed end of race},
|
||||
* {@link #ceilingToResolution(Wind) ceiled to the resolution of this set} will be returned instead. If no valid
|
||||
* time of a newest event can be obtained from the race, <code>MillisecondsTimePoint(1)</code> is returned instead.
|
||||
* {@link TrackedRace#getEndOfRace() assumed end of race}, {@link #ceilingToResolution(Wind) ceiled to the
|
||||
* resolution of this set} will be considered instead. If no assumed end of race can be determined either, the time
|
||||
* point of the last event received so far is used. If no valid time of a newest event can be obtained from the
|
||||
* race, <code>MillisecondsTimePoint(1)</code> is returned instead.
|
||||
*/
|
||||
protected TimePoint getTo() {
|
||||
return getToInternal() == null ? getTrackedRace().getEndOfRace() == null ? new MillisecondsTimePoint(1)
|
||||
return getToInternal() == null ? getTrackedRace().getEndOfRace() == null ? getTrackedRace().getTimePointOfLastEvent() == null ?
|
||||
new MillisecondsTimePoint(1) : getTrackedRace().getTimePointOfLastEvent()
|
||||
: ceilingToResolution(getTrackedRace().getEndOfRace()) : getToInternal();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -11,11 +11,11 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.Timed;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
|
||||
+19
@@ -1,5 +1,8 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.GPSFix;
|
||||
@@ -8,6 +11,7 @@ import com.sap.sailing.domain.tracking.GPSTrackListener;
|
||||
public class DynamicTrackImpl<ItemType, FixType extends GPSFix> extends
|
||||
GPSFixTrackImpl<ItemType, FixType> implements DynamicGPSFixTrack<ItemType, FixType> {
|
||||
private static final long serialVersionUID = 917778209274148097L;
|
||||
private static final Logger logger = Logger.getLogger(DynamicTrackImpl.class.getName());
|
||||
|
||||
public DynamicTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) {
|
||||
super(trackedItem, millisecondsOverWhichToAverage);
|
||||
@@ -26,6 +30,21 @@ public class DynamicTrackImpl<ItemType, FixType extends GPSFix> extends
|
||||
} finally {
|
||||
unlockAfterWrite();
|
||||
}
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
FixType last;
|
||||
lockForRead();
|
||||
try {
|
||||
logger.finest("GPS fix "+gpsFix+" for "+getTrackedItem()+", isValid="+isValid(getInternalRawFixes(), gpsFix)+
|
||||
", time/distance/speed from last: "+
|
||||
((last=getInternalRawFixes().lower(gpsFix))==null
|
||||
? "null"
|
||||
: (gpsFix.getTimePoint().asMillis()-last.getTimePoint().asMillis()+"ms/"+
|
||||
gpsFix.getPosition().getDistance(last.getPosition())) + "/"+
|
||||
gpsFix.getPosition().getDistance(last.getPosition()).inTime(gpsFix.getTimePoint().asMillis()-last.getTimePoint().asMillis())));
|
||||
} finally {
|
||||
unlockAfterRead();
|
||||
}
|
||||
}
|
||||
for (GPSTrackListener<ItemType, FixType> listener : getListeners()) {
|
||||
listener.gpsFixReceived(gpsFix, getTrackedItem());
|
||||
}
|
||||
|
||||
-1
@@ -63,7 +63,6 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
|
||||
windSourcesToExclude.add(windSourceToExclude);
|
||||
}
|
||||
windSourcesToExclude.add(new WindSourceImpl(WindSourceType.COURSE_BASED));
|
||||
windSourcesToExclude.add(new WindSourceImpl(WindSourceType.TRACK_BASED_ESTIMATION));
|
||||
setWindSourcesToExclude(windSourcesToExclude);
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -794,6 +794,14 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
|
||||
protected void invalidateValidityAndDistanceCaches(FixType gpsFix) {
|
||||
assertWriteLock();
|
||||
TimePoint distanceCacheInvalidationStart = gpsFix.getTimePoint();
|
||||
// see also bug 968: cache entries for intervals ending after the last fix need to be removed because they are
|
||||
// based on the last fix's position and don't extrapolate; now, with the new gpsFix, the positions between the last
|
||||
// and the new fix are defined by interpolation and hence differ from the previous assumption the competitor would have
|
||||
// stopped moving at the last fix.
|
||||
FixType last = getInternalFixes().lower(gpsFix);
|
||||
if (last != null) {
|
||||
distanceCacheInvalidationStart = last.getTimePoint().plus(1); // add one millisecond to invalidate *after* the last fix only
|
||||
}
|
||||
gpsFix.invalidateCache();
|
||||
Iterable<FixType> lowers = getEarlierFixesWhoseValidityMayBeAffected(gpsFix);
|
||||
for (FixType lower : lowers) {
|
||||
|
||||
+14
-2
@@ -1,12 +1,17 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
|
||||
/**
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
|
||||
private static final long serialVersionUID = -5317959066507472580L;
|
||||
private final ManeuverType type;
|
||||
@@ -16,9 +21,10 @@ public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
|
||||
private final SpeedWithBearing speedWithBearingBefore;
|
||||
private final SpeedWithBearing speedWithBearingAfter;
|
||||
private final double directionChangeInDegrees;
|
||||
private final Distance maneuverLoss;
|
||||
|
||||
public ManeuverImpl(ManeuverType type, Tack newTack, Position position, TimePoint timePoint, SpeedWithBearing speedWithBearingBefore,
|
||||
SpeedWithBearing speedWithBearingAfter, double directionChangeInDegrees) {
|
||||
SpeedWithBearing speedWithBearingAfter, double directionChangeInDegrees, Distance maneuverLoss) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.newTack = newTack;
|
||||
@@ -27,6 +33,7 @@ public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
|
||||
this.speedWithBearingBefore = speedWithBearingBefore;
|
||||
this.speedWithBearingAfter = speedWithBearingAfter;
|
||||
this.directionChangeInDegrees = directionChangeInDegrees;
|
||||
this.maneuverLoss = maneuverLoss;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,11 +71,16 @@ public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
|
||||
return newTack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance getManeuverLoss() {
|
||||
return maneuverLoss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " " + type + " on new tack " + newTack + " on position " + position
|
||||
+ " at time point " + timePoint + ". " + "Speed before maneuver " + speedWithBearingBefore
|
||||
+ " speed after maneuver " + speedWithBearingAfter + ". The maneuver changed the course by "
|
||||
+ directionChangeInDegrees + "deg.";
|
||||
+ directionChangeInDegrees + "deg." + (getManeuverLoss() == null ? "" : " Lost approximately "+getManeuverLoss());
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -35,7 +35,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(TrackedLegImpl.class.getName());
|
||||
|
||||
private final static double UPWIND_DOWNWIND_TOLERANCE_IN_DEG = 45; // TracTrac does 22.5, Marcus Baur suggest 40; Nils Schröder suggests 60
|
||||
private final static double UPWIND_DOWNWIND_TOLERANCE_IN_DEG = 45; // TracTrac does 22.5, Marcus Baur suggest 40; Nils Schr�der suggests 60
|
||||
|
||||
private final Leg leg;
|
||||
private final Map<Competitor, TrackedLegOfCompetitor> trackedLegsOfCompetitors;
|
||||
@@ -135,7 +135,8 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
public LegType getLegType(TimePoint at) throws NoWindException {
|
||||
Wind wind = getWindOnLeg(at);
|
||||
if (wind == null) {
|
||||
throw new NoWindException("Need to know wind direction to determine whether leg "+getLeg()+
|
||||
throw new NoWindException("Need to know wind direction in race "+getTrackedRace().getRace().getName()+
|
||||
" to determine whether leg "+getLeg()+
|
||||
" is an upwind or downwind leg");
|
||||
}
|
||||
Bearing legBearing = getLegBearing(at);
|
||||
|
||||
+37
-3
@@ -8,7 +8,7 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.base.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
@@ -196,10 +197,10 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current {@link #getLeg() leg} is +/- {@link #UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees collinear with the
|
||||
* If the current {@link #getLeg() leg} is +/- {@link TrackedLegImpl#UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees collinear with the
|
||||
* wind's bearing, the competitor's position is projected onto the line crossing <code>buoy</code> in the wind's
|
||||
* bearing, and the distance from the projection to the <code>buoy</code> is returned. Otherwise, it is assumed that
|
||||
* the leg is neither an upwind nor a downwind leg, and hence the true distance to <code>buoy</code> is returned.
|
||||
* the leg is neither an upwind nor a downwind leg, and hence the along-track distance to <code>buoy</code> is returned.
|
||||
*
|
||||
* @param at the wind estimation is performed for this point in time
|
||||
*/
|
||||
@@ -578,4 +579,37 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance getManeuverLoss(MillisecondsTimePoint timePointBeforeManeuver,
|
||||
MillisecondsTimePoint timePointAfterManeuver) throws NoWindException {
|
||||
assert timePointBeforeManeuver != null;
|
||||
assert timePointAfterManeuver != null;
|
||||
Distance result;
|
||||
long millisecondsOverWhichToAverageSpeed = getTrackedRace().getMillisecondsOverWhichToAverageSpeed();
|
||||
TimePoint extrapolationBase = timePointBeforeManeuver.minus(millisecondsOverWhichToAverageSpeed/2);
|
||||
final GPSFixTrack<Competitor, GPSFixMoving> track = getTrackedRace().getTrack(getCompetitor());
|
||||
Position estimatedPositionAtBase = track.getEstimatedPosition(extrapolationBase, /* extrapolate */ false);
|
||||
if (estimatedPositionAtBase != null) {
|
||||
SpeedWithBearing estimatedSpeedAtBase = track.getEstimatedSpeed(extrapolationBase);
|
||||
if (estimatedSpeedAtBase != null) {
|
||||
TimePoint extrapolationTarget = timePointAfterManeuver.plus(millisecondsOverWhichToAverageSpeed / 2);
|
||||
Position extrapolatedPositionWithoutManeuver = estimatedSpeedAtBase.travelTo(estimatedPositionAtBase,
|
||||
extrapolationBase, extrapolationTarget);
|
||||
Position estimatedActualPositionAfterManeuver = track.getEstimatedPosition(extrapolationTarget, /* extrapolate */
|
||||
false);
|
||||
if (estimatedActualPositionAfterManeuver != null) {
|
||||
result = getWindwardDistance(estimatedActualPositionAfterManeuver, extrapolatedPositionWithoutManeuver,
|
||||
new MillisecondsTimePoint((timePointBeforeManeuver.asMillis() + timePointAfterManeuver.asMillis())/2));
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+27
-19
@@ -92,7 +92,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName());
|
||||
|
||||
private static final double PENALTY_CIRCLE_DEGREES_THRESHOLD = 320;
|
||||
private static final double PENALTY_CIRCLE_DEGREES_THRESHOLD = 270;
|
||||
|
||||
// TODO make this variable
|
||||
private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500;
|
||||
@@ -1075,9 +1075,9 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
@Override
|
||||
public boolean hasGPSData() {
|
||||
boolean result = false;
|
||||
if(!tracks.values().isEmpty()) {
|
||||
for(GPSFixTrack<Competitor, GPSFixMoving> gpsTrack: tracks.values()) {
|
||||
if(gpsTrack.getFirstRawFix() != null) {
|
||||
if (!tracks.values().isEmpty()) {
|
||||
for (GPSFixTrack<Competitor, GPSFixMoving> gpsTrack : tracks.values()) {
|
||||
if (gpsTrack.getFirstRawFix() != null) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
@@ -1379,7 +1379,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
invalidateMarkPassingTimes();
|
||||
LockUtil.lockForRead(serializationLock);
|
||||
try {
|
||||
@@ -1402,6 +1402,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
toRemove = last;
|
||||
}
|
||||
if (toRemove != null) {
|
||||
logger.info("Removing tracked leg starting at "+toRemove.getFrom()+" from tracked race "+getRace().getName());
|
||||
trackedLegs.remove(toRemove);
|
||||
updated(/* time point */null);
|
||||
}
|
||||
@@ -1526,7 +1527,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
try {
|
||||
for (Competitor competitor : getRace().getCompetitors()) {
|
||||
TrackedLegOfCompetitor leg = getTrackedLeg(competitor, timePoint);
|
||||
if (leg != null) {
|
||||
// if bearings was set to null this indicates there was an exception; no need for further calculations, return null
|
||||
if (bearings != null && leg != null) {
|
||||
TrackedLeg trackedLeg = getTrackedLeg(leg.getLeg());
|
||||
LegType legType;
|
||||
try {
|
||||
@@ -1543,8 +1545,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
if (estimatedSpeedWithConfidence != null
|
||||
&& estimatedSpeedWithConfidence.getObject() != null &&
|
||||
// Mark passings may be missing or far off. This can lead to boats apparently
|
||||
// going
|
||||
// "backwards" regarding the leg's direction; ignore those
|
||||
// going "backwards" regarding the leg's direction; ignore those
|
||||
isNavigatingForward(estimatedSpeedWithConfidence.getObject().getBearing(),
|
||||
trackedLeg, timePoint)) {
|
||||
// additionally to generally excluding maneuvers, reduce confidence around mark
|
||||
@@ -1846,11 +1847,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
List<Pair<GPSFixMoving, CourseChange>> group = new ArrayList<Pair<GPSFixMoving, CourseChange>>();
|
||||
if (!courseChangeSequenceInSameDirection.isEmpty()) {
|
||||
Distance twoHullLengths = competitor.getBoat().getBoatClass().getHullLength().scale(2);
|
||||
SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing
|
||||
// before group
|
||||
SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing
|
||||
// before current
|
||||
// course change
|
||||
SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing before group
|
||||
SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing before current course change
|
||||
Iterator<Pair<GPSFixMoving, CourseChange>> iter = courseChangeSequenceInSameDirection.iterator();
|
||||
double totalCourseChangeInDegrees = 0.0;
|
||||
long totalMilliseconds = 0l;
|
||||
@@ -1909,17 +1907,21 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver,
|
||||
speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
ManeuverType maneuverType;
|
||||
Distance maneuverLoss = null;
|
||||
// the TrackedLegOfCompetitor variables may be null, e.g., in case the time points are before or after the race
|
||||
TrackedLegOfCompetitor legBeforeManeuver = getTrackedLeg(competitor, timePointBeforeManeuver);
|
||||
TrackedLegOfCompetitor legAfterManeuver = getTrackedLeg(competitor, timePointAfterManeuver);
|
||||
if (Math.abs(totalCourseChangeInDegrees) > PENALTY_CIRCLE_DEGREES_THRESHOLD) {
|
||||
maneuverType = ManeuverType.PENALTY_CIRCLE;
|
||||
} else if (legBeforeManeuver != legAfterManeuver
|
||||
&&
|
||||
// check for mask passing first; a tacking / jibe-setting mark rounding thus takes precedence over being detected as a penalty circle
|
||||
if (legBeforeManeuver != legAfterManeuver
|
||||
// a maneuver at the start line is not to be considered a MARK_PASSING maneuver; show a tack as a tack
|
||||
legAfterManeuver != null
|
||||
&& legAfterManeuver != null
|
||||
&& legAfterManeuver.getLeg().getFrom() != getRace().getCourse().getFirstWaypoint()) {
|
||||
maneuverType = ManeuverType.MARK_PASSING;
|
||||
} else if (Math.abs(totalCourseChangeInDegrees) > PENALTY_CIRCLE_DEGREES_THRESHOLD) {
|
||||
maneuverType = ManeuverType.PENALTY_CIRCLE;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
|
||||
}
|
||||
} else {
|
||||
if (tackBeforeManeuver != tackAfterManeuver) {
|
||||
LegType legType = legBeforeManeuver != null ? getTrackedLeg(legBeforeManeuver.getLeg()).getLegType(
|
||||
@@ -1930,9 +1932,15 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
switch (legType) {
|
||||
case UPWIND:
|
||||
maneuverType = ManeuverType.TACK;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
|
||||
}
|
||||
break;
|
||||
case DOWNWIND:
|
||||
maneuverType = ManeuverType.JIBE;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
maneuverType = ManeuverType.UNKNOWN;
|
||||
@@ -1963,7 +1971,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverTimePoint,
|
||||
speedWithBearingOnApproximationAtBeginning, speedWithBearingOnApproximationAtEnd,
|
||||
totalCourseChangeInDegrees);
|
||||
totalCourseChangeInDegrees, maneuverLoss);
|
||||
return maneuver;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
if (trbbc != null) {
|
||||
trbbc.remove(trackedRace);
|
||||
if (trbbc.isEmpty()) {
|
||||
trbbc.remove(trackedRace.getRace().getBoatClass());
|
||||
trackedRacesByBoatClass.remove(trackedRace.getRace().getBoatClass());
|
||||
}
|
||||
}
|
||||
for (RaceListener listener : raceListeners) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.sap.sailing.util.impl;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -16,25 +14,40 @@ public class LockUtil {
|
||||
private static final int NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK = 5;
|
||||
private static final Logger logger = Logger.getLogger(Util.class.getName());
|
||||
private static final Map<NamedReentrantReadWriteLock, TimePoint> lastTimeWriteLockWasObtained = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Bug <a href="http://bugs.sun.com/view_bug.do?bug_id=6822370">http://bugs.sun.com/view_bug.do?bug_id=6822370</a> seems
|
||||
* dangerous, particularly if it happens in a <code>LiveLeaderboardUpdater</code> thread. Even though the bug is reported to
|
||||
* have been fixed in JDK 7(b79) we should be careful. This method tries to acquire a lock, allowing for five seconds to pass.
|
||||
* After five seconds and not having retrieved the lock, tries again until the lock has been acquired.
|
||||
* @param lockParent TODO
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public static void lock(Lock lock, String lockDescriptionForTimeoutLogMessage) {
|
||||
private static void lock(Lock lock, String lockDescriptionForTimeoutLogMessage, NamedReentrantReadWriteLock lockParent) {
|
||||
boolean locked = false;
|
||||
boolean interrupted = false;
|
||||
while (!locked) {
|
||||
try {
|
||||
locked = lock.tryLock(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK, TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
new Throwable("This is where the lock couldn't be acquired").printStackTrace(new PrintStream(
|
||||
bos));
|
||||
logger.info("Couldn't acquire lock "+lockDescriptionForTimeoutLogMessage+" in "+NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK+"s at "+
|
||||
getCurrentStackTrace()+"Trying again...");
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append("Couldn't acquire lock ");
|
||||
message.append(lockDescriptionForTimeoutLogMessage);
|
||||
message.append(" in ");
|
||||
message.append(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK);
|
||||
message.append("s at ");
|
||||
message.append(getCurrentStackTrace());
|
||||
Thread writer = lockParent.getWriter();
|
||||
if (writer != null) {
|
||||
message.append("\nThe current writer is:\n");
|
||||
appendThreadData(message, writer);
|
||||
}
|
||||
message.append("\nThe current readers are:\n");
|
||||
for (Thread reader : lockParent.getReaders()) {
|
||||
appendThreadData(message, reader);
|
||||
}
|
||||
message.append("Trying again...");
|
||||
logger.info(message.toString());
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
@@ -47,9 +60,16 @@ public class LockUtil {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendThreadData(StringBuilder message, Thread writer) {
|
||||
message.append(writer);
|
||||
message.append('\n');
|
||||
message.append(getStackTrace(writer));
|
||||
message.append('\n');
|
||||
}
|
||||
|
||||
public static void lockForRead(NamedReentrantReadWriteLock lock) {
|
||||
lock(lock.readLock(), "readLock "+lock.getName());
|
||||
lock(lock.readLock(), "readLock "+lock.getName(), lock);
|
||||
}
|
||||
|
||||
public static void unlockAfterRead(NamedReentrantReadWriteLock lock) {
|
||||
@@ -57,7 +77,7 @@ public class LockUtil {
|
||||
}
|
||||
|
||||
public static void lockForWrite(NamedReentrantReadWriteLock lock) {
|
||||
lock(lock.writeLock(), "writeLock "+lock.getName());
|
||||
lock(lock.writeLock(), "writeLock "+lock.getName(), lock);
|
||||
synchronized (lastTimeWriteLockWasObtained) {
|
||||
lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now());
|
||||
}
|
||||
@@ -71,7 +91,7 @@ public class LockUtil {
|
||||
}
|
||||
if (timePointWriteLockWasObtained == null) {
|
||||
logger.info("Internal error: write lock "+lock.getName()+" to be unlocked but no time recorded for when it was last obtained.\n"+
|
||||
getCurrentStackTrace());
|
||||
"This is where the lock interaction happened:\n"+getCurrentStackTrace());
|
||||
} else {
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
if (now.asMillis()-timePointWriteLockWasObtained.asMillis() > 10000l) {
|
||||
@@ -81,12 +101,17 @@ public class LockUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getStackTrace(Thread thread) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StackTraceElement sf : thread.getStackTrace()) {
|
||||
sb.append(sf.toString());
|
||||
sb.append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String getCurrentStackTrace() {
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
new Throwable("This is where the lock interaction happened").printStackTrace(new PrintStream(
|
||||
bos));
|
||||
String stackTrace = new String(bos.toByteArray());
|
||||
return stackTrace;
|
||||
return getStackTrace(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
+152
-1
@@ -1,5 +1,12 @@
|
||||
package com.sap.sailing.util.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import com.sap.sailing.domain.common.Named;
|
||||
@@ -7,10 +14,136 @@ import com.sap.sailing.domain.common.Named;
|
||||
public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implements Named {
|
||||
private static final long serialVersionUID = 2906084982209339774L;
|
||||
private final String name;
|
||||
private final WriteLockWrapper writeLockWrapper;
|
||||
private final ReadLockWrapper readLockWrapper;
|
||||
private transient List<Thread> readers;
|
||||
|
||||
private class WriteLockWrapper extends WriteLock {
|
||||
private static final long serialVersionUID = -4234819025137348944L;
|
||||
private final WriteLock writeLock;
|
||||
|
||||
protected WriteLockWrapper(WriteLock writeLock) {
|
||||
super(NamedReentrantReadWriteLock.this);
|
||||
this.writeLock = writeLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
writeLock.lock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockInterruptibly() throws InterruptedException {
|
||||
writeLock.lockInterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryLock() {
|
||||
return writeLock.tryLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return writeLock.tryLock(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
writeLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Condition newCondition() {
|
||||
return writeLock.newCondition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return writeLock.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHeldByCurrentThread() {
|
||||
return writeLock.isHeldByCurrentThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHoldCount() {
|
||||
return writeLock.getHoldCount();
|
||||
}
|
||||
}
|
||||
|
||||
private class ReadLockWrapper extends ReadLock {
|
||||
private static final long serialVersionUID = -4232071609936663619L;
|
||||
private final ReadLock readLock;
|
||||
|
||||
protected ReadLockWrapper(ReadLock readLock) {
|
||||
super(NamedReentrantReadWriteLock.this);
|
||||
this.readLock = readLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
readLock.lock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockInterruptibly() throws InterruptedException {
|
||||
try {
|
||||
readLock.lockInterruptibly();
|
||||
readers.add(Thread.currentThread());
|
||||
} catch (InterruptedException ie) {
|
||||
throw ie;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryLock() {
|
||||
boolean result = readLock.tryLock();
|
||||
if (result) {
|
||||
readers.add(Thread.currentThread());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
boolean result = readLock.tryLock(timeout, unit);
|
||||
if (result) {
|
||||
readers.add(Thread.currentThread());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
readLock.unlock();
|
||||
readers.remove(Thread.currentThread());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Condition newCondition() {
|
||||
return readLock.newCondition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return readLock.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public NamedReentrantReadWriteLock(String name, boolean fair) {
|
||||
super(fair);
|
||||
this.name = name;
|
||||
this.writeLockWrapper = new WriteLockWrapper(super.writeLock());
|
||||
this.readLockWrapper = new ReadLockWrapper(super.readLock());
|
||||
this.readers = Collections.synchronizedList(new ArrayList<Thread>());
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
ois.defaultReadObject();
|
||||
this.readers = Collections.synchronizedList(new ArrayList<Thread>());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -18,8 +151,26 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteLock writeLock() {
|
||||
return writeLockWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadLock readLock() {
|
||||
return readLockWrapper;
|
||||
}
|
||||
|
||||
public List<Thread> getReaders() {
|
||||
return new ArrayList<Thread>(readers);
|
||||
}
|
||||
|
||||
public Thread getWriter() {
|
||||
return getOwner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReentrantReadWriteLock "+getName()+" ("+(isFair()?"fair":"unfair")+")";
|
||||
return "ReentrantReadWriteLock "+getName()+" ("+(isFair()?"fair":"unfair")+")";
|
||||
}
|
||||
}
|
||||
Loaded 100 of 586 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user