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

This commit is contained in:
Jens Rommel committed 2012-11-16 11:47:32 +01:00
commit f4b72a4873
586 files changed
+21759 -8138

No files matched your search

Binary file not shown.
+11
View File
@@ -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
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>
@@ -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
@@ -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);
@@ -0,0 +1,3 @@
package com.sap.sailing.domain.common;
public enum PassingDirection { PORT, STARBOARD };
@@ -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,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;
@@ -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.
@@ -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;
@@ -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;
}
@@ -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;
@@ -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());
@@ -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);
@@ -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>
@@ -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) {
@@ -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
File diff suppressed because it is too large. Load diff
@@ -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
@@ -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);
}
@@ -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 {
@@ -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);
@@ -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
@@ -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();
}
@@ -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();
}
@@ -54,12 +54,12 @@ public enum MessageType {
/**
* Weather Information
*/
WEA(/* isRaceSpecific */ false),
WEA(/* isRaceSpecific */ true),
/**
* Wind Data
*/
WND(/* isRaceSpecific */ false),
WND(/* isRaceSpecific */ true),
/**
* Version
@@ -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);
}
@@ -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,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;
@@ -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);
}
@@ -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) {
@@ -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>
@@ -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;
@@ -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());
@@ -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);
@@ -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++) {
@@ -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();
}
}
@@ -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()));
}
}
@@ -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
@@ -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>();
@@ -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);
@@ -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) {
@@ -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;
}
}
@@ -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;
@@ -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;
}
}
@@ -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();
}
@@ -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);
@@ -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);
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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);
}
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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
@@ -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;
@@ -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++;
}
}
@@ -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);
@@ -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);
@@ -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);
@@ -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();
}
@@ -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
*/
@@ -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>.
*/
@@ -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();
}
@@ -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;
@@ -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());
}
@@ -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);
}
@@ -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) {
@@ -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());
}
}
@@ -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);
@@ -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;
}
}
@@ -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;
}
@@ -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());
}
}
@@ -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