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

This commit is contained in:
fmittag committed 2012-11-12 10:43:16 +01:00
commit 05b81d7646
243 files changed
+11279 -3342

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"> <!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> <module>
<source path='common'/> <source path='common'/>
<source path='common/impl'/>
</module> </module>
@@ -24,6 +24,8 @@ public enum DetailType implements Serializable {
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), MARK_PASSING(0, ASCENDING), CURRENT_LEG(0, ASCENDING),
DISPLAY_LEGS(0, NONE), TIME_TRAVELED(0, ASCENDING), DISPLAY_LEGS(0, NONE), TIME_TRAVELED(0, ASCENDING),
TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS(1, 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), MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS(1, DESCENDING),
TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING); TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING);
@@ -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.AbstractDistance;
import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Distance;
@@ -39,8 +39,9 @@ public interface MongoObjectFactory {
void renameLeaderboard(String oldName, String newName); void renameLeaderboard(String oldName, String newName);
/** /**
* Stores the group, if it doesn't exist or updates it.<br /> * Stores the group, if it doesn't exist or updates it. Leaderboards in the group, which aren't stored in the
* Leaderboards in the group, which aren't stored in the database, will be stored. * 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); void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup);
@@ -198,6 +198,14 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr
public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { public void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
} }
@Override
public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
}
@Override
public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
}
@Override @Override
public boolean isTransient() { public boolean isTransient() {
return false; return false;
@@ -220,6 +220,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result); DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result);
loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, scoreCorrection); loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, scoreCorrection);
loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections); loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections);
loadColumnFactors(dbLeaderboard, result);
// add the leaderboard to the registry // add the leaderboard to the registry
if (leaderboardRegistry != null) { if (leaderboardRegistry != null) {
leaderboardRegistry.addLeaderboard(result); leaderboardRegistry.addLeaderboard(result);
@@ -230,6 +231,17 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return result; 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, private void loadSuppressedCompetitors(DBObject dbLeaderboard,
DelayedLeaderboardCorrections loadedLeaderboardCorrections) { DelayedLeaderboardCorrections loadedLeaderboardCorrections) {
BasicDBList dbSuppressedCompetitorNames = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITORS.name()); BasicDBList dbSuppressedCompetitorNames = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITORS.name());
@@ -432,6 +444,11 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
String name = (String) o.get(FieldNames.LEADERBOARD_GROUP_NAME.name()); String name = (String) o.get(FieldNames.LEADERBOARD_GROUP_NAME.name());
String description = (String) o.get(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.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>(); ArrayList<Leaderboard> leaderboards = new ArrayList<Leaderboard>();
BasicDBList dbLeaderboardIds = (BasicDBList) o.get(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name()); BasicDBList dbLeaderboardIds = (BasicDBList) o.get(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name());
for (Object object : dbLeaderboardIds) { for (Object object : dbLeaderboardIds) {
@@ -443,7 +460,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
} }
} }
logger.info("loaded leaderboard group "+name); 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()); Object overallLeaderboardIdOrName = o.get(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name());
if (overallLeaderboardIdOrName != null) { if (overallLeaderboardIdOrName != null) {
final DBObject dbOverallLeaderboard; final DBObject dbOverallLeaderboard;
@@ -47,10 +47,11 @@ public enum FieldNames {
LEADERBOARD_NAME, LEADERBOARD_SUPPRESSED_COMPETITORS, LEADERBOARD_COLUMNS, LEADERBOARD_COLUMN_NAME, LEADERBOARD_COMPETITOR_DISPLAY_NAMES, 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_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_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
LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, LEADERBOARD_GROUP_LEADERBOARDS, LEADERBOARD_GROUP_OVERALL_LEADERBOARD, 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 to regatta name
RACE_ID_AS_STRING; RACE_ID_AS_STRING;
@@ -152,10 +152,22 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
// at least store the scoring scheme // at least store the scoring scheme
dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name()); dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
} }
storeColumnFactors(leaderboard, dbLeaderboard);
storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard); storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false); 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) { private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) {
dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName()); dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
} }
@@ -234,6 +246,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
} }
} }
if (!dbCorrectionForRace.isEmpty()) { 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); dbScoreCorrections.put(raceColumn.getName(), dbCorrectionForRace);
} }
} }
@@ -276,6 +290,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
BasicDBObject dbLeaderboardGroup = new BasicDBObject(); BasicDBObject dbLeaderboardGroup = new BasicDBObject();
dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName()); dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription()); 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(); final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard();
if (overallLeaderboard != null) { if (overallLeaderboard != null) {
BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName()); BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName());
@@ -157,7 +157,7 @@ public class StoreAndForward implements Runnable {
if (lastMessageCountRecord == null) { if (lastMessageCountRecord == null) {
lastMessageCountCollection.insert(new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 0l)); 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 { public static void main(String[] args) throws InterruptedException, IOException {
int listenPort = Integer.valueOf(args[0]); String hostname = null;
int clientPort = Integer.valueOf(args[1]); 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 mongoDBService = MongoDBService.INSTANCE;
mongoDBService.setConfiguration(MongoDBConfiguration.getDefaultConfiguration()); mongoDBService.setConfiguration(MongoDBConfiguration.getDefaultConfiguration());
SwissTimingAdapterPersistence swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE; 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() { public void run() {
@@ -125,7 +125,7 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
List<SailMasterMessage> result = new ArrayList<SailMasterMessage>(); List<SailMasterMessage> result = new ArrayList<SailMasterMessage>();
for (DBObject o : results) { for (DBObject o : results) {
SailMasterMessage msg = swissTimingFactory.createMessage((String) o.get(FieldNames.MESSAGE_CONTENT.name()), 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); result.add(msg);
} }
return result; 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.SpeedWithBearing;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl; import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; 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.Distance;
import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl; 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.Competitor;
import com.sap.sailing.domain.swisstimingadapter.Mark; import com.sap.sailing.domain.swisstimingadapter.Mark;
import com.sap.sailing.domain.swisstimingadapter.Race; import com.sap.sailing.domain.swisstimingadapter.Race;
import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; 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.CAMMessage;
import com.sap.sailing.domain.swisstimingadapter.classes.messages.CCGMessage; import com.sap.sailing.domain.swisstimingadapter.classes.messages.CCGMessage;
import com.sap.sailing.domain.swisstimingadapter.classes.messages.ClockAtMarkElement; import com.sap.sailing.domain.swisstimingadapter.classes.messages.ClockAtMarkElement;
@@ -193,8 +194,20 @@ public class MessageFileServiceImpl implements MessageFileService {
List<Mark> marks = new ArrayList<Mark>(); List<Mark> marks = new ArrayList<Mark>();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
String[] markDetails = message.getSections()[3 + i].split(";"); String[] markDetails = message.getSections()[3 + i].split(";");
marks.add(new MarkImpl(markDetails[1], Integer.valueOf(markDetails[0]), Arrays.asList(markDetails).subList( MarkType markType = null;
2, markDetails.length))); 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); return new CCGMessage(raceId, marks);
} }
@@ -89,9 +89,10 @@ public class EndToEndListeningStoreAndFowardTest {
new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 0l), new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 0l),
/* upsert */true, /* multi */false); /* upsert */true, /* multi */false);
// important: construct a new domain factory each time to make sure the competitor cache starts out empty // 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, racingEventService = new RacingEventServiceImpl(mongoDBService, SwissTimingFactory.INSTANCE,
new DomainFactoryImpl(new com.sap.sailing.domain.base.impl.DomainFactoryImpl()), new DomainFactoryImpl(baseDomainFactory),
com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE); new com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl(baseDomainFactory));
raceHandles = new ArrayList<RacesHandle>(); raceHandles = new ArrayList<RacesHandle>();
} }
@@ -140,7 +141,19 @@ public class EndToEndListeningStoreAndFowardTest {
String scriptName1 = "/SailMasterDataInterfaceRACandSTL.txt"; String scriptName1 = "/SailMasterDataInterfaceRACandSTL.txt";
String scriptName2 = "/SailMasterDataInterface-ExampleAsText.txt"; String scriptName2 = "/SailMasterDataInterface-ExampleAsText.txt";
setUpUsingScript(racesToTrack, scriptName1, scriptName2); 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>(); Set<TrackedRace> allTrackedRaces = new HashSet<TrackedRace>();
Iterable<Regatta> allRegattas = racingEventService.getAllRegattas(); Iterable<Regatta> allRegattas = racingEventService.getAllRegattas();
for (Regatta regatta : allRegattas) { for (Regatta regatta : allRegattas) {
@@ -163,7 +176,8 @@ public class EndToEndListeningStoreAndFowardTest {
assertEquals(7, Util.size(race.getCourse().getWaypoints())); assertEquals(7, Util.size(race.getCourse().getWaypoints()));
assertEquals(6, Util.size(race.getCourse().getLegs())); assertEquals(6, Util.size(race.getCourse().getLegs()));
for (Competitor competitor : race.getCompetitors()) { 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); final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
track.lockForRead(); track.lockForRead();
try { try {
@@ -227,7 +241,7 @@ public class EndToEndListeningStoreAndFowardTest {
assertEquals(2, Util.size(race.getCourse().getLegs())); assertEquals(2, Util.size(race.getCourse().getLegs()));
for (Competitor competitor : race.getCompetitors()) { for (Competitor competitor : race.getCompetitors()) {
if (!competitor.getName().equals("NED 24") && !competitor.getName().equals("Competitor 35") 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); final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
track.lockForRead(); track.lockForRead();
try { try {
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.swisstimingadapter.test; package com.sap.sailing.domain.swisstimingadapter.test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; 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.Waypoint;
import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.common.ScoringSchemeType; 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.common.impl.Util;
import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Competitor;
import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.Course;
@@ -30,6 +32,62 @@ import com.sap.sailing.server.impl.RacingEventServiceImpl;
import difflib.PatchFailedException; import difflib.PatchFailedException;
public class SimpleDomainFactoryTest { 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 @Test
public void testCourseConfigForBuoy() throws PatchFailedException { public void testCourseConfigForBuoy() throws PatchFailedException {
DomainFactory domainFactory = DomainFactory.INSTANCE; DomainFactory domainFactory = DomainFactory.INSTANCE;
@@ -38,8 +96,8 @@ public class SimpleDomainFactoryTest {
Race race = new RaceImpl("1234", "Race 1234"); Race race = new RaceImpl("1234", "Race 1234");
Iterable<Competitor> competitors = Collections.emptyList(); Iterable<Competitor> competitors = Collections.emptyList();
StartList startList = new StartListImpl("1234", competitors); StartList startList = new StartListImpl("1234", competitors);
Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2")); Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"), /* markType */ null);
Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4")); Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4"), /* markType */ null);
List<Mark> marks = Arrays.asList(mark1, mark2); List<Mark> marks = Arrays.asList(mark1, mark2);
Course course = new CourseImpl("1234", marks); Course course = new CourseImpl("1234", marks);
RaceDefinition raceDefinition = domainFactory.createRaceDefinition(regatta, race, startList, course); RaceDefinition raceDefinition = domainFactory.createRaceDefinition(regatta, race, startList, course);
@@ -63,8 +121,8 @@ public class SimpleDomainFactoryTest {
Race race = new RaceImpl("1234", "Race 1234"); Race race = new RaceImpl("1234", "Race 1234");
Iterable<Competitor> competitors = Collections.emptyList(); Iterable<Competitor> competitors = Collections.emptyList();
StartList startList = new StartListImpl("1234", competitors); StartList startList = new StartListImpl("1234", competitors);
Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2")); Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"), /* markType */ null);
Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4")); Mark mark2 = new MarkImpl("M1", 0, Arrays.asList("D3", "D4"), /* markType */ null);
List<Mark> marks = Arrays.asList(mark1, mark2); List<Mark> marks = Arrays.asList(mark1, mark2);
Course course = new CourseImpl("1234", marks); Course course = new CourseImpl("1234", marks);
RaceDefinition raceDefinition = domainFactory.createRaceDefinition(regatta, race, startList, course); RaceDefinition raceDefinition = domainFactory.createRaceDefinition(regatta, race, startList, course);
@@ -269,8 +269,8 @@ public class EditCCG extends javax.swing.JDialog {
private class MarkWithDesc extends MarkImpl { private class MarkWithDesc extends MarkImpl {
public MarkWithDesc(String description, int index, Iterable<String> devices ){ public MarkWithDesc(String description, int index, Iterable<String> devices) {
super(description, index, devices); super(description, index, devices, /* markType */ null);
} }
@Override @Override
@@ -34,4 +34,10 @@ public interface Fix {
Distance getDistanceToLeader(); Distance getDistanceToLeader();
Distance getDistanceToNextMark(); 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; package com.sap.sailing.domain.swisstimingadapter;
public interface Mark extends WithDescription { public interface Mark extends WithDescription {
public enum MarkType { MANUAL, LINE_CROSSING, BUOY_ROUNDING };
int getIndex(); int getIndex();
Iterable<String> getDevices(); Iterable<String> getDevices();
MarkType getMarkType();
} }
@@ -54,12 +54,12 @@ public enum MessageType {
/** /**
* Weather Information * Weather Information
*/ */
WEA(/* isRaceSpecific */ false), WEA(/* isRaceSpecific */ true),
/** /**
* Wind Data * Wind Data
*/ */
WND(/* isRaceSpecific */ false), WND(/* isRaceSpecific */ true),
/** /**
* Version * Version
@@ -70,15 +70,15 @@ public class DomainFactoryImpl implements DomainFactory {
controlPointCache = new HashMap<Iterable<String>, ControlPoint>(); controlPointCache = new HashMap<Iterable<String>, ControlPoint>();
olympicClassesByID = new HashMap<String, BoatClass>(); 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 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 SAW103000 Women's One Person Dinghy = Laser Damen Laser Radial
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy Männer SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy M�nner
SAM005000 Men's Two Person Dinghy = 470er Männer SAM005000 Men's Two Person Dinghy = 470er M�nner
SAW005000 Women's Two Person Dinghy = 470er Damen SAW005000 Women's Two Person Dinghy = 470er Damen
SAM009000 Men's Skiff = 49er Männer SAM009000 Men's Skiff = 49er M�nner
SAM007000 Men's Keelboat = Starboot Männer SAM007000 Men's Keelboat = Starboot M�nner
SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified) SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified)
*/ */
olympicClassesByID.put("102", baseDomainFactory.getOrCreateBoatClass("RS:X", /* typicallyStartsUpwind */ true)); olympicClassesByID.put("102", baseDomainFactory.getOrCreateBoatClass("RS:X", /* typicallyStartsUpwind */ true));
@@ -148,20 +148,24 @@ public class DomainFactoryImpl implements DomainFactory {
private BoatClass getOrCreateBoatClassFromRaceID(String raceID) { private BoatClass getOrCreateBoatClassFromRaceID(String raceID) {
BoatClass result; 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 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 SAW103000 Women's One Person Dinghy = Laser Damen Laser Radial
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy Männer SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy M�nner
SAM005000 Men's Two Person Dinghy = 470er Männer SAM005000 Men's Two Person Dinghy = 470er M�nner
SAW005000 Women's Two Person Dinghy = 470er Damen SAW005000 Women's Two Person Dinghy = 470er Damen
SAM009000 Men's Skiff = 49er Männer SAM009000 Men's Skiff = 49er M�nner
SAM007000 Men's Keelboat = Starboot Männer SAM007000 Men's Keelboat = Starboot M�nner
SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified) SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified)
*/ */
if (raceID.startsWith("SA") && raceID.length() == 9) { if (raceID.startsWith("SA") && raceID.length() == 9) {
String classID = raceID.substring(3, 6); String classID = raceID.substring(3, 6);
result = olympicClassesByID.get(classID); result = olympicClassesByID.get(classID);
if (result == null) {
// unknown code
result = unknownBoatClass;
}
} else { } else {
result = unknownBoatClass; result = unknownBoatClass;
} }
@@ -19,11 +19,15 @@ public class FixImpl implements Fix {
private final Speed velocityMadeGood; private final Speed velocityMadeGood;
private final Distance distanceToLeader; private final Distance distanceToLeader;
private final Distance distanceToNextMark; 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, public FixImpl(String boatID, TrackerType trackerType, Long ageOfDataInMilliseconds, Position position,
SpeedWithBearing speed, Integer nextMarkIndex, Integer rank, Speed averageSpeedOverGroundPerLeg, SpeedWithBearing speed, Integer nextMarkIndex, Integer rank, Speed averageSpeedOverGroundPerLeg,
Speed velocityMadeGood, Distance distanceToLeader, Distance distanceToNextMark) { Speed velocityMadeGood, Distance distanceToLeader, Distance distanceToNextMark, String boatIRM) {
super(); super();
this.boatID = boatID; this.boatID = boatID;
this.trackerType = trackerType; this.trackerType = trackerType;
@@ -36,6 +40,12 @@ public class FixImpl implements Fix {
this.velocityMadeGood = velocityMadeGood; this.velocityMadeGood = velocityMadeGood;
this.distanceToLeader = distanceToLeader; this.distanceToLeader = distanceToLeader;
this.distanceToNextMark = distanceToNextMark; this.distanceToNextMark = distanceToNextMark;
this.boatIRM = boatIRM;
}
@Override
public String getBoatIRM() {
return boatIRM;
} }
@Override @Override
@@ -8,12 +8,13 @@ public class MarkImpl implements Mark {
private final String description; private final String description;
private final int index; private final int index;
private final Iterable<String> devices; private final Iterable<String> devices;
private final MarkType markType;
public MarkImpl(String description, int index, Iterable<String> devices, MarkType markType) {
public MarkImpl(String description, int index, Iterable<String> devices) {
super(); super();
this.description = description; this.description = description;
this.index = index; this.index = index;
this.markType = markType;
ArrayList<String> l = new ArrayList<String>(); ArrayList<String> l = new ArrayList<String>();
for (String device : devices) { for (String device : devices) {
l.add(device); l.add(device);
@@ -21,6 +22,11 @@ public class MarkImpl implements Mark {
this.devices = l; this.devices = l;
} }
@Override
public MarkType getMarkType() {
return markType;
}
@Override @Override
public String getDescription() { public String getDescription() {
return description; return description;
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
import com.sap.sailing.domain.base.SpeedWithBearing; import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl; import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; 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.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position; 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.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl; 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.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Competitor;
import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.Course;
import com.sap.sailing.domain.swisstimingadapter.Fix; import com.sap.sailing.domain.swisstimingadapter.Fix;
import com.sap.sailing.domain.swisstimingadapter.Mark; 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.MessageType;
import com.sap.sailing.domain.swisstimingadapter.Race; import com.sap.sailing.domain.swisstimingadapter.Race;
import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader; import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader;
@@ -336,7 +337,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
parseHHMMSSToMilliseconds(details[2]); parseHHMMSSToMilliseconds(details[2]);
markIndicesRanksAndTimesSinceStartInMilliseconds.add(new Triple<Integer, Integer, Long>(markIndex, rank, timeSinceStartInMilliseconds)); markIndicesRanksAndTimesSinceStartInMilliseconds.add(new Triple<Integer, Integer, Long>(markIndex, rank, timeSinceStartInMilliseconds));
} }
for (SailMasterListener listener : listeners) { for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
try { try {
listener.receivedTimingData(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds); listener.receivedTimingData(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds);
} catch (Throwable t) { } catch (Throwable t) {
@@ -344,22 +345,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersTMD", t); 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 { private void notifyListenersCAM(SailMasterMessage message) throws ParseException {
List<Triple<Integer, TimePoint, String>> clockAtMarkResults = parseClockAtMarkMessage(message); List<Triple<Integer, TimePoint, String>> clockAtMarkResults = parseClockAtMarkMessage(message);
for (SailMasterListener listener : listeners) { for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
try { try {
listener.receivedClockAtMark(message.getSections()[1], clockAtMarkResults); listener.receivedClockAtMark(message.getSections()[1], clockAtMarkResults);
} catch (Throwable t) { } catch (Throwable t) {
@@ -367,22 +357,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCAM", t); 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) { private void notifyListenersSTL(SailMasterMessage message) {
StartList startListMessage = parseStartListMessage(message); StartList startListMessage = parseStartListMessage(message);
for (SailMasterListener listener : listeners) { for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
try { try {
listener.receivedStartList(message.getSections()[1], startListMessage); listener.receivedStartList(message.getSections()[1], startListMessage);
} catch (Throwable t) { } catch (Throwable t) {
@@ -390,22 +369,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersSTL", t); 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) { private void notifyListenersCCG(SailMasterMessage message) {
Course course = parseCourseConfigurationMessage(message); Course course = parseCourseConfigurationMessage(message);
for (SailMasterListener listener : listeners) { for (SailMasterListener listener : getGeneralAndRaceSpecificListeners(message.getRaceID())) {
try { try {
listener.receivedCourseConfiguration(message.getSections()[1], course); listener.receivedCourseConfiguration(message.getSections()[1], course);
} catch (Throwable t) { } catch (Throwable t) {
@@ -413,16 +381,6 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersCCG", t); 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) { private void notifyListenersRAC(SailMasterMessage message) {
@@ -458,42 +416,52 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
Collection<Fix> fixes = new ArrayList<Fix>(); Collection<Fix> fixes = new ArrayList<Fix>();
for (int i=0; i<count; i++) { for (int i=0; i<count; i++) {
int fixDetailIndex = 0; 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) { if (fixSections.length > 2) {
String boatID = fixSections[fixDetailIndex++]; final String boatID = fixSections[fixDetailIndex++];
TrackerType trackerType = TrackerType.values()[Integer.valueOf(fixSections[fixDetailIndex++])]; final TrackerType trackerType = TrackerType.values()[Integer.valueOf(fixSections[fixDetailIndex++])];
Long ageOfDataInMilliseconds = 1000l * Long.valueOf(fixSections[fixDetailIndex++]); final Long ageOfDataInMilliseconds = 1000l * Long.valueOf(fixSections[fixDetailIndex++]);
Position position = new DegreePosition(Double.valueOf(fixSections[fixDetailIndex++]), final Position position = new DegreePosition(Double.valueOf(fixSections[fixDetailIndex++]),
Double.valueOf(fixSections[fixDetailIndex++])); Double.valueOf(fixSections[fixDetailIndex++]));
Double speedOverGroundInKnots = Double.valueOf(fixSections[fixDetailIndex++]); final Double speedOverGroundInKnots = Double.valueOf(fixSections[fixDetailIndex++]);
Speed averageSpeedOverGround = fixSections[fixDetailIndex].trim().length() == 0 ? null final int alsIndex = postVersion1_0 ? fixDetailIndex+1 : fixDetailIndex;
: new KnotSpeedImpl(Double.valueOf(fixSections[fixDetailIndex])); final int vmgIndex = postVersion1_0 ? fixDetailIndex : fixDetailIndex+1;
fixDetailIndex++; final Speed averageSpeedOverGround = fixSections[alsIndex].trim().length() == 0 ? null
Speed velocityMadeGood = fixSections[fixDetailIndex].trim().length() == 0 ? null : new KnotSpeedImpl( : new KnotSpeedImpl(Double.valueOf(fixSections[alsIndex]));
Double.valueOf(fixSections[fixDetailIndex])); final Speed velocityMadeGood = fixSections[vmgIndex].trim().length() == 0 ? null : new KnotSpeedImpl(
fixDetailIndex++; Double.valueOf(fixSections[vmgIndex]));
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(speedOverGroundInKnots, new DegreeBearingImpl( fixDetailIndex += 2;
Double.valueOf(fixSections[fixDetailIndex++]))); final DegreeBearingImpl cog = new DegreeBearingImpl(
Integer nextMarkIndex = fixSections.length <= fixDetailIndex Double.valueOf(fixSections[fixDetailIndex++]));
final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(speedOverGroundInKnots, cog);
final Integer nextMarkIndex = fixSections.length <= fixDetailIndex
|| fixSections[fixDetailIndex].trim().length() == 0 ? null : Integer || fixSections[fixDetailIndex].trim().length() == 0 ? null : Integer
.valueOf(fixSections[fixDetailIndex]); .valueOf(fixSections[fixDetailIndex]);
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]); : Integer.valueOf(fixSections[fixDetailIndex]);
fixDetailIndex++; fixDetailIndex++;
Distance distanceToLeader = fixSections.length <= fixDetailIndex final Distance distanceToLeader = fixSections.length <= fixDetailIndex
|| fixSections[fixDetailIndex].trim().length() == 0 ? null : new MeterDistance( || fixSections[fixDetailIndex].trim().length() == 0 ? null : new MeterDistance(
Double.valueOf(fixSections[fixDetailIndex])); Double.valueOf(fixSections[fixDetailIndex]));
fixDetailIndex++; fixDetailIndex++;
Distance distanceToNextMark = fixSections.length <= fixDetailIndex final Distance distanceToNextMark = fixSections.length <= fixDetailIndex
|| fixSections[fixDetailIndex].trim().length() == 0 ? null : new MeterDistance( || fixSections[fixDetailIndex].trim().length() == 0 ? null : new MeterDistance(
Double.valueOf(fixSections[fixDetailIndex])); Double.valueOf(fixSections[fixDetailIndex]));
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, 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 { try {
listener.receivedRacePositionData(raceID, status, timePoint, startTimeEstimatedStartTime, millisecondsSinceRaceStart, listener.receivedRacePositionData(raceID, status, timePoint, startTimeEstimatedStartTime, millisecondsSinceRaceStart,
nextMarkIndexForLeader, distanceToNextMarkForLeader, fixes); nextMarkIndexForLeader, distanceToNextMarkForLeader, fixes);
@@ -502,18 +470,15 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersRPD", t); logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersRPD", t);
} }
} }
Set<SailMasterListener> raceSpecificListenerSet = raceSpecificListeners.get(message.getRaceID()); }
if (raceSpecificListenerSet != null) {
for (SailMasterListener listener : raceSpecificListenerSet) { private Set<SailMasterListener> getGeneralAndRaceSpecificListeners(String raceID) {
try { Set<SailMasterListener> allListeners = new HashSet<SailMasterListener>(listeners);
listener.receivedRacePositionData(raceID, status, timePoint, startTimeEstimatedStartTime, millisecondsSinceRaceStart, Set<SailMasterListener> listenersForThisRace = raceSpecificListeners.get(raceID);
nextMarkIndexForLeader, distanceToNextMarkForLeader, fixes); if (listenersForThisRace != null) {
} catch (Throwable t) { allListeners.addAll(listenersForThisRace);
logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+t.getMessage());
logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersRPD", t);
}
}
} }
return allListeners;
} }
@Override @Override
@@ -630,7 +595,20 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
List<Mark> marks = new ArrayList<Mark>(); List<Mark> marks = new ArrayList<Mark>();
for (int i=0; i<count; i++) { for (int i=0; i<count; i++) {
String[] markDetails = courseConfigurationMessage.getSections()[3+i].split(";"); 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); 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 { public SailMasterLiveSimulatorConnectorImpl(String host, int port, RaceSpecificMessageLoader messageLoader, boolean canSendRequests) throws InterruptedException {
super(host, port, messageLoader, canSendRequests); super(host, port, messageLoader, canSendRequests);
bufferedMessageList = Collections.synchronizedList(new ArrayList<SailMasterMessage>()); bufferedMessageList = Collections.synchronizedList(new ArrayList<SailMasterMessage>());
Thread messageDeliveryThread = new Thread("SailMasterLiveSimulatorConnector") { Thread messageDeliveryThread = new Thread("SailMasterLiveSimulatorConnector") {
public void run() { public void run() {
while(true) { while(true) {
@@ -247,7 +247,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
public void receivedClockAtMark(String raceID, public void receivedClockAtMark(String raceID,
List<Triple<Integer, TimePoint, String>> markIndicesTimePointsAndBoatIDs) { List<Triple<Integer, TimePoint, String>> markIndicesTimePointsAndBoatIDs) {
// Ignored because it's covered by TMD. Mail from Kai Hahndorf of 2011-11-15T12:42:00Z: // 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 @Override
@@ -17,10 +17,10 @@ import org.junit.Test;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.impl.DouglasPeucker; import com.sap.sailing.domain.base.impl.DouglasPeucker;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; 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.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl; 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.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixMoving;
@@ -8,8 +8,11 @@ import static org.junit.Assert.assertTrue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import org.junit.Test; import org.junit.Test;
@@ -20,10 +23,14 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series; 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.FleetImpl;
import com.sap.sailing.domain.base.impl.GateImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl; 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.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
@@ -41,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.impl.ScoreCorrectionImpl;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; 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.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 { public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
private ArrayList<Series> series; private ArrayList<Series> series;
@@ -511,6 +522,48 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
// assert that the ordering of competitors equals that of the last race // assert that the ordering of competitors equals that of the last race
assertEquals(reversedCompetitors, leaderboard.getCompetitorsFromBestToWorst(later)); 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 @Test
public void testTieBreakWithTwoVersusOneWins() throws NoWindException { public void testTieBreakWithTwoVersusOneWins() throws NoWindException {
@@ -644,8 +697,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
leaderboard1.getFleet(null)); leaderboard1.getFleet(null));
assertTrue(leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[3]) < assertTrue(leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[3]) <
leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[0])); // c3 better than c0; won last race leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[0])); // c3 better than c0; won last race
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", Arrays.asList(leaderboard1, LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", false, Arrays.asList(leaderboard1,
leaderboard2, leaderboard3)); leaderboard2, leaderboard3));
leaderboardGroup.setOverallLeaderboard(new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), leaderboardGroup.setOverallLeaderboard(new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(),
new ResultDiscardingRuleImpl(new int[0]))); new ResultDiscardingRuleImpl(new int[0])));
leaderboardGroup.getOverallLeaderboard().setSuppressed(c[1], true); leaderboardGroup.getOverallLeaderboard().setSuppressed(c[1], true);
@@ -670,6 +723,32 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
return later; 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) { private List<Competitor> createCompetitors(int numberOfCompetitorsToCreate) {
List<Competitor> result = new ArrayList<Competitor>(); List<Competitor> result = new ArrayList<Competitor>();
for (int i=1; i<=numberOfCompetitorsToCreate; i++) { for (int i=1; i<=numberOfCompetitorsToCreate; i++) {
@@ -20,7 +20,6 @@ import org.junit.Test;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.impl.DouglasPeucker; import com.sap.sailing.domain.base.impl.DouglasPeucker;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; 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.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NoWindException; 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.TimePoint;
import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl; 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.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixTrack;
@@ -78,8 +78,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
@Override @Override
public TimePoint getStartOfRace() { public TimePoint getStartOfRace() {
// TODO Auto-generated method stub return startTime;
return null;
} }
@Override @Override
@@ -10,7 +10,7 @@ Require-Bundle: com.sap.sailing.domain,
com.googlecode.java-diff-utils;bundle-version="1.3.0", com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.domain.common com.sap.sailing.domain.common
Export-Package: com.sap.sailing.domain.tractracadapter, 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 Bundle-ActivationPolicy: lazy
Import-Package: org.json.simple, Import-Package: org.json.simple,
org.json.simple.parser org.json.simple.parser
@@ -108,11 +108,11 @@ public interface RaceColumn extends Named {
*/ */
Pair<Competitor, RaceColumn> getKey(Competitor competitor); 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 * Releases the {@link TrackedRace} previously set by {@link #setTrackedRace(Fleet, TrackedRace)} but leaves the
@@ -123,4 +123,24 @@ public interface RaceColumn extends Named {
* the fleet for which to release its tracked race * the fleet for which to release its tracked race
*/ */
void releaseTrackedRace(Fleet fleet); 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();
} }
@@ -17,6 +17,10 @@ public interface RaceColumnListener extends Serializable {
void raceColumnRemovedFromContainer(RaceColumn raceColumn); void raceColumnRemovedFromContainer(RaceColumn raceColumn);
void raceColumnMoved(RaceColumn raceColumn, int newIndex);
void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor);
/** /**
* A listener can use this to specify that it must not be serialized together with other listeners. * 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 * Background: the collections holding race column listeners are usually non-transient. This will cause
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.impl.NamedImpl; import com.sap.sailing.domain.common.impl.NamedImpl;
public class BoatClassImpl extends NamedImpl implements BoatClass { public class BoatClassImpl extends NamedImpl implements BoatClass {
@@ -48,7 +48,7 @@ public class BuoyImpl extends NamedImpl implements Buoy {
@Override @Override
public Buoy resolve(DomainFactory domainFactory) { public Buoy resolve(DomainFactory domainFactory) {
Buoy result = domainFactory.getOrCreateBuoy(getName()); Buoy result = domainFactory.getOrCreateBuoy(getName(), getDisplayColor());
return result; return result;
} }
@@ -230,6 +230,16 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
raceColumnListeners.notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); 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 @Override
public boolean isTransient() { public boolean isTransient() {
return false; return false;
@@ -223,6 +223,16 @@ public class SeriesImpl extends NamedImpl implements Series, RaceColumnListener
raceColumnListeners.notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); 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 @Override
public boolean isTransient() { public boolean isTransient() {
return false; return false;
@@ -12,6 +12,19 @@ import com.sap.sailing.util.impl.RaceColumnListeners;
public abstract class SimpleAbstractRaceColumn implements RaceColumn { public abstract class SimpleAbstractRaceColumn implements RaceColumn {
private static final long serialVersionUID = -3590156714385187908L; private static final long serialVersionUID = -3590156714385187908L;
private final RaceColumnListeners raceColumnListeners; 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() { public SimpleAbstractRaceColumn() {
raceColumnListeners = new RaceColumnListeners(); raceColumnListeners = new RaceColumnListeners();
@@ -45,4 +58,21 @@ public abstract class SimpleAbstractRaceColumn implements RaceColumn {
} }
return result; 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;
}
} }
@@ -15,6 +15,8 @@ public interface LeaderboardGroup extends Renamable {
String getDescription(); String getDescription();
void setDescriptiom(String description); void setDescriptiom(String description);
boolean isDisplayGroupsInReverseOrder();
Iterable<Leaderboard> getLeaderboards(); Iterable<Leaderboard> getLeaderboards();
int getIndexOf(Leaderboard leaderboard); int getIndexOf(Leaderboard leaderboard);
void addLeaderboard(Leaderboard leaderboard); void addLeaderboard(Leaderboard leaderboard);
@@ -47,11 +47,6 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
static final Double DOUBLE_0 = new Double(0); 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 final SettableScoreCorrection scoreCorrection;
private ThresholdBasedResultDiscardingRule resultDiscardingRule; private ThresholdBasedResultDiscardingRule resultDiscardingRule;
@@ -232,7 +227,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
if (netPoints == null) { if (netPoints == null) {
result = null; result = null;
} else { } else {
result = (raceColumn.isMedalRace() ? MEDAL_RACE_FACTOR : 1.0) * netPoints; result = raceColumn.getFactor() * netPoints;
} }
} }
return result; return result;
@@ -367,7 +362,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
final Double correctedScore = correctedResults.getCorrectedScore(); final Double correctedScore = correctedResults.getCorrectedScore();
return new EntryImpl(trackedRankProvider, correctedScore, correctedResults.isCorrected(), return new EntryImpl(trackedRankProvider, correctedScore, correctedResults.isCorrected(),
discarded ? DOUBLE_0 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)); correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor));
} }
@@ -394,7 +389,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
final Double correctedScore = correctedResults.getCorrectedScore(); final Double correctedScore = correctedResults.getCorrectedScore();
Entry entry = new EntryImpl(trackedRankProvider, correctedScore, Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
correctedResults.isCorrected(), discarded ? DOUBLE_0 : (correctedScore==null?null: 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, correctedResults.getMaxPointsReason(), discarded,
raceColumn.getFleetOfCompetitor(competitor)); raceColumn.getFleetOfCompetitor(competitor));
result.put(new Pair<Competitor, RaceColumn>(competitor, raceColumn), entry); result.put(new Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
@@ -417,6 +412,16 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, 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. * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard.
@@ -566,24 +571,29 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor); NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
if (!markPassings.isEmpty()) { if (!markPassings.isEmpty()) {
TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line
TimePoint to; if (!timePoint.before(from)) { // but only if the race started after timePoint
if (timePoint.after(markPassings.last().getTimePoint()) && TimePoint to;
markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { if (timePoint.after(markPassings.last().getTimePoint())
// stop counting when competitor finished the race && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse()
to = markPassings.last().getTimePoint(); .getLastWaypoint()) {
} else { // stop counting when competitor finished the race
if (trackedRace.getEndOfTracking() != null && timePoint.after(trackedRace.getEndOfTracking())) { to = markPassings.last().getTimePoint();
result = null; // race not finished until end of tracking; no reasonable value can be computed for competitor
break;
} else { } 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;
} }
} }
} }
@@ -10,8 +10,8 @@ public class EventLeaderboardImpl extends LeaderboardGroupImpl implements EventL
private static final long serialVersionUID = -8933204023833087837L; private static final long serialVersionUID = -8933204023833087837L;
private Event event; private Event event;
public EventLeaderboardImpl(Event event, String name, String description, List<Leaderboard> leaderboards) { public EventLeaderboardImpl(Event event, String name, String description, boolean displayGroupsInReverseOrder, List<Leaderboard> leaderboards) {
super(name, description, leaderboards); super(name, description, displayGroupsInReverseOrder, leaderboards);
this.event = event; this.event = event;
} }
@@ -140,15 +140,15 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
race = r; race = r;
} }
} }
if (race == null) { if (race != null) {
return; int index = 0;
} index = races.lastIndexOf(race);
int index = 0; index--;
index = races.lastIndexOf(race); if (index >= 0) {
index--; races.remove(race);
if (index >= 0) { races.add(index, race);
races.remove(race); getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
races.add(index, race); }
} }
} }
@@ -160,18 +160,17 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
race = r; race = r;
} }
} }
if (race == null) { if (race != null) {
return; int index = 0;
} index = races.lastIndexOf(race);
int index = 0; if (index != -1) {
index = races.lastIndexOf(race); index++;
if (index == -1) { if (index < races.size()) {
return; races.remove(race);
} races.add(index, race);
index++; getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
if (index < races.size()) { }
races.remove(race); }
races.add(index, race);
} }
} }
@@ -5,7 +5,10 @@ import java.util.List;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
/** /**
* In this variant of the {@link HighPoint} * 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) * @author Axel Uhl (d043530)
* *
*/ */
@@ -15,6 +15,7 @@ public class LeaderboardGroupImpl implements LeaderboardGroup {
private static final long serialVersionUID = 2035927369446736934L; private static final long serialVersionUID = 2035927369446736934L;
private String name; private String name;
private String description; private String description;
private boolean displayGroupsInReverseOrder;
private final List<Leaderboard> leaderboards; private final List<Leaderboard> leaderboards;
private final Set<LeaderboardGroupListener> listeners; private final Set<LeaderboardGroupListener> listeners;
@@ -25,9 +26,10 @@ public class LeaderboardGroupImpl implements LeaderboardGroup {
*/ */
private Leaderboard overallLeaderboard; 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.name = name;
this.description = description; this.description = description;
this.displayGroupsInReverseOrder = displayGroupsInReverseOrder;
this.leaderboards = new ArrayList<Leaderboard>(leaderboards); this.leaderboards = new ArrayList<Leaderboard>(leaderboards);
this.listeners = new HashSet<LeaderboardGroupListener>(); this.listeners = new HashSet<LeaderboardGroupListener>();
} }
@@ -140,4 +142,8 @@ public class LeaderboardGroupImpl implements LeaderboardGroup {
} }
} }
@Override
public boolean isDisplayGroupsInReverseOrder() {
return displayGroupsInReverseOrder;
}
} }
@@ -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 * 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 * 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 * to {@link ScoreCorrectionListeners} on this meta leaderboard's score correction. See
* {@link #registerScoreCorrectionChangeForwarder(Leaderboard)} and * {@link #registerScoreCorrectionAndRaceColumnChangeForwarder(Leaderboard)} and
* {@link #unregisterScoreCorrectionChangeForwarder(Leaderboard)}. * {@link #unregisterScoreCorrectionChangeForwarder(Leaderboard)}.
* *
* @author Axel Uhl (d043530) * @author Axel Uhl (d043530)
@@ -130,12 +130,14 @@ public abstract class AbstractMetaLeaderboard extends AbstractSimpleLeaderboardI
RaceColumn result = columnsForLeaderboards.get(leaderboard); RaceColumn result = columnsForLeaderboards.get(leaderboard);
if (result == null) { if (result == null) {
result = new MetaLeaderboardColumn(leaderboard, metaFleet); result = new MetaLeaderboardColumn(leaderboard, metaFleet);
result.addRaceColumnListener(this);
columnsForLeaderboards.put(leaderboard, result); columnsForLeaderboards.put(leaderboard, result);
} }
return result; return result;
} }
protected void registerScoreCorrectionChangeForwarder(Leaderboard leaderboard) { protected void registerScoreCorrectionAndRaceColumnChangeForwarder(Leaderboard leaderboard) {
leaderboard.addRaceColumnListener(this);
final ScoreCorrectionChangeForwarder listener = new ScoreCorrectionChangeForwarder(); final ScoreCorrectionChangeForwarder listener = new ScoreCorrectionChangeForwarder();
scoreCorrectionChangeForwardersByLeaderboard.put(leaderboard, listener); scoreCorrectionChangeForwardersByLeaderboard.put(leaderboard, listener);
leaderboard.getScoreCorrection().addScoreCorrectionListener(listener); leaderboard.getScoreCorrection().addScoreCorrectionListener(listener);
@@ -38,7 +38,7 @@ public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
LockUtil.lockForWrite(leaderboardsLock); LockUtil.lockForWrite(leaderboardsLock);
try { try {
leaderboards.add(leaderboard); leaderboards.add(leaderboard);
registerScoreCorrectionChangeForwarder(leaderboard); registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard)); getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
} finally { } finally {
LockUtil.unlockAfterWrite(leaderboardsLock); LockUtil.unlockAfterWrite(leaderboardsLock);
@@ -20,9 +20,9 @@ import com.sap.sailing.domain.tracking.TrackedRace;
* <p> * <p>
* *
* After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard * 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" * 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 * 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 * initialized yet when those methods are called. Hence, the structures for registering the score correction listeners
* are not yet in place at that time. * 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); super(leaderboardGroup.getName()+" "+LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule);
this.leaderboardGroup = leaderboardGroup; this.leaderboardGroup = leaderboardGroup;
leaderboardGroup.addLeaderboardGroupListener(this); leaderboardGroup.addLeaderboardGroupListener(this);
registerAsScoreCorrectionChangeForwarderOnAllLeaderboards(); registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
} }
public void registerAsScoreCorrectionChangeForwarderOnAllLeaderboards() { public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() {
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
leaderboard.addRaceColumnListener(this); registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
registerScoreCorrectionChangeForwarder(leaderboard);
} }
} }
@@ -57,8 +56,7 @@ public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard imp
@Override @Override
public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) { public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) {
leaderboard.addRaceColumnListener(this); registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
registerScoreCorrectionChangeForwarder(leaderboard);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) { for (Fleet fleet : raceColumn.getFleets()) {
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
@@ -39,14 +39,6 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
return leaderboard.getName(); return leaderboard.getName();
} }
@Override
public void addRaceColumnListener(RaceColumnListener listener) {
}
@Override
public void removeRaceColumnListener(RaceColumnListener listener) {
}
@Override @Override
public Iterable<? extends Fleet> getFleets() { public Iterable<? extends Fleet> getFleets() {
return Collections.singleton(metaFleet); return Collections.singleton(metaFleet);
@@ -129,6 +121,16 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); 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 @Override
public boolean isTransient() { public boolean isTransient() {
return false; return false;
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.tracking; package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.base.SpeedWithBearing; 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.ManeuverType;
import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.Tack;
@@ -14,5 +15,7 @@ public interface Maneuver extends GPSFix {
SpeedWithBearing getSpeedWithBearingAfter(); SpeedWithBearing getSpeedWithBearingAfter();
double getDirectionChangeInDegrees(); double getDirectionChangeInDegrees();
Distance getManeuverLoss();
} }
@@ -6,6 +6,7 @@ import java.util.List;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.SpeedWithBearing; 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.Distance;
import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException; 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.Speed;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.tracking.impl.TrackedLegImpl;
public interface TrackedLegOfCompetitor extends Serializable { public interface TrackedLegOfCompetitor extends Serializable {
Leg getLeg(); Leg getLeg();
@@ -169,6 +171,26 @@ public interface TrackedLegOfCompetitor extends Serializable {
Distance getAverageCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException; 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; 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;
} }
@@ -11,11 +11,11 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.base.Timed;
import com.sap.sailing.domain.base.Waypoint; 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.Distance;
import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint; 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.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixTrack;
@@ -1,12 +1,17 @@
package com.sap.sailing.domain.tracking.impl; package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.SpeedWithBearing; 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.ManeuverType;
import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.Maneuver;
/**
* @author Axel Uhl (d043530)
*
*/
public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver { public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
private static final long serialVersionUID = -5317959066507472580L; private static final long serialVersionUID = -5317959066507472580L;
private final ManeuverType type; private final ManeuverType type;
@@ -16,9 +21,10 @@ public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
private final SpeedWithBearing speedWithBearingBefore; private final SpeedWithBearing speedWithBearingBefore;
private final SpeedWithBearing speedWithBearingAfter; private final SpeedWithBearing speedWithBearingAfter;
private final double directionChangeInDegrees; private final double directionChangeInDegrees;
private final Distance maneuverLoss;
public ManeuverImpl(ManeuverType type, Tack newTack, Position position, TimePoint timePoint, SpeedWithBearing speedWithBearingBefore, public ManeuverImpl(ManeuverType type, Tack newTack, Position position, TimePoint timePoint, SpeedWithBearing speedWithBearingBefore,
SpeedWithBearing speedWithBearingAfter, double directionChangeInDegrees) { SpeedWithBearing speedWithBearingAfter, double directionChangeInDegrees, Distance maneuverLoss) {
super(); super();
this.type = type; this.type = type;
this.newTack = newTack; this.newTack = newTack;
@@ -27,6 +33,7 @@ public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
this.speedWithBearingBefore = speedWithBearingBefore; this.speedWithBearingBefore = speedWithBearingBefore;
this.speedWithBearingAfter = speedWithBearingAfter; this.speedWithBearingAfter = speedWithBearingAfter;
this.directionChangeInDegrees = directionChangeInDegrees; this.directionChangeInDegrees = directionChangeInDegrees;
this.maneuverLoss = maneuverLoss;
} }
@Override @Override
@@ -64,11 +71,16 @@ public class ManeuverImpl extends AbstractGPSFixImpl implements Maneuver {
return newTack; return newTack;
} }
@Override
public Distance getManeuverLoss() {
return maneuverLoss;
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + " " + type + " on new tack " + newTack + " on position " + position return super.toString() + " " + type + " on new tack " + newTack + " on position " + position
+ " at time point " + timePoint + ". " + "Speed before maneuver " + speedWithBearingBefore + " at time point " + timePoint + ". " + "Speed before maneuver " + speedWithBearingBefore
+ " speed after maneuver " + speedWithBearingAfter + ". The maneuver changed the course by " + " 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 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 Leg leg;
private final Map<Competitor, TrackedLegOfCompetitor> trackedLegsOfCompetitors; private final Map<Competitor, TrackedLegOfCompetitor> trackedLegsOfCompetitors;
@@ -135,7 +135,8 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
public LegType getLegType(TimePoint at) throws NoWindException { public LegType getLegType(TimePoint at) throws NoWindException {
Wind wind = getWindOnLeg(at); Wind wind = getWindOnLeg(at);
if (wind == null) { 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"); " is an upwind or downwind leg");
} }
Bearing legBearing = getLegBearing(at); 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.Leg;
import com.sap.sailing.domain.base.SpeedWithBearing; import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; 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.Bearing;
import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType; 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.Position;
import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint; 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.common.impl.Util.Pair;
import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack; 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 * 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 * 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 * @param at the wind estimation is performed for this point in time
*/ */
@@ -578,4 +579,37 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
return result; 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 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 // TODO make this variable
private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500; private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500;
@@ -1075,9 +1075,9 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
@Override @Override
public boolean hasGPSData() { public boolean hasGPSData() {
boolean result = false; boolean result = false;
if(!tracks.values().isEmpty()) { if (!tracks.values().isEmpty()) {
for(GPSFixTrack<Competitor, GPSFixMoving> gpsTrack: tracks.values()) { for (GPSFixTrack<Competitor, GPSFixMoving> gpsTrack : tracks.values()) {
if(gpsTrack.getFirstRawFix() != null) { if (gpsTrack.getFirstRawFix() != null) {
result = true; result = true;
break; break;
} }
@@ -1379,7 +1379,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
} }
@Override @Override
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
invalidateMarkPassingTimes(); invalidateMarkPassingTimes();
LockUtil.lockForRead(serializationLock); LockUtil.lockForRead(serializationLock);
try { try {
@@ -1402,6 +1402,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
toRemove = last; toRemove = last;
} }
if (toRemove != null) { if (toRemove != null) {
logger.info("Removing tracked leg starting at "+toRemove.getFrom()+" from tracked race "+getRace().getName());
trackedLegs.remove(toRemove); trackedLegs.remove(toRemove);
updated(/* time point */null); updated(/* time point */null);
} }
@@ -1846,11 +1847,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
List<Pair<GPSFixMoving, CourseChange>> group = new ArrayList<Pair<GPSFixMoving, CourseChange>>(); List<Pair<GPSFixMoving, CourseChange>> group = new ArrayList<Pair<GPSFixMoving, CourseChange>>();
if (!courseChangeSequenceInSameDirection.isEmpty()) { if (!courseChangeSequenceInSameDirection.isEmpty()) {
Distance twoHullLengths = competitor.getBoat().getBoatClass().getHullLength().scale(2); Distance twoHullLengths = competitor.getBoat().getBoatClass().getHullLength().scale(2);
SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing before group
// before group SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing before current course change
SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing
// before current
// course change
Iterator<Pair<GPSFixMoving, CourseChange>> iter = courseChangeSequenceInSameDirection.iterator(); Iterator<Pair<GPSFixMoving, CourseChange>> iter = courseChangeSequenceInSameDirection.iterator();
double totalCourseChangeInDegrees = 0.0; double totalCourseChangeInDegrees = 0.0;
long totalMilliseconds = 0l; long totalMilliseconds = 0l;
@@ -1909,17 +1907,21 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver, Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver,
speedWithBearingOnApproximationAtEnd.getBearing()); speedWithBearingOnApproximationAtEnd.getBearing());
ManeuverType maneuverType; ManeuverType maneuverType;
Distance maneuverLoss = null;
// the TrackedLegOfCompetitor variables may be null, e.g., in case the time points are before or after the race // 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 legBeforeManeuver = getTrackedLeg(competitor, timePointBeforeManeuver);
TrackedLegOfCompetitor legAfterManeuver = getTrackedLeg(competitor, timePointAfterManeuver); TrackedLegOfCompetitor legAfterManeuver = getTrackedLeg(competitor, timePointAfterManeuver);
if (Math.abs(totalCourseChangeInDegrees) > PENALTY_CIRCLE_DEGREES_THRESHOLD) { // check for mask passing first; a tacking / jibe-setting mark rounding thus takes precedence over being detected as a penalty circle
maneuverType = ManeuverType.PENALTY_CIRCLE; if (legBeforeManeuver != legAfterManeuver
} else if (legBeforeManeuver != legAfterManeuver
&&
// a maneuver at the start line is not to be considered a MARK_PASSING maneuver; show a tack as a tack // 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()) { && legAfterManeuver.getLeg().getFrom() != getRace().getCourse().getFirstWaypoint()) {
maneuverType = ManeuverType.MARK_PASSING; 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 { } else {
if (tackBeforeManeuver != tackAfterManeuver) { if (tackBeforeManeuver != tackAfterManeuver) {
LegType legType = legBeforeManeuver != null ? getTrackedLeg(legBeforeManeuver.getLeg()).getLegType( LegType legType = legBeforeManeuver != null ? getTrackedLeg(legBeforeManeuver.getLeg()).getLegType(
@@ -1930,9 +1932,15 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
switch (legType) { switch (legType) {
case UPWIND: case UPWIND:
maneuverType = ManeuverType.TACK; maneuverType = ManeuverType.TACK;
if (legBeforeManeuver != null) {
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
}
break; break;
case DOWNWIND: case DOWNWIND:
maneuverType = ManeuverType.JIBE; maneuverType = ManeuverType.JIBE;
if (legBeforeManeuver != null) {
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
}
break; break;
default: default:
maneuverType = ManeuverType.UNKNOWN; maneuverType = ManeuverType.UNKNOWN;
@@ -1963,7 +1971,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
} }
Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverTimePoint, Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverTimePoint,
speedWithBearingOnApproximationAtBeginning, speedWithBearingOnApproximationAtEnd, speedWithBearingOnApproximationAtBeginning, speedWithBearingOnApproximationAtEnd,
totalCourseChangeInDegrees); totalCourseChangeInDegrees, maneuverLoss);
return maneuver; return maneuver;
} }
@@ -69,6 +69,12 @@ public class RaceColumnListeners implements Serializable {
} }
} }
public void notifyListenersAboutFactorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
for (RaceColumnListener listener : getRaceColumnListeners()) {
listener.factorChanged(raceColumn, oldFactor, newFactor);
}
}
public void notifyListenersAboutIsMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { public void notifyListenersAboutIsMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
for (RaceColumnListener listener : getRaceColumnListeners()) { for (RaceColumnListener listener : getRaceColumnListeners()) {
listener.isMedalRaceChanged(raceColumn, newIsMedalRace); listener.isMedalRaceChanged(raceColumn, newIsMedalRace);
@@ -87,6 +93,12 @@ public class RaceColumnListeners implements Serializable {
} }
} }
public void notifyListenersAboutRaceColumnMoved(RaceColumn raceColumn, int newIndex) {
for (RaceColumnListener listener : getRaceColumnListeners()) {
listener.raceColumnMoved(raceColumn, newIndex);
}
}
private Set<RaceColumnListener> getRaceColumnListeners() { private Set<RaceColumnListener> getRaceColumnListeners() {
synchronized (raceColumnListeners) { synchronized (raceColumnListeners) {
return new HashSet<RaceColumnListener>(raceColumnListeners); return new HashSet<RaceColumnListener>(raceColumnListeners);
@@ -103,4 +115,5 @@ public class RaceColumnListeners implements Serializable {
} }
return result; return result;
} }
} }
@@ -17,9 +17,26 @@ import com.sap.sailing.util.impl.SmartFutureCache.UpdateInterval;
* A cache for which a background update can be triggered. Readers can decide whether they want to wait for any ongoing * A cache for which a background update can be triggered. Readers can decide whether they want to wait for any ongoing
* background update or read the latest cached value for a key. An update trigger can provide an optional parameter for * background update or read the latest cached value for a key. An update trigger can provide an optional parameter for
* the update which may, e.g., control the interval of the cached value to update. When an update is triggered and * the update which may, e.g., control the interval of the cached value to update. When an update is triggered and
* another update is already running, the update is queued. If there already is an update queued for the same key, * another update is already running, the update is queued. If there already is an update queued for the same key, the
* the optional update parameters are "joined" (for example, the two update intervals are joined to form one interval * optional update parameters are "joined" (for example, the two update intervals are joined to form one interval which
* which incorporates both original update intervals). * incorporates both original update intervals).
* <p>
*
* A {@link CacheUpdater} needs to be passed to the constructor which carries out the actual calculation whose values
* are to be cached. The {@link CacheUpdater} interface assumes that a cache update may be computed in two steps: first,
* a value is computed for a key and an update interval which may be computationally expensive. Then, in a second step,
* the new value is combined with the previous cache value for the same key and update interval. The default
* implementation of {@link CacheUpdater#provideNewCacheValue(Object, Object, Object, UpdateInterval)} simply returns
* the <code>computedCacheUpdate</code> parameter which is the result computed by
* {@link CacheUpdater#computeCacheUpdate(Object, UpdateInterval)} before.
*
* @param <K>
* the key type for which values of type <code>V</code> are cached
* @param <V>
* the value type of which instances are cached for particular keys of type <code>K</code>
* @param <U>
* a parameter type for the cache update method for a single key, such that the parameters of multiple queued
* requests for the same key can be joined into one for a faster update
* *
* @author Axel Uhl (D043530) * @author Axel Uhl (D043530)
* *
@@ -85,7 +102,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
V computeCacheUpdate(K key, U updateInterval) throws Exception; V computeCacheUpdate(K key, U updateInterval) throws Exception;
/** /**
* Expected to deliver an updated cache value quick (compared to the potentially much more expensive * Expected to deliver an updated cache value quickly (compared to the potentially much more expensive
* {@link #computeCacheUpdate(Object, UpdateInterval)} method which is run in a background task and doesn't lock * {@link #computeCacheUpdate(Object, UpdateInterval)} method which is run in a background task and doesn't lock
* the cache for readers). * the cache for readers).
* *
@@ -220,6 +237,12 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
recalculator.execute(future); recalculator.execute(future);
} }
/**
* Fetches a value for <code>key</code> from the cache. If no {@link #triggerUpdate(Object, UpdateInterval)} for the <code>key</code>
* has ever happened, <code>null</code> will be returned. Otherwise, depending on <code>waitForLatest</code> the result is taken
* from the cache straight away (<code>waitForLatest==false</code>) or, if a re-calculation for the <code>key</code> is still
* ongoing, the result of that ongoing re-calculation is returned.
*/
public V get(K key, boolean waitForLatest) { public V get(K key, boolean waitForLatest) {
V value = null; V value = null;
if (waitForLatest) { if (waitForLatest) {
@@ -53,9 +53,9 @@ public class TestColumnSwapping {
try { try {
// get Leaderboard with name and current date // get Leaderboard with name and current date
leaderboardOriginalDTO = new LeaderboardDTO(null, null, /* higherScoreIsBetter */ false); leaderboardOriginalDTO = new LeaderboardDTO(null, null, /* higherScoreIsBetter */ false);
leaderboardOriginalDTO.addRace("Race1", DEFAULT_FLEET, true, null, null); leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, DEFAULT_FLEET, true, null, null);
leaderboardOriginalDTO.addRace("Race3", DEFAULT_FLEET, true, null, null); leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, DEFAULT_FLEET, true, null, null);
leaderboardOriginalDTO.addRace("Race2", DEFAULT_FLEET, true, null, null); leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, DEFAULT_FLEET, true, null, null);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -104,9 +104,9 @@ public class TestColumnSwapping {
public void testLeaderBoardDTOMethods() { public void testLeaderBoardDTOMethods() {
lb = new LeaderboardDTO(null, null, /* higherScoreIsBetter */ false); lb = new LeaderboardDTO(null, null, /* higherScoreIsBetter */ false);
assertNotNull("Leaderboard != NULL", lb); assertNotNull("Leaderboard != NULL", lb);
lb.addRace("1", DEFAULT_FLEET, false, null, null); lb.addRace("1", /* explicitFactor */ null, DEFAULT_FLEET, false, null, null);
lb.addRace("2", DEFAULT_FLEET, false, null, null); lb.addRace("2", /* explicitFactor */ null, DEFAULT_FLEET, false, null, null);
lb.addRace("3", DEFAULT_FLEET, true, null, null); lb.addRace("3", /* explicitFactor */ null, DEFAULT_FLEET, true, null, null);
lb.moveRaceDown("1"); lb.moveRaceDown("1");
String[] s = new String[] { "2", "1", "3" }; String[] s = new String[] { "2", "1", "3" };
for (int i = 0; i < lb.getRaceList().size(); i++) for (int i = 0; i < lb.getRaceList().size(); i++)
@@ -186,7 +186,7 @@ body ::-webkit-scrollbar-track {
.subTitleLabelWrapper { .subTitleLabelWrapper {
position: absolute; position: absolute;
min-width: 400px; min-width: 200px;
text-align: right; text-align: right;
height: 68px; height: 68px;
right: 0; right: 0;
@@ -4,7 +4,7 @@
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/> <stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/> <booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES"> <listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.ui.RaceBoard"/> <listEntry value="com.sap.sailing.gwt.ui.TvView"/>
</listAttribute> </listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/AdminConsole.html"/> <stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/AdminConsole.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
@@ -19,7 +19,7 @@
</listAttribute> </listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/> <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard"/> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m"/> <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m"/>
</launchConfiguration> </launchConfiguration>
+33
View File
@@ -0,0 +1,33 @@
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>SAP Sailing Analytics TV View</title>
<meta content='width = device-width, initial-scale = 1.0, user-scalable = yes' name='viewport'>
<meta content='yes' name='apple-mobile-web-app-capable'>
<meta content='black' name='apple-mobile-web-app-status-bar-style'>
<!-- <link href='iphone-splash-screen.png' rel='apple-touch-startup-image'> -->
<link href='images/sap-sailing-app-icon.png' rel='apple-touch-icon'>
<link rel="shortcut icon" type="image/x-icon" href="images/sap.ico" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts.js"></script>
<!-- Optionally, add a highcharts theme file -->
<script type="text/javascript" src="highcharts/js/themes/grid.js"></script>
<script type="text/javascript" language="javascript" src="com.sap.sailing.gwt.ui.TvView/com.sap.sailing.gwt.ui.TvView.nocache.js"></script>
</head>
<body>
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
<noscript>
<div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
Your web browser must have JavaScript enabled
in order for this application to display correctly.
</div>
</noscript>
</body>
</html>
+3 -1
View File
@@ -28,5 +28,7 @@ bin.includes = META-INF/,\
highcharts/,\ highcharts/,\
Spectator.css,\ Spectator.css,\
fontface.css,\ fontface.css,\
webfonts/ webfonts/,\
TvView.html,\
com.sap.sailing.gwt.ui.TvView/
output.. = WEB-INF/classes/ output.. = WEB-INF/classes/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 19 KiB

+1
View File
@@ -50,6 +50,7 @@
<module>com.sap.sailing.gwt.ui.AdminConsole</module> <module>com.sap.sailing.gwt.ui.AdminConsole</module>
<module>com.sap.sailing.gwt.ui.Leaderboard</module> <module>com.sap.sailing.gwt.ui.Leaderboard</module>
<module>com.sap.sailing.gwt.ui.UserManagement</module> <module>com.sap.sailing.gwt.ui.UserManagement</module>
<module>com.sap.sailing.gwt.ui.TvView</module>
</modules> </modules>
<gen>${basedir}/.generated</gen> <gen>${basedir}/.generated</gen>
<localWorkers>3</localWorkers> <localWorkers>3</localWorkers>
@@ -8,6 +8,9 @@ interface AdminConsoleResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/settings.png") @Source("com/sap/sailing/gwt/ui/client/images/settings.png")
ImageResource settingsIcon(); ImageResource settingsIcon();
@Source("com/sap/sailing/gwt/ui/client/images/settingsActionIcon.png")
ImageResource settingsActionIcon();
@Source("com/sap/sailing/gwt/ui/client/images/remove.png") @Source("com/sap/sailing/gwt/ui/client/images/remove.png")
ImageResource removeIcon(); ImageResource removeIcon();
@@ -1,21 +1,66 @@
package com.sap.sailing.gwt.ui.adminconsole; package com.sap.sailing.gwt.ui.adminconsole;
import com.google.gwt.cell.client.AbstractSafeHtmlCell; import com.google.gwt.cell.client.AbstractSafeHtmlCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.ValueUpdater; import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.resources.client.ImageResource; import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safecss.shared.SafeStyles; import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safecss.shared.SafeStylesUtils;
import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.text.shared.SimpleSafeHtmlRenderer; import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.client.ui.AbstractImagePrototype; import com.google.gwt.user.client.ui.AbstractImagePrototype;
/**
* A cell type for use in a {@link CellTable} which renders a horizontal sequence of images. Use by subclassing and
* overriding the {@link #getImageSpecs()} method where action name, tool tip and image prototype can be provided for
* each image to be rendered. Add a {@link FieldUpdater} to the column rendered using this cell type; the updater will
* receive the clicked image's action name as the <code>value</code> parameter. The image rendering style can be adjusted by
* overriding the default {@link #getImageStyle()} implementation.
*/
public abstract class ImagesBarCell extends AbstractSafeHtmlCell<String> { public abstract class ImagesBarCell extends AbstractSafeHtmlCell<String> {
/**
* Subclasses use this template to render a single image tag
*/
private final static ImagesBarTemplates imageTemplate = GWT.create(ImagesBarTemplates.class);
protected class ImageSpec {
private final AbstractImagePrototype imagePrototype;
private final String actionName;
private final String tooltip;
public ImageSpec(String actionName, String tooltip, AbstractImagePrototype imagePrototype) {
super();
this.imagePrototype = imagePrototype;
this.actionName = actionName;
this.tooltip = tooltip;
}
public AbstractImagePrototype getImagePrototype() {
return imagePrototype;
}
public String getActionName() {
return actionName;
}
public String getTooltip() {
return tooltip;
}
}
interface ImagesBarTemplates extends SafeHtmlTemplates { interface ImagesBarTemplates extends SafeHtmlTemplates {
/**
* @param name
* @param title
* the tool-tip to display for the image on mouse-over
* @param value
* how to render the image; this needs to be an &lt;img&gt; tag, not enclosed by any other element,
* as returned by {@link ImagesBarCell#makeImagePrototype(ImageResource)}
*/
@SafeHtmlTemplates.Template("<div name=\"{0}\" style=\"{1}\" title=\"{2}\">{3}</div>") @SafeHtmlTemplates.Template("<div name=\"{0}\" style=\"{1}\" title=\"{2}\">{3}</div>")
SafeHtml cell(String name, SafeStyles styles, String title, SafeHtml value); SafeHtml cell(String name, SafeStyles styles, String title, SafeHtml value);
} }
@@ -28,6 +73,10 @@ public abstract class ImagesBarCell extends AbstractSafeHtmlCell<String> {
super(renderer, "click", "keydown"); super(renderer, "click", "keydown");
} }
protected static ImagesBarTemplates getImageTemplate() {
return imageTemplate;
}
/** /**
* Called when an event occurs in a rendered instance of this Cell. The * Called when an event occurs in a rendered instance of this Cell. The
* parent element refers to the element that contains the rendered cell, NOT * parent element refers to the element that contains the rendered cell, NOT
@@ -42,26 +91,22 @@ public abstract class ImagesBarCell extends AbstractSafeHtmlCell<String> {
if ("click".equals(event.getType())) { if ("click".equals(event.getType())) {
// Ignore clicks that occur outside of the outermost element. // Ignore clicks that occur outside of the outermost element.
EventTarget eventTarget = event.getEventTarget(); EventTarget eventTarget = event.getEventTarget();
if (parent.isOrHasChild(Element.as(eventTarget))) { if (parent.isOrHasChild(Element.as(eventTarget))) {
// if (parent.getFirstChildElement().isOrHasChild( // if (parent.getFirstChildElement().isOrHasChild(
// Element.as(eventTarget))) { // Element.as(eventTarget))) {
// use this to get the selected element!! // use this to get the selected element!!
Element el = Element.as(eventTarget); Element el = Element.as(eventTarget);
// check if we really click on the image // check if we really click on the image
if (el.getNodeName().equalsIgnoreCase("IMG")) { if (el.getNodeName().equalsIgnoreCase("IMG")) {
doAction(el.getParentElement().getAttribute("name"), valueUpdater); doAction(el.getParentElement().getAttribute("name"), valueUpdater);
} }
} }
} }
}; }
/** /**
* onEnterKeyDown is called when the user presses the ENTER key will the * onEnterKeyDown is called when the user presses the ENTER key while the Cell is selected. You are not required to
* Cell is selected. You are not required to override this method, but its a * override this method, but it is a common convention that allows your cell to respond to key events.
* common convention that allows your cell to respond to key events.
*/ */
@Override @Override
protected void onEnterKeyDown(Context context, Element parent, protected void onEnterKeyDown(Context context, Element parent,
@@ -80,16 +125,38 @@ public abstract class ImagesBarCell extends AbstractSafeHtmlCell<String> {
// Trigger a value updater. In this case, the value doesn't actually // Trigger a value updater. In this case, the value doesn't actually
// change, but we use a ValueUpdater to let the app know that a value // change, but we use a ValueUpdater to let the app know that a value
// was clicked. // was clicked.
if (valueUpdater != null) if (valueUpdater != null) {
valueUpdater.update(value); valueUpdater.update(value);
}
} }
/** /**
* Make icons available as SafeHtml * Make icons available as image prototype
* @param resource
* @return
*/ */
protected static SafeHtml makeImage(ImageResource resource) { protected static AbstractImagePrototype makeImagePrototype(ImageResource resource) {
return AbstractImagePrototype.create(resource).getSafeHtml(); return AbstractImagePrototype.create(resource);
}
protected abstract Iterable<ImageSpec> getImageSpecs();
@Override
protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) {
/*
* Always do a null check on the value. Cell widgets can pass null to
* cells if the underlying data contains a null, or if the data arrives
* out of order.
*/
if (data != null) {
SafeStyles imgStyle = getImageStyle();
for (ImageSpec imageSpec : getImageSpecs()) {
SafeHtml rendered = getImageTemplate().cell(imageSpec.getActionName(), imgStyle, imageSpec.getTooltip(),
imageSpec.getImagePrototype().getSafeHtml());
sb.append(rendered);
}
}
}
protected SafeStyles getImageStyle() {
return SafeStylesUtils.fromTrustedString("float:left;cursor:hand;cursor:pointer;padding-right:5px;");
} }
} }
@@ -1,57 +1,34 @@
package com.sap.sailing.gwt.ui.adminconsole; package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Arrays;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safecss.shared.SafeStylesUtils;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
public class LeaderboardConfigImagesBarCell extends ImagesBarCell { public class LeaderboardConfigImagesBarCell extends ImagesBarCell {
public static final String ACTION_REMOVE = "ACTION_REMOVE";
public LeaderboardConfigImagesBarCell(StringMessages stringConstants) { public static final String ACTION_EDIT_SCORES = "ACTION_EDIT_SCORES";
public static final String ACTION_EDIT = "ACTION_EDIT";
public static final String ACTION_CONFIGURE_URL = "ACTION_CONFIGURE_URL";
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
private final StringMessages stringMessages;
public LeaderboardConfigImagesBarCell(StringMessages stringMessages) {
super(); super();
this.stringConstants = stringConstants; this.stringMessages = stringMessages;
} }
public LeaderboardConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringConstants) { public LeaderboardConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringConstants) {
super(); super();
this.stringConstants = stringConstants; this.stringMessages = stringConstants;
} }
private static ImagesBarTemplates templates = GWT.create(ImagesBarTemplates.class);
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
private static final SafeHtml ICON_EDIT = makeImage(resources.editIcon());
private static final SafeHtml ICON_REMOVE = makeImage(resources.removeIcon());
private static final SafeHtml ICON_EDIT_SCORES = makeImage(resources.scoresIcon());
private StringMessages stringConstants;
@Override @Override
protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { protected Iterable<ImageSpec> getImageSpecs() {
/* return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())),
* Always do a null check on the value. Cell widgets can pass null to new ImageSpec(ACTION_EDIT_SCORES, stringMessages.actionEditScores(), makeImagePrototype(resources.scoresIcon())),
* cells if the underlying data contains a null, or if the data arrives new ImageSpec(ACTION_CONFIGURE_URL, stringMessages.actionConfigureUrl(), makeImagePrototype(resources.settingsActionIcon())),
* out of order. new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon())));
*/
if (data == null) {
return;
}
SafeStyles imgStyle = SafeStylesUtils.fromTrustedString("float:left;cursor:hand;cursor:pointer;padding-right:5px;");
SafeHtml rendered = templates.cell("ACTION_EDIT", imgStyle, stringConstants.actionEdit(), ICON_EDIT);
sb.append(rendered);
rendered = templates.cell("ACTION_EDIT_SCORES", imgStyle, stringConstants.actionEditScores(), ICON_EDIT_SCORES);
sb.append(rendered);
rendered = templates.cell("ACTION_REMOVE", imgStyle, stringConstants.actionRemove(), ICON_REMOVE);
sb.append(rendered);
} }
} }
@@ -47,6 +47,7 @@ import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback; import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback;
@@ -58,7 +59,9 @@ import com.sap.sailing.gwt.ui.client.RegattaDisplayer;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.URLFactory; import com.sap.sailing.gwt.ui.client.URLFactory;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.shared.AbstractLeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.FleetDTO; import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO; import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
@@ -231,19 +234,19 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
leaderboardActionColumn.setFieldUpdater(new FieldUpdater<StrippedLeaderboardDTO, String>() { leaderboardActionColumn.setFieldUpdater(new FieldUpdater<StrippedLeaderboardDTO, String>() {
@Override @Override
public void update(int index, StrippedLeaderboardDTO leaderboardDTO, String value) { public void update(int index, StrippedLeaderboardDTO leaderboardDTO, String value) {
if ("ACTION_REMOVE".equals(value)) { if (LeaderboardConfigImagesBarCell.ACTION_REMOVE.equals(value)) {
if (Window.confirm("Do you really want to remove the leaderboard: '" + leaderboardDTO.name + "' ?")) { if (Window.confirm("Do you really want to remove the leaderboard: '" + leaderboardDTO.name + "' ?")) {
removeLeaderboard(leaderboardDTO); removeLeaderboard(leaderboardDTO);
} }
} else if ("ACTION_EDIT".equals(value)) { } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT.equals(value)) {
final String oldLeaderboardName = leaderboardDTO.name; final String oldLeaderboardName = leaderboardDTO.name;
List<StrippedLeaderboardDTO> otherExistingLeaderboard = new ArrayList<StrippedLeaderboardDTO>(); List<StrippedLeaderboardDTO> otherExistingLeaderboard = new ArrayList<StrippedLeaderboardDTO>();
otherExistingLeaderboard.addAll(availableLeaderboardList); otherExistingLeaderboard.addAll(availableLeaderboardList);
otherExistingLeaderboard.remove(leaderboardDTO); otherExistingLeaderboard.remove(leaderboardDTO);
if(leaderboardDTO.isMetaLeaderboard) { if (leaderboardDTO.isMetaLeaderboard) {
Window.alert("This is a meta leaderboard. It can't be changed here."); Window.alert("This is a meta leaderboard. It can't be changed here.");
} else { } else {
if(leaderboardDTO.isRegattaLeaderboard) { if (leaderboardDTO.isRegattaLeaderboard) {
LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name, LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name,
null, leaderboardDTO.discardThresholds, leaderboardDTO.regattaName); null, leaderboardDTO.discardThresholds, leaderboardDTO.regattaName);
AbstractLeaderboardDialog dialog = new RegattaLeaderboardEditDialog(Collections AbstractLeaderboardDialog dialog = new RegattaLeaderboardEditDialog(Collections
@@ -278,10 +281,12 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
dialog.show(); dialog.show();
} }
} }
} else if ("ACTION_EDIT_SCORES".equals(value)) { } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_SCORES.equals(value)) {
String debugParam = Window.Location.getParameter("gwt.codesvr"); String debugParam = Window.Location.getParameter("gwt.codesvr");
Window.open("/gwt/LeaderboardEditing.html?name=" + leaderboardDTO.name Window.open("/gwt/LeaderboardEditing.html?name=" + leaderboardDTO.name
+ (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""), "_blank", null); + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""), "_blank", null);
} else if (LeaderboardConfigImagesBarCell.ACTION_CONFIGURE_URL.equals(value)) {
openLeaderboardUrlConfigDialog(leaderboardDTO, stringMessages);
} }
} }
}); });
@@ -382,6 +387,12 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
return object.getB().name; return object.getB().name;
} }
}; };
TextColumn<Pair<RaceColumnDTO, FleetDTO>> explicitFactorColumn = new TextColumn<Pair<RaceColumnDTO, FleetDTO>>() {
@Override
public String getValue(Pair<RaceColumnDTO, FleetDTO> object) {
return object.getA().getExplicitFactor() == null ? "" : object.getA().getExplicitFactor().toString();
}
};
Column<Pair<RaceColumnDTO, FleetDTO>, Boolean> isMedalRaceCheckboxColumn = new Column<Pair<RaceColumnDTO, FleetDTO>, Boolean>( Column<Pair<RaceColumnDTO, FleetDTO>, Boolean> isMedalRaceCheckboxColumn = new Column<Pair<RaceColumnDTO, FleetDTO>, Boolean>(
new CheckboxCell()) { new CheckboxCell()) {
@@ -411,39 +422,35 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
raceActionColumn.setFieldUpdater(new FieldUpdater<Pair<RaceColumnDTO, FleetDTO>, String>() { raceActionColumn.setFieldUpdater(new FieldUpdater<Pair<RaceColumnDTO, FleetDTO>, String>() {
@Override @Override
public void update(int index, Pair<RaceColumnDTO, FleetDTO> object, String value) { public void update(int index, Pair<RaceColumnDTO, FleetDTO> object, String value) {
if ("ACTION_REMOVE".equals(value)) { if (LeaderboardRaceConfigImagesBarCell.ACTION_REMOVE.equals(value)) {
if (Window.confirm(stringMessages.reallyRemoveRace(object.getA().getRaceColumnName()))) { if (Window.confirm(stringMessages.reallyRemoveRace(object.getA().getRaceColumnName()))) {
removeRaceColumn(object.getA()); removeRaceColumn(object.getA());
} }
} else if ("ACTION_EDIT".equals(value)) { } else if (LeaderboardRaceConfigImagesBarCell.ACTION_EDIT.equals(value)) {
editRaceColumnOfLeaderboard(object); editRaceColumnOfLeaderboard(object);
} else if ("ACTION_UNLINK".equals(value)) { } else if (LeaderboardRaceConfigImagesBarCell.ACTION_UNLINK.equals(value)) {
unlinkRaceColumnFromTrackedRace(object.getA().getRaceColumnName(), object.getB()); unlinkRaceColumnFromTrackedRace(object.getA().getRaceColumnName(), object.getB());
} }
} }
}); });
Label lblRaceNamesIn = new Label(stringMessages.races()); Label lblRaceNamesIn = new Label(stringMessages.races());
vPanel.add(lblRaceNamesIn); vPanel.add(lblRaceNamesIn);
raceColumnTable = new CellTable<Pair<RaceColumnDTO, FleetDTO>>(/* pageSize */200, tableRes); raceColumnTable = new CellTable<Pair<RaceColumnDTO, FleetDTO>>(/* pageSize */200, tableRes);
raceColumnTable.addColumn(raceLinkColumn, stringMessages.name()); raceColumnTable.addColumn(raceLinkColumn, stringMessages.name());
raceColumnTable.addColumn(fleetNameColumn, stringMessages.fleet()); raceColumnTable.addColumn(fleetNameColumn, stringMessages.fleet());
raceColumnTable.addColumn(isMedalRaceCheckboxColumn, stringMessages.medalRace()); raceColumnTable.addColumn(isMedalRaceCheckboxColumn, stringMessages.medalRace());
raceColumnTable.addColumn(isLinkedRaceColumn, stringMessages.islinked()); raceColumnTable.addColumn(isLinkedRaceColumn, stringMessages.islinked());
raceColumnTable.addColumn(explicitFactorColumn, stringMessages.factor());
raceColumnTable.addColumn(raceActionColumn, stringMessages.actions()); raceColumnTable.addColumn(raceActionColumn, stringMessages.actions());
raceColumnAndFleetList.addDataDisplay(raceColumnTable); raceColumnAndFleetList.addDataDisplay(raceColumnTable);
raceColumnTable.setWidth("500px"); raceColumnTable.setWidth("500px");
raceColumnTableSelectionModel = new SingleSelectionModel<Pair<RaceColumnDTO, FleetDTO>>(); raceColumnTableSelectionModel = new SingleSelectionModel<Pair<RaceColumnDTO, FleetDTO>>();
raceColumnTable.setSelectionModel(raceColumnTableSelectionModel); raceColumnTable.setSelectionModel(raceColumnTableSelectionModel);
raceColumnTableSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { raceColumnTableSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
public void onSelectionChange(SelectionChangeEvent event) { public void onSelectionChange(SelectionChangeEvent event) {
leaderboardRaceColumnSelectionChanged(); leaderboardRaceColumnSelectionChanged();
} }
}); });
vPanel.add(raceColumnTable); vPanel.add(raceColumnTable);
HorizontalPanel selectedLeaderboardRaceButtonPanel = new HorizontalPanel(); HorizontalPanel selectedLeaderboardRaceButtonPanel = new HorizontalPanel();
@@ -483,6 +490,16 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
loadAndRefreshLeaderboards(); loadAndRefreshLeaderboards();
} }
/**
* Allow the user to combine the various URL parameters that exist for the {@link LeaderboardEntryPoint} and obtain the
* resulting URL in a link. The link's reference target is updated dynamically as the user adjusts the settings. Therefore,
* the link can be clicked, bookmarked or copied to the clipboard at any time. The OK / Cancel actions for the dialog shown
* are no-ops.
*/
private void openLeaderboardUrlConfigDialog(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages) {
LeaderboardEntryPoint.getUrlConfigurationDialog(leaderboard, stringMessages).show();
}
public void loadAndRefreshLeaderboards() { public void loadAndRefreshLeaderboards() {
sailingService.getLeaderboards(new AsyncCallback<List<StrippedLeaderboardDTO>>() { sailingService.getLeaderboards(new AsyncCallback<List<StrippedLeaderboardDTO>>() {
@Override @Override
@@ -687,27 +704,43 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
private void editRaceColumnOfLeaderboard(final Pair<RaceColumnDTO, FleetDTO> raceColumnWithFleet) { private void editRaceColumnOfLeaderboard(final Pair<RaceColumnDTO, FleetDTO> raceColumnWithFleet) {
final String selectedLeaderboardName = getSelectedLeaderboardName(); final String selectedLeaderboardName = getSelectedLeaderboardName();
final boolean oldIsMedalRace = raceColumnWithFleet.getA().isMedalRace();
final String oldRaceColumnName = raceColumnWithFleet.getA().getRaceColumnName(); final String oldRaceColumnName = raceColumnWithFleet.getA().getRaceColumnName();
final Double oldExplicitFactor = raceColumnWithFleet.getA().getExplicitFactor();
List<RaceColumnDTO> existingRacesWithoutThisRace = new ArrayList<RaceColumnDTO>(); List<RaceColumnDTO> existingRacesWithoutThisRace = new ArrayList<RaceColumnDTO>();
for(Pair<RaceColumnDTO, FleetDTO> pair: raceColumnAndFleetList.getList()) { for(Pair<RaceColumnDTO, FleetDTO> pair: raceColumnAndFleetList.getList()) {
existingRacesWithoutThisRace.add(pair.getA()); existingRacesWithoutThisRace.add(pair.getA());
} }
existingRacesWithoutThisRace.remove(raceColumnWithFleet.getA()); existingRacesWithoutThisRace.remove(raceColumnWithFleet.getA());
final RaceColumnInLeaderboardDialog raceDialog = new RaceColumnInLeaderboardDialog(existingRacesWithoutThisRace, final RaceColumnInLeaderboardDialog raceDialog = new RaceColumnInLeaderboardDialog(existingRacesWithoutThisRace,
raceColumnWithFleet.getA(), stringMessages, new DialogCallback<RaceColumnDTO>() { raceColumnWithFleet.getA(), stringMessages, new DialogCallback<RaceColumnDescriptor>() {
@Override @Override
public void cancel() { public void cancel() {
} }
@Override @Override
public void ok(final RaceColumnDTO result) { public void ok(final RaceColumnDescriptor result) {
final ParallelExecutionCallback<Void> renameLeaderboardColumnCallback = new ParallelExecutionCallback<Void>(); boolean rename = !oldRaceColumnName.equals(result.getName());
final ParallelExecutionCallback<Void> updateIsMedalRaceCallback = new ParallelExecutionCallback<Void>(); boolean updateIsMedalRace = oldIsMedalRace != result.isMedalRace();
new ParallelExecutionHolder(renameLeaderboardColumnCallback, updateIsMedalRaceCallback) { boolean updateFactor = oldExplicitFactor != result.getExplicitFactor();
List<ParallelExecutionCallback<Void>> callbacks = new ArrayList<ParallelExecutionCallback<Void>>();
final ParallelExecutionCallback<Void> renameLeaderboardColumnCallback = new ParallelExecutionCallback<Void>();
if (rename) {
callbacks.add(renameLeaderboardColumnCallback);
}
final ParallelExecutionCallback<Void> updateIsMedalRaceCallback = new ParallelExecutionCallback<Void>();
if (updateIsMedalRace) {
callbacks.add(updateIsMedalRaceCallback);
}
final ParallelExecutionCallback<Void> updateLeaderboardColumnFactorCallback = new ParallelExecutionCallback<Void>();
if (updateFactor) {
callbacks.add(updateLeaderboardColumnFactorCallback);
}
new ParallelExecutionHolder(callbacks.toArray(new ParallelExecutionCallback<?>[0])) {
@Override @Override
public void handleSuccess() { public void handleSuccess() {
loadAndRefreshLeaderboard(selectedLeaderboardName); loadAndRefreshLeaderboard(selectedLeaderboardName);
selectRaceColumn(result.getRaceColumnName()); selectRaceColumn(result.getName());
} }
@Override @Override
public void handleFailure(Throwable t) { public void handleFailure(Throwable t) {
@@ -716,10 +749,18 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
+ t.getMessage()); + t.getMessage());
} }
}; };
sailingService.renameLeaderboardColumn(selectedLeaderboardName, oldRaceColumnName, if (rename) {
result.getRaceColumnName(), renameLeaderboardColumnCallback); sailingService.renameLeaderboardColumn(selectedLeaderboardName, oldRaceColumnName,
sailingService.updateIsMedalRace(selectedLeaderboardName, result.getRaceColumnName(), result.getName(), renameLeaderboardColumnCallback);
result.isMedalRace(), updateIsMedalRaceCallback); }
if (updateIsMedalRace) {
sailingService.updateIsMedalRace(selectedLeaderboardName, result.getName(),
result.isMedalRace(), updateIsMedalRaceCallback);
}
if (updateFactor) {
sailingService.updateLeaderboardColumnFactor(selectedLeaderboardName, result.getName(),
result.getExplicitFactor(), updateLeaderboardColumnFactorCallback);
}
} }
}); });
raceDialog.show(); raceDialog.show();
@@ -729,11 +770,10 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
final boolean isMedalRace) { final boolean isMedalRace) {
sailingService.updateIsMedalRace(leaderboardName, raceInLeaderboard.getRaceColumnName(), isMedalRace, sailingService.updateIsMedalRace(leaderboardName, raceInLeaderboard.getRaceColumnName(), isMedalRace,
new AsyncCallback<Void>() { new AsyncCallback<Void>() {
@Override @Override
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorUpdatingIsMedalRace(caught.getMessage()));
} }
@Override @Override
public void onSuccess(Void result) { public void onSuccess(Void result) {
selectedLeaderboard.setIsMedalRace(raceInLeaderboard.getRaceColumnName(), isMedalRace); selectedLeaderboard.setIsMedalRace(raceInLeaderboard.getRaceColumnName(), isMedalRace);
@@ -801,7 +841,7 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
raceSelectionProvider.addRaceSelectionChangeListener(LeaderboardConfigPanel.this); raceSelectionProvider.addRaceSelectionChangeListener(LeaderboardConfigPanel.this);
} }
}); });
if (selectedLeaderboard != null && !selectedLeaderboard.isMetaLeaderboard) { if (selectedLeaderboard != null) {
raceColumnAndFleetList.getList().clear(); raceColumnAndFleetList.getList().clear();
for (RaceColumnDTO raceColumn : selectedLeaderboard.getRaceList()) { for (RaceColumnDTO raceColumn : selectedLeaderboard.getRaceList()) {
for (FleetDTO fleet : raceColumn.getFleets()) { for (FleetDTO fleet : raceColumn.getFleets()) {
@@ -809,8 +849,10 @@ public class LeaderboardConfigPanel extends FormPanel implements RegattaDisplaye
} }
} }
selectedLeaderBoardPanel.setVisible(true); selectedLeaderBoardPanel.setVisible(true);
trackedRacesCaptionPanel.setVisible(true);
selectedLeaderBoardPanel.setCaptionText("Details of leaderboard '" + selectedLeaderboard.name + "'"); selectedLeaderBoardPanel.setCaptionText("Details of leaderboard '" + selectedLeaderboard.name + "'");
if (!selectedLeaderboard.isMetaLeaderboard) {
trackedRacesCaptionPanel.setVisible(true);
}
} else { } else {
selectedLeaderBoardPanel.setVisible(false); selectedLeaderBoardPanel.setVisible(false);
trackedRacesCaptionPanel.setVisible(false); trackedRacesCaptionPanel.setVisible(false);
@@ -1,52 +1,30 @@
package com.sap.sailing.gwt.ui.adminconsole; package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Arrays;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safecss.shared.SafeStylesUtils;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
public class LeaderboardGroupConfigImagesBarCell extends ImagesBarCell { public class LeaderboardGroupConfigImagesBarCell extends ImagesBarCell {
public static final String ACTION_REMOVE = "ACTION_REMOVE";
public LeaderboardGroupConfigImagesBarCell(StringMessages stringConstants) { public static final String ACTION_EDIT = "ACTION_EDIT";
super();
this.stringConstants = stringConstants;
}
public LeaderboardGroupConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringConstants) {
super();
this.stringConstants = stringConstants;
}
private static ImagesBarTemplates templates = GWT.create(ImagesBarTemplates.class);
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
private final StringMessages stringMessages;
private static final SafeHtml ICON_EDIT = makeImage(resources.editIcon()); public LeaderboardGroupConfigImagesBarCell(StringMessages stringMessages) {
super();
this.stringMessages = stringMessages;
}
private static final SafeHtml ICON_REMOVE = makeImage(resources.removeIcon()); public LeaderboardGroupConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringMessages) {
super();
this.stringMessages = stringMessages;
}
private StringMessages stringConstants;
@Override @Override
protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { protected Iterable<ImageSpec> getImageSpecs() {
/* return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())),
* Always do a null check on the value. Cell widgets can pass null to new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon())));
* cells if the underlying data contains a null, or if the data arrives
* out of order.
*/
if (data == null) {
return;
}
SafeStyles imgStyle = SafeStylesUtils.fromTrustedString("float:left;cursor:hand;cursor:pointer;padding-right:5px;");
SafeHtml rendered = templates.cell("ACTION_EDIT", imgStyle, stringConstants.actionEdit(), ICON_EDIT);
sb.append(rendered);
rendered = templates.cell("ACTION_REMOVE", imgStyle, stringConstants.actionRemove(), ICON_REMOVE);
sb.append(rendered);
} }
} }
@@ -36,6 +36,7 @@ import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel; import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell;
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardGroupDialog.LeaderboardGroupDescriptor;
import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel; import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
@@ -446,21 +447,22 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel {
new LeaderboardGroupConfigImagesBarCell(stringMessages)); new LeaderboardGroupConfigImagesBarCell(stringMessages));
groupActionsColumn.setFieldUpdater(new FieldUpdater<LeaderboardGroupDTO, String>() { groupActionsColumn.setFieldUpdater(new FieldUpdater<LeaderboardGroupDTO, String>() {
@Override @Override
public void update(int index, LeaderboardGroupDTO group, String command) { public void update(int index, final LeaderboardGroupDTO group, String command) {
if (command.equals("ACTION_EDIT")) { if (command.equals(LeaderboardGroupConfigImagesBarCell.ACTION_EDIT)) {
final String oldGroupName = group.name; final String oldGroupName = group.name;
ArrayList<LeaderboardGroupDTO> otherExistingGroups = new ArrayList<LeaderboardGroupDTO>(availableLeaderboardGroups); ArrayList<LeaderboardGroupDTO> otherExistingGroups = new ArrayList<LeaderboardGroupDTO>(availableLeaderboardGroups);
otherExistingGroups.remove(group); otherExistingGroups.remove(group);
LeaderboardGroupEditDialog dialog = new LeaderboardGroupEditDialog(group, otherExistingGroups, stringMessages, new DialogCallback<LeaderboardGroupDTO>() { LeaderboardGroupEditDialog dialog = new LeaderboardGroupEditDialog(group, otherExistingGroups, stringMessages,
new DialogCallback<LeaderboardGroupDescriptor>() {
@Override @Override
public void cancel() {} public void cancel() {}
@Override @Override
public void ok(LeaderboardGroupDTO group) { public void ok(LeaderboardGroupDescriptor groupDescriptor) {
updateGroup(oldGroupName, group); updateGroup(oldGroupName, group, groupDescriptor);
} }
}); });
dialog.show(); dialog.show();
} else if (command.equals("ACTION_REMOVE")) { } else if (command.equals(LeaderboardGroupConfigImagesBarCell.ACTION_REMOVE)) {
if (Window.confirm("Do you really want to remove the leaderboard group: '" + group.name + "' ?")) { if (Window.confirm("Do you really want to remove the leaderboard group: '" + group.name + "' ?")) {
removeLeaderboardGroup(group); removeLeaderboardGroup(group);
} }
@@ -558,24 +560,24 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel {
private void addNewGroup() { private void addNewGroup() {
LeaderboardGroupCreateDialog dialog = new LeaderboardGroupCreateDialog( LeaderboardGroupCreateDialog dialog = new LeaderboardGroupCreateDialog(
Collections.unmodifiableCollection(availableLeaderboardGroups), stringMessages, Collections.unmodifiableCollection(availableLeaderboardGroups), stringMessages,
new DialogCallback<LeaderboardGroupDTO>() { new DialogCallback<LeaderboardGroupDescriptor>() {
@Override @Override
public void cancel() {} public void cancel() {}
@Override @Override
public void ok(LeaderboardGroupDTO newGroup) { public void ok(LeaderboardGroupDescriptor newGroup) {
createNewGroup(newGroup); createNewGroup(newGroup);
} }
}); });
dialog.show(); dialog.show();
} }
private void createNewGroup(final LeaderboardGroupDTO newGroup) { private void createNewGroup(final LeaderboardGroupDescriptor newGroup) {
sailingService.createLeaderboardGroup(newGroup.name, newGroup.description, sailingService.createLeaderboardGroup(newGroup.getName(), newGroup.getDescription(), newGroup.isDisplayLeaderboardsInReverseOrder(),
newGroup.getOverallLeaderboardDiscardThresholds(), newGroup.getOverallLeaderboardScoringSchemeType(), newGroup.getOverallLeaderboardDiscardThresholds(),
new AsyncCallback<LeaderboardGroupDTO>() { newGroup.getOverallLeaderboardScoringSchemeType(), new AsyncCallback<LeaderboardGroupDTO>() {
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
errorReporter.reportError("Error trying to create new leaderboard group" + newGroup.name errorReporter.reportError("Error trying to create new leaderboard group" + newGroup.getName()
+ ": " + t.getMessage()); + ": " + t.getMessage());
} }
@Override @Override
@@ -588,14 +590,14 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel {
}); });
} }
private void updateGroup(final String oldGroupName, final LeaderboardGroupDTO groupToUpdate) { private void updateGroup(final String oldGroupName, final LeaderboardGroupDTO groupToUpdate, final LeaderboardGroupDescriptor updateDescriptor) {
List<String> leaderboardNames = new ArrayList<String>(); List<String> leaderboardNames = new ArrayList<String>();
for (StrippedLeaderboardDTO leaderboardDTO : groupToUpdate.leaderboards) { for (StrippedLeaderboardDTO leaderboardDTO : groupToUpdate.leaderboards) {
leaderboardNames.add(leaderboardDTO.name); leaderboardNames.add(leaderboardDTO.name);
} }
sailingService.updateLeaderboardGroup(oldGroupName, groupToUpdate.name, groupToUpdate.description, sailingService.updateLeaderboardGroup(oldGroupName, updateDescriptor.getName(), updateDescriptor.getDescription(),
leaderboardNames, groupToUpdate.getOverallLeaderboardDiscardThresholds(), leaderboardNames, updateDescriptor.getOverallLeaderboardDiscardThresholds(),
groupToUpdate.getOverallLeaderboardScoringSchemeType(), new AsyncCallback<Void>() { updateDescriptor.getOverallLeaderboardScoringSchemeType(), new AsyncCallback<Void>() {
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
errorReporter.reportError("Error trying to update leaderboard group " + oldGroupName + ": " errorReporter.reportError("Error trying to update leaderboard group " + oldGroupName + ": "
@@ -603,10 +605,15 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel {
} }
@Override @Override
public void onSuccess(Void v) { public void onSuccess(Void v) {
//Update the availableLeaderboardGroups and the list of displayed groups // Update the availableLeaderboardGroups and the list of displayed groups
for (int i = 0; i < availableLeaderboardGroups.size(); i++) { for (int i = 0; i < availableLeaderboardGroups.size(); i++) {
LeaderboardGroupDTO group = availableLeaderboardGroups.get(i); LeaderboardGroupDTO group = availableLeaderboardGroups.get(i);
if (oldGroupName.equals(group.name)) { if (oldGroupName.equals(group.name)) {
groupToUpdate.name = updateDescriptor.getName();
groupToUpdate.description = updateDescriptor.getDescription();
groupToUpdate.displayLeaderboardsInReverseOrder = updateDescriptor.isDisplayLeaderboardsInReverseOrder();
groupToUpdate.setOverallLeaderboardDiscardThresholds(updateDescriptor.getOverallLeaderboardDiscardThresholds());
groupToUpdate.setOverallLeaderboardScoringSchemeType(updateDescriptor.getOverallLeaderboardScoringSchemeType());
availableLeaderboardGroups.set(i, groupToUpdate); availableLeaderboardGroups.set(i, groupToUpdate);
int displayedIndex = groupsProvider.getList().indexOf(group); int displayedIndex = groupsProvider.getList().indexOf(group);
if (displayedIndex != -1) { if (displayedIndex != -1) {
@@ -618,8 +625,27 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel {
} }
}); });
} }
/**
* Updates a change in the group's leaderboard list to the server
*/
private void updateGroup(final LeaderboardGroupDTO group) { private void updateGroup(final LeaderboardGroupDTO group) {
updateGroup(group.name, group); List<String> leaderboardNames = new ArrayList<String>();
for (StrippedLeaderboardDTO leaderboardDTO : group.leaderboards) {
leaderboardNames.add(leaderboardDTO.name);
}
sailingService.updateLeaderboardGroup(group.name, group.name, group.description,
leaderboardNames, group.getOverallLeaderboardDiscardThresholds(),
group.getOverallLeaderboardScoringSchemeType(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable t) {
errorReporter.reportError("Error trying to update leaderboard group " + group.name + ": "
+ t.getMessage());
}
@Override
public void onSuccess(Void v) {
}
});
} }
private void removeLeaderboardGroup(final LeaderboardGroupDTO group) { private void removeLeaderboardGroup(final LeaderboardGroupDTO group) {
@@ -8,11 +8,12 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
public class LeaderboardGroupCreateDialog extends LeaderboardGroupDialog { public class LeaderboardGroupCreateDialog extends LeaderboardGroupDialog {
public LeaderboardGroupCreateDialog(Collection<LeaderboardGroupDTO> existingGroups, public LeaderboardGroupCreateDialog(Collection<LeaderboardGroupDTO> existingGroups,
StringMessages stringMessages, DialogCallback<LeaderboardGroupDTO> callback) { StringMessages stringMessages, DialogCallback<LeaderboardGroupDescriptor> callback) {
super(new LeaderboardGroupDTO(), stringMessages, super(new LeaderboardGroupDTO(), stringMessages,
callback, existingGroups); callback, existingGroups);
nameEntryField = createTextBox(null); nameEntryField = createTextBox(null);
descriptionEntryField = createTextArea(null); descriptionEntryField = createTextArea(null);
displayLeaderboardsInReverseOrderCheckBox.setValue(false);
useOverallLeaderboardCheckBox.setValue(false);
} }
} }
@@ -25,20 +25,55 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
public class LeaderboardGroupDialog extends DataEntryDialog<LeaderboardGroupDTO> { public class LeaderboardGroupDialog extends DataEntryDialog<LeaderboardGroupDialog.LeaderboardGroupDescriptor> {
protected StringMessages stringMessages; protected StringMessages stringMessages;
protected LeaderboardGroupDTO group;
protected TextBox nameEntryField; protected TextBox nameEntryField;
protected TextArea descriptionEntryField; protected TextArea descriptionEntryField;
protected CheckBox displayLeaderboardsInReverseOrderCheckBox;
private CheckBox useOverallLeaderboardCheckBox; protected CheckBox useOverallLeaderboardCheckBox;
private Panel overallLeaderboardConfigPanel; private Panel overallLeaderboardConfigPanel;
private LongBox[] overallLeaderboardDiscardThresholdFields; private LongBox[] overallLeaderboardDiscardThresholdFields;
private ListBox overallLeaderboardScoringSchemeListBox; private ListBox overallLeaderboardScoringSchemeListBox;
protected static class LeaderboardGroupParameterValidator implements Validator<LeaderboardGroupDTO> { public static class LeaderboardGroupDescriptor {
private final String name;
private final String description;
private final boolean displayLeaderboardsInReverseOrder;
private final boolean useOverallLeaderboard;
private final int[] overallLeaderboardDiscardThresholds;
private final ScoringSchemeType overallLeaderboardScoringSchemeType;
public LeaderboardGroupDescriptor(String name, String description, boolean displayLeaderboardsInReverseOrder,
boolean useOverallLeaderboard, int[] overallLeaderboardDiscardThresholds,
ScoringSchemeType overallLeaderboardScoringSchemeType) {
super();
this.name = name;
this.description = description;
this.displayLeaderboardsInReverseOrder = displayLeaderboardsInReverseOrder;
this.useOverallLeaderboard = useOverallLeaderboard;
this.overallLeaderboardDiscardThresholds = overallLeaderboardDiscardThresholds;
this.overallLeaderboardScoringSchemeType = overallLeaderboardScoringSchemeType;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public boolean isDisplayLeaderboardsInReverseOrder() {
return displayLeaderboardsInReverseOrder;
}
public boolean isUseOverallLeaderboard() {
return useOverallLeaderboard;
}
public int[] getOverallLeaderboardDiscardThresholds() {
return overallLeaderboardDiscardThresholds;
}
public ScoringSchemeType getOverallLeaderboardScoringSchemeType() {
return overallLeaderboardScoringSchemeType;
}
}
protected static class LeaderboardGroupParameterValidator implements Validator<LeaderboardGroupDescriptor> {
private final StringMessages stringMessages; private final StringMessages stringMessages;
private final ArrayList<LeaderboardGroupDTO> existingGroups; private final ArrayList<LeaderboardGroupDTO> existingGroups;
@@ -49,14 +84,14 @@ public class LeaderboardGroupDialog extends DataEntryDialog<LeaderboardGroupDTO>
} }
@Override @Override
public String getErrorMessage(LeaderboardGroupDTO groupToValidate) { public String getErrorMessage(LeaderboardGroupDescriptor groupToValidate) {
String errorMessage = null; String errorMessage = null;
boolean nameNotEmpty = groupToValidate.name != null && groupToValidate.name.length() > 0; boolean nameNotEmpty = groupToValidate.getName() != null && groupToValidate.getName().length() > 0;
boolean descrNotEmpty = groupToValidate.description != null && groupToValidate.description.length() > 0; boolean descrNotEmpty = groupToValidate.getDescription() != null && groupToValidate.getDescription().length() > 0;
boolean unique = true; boolean unique = true;
for (LeaderboardGroupDTO group : existingGroups) { for (LeaderboardGroupDTO group : existingGroups) {
if (group.name.equals(groupToValidate.name)) { if (group.name.equals(groupToValidate.getName())) {
unique = false; unique = false;
break; break;
} }
@@ -87,17 +122,13 @@ public class LeaderboardGroupDialog extends DataEntryDialog<LeaderboardGroupDTO>
protected LongBox[] getOverallLeaderboardDiscardThresholdFields() { protected LongBox[] getOverallLeaderboardDiscardThresholdFields() {
return overallLeaderboardDiscardThresholdFields; return overallLeaderboardDiscardThresholdFields;
} }
protected CheckBox getUseOverallLeaderboardCheckBox() {
return useOverallLeaderboardCheckBox;
}
public LeaderboardGroupDialog(LeaderboardGroupDTO group, StringMessages stringMessages, public LeaderboardGroupDialog(LeaderboardGroupDTO group, StringMessages stringMessages,
DialogCallback<LeaderboardGroupDTO> callback, Collection<LeaderboardGroupDTO> existingLeaderboardGroups) { DialogCallback<LeaderboardGroupDescriptor> callback, Collection<LeaderboardGroupDTO> existingLeaderboardGroups) {
super(stringMessages.leaderboardGroup(), null, stringMessages.ok(), stringMessages.cancel(), super(stringMessages.leaderboardGroup(), null, stringMessages.ok(), stringMessages.cancel(),
new LeaderboardGroupParameterValidator(stringMessages, existingLeaderboardGroups), callback); new LeaderboardGroupParameterValidator(stringMessages, existingLeaderboardGroups), callback);
this.stringMessages = stringMessages; this.stringMessages = stringMessages;
this.group = group; displayLeaderboardsInReverseOrderCheckBox = createCheckbox(stringMessages.displayGroupsInReverseOrder());
useOverallLeaderboardCheckBox = createCheckbox(stringMessages.useOverallLeaderboard()); useOverallLeaderboardCheckBox = createCheckbox(stringMessages.useOverallLeaderboard());
Grid formGrid = new Grid(3,2); Grid formGrid = new Grid(3,2);
formGrid.setCellSpacing(3); formGrid.setCellSpacing(3);
@@ -126,19 +157,14 @@ public class LeaderboardGroupDialog extends DataEntryDialog<LeaderboardGroupDTO>
} }
@Override @Override
protected LeaderboardGroupDTO getResult() { protected LeaderboardGroupDescriptor getResult() {
group.name = nameEntryField.getText(); return new LeaderboardGroupDescriptor(nameEntryField.getText(), descriptionEntryField.getText(),
group.description = descriptionEntryField.getText(); displayLeaderboardsInReverseOrderCheckBox.getValue(),
if (useOverallLeaderboardCheckBox.getValue()) { useOverallLeaderboardCheckBox.getValue(),
group.setOverallLeaderboardDiscardThresholds(AbstractLeaderboardDialog useOverallLeaderboardCheckBox.getValue() ? AbstractLeaderboardDialog
.getDiscardThresholds(getOverallLeaderboardDiscardThresholdFields())); .getDiscardThresholds(getOverallLeaderboardDiscardThresholdFields()) : null,
group.setOverallLeaderboardScoringSchemeType(AbstractLeaderboardDialog.getSelectedScoringSchemeType( useOverallLeaderboardCheckBox.getValue() ? AbstractLeaderboardDialog.getSelectedScoringSchemeType(
overallLeaderboardScoringSchemeListBox, stringMessages)); overallLeaderboardScoringSchemeListBox, stringMessages) : null);
} else {
group.setOverallLeaderboardDiscardThresholds(null);
group.setOverallLeaderboardScoringSchemeType(null);
}
return group;
} }
@Override @Override
@@ -155,26 +181,31 @@ public class LeaderboardGroupDialog extends DataEntryDialog<LeaderboardGroupDTO>
descriptionEntryField.setVisibleLines(6); descriptionEntryField.setVisibleLines(6);
descriptionEntryField.getElement().getStyle().setProperty("resize", "none"); descriptionEntryField.getElement().getStyle().setProperty("resize", "none");
panel.add(descriptionEntryField); panel.add(descriptionEntryField);
panel.add(displayLeaderboardsInReverseOrderCheckBox);
panel.add(useOverallLeaderboardCheckBox); panel.add(useOverallLeaderboardCheckBox);
useOverallLeaderboardCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() { useOverallLeaderboardCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override @Override
public void onValueChange(ValueChangeEvent<Boolean> event) { public void onValueChange(ValueChangeEvent<Boolean> event) {
if (event.getValue()) { updateOverallLeaderboardDetailsVisibility(event.getValue(), panel);
useOverallLeaderboard(panel);
} else {
dontUseOverallLeaderboard(panel);
}
} }
}); });
updateOverallLeaderboardDetailsVisibility(useOverallLeaderboardCheckBox.getValue(), panel);
return panel; return panel;
} }
protected void useOverallLeaderboard(final VerticalPanel panel) { private void updateOverallLeaderboardDetailsVisibility(boolean overallLeaderboardDetailsVisible, Panel panel) {
if (overallLeaderboardDetailsVisible) {
useOverallLeaderboard(panel);
} else {
dontUseOverallLeaderboard(panel);
}
}
protected void useOverallLeaderboard(final Panel panel) {
panel.add(overallLeaderboardConfigPanel); panel.add(overallLeaderboardConfigPanel);
} }
protected void dontUseOverallLeaderboard(final VerticalPanel panel) { protected void dontUseOverallLeaderboard(final Panel panel) {
panel.remove(overallLeaderboardConfigPanel); panel.remove(overallLeaderboardConfigPanel);
} }
@@ -2,24 +2,19 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection; import java.util.Collection;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
public class LeaderboardGroupEditDialog extends LeaderboardGroupDialog { public class LeaderboardGroupEditDialog extends LeaderboardGroupDialog {
public LeaderboardGroupEditDialog(LeaderboardGroupDTO group, Collection<LeaderboardGroupDTO> otherExistingGroups, public LeaderboardGroupEditDialog(LeaderboardGroupDTO group, Collection<LeaderboardGroupDTO> otherExistingGroups,
StringMessages stringMessages, DialogCallback<LeaderboardGroupDTO> callback) { StringMessages stringMessages, DialogCallback<LeaderboardGroupDescriptor> callback) {
super(group, stringMessages, callback, otherExistingGroups); super(group, stringMessages, callback, otherExistingGroups);
// don't allow editing the overall leaderboard's scoring scheme if the group already uses an overall leaderboard // don't allow editing the overall leaderboard's scoring scheme if the group already uses an overall leaderboard
getOverallLeaderboardScoringSchemeListBox().setEnabled(!group.hasOverallLeaderboard()); getOverallLeaderboardScoringSchemeListBox().setEnabled(!group.hasOverallLeaderboard());
nameEntryField = createTextBox(group.name); nameEntryField = createTextBox(group.name);
descriptionEntryField = createTextArea(group.description); descriptionEntryField = createTextArea(group.description);
} displayLeaderboardsInReverseOrderCheckBox.setValue(group.displayLeaderboardsInReverseOrder);
displayLeaderboardsInReverseOrderCheckBox.setEnabled(false);
@Override useOverallLeaderboardCheckBox.setValue(group.hasOverallLeaderboard());
protected Widget getAdditionalWidget() {
Widget result = super.getAdditionalWidget();
getUseOverallLeaderboardCheckBox().setValue(group.hasOverallLeaderboard(), /* fire event */ true);
return result;
} }
} }
@@ -1,57 +1,32 @@
package com.sap.sailing.gwt.ui.adminconsole; package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Arrays;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safecss.shared.SafeStylesUtils;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell {
public static final String ACTION_REMOVE = "ACTION_REMOVE";
private StringMessages stringConstants; public static final String ACTION_UNLINK = "ACTION_UNLINK";
public static final String ACTION_EDIT = "ACTION_EDIT";
private final StringMessages stringMessages;
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
public LeaderboardRaceConfigImagesBarCell(StringMessages stringConstants) { public LeaderboardRaceConfigImagesBarCell(StringMessages stringConstants) {
super(); super();
this.stringConstants = stringConstants; this.stringMessages = stringConstants;
} }
public LeaderboardRaceConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringConstants) { public LeaderboardRaceConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringMessages) {
super(); super();
this.stringConstants = stringConstants; this.stringMessages = stringMessages;
} }
private static ImagesBarTemplates templates = GWT.create(ImagesBarTemplates.class);
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
private static final SafeHtml ICON_EDIT = makeImage(resources.editIcon());
private static final SafeHtml ICON_UNLINK = makeImage(resources.unlinkIcon());
private static final SafeHtml ICON_REMOVE = makeImage(resources.removeIcon());
@Override @Override
protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { protected Iterable<ImageSpec> getImageSpecs() {
/* return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())),
* Always do a null check on the value. Cell widgets can pass null to cells if the underlying data contains a new ImageSpec(ACTION_UNLINK, stringMessages.actionRaceUnlink(), makeImagePrototype(resources.unlinkIcon())),
* null, or if the data arrives out of order. new ImageSpec(ACTION_REMOVE, stringMessages.actionRaceRemove(), makeImagePrototype(resources.removeIcon())));
*/
if (data == null) {
return;
}
SafeStyles imgStyle = SafeStylesUtils
.fromTrustedString("float:left;cursor:hand;cursor:pointer;padding-right:5px;");
SafeHtml rendered = templates.cell("ACTION_EDIT", imgStyle, stringConstants.actionRaceEdit(), ICON_EDIT);
sb.append(rendered);
rendered = templates.cell("ACTION_UNLINK", imgStyle, stringConstants.actionRaceUnlink(), ICON_UNLINK);
sb.append(rendered);
rendered = templates.cell("ACTION_REMOVE", imgStyle, stringConstants.actionRaceRemove(), ICON_REMOVE);
sb.append(rendered);
} }
} }
@@ -3,6 +3,7 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.List; import java.util.List;
import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DoubleBox;
import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Label;
@@ -18,14 +19,43 @@ import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
* @author Axel Uhl (D043530) * @author Axel Uhl (D043530)
* *
*/ */
public class RaceColumnInLeaderboardDialog extends DataEntryDialog<RaceColumnDTO> { public class RaceColumnInLeaderboardDialog extends DataEntryDialog<RaceColumnInLeaderboardDialog.RaceColumnDescriptor> {
private final TextBox raceNameBox; private final TextBox raceNameBox;
private final DoubleBox explicitFactorBox;
private final CheckBox isMedalRace; private final CheckBox isMedalRace;
private final StringMessages stringConstants; private final StringMessages stringMessages;
private RaceColumnDTO raceColumnToEdit; public static class RaceColumnDescriptor {
private String name;
private boolean isMedalRace;
private Double explicitFactor;
public RaceColumnDescriptor(String name, boolean isMedalRace, Double explicitFactor) {
super();
this.name = name;
this.isMedalRace = isMedalRace;
this.explicitFactor = explicitFactor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMedalRace() {
return isMedalRace;
}
public void setMedalRace(boolean isMedalRace) {
this.isMedalRace = isMedalRace;
}
public Double getExplicitFactor() {
return explicitFactor;
}
public void setExplicitFactor(Double explicitFactor) {
this.explicitFactor = explicitFactor;
}
}
private static class RaceDialogValidator implements Validator<RaceColumnDTO> { private static class RaceDialogValidator implements Validator<RaceColumnDescriptor> {
private StringMessages stringConstants; private StringMessages stringConstants;
private List<RaceColumnDTO> existingRaces; private List<RaceColumnDTO> existingRaces;
@@ -36,16 +66,16 @@ public class RaceColumnInLeaderboardDialog extends DataEntryDialog<RaceColumnDTO
} }
@Override @Override
public String getErrorMessage(RaceColumnDTO valueToValidate) { public String getErrorMessage(RaceColumnDescriptor valueToValidate) {
String errorMessage; String errorMessage;
String racename = valueToValidate.getRaceColumnName(); String racename = valueToValidate.getName();
Boolean isMedalRace = valueToValidate.isMedalRace(); Boolean isMedalRace = valueToValidate.isMedalRace();
boolean isNameNotEmpty = racename != null & racename != ""; boolean isNameNotEmpty = racename != null & racename != "";
boolean medalRaceNotNull = isMedalRace != null; boolean medalRaceNotNull = isMedalRace != null;
boolean unique = true; boolean unique = true;
for (RaceColumnDTO raceColumn : existingRaces) { for (RaceColumnDTO raceColumn : existingRaces) {
if (raceColumn.getRaceColumnName().equals(valueToValidate.getRaceColumnName())) { if (raceColumn.getRaceColumnName().equals(valueToValidate.getName())) {
unique = false; unique = false;
} }
} }
@@ -64,21 +94,21 @@ public class RaceColumnInLeaderboardDialog extends DataEntryDialog<RaceColumnDTO
} }
public RaceColumnInLeaderboardDialog(List<RaceColumnDTO> existingRaces, RaceColumnDTO raceColumnToEdit, public RaceColumnInLeaderboardDialog(List<RaceColumnDTO> existingRaces, RaceColumnDTO raceColumnToEdit,
StringMessages stringConstants, DialogCallback<RaceColumnDTO> callback) { StringMessages stringMessages, DialogCallback<RaceColumnDescriptor> callback) {
super(stringConstants.actionRaceEdit(), null, stringConstants.ok(), stringConstants.cancel(), super(stringMessages.actionRaceEdit(), null, stringMessages.ok(), stringMessages.cancel(),
new RaceDialogValidator(stringConstants, existingRaces), callback); new RaceDialogValidator(stringMessages, existingRaces), callback);
this.raceColumnToEdit = raceColumnToEdit; this.stringMessages = stringMessages;
this.stringConstants = stringConstants;
raceNameBox = createTextBox(raceColumnToEdit.getRaceColumnName()); raceNameBox = createTextBox(raceColumnToEdit.getRaceColumnName());
isMedalRace = createCheckbox(stringConstants.medalRace()); explicitFactorBox = raceColumnToEdit.getExplicitFactor() == null ?
createDoubleBox(/* visibleLength */ 4) : createDoubleBox(raceColumnToEdit.getExplicitFactor(), /* visibleLength */ 4);
isMedalRace = createCheckbox(stringMessages.medalRace());
isMedalRace.setValue(raceColumnToEdit.isMedalRace()); isMedalRace.setValue(raceColumnToEdit.isMedalRace());
} }
@Override @Override
protected RaceColumnDTO getResult() { protected RaceColumnDescriptor getResult() {
raceColumnToEdit.name = raceNameBox.getValue(); return new RaceColumnDescriptor(raceNameBox.getValue(), isMedalRace.getValue(),
raceColumnToEdit.setMedalRace(isMedalRace.getValue()); explicitFactorBox.getText().trim().length() == 0 ? null : explicitFactorBox.getValue());
return raceColumnToEdit;
} }
@Override @Override
@@ -88,10 +118,14 @@ public class RaceColumnInLeaderboardDialog extends DataEntryDialog<RaceColumnDTO
HorizontalPanel raceNamePanel = new HorizontalPanel(); HorizontalPanel raceNamePanel = new HorizontalPanel();
raceNamePanel.setSpacing(3); raceNamePanel.setSpacing(3);
mainPanel.add(raceNamePanel); mainPanel.add(raceNamePanel);
raceNamePanel.add(new Label(stringConstants.name() + ":")); raceNamePanel.add(new Label(stringMessages.name() + ":"));
raceNamePanel.add(raceNameBox); raceNamePanel.add(raceNameBox);
HorizontalPanel factorPanel = new HorizontalPanel();
factorPanel.setSpacing(3);
mainPanel.add(factorPanel);
factorPanel.add(new Label(stringMessages.factor() + ":"));
factorPanel.add(explicitFactorBox);
alignAllPanelWidgetsVertically(raceNamePanel, HasVerticalAlignment.ALIGN_MIDDLE); alignAllPanelWidgetsVertically(raceNamePanel, HasVerticalAlignment.ALIGN_MIDDLE);
mainPanel.add(isMedalRace); mainPanel.add(isMedalRace);
return mainPanel; return mainPanel;
} }
@@ -206,7 +206,7 @@ public class RaceColumnInRegattaSeriesDialog extends DataEntryDialog<Pair<Series
raceNamePrefixTextBox.setText(seriesName.substring(0, 1).toUpperCase()); raceNamePrefixTextBox.setText(seriesName.substring(0, 1).toUpperCase());
} }
} else { } else {
seriesListBox.addItem("Please select a series"); seriesListBox.addItem(stringMessages.pleaseSelectASeries());
for (SeriesDTO series : regatta.series) { for (SeriesDTO series : regatta.series) {
seriesListBox.addItem(series.name); seriesListBox.addItem(series.name);
} }
@@ -378,6 +378,7 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
} }
}; };
result.addColumn(buoyNameColumn, stringMessages.buoy()); result.addColumn(buoyNameColumn, stringMessages.buoy());
final SafeHtmlCell buoyPositionCell = new SafeHtmlCell(); final SafeHtmlCell buoyPositionCell = new SafeHtmlCell();
Column<BuoyDTO, SafeHtml> buoyPositionColumn = new Column<BuoyDTO, SafeHtml>(buoyPositionCell) { Column<BuoyDTO, SafeHtml> buoyPositionColumn = new Column<BuoyDTO, SafeHtml>(buoyPositionCell) {
@Override @Override
@@ -388,6 +389,15 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
} }
}; };
result.addColumn(buoyPositionColumn, stringMessages.position()); result.addColumn(buoyPositionColumn, stringMessages.position());
TextColumn<BuoyDTO> buoyColorColumn = new TextColumn<BuoyDTO>() {
@Override
public String getValue(BuoyDTO buoyDTO) {
return buoyDTO.displayColor != null ? buoyDTO.displayColor : "";
}
};
result.addColumn(buoyColorColumn, stringMessages.color());
return result; return result;
} }
@@ -1,56 +1,30 @@
package com.sap.sailing.gwt.ui.adminconsole; package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Arrays;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safecss.shared.SafeStylesUtils;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
public class RegattaConfigImagesBarCell extends ImagesBarCell { public class RegattaConfigImagesBarCell extends ImagesBarCell {
static final String ACTION_REMOVE = "ACTION_REMOVE"; static final String ACTION_REMOVE = "ACTION_REMOVE";
static final String ACTION_EDIT = "ACTION_EDIT"; static final String ACTION_EDIT = "ACTION_EDIT";
private final StringMessages stringMessages;
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
public RegattaConfigImagesBarCell(StringMessages stringConstants) { public RegattaConfigImagesBarCell(StringMessages stringConstants) {
super(); super();
this.stringMessages = stringConstants; this.stringMessages = stringConstants;
} }
public RegattaConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringConstants) { public RegattaConfigImagesBarCell(SafeHtmlRenderer<String> renderer, StringMessages stringMessages) {
super(); super();
this.stringMessages = stringConstants; this.stringMessages = stringMessages;
} }
private static ImagesBarTemplates templates = GWT.create(ImagesBarTemplates.class);
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
private static final SafeHtml ICON_EDIT = makeImage(resources.editIcon());
private static final SafeHtml ICON_REMOVE = makeImage(resources.removeIcon());
private StringMessages stringMessages;
@Override @Override
protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { protected Iterable<ImageSpec> getImageSpecs() {
/* return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())),
* Always do a null check on the value. Cell widgets can pass null to new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon())));
* cells if the underlying data contains a null, or if the data arrives
* out of order.
*/
if (data == null) {
return;
}
SafeStyles imgStyle = SafeStylesUtils.fromTrustedString("float:left;cursor:hand;cursor:pointer;padding-right:5px;");
SafeHtml rendered = templates.cell(ACTION_EDIT, imgStyle, stringMessages.actionEdit(), ICON_EDIT);
sb.append(rendered);
rendered = templates.cell(ACTION_REMOVE, imgStyle, stringMessages.actionRemove(), ICON_REMOVE);
sb.append(rendered);
} }
} }
@@ -499,17 +499,17 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
private boolean checkBoatClassMatch(TracTracRaceRecordDTO tracTracRecord, RegattaDTO selectedRegatta) { private boolean checkBoatClassMatch(TracTracRaceRecordDTO tracTracRecord, RegattaDTO selectedRegatta) {
Iterable<String> boatClassNames = tracTracRecord.boatClassNames; Iterable<String> boatClassNames = tracTracRecord.boatClassNames;
if(boatClassNames != null && Util.size(boatClassNames) > 0) { if (boatClassNames != null && Util.size(boatClassNames) > 0) {
String tracTracBoatClass = boatClassNames.iterator().next(); String tracTracBoatClass = boatClassNames.iterator().next();
if(selectedRegatta == null) { if (selectedRegatta == null) {
// in case no regatta has been selected we check if there would be a matching regatta // in case no regatta has been selected we check if there would be a matching regatta
for(RegattaDTO regatta: allRegattas) { for (RegattaDTO regatta : allRegattas) {
if(tracTracBoatClass.equalsIgnoreCase(regatta.boatClass.name)) { if (tracTracBoatClass.equalsIgnoreCase(regatta.boatClass.name)) {
return false; return false;
} }
} }
} else { } else {
if(!tracTracBoatClass.equalsIgnoreCase(selectedRegatta.boatClass.name)) { if (!tracTracBoatClass.equalsIgnoreCase(selectedRegatta.boatClass.name)) {
return false; return false;
} }
} }
@@ -530,21 +530,22 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
List<TracTracRaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<TracTracRaceRecordDTO>(); List<TracTracRaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<TracTracRaceRecordDTO>();
for (TracTracRaceRecordDTO rr : raceList.getList()) { for (TracTracRaceRecordDTO rr : raceList.getList()) {
if (raceTable.getSelectionModel().isSelected(rr)) { if (raceTable.getSelectionModel().isSelected(rr)) {
if(!checkBoatClassMatch(rr, selectedRegatta)) if (!checkBoatClassMatch(rr, selectedRegatta)) {
racesWithNotMatchingBoatClasses.add(rr); racesWithNotMatchingBoatClasses.add(rr);
}
} }
} }
if(racesWithNotMatchingBoatClasses.size() > 0) { if (racesWithNotMatchingBoatClasses.size() > 0) {
String warningText = "WARNING\n"; String warningText = "WARNING\n";
if(selectedRegatta != null) { if (selectedRegatta != null) {
warningText += stringMessages.boatClassDoesNotMatchSelectedRegatta(selectedRegatta.boatClass.name, selectedRegatta.name); warningText += stringMessages.boatClassDoesNotMatchSelectedRegatta(selectedRegatta.boatClass.name, selectedRegatta.name);
} else { } else {
warningText += stringMessages.regattaExistForSelectedBoatClass(); warningText += stringMessages.regattaExistForSelectedBoatClass();
} }
warningText += "\n\n"; warningText += "\n\n";
warningText += stringMessages.races() + "\n"; warningText += stringMessages.races() + "\n";
for(TracTracRaceRecordDTO record: racesWithNotMatchingBoatClasses) { for (TracTracRaceRecordDTO record : racesWithNotMatchingBoatClasses) {
warningText += record.name + "\n"; warningText += record.name + "\n";
} }
if(!Window.confirm(warningText)) { if(!Window.confirm(warningText)) {
@@ -297,7 +297,7 @@ public class TrackedRacesListComposite extends SimplePanel implements Component<
TextColumn<RaceDTO> hasWindDataColumn = new TextColumn<RaceDTO>() { TextColumn<RaceDTO> hasWindDataColumn = new TextColumn<RaceDTO>() {
@Override @Override
public String getValue(RaceDTO raceDTO) { public String getValue(RaceDTO raceDTO) {
if (raceDTO.isTracked && raceDTO.trackedRace != null && raceDTO.trackedRace.hasWindData == true) if (raceDTO.trackedRace != null && raceDTO.trackedRace.hasWindData == true)
return stringMessages.yes(); return stringMessages.yes();
else else
return stringMessages.no(); return stringMessages.no();
@@ -307,17 +307,18 @@ public class TrackedRacesListComposite extends SimplePanel implements Component<
TextColumn<RaceDTO> hasGPSDataColumn = new TextColumn<RaceDTO>() { TextColumn<RaceDTO> hasGPSDataColumn = new TextColumn<RaceDTO>() {
@Override @Override
public String getValue(RaceDTO raceDTO) { public String getValue(RaceDTO raceDTO) {
if (raceDTO.isTracked && raceDTO.trackedRace != null && raceDTO.trackedRace.hasGPSData == true) if (raceDTO.trackedRace != null && raceDTO.trackedRace.hasGPSData == true) {
return stringMessages.yes(); return stringMessages.yes();
else } else {
return stringMessages.no(); return stringMessages.no();
}
} }
}; };
TextColumn<RaceDTO> raceLiveDelayColumn = new TextColumn<RaceDTO>() { TextColumn<RaceDTO> raceLiveDelayColumn = new TextColumn<RaceDTO>() {
@Override @Override
public String getValue(RaceDTO raceDTO) { public String getValue(RaceDTO raceDTO) {
if (raceDTO.isTracked && raceDTO.trackedRace.delayToLiveInMs > 0) { if (raceDTO.isTracked && raceDTO.trackedRace != null && raceDTO.trackedRace.delayToLiveInMs > 0) {
return "" + raceDTO.trackedRace.delayToLiveInMs / 1000; return "" + raceDTO.trackedRace.delayToLiveInMs / 1000;
} }
return ""; return "";
@@ -39,7 +39,8 @@ public class ColorMap<T> {
* Returns a color that is computed once. * Returns a color that is computed once.
* *
* @param object * @param object
* An ID unique for a competitor. * An ID unique for something this map is to provide a color for. Color assignment is based on the
* object's {@link Object#equals(Object)} and {@link Object#hashCode()}.
* @return A color in hex/html-format (e.g. #ff0000) * @return A color in hex/html-format (e.g. #ff0000)
*/ */
public String getColorByID(T object) { public String getColorByID(T object) {
@@ -22,6 +22,7 @@ import com.google.gwt.user.client.ui.IntegerBox;
import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.LongBox; import com.google.gwt.user.client.ui.LongBox;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
@@ -296,15 +297,12 @@ public abstract class DataEntryDialog<T> {
public FlowPanel createHeadline(String headlineText, boolean regularHeadline) { public FlowPanel createHeadline(String headlineText, boolean regularHeadline) {
FlowPanel headlinePanel = new FlowPanel(); FlowPanel headlinePanel = new FlowPanel();
Label headlineLabel = new Label(headlineText); Label headlineLabel = new Label(headlineText);
if (regularHeadline) { if (regularHeadline) {
headlinePanel.addStyleName("dialogInnerHeadline"); headlinePanel.addStyleName("dialogInnerHeadline");
} else { } else {
headlinePanel.addStyleName("dialogInnerHeadlineOther"); headlinePanel.addStyleName("dialogInnerHeadlineOther");
} }
headlinePanel.add(headlineLabel); headlinePanel.add(headlineLabel);
return headlinePanel; return headlinePanel;
} }
@@ -323,6 +321,20 @@ public abstract class DataEntryDialog<T> {
return result; return result;
} }
public RadioButton createRadioButton(String radioButtonGroupName, String radioButtonLabel) {
RadioButton result = new RadioButton(radioButtonGroupName, radioButtonLabel);
result.setWordWrap(false);
result.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
validate();
}
});
AbstractEntryPoint.linkEnterToButton(getOkButton(), result);
AbstractEntryPoint.linkEscapeToButton(getCancelButton(), result);
return result;
}
public ListBox createListBox(boolean isMultipleSelect) { public ListBox createListBox(boolean isMultipleSelect) {
ListBox result = new ListBox(isMultipleSelect); ListBox result = new ListBox(isMultipleSelect);
result.addChangeHandler(new ChangeHandler() { result.addChangeHandler(new ChangeHandler() {
@@ -336,12 +348,11 @@ public abstract class DataEntryDialog<T> {
return result; return result;
} }
protected void alignAllPanelWidgetsVertically(HorizontalPanel panel, HasVerticalAlignment.VerticalAlignmentConstant alignment) { public void alignAllPanelWidgetsVertically(HorizontalPanel panel, HasVerticalAlignment.VerticalAlignmentConstant alignment) {
for(int i = 0; i < panel.getWidgetCount(); i++) { for(int i = 0; i < panel.getWidgetCount(); i++) {
panel.setCellVerticalAlignment(panel.getWidget(i), alignment); panel.setCellVerticalAlignment(panel.getWidget(i), alignment);
} }
} }
/** /**
* Can contribute an additional widget to be displayed underneath the text entry field. If <code>null</code> is * Can contribute an additional widget to be displayed underneath the text entry field. If <code>null</code> is
@@ -62,6 +62,10 @@ public class DetailTypeFormatter {
return stringMessages.maximumSpeedOverGroundInKnots(); return stringMessages.maximumSpeedOverGroundInKnots();
case TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS: case TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS:
return stringMessages.totalTimeSailedDownwindInSeconds(); return stringMessages.totalTimeSailedDownwindInSeconds();
case TOTAL_TIME_SAILED_UPWIND_IN_SECONDS:
return stringMessages.totalTimeSailedUpwindInSeconds();
case TOTAL_TIME_SAILED_REACHING_IN_SECONDS:
return stringMessages.totalTimeSailedReachingInSeconds();
case TOTAL_TIME_SAILED_IN_SECONDS: case TOTAL_TIME_SAILED_IN_SECONDS:
return stringMessages.totalTimeSailedInSeconds(); return stringMessages.totalTimeSailedInSeconds();
} }
@@ -49,5 +49,24 @@ public class LogoAndTitlePanel extends FlowPanel {
this.add(subTitleLabelWrapper); this.add(subTitleLabelWrapper);
} }
} }
public String getTitle() {
return titleLabel != null ? titleLabel.getText() : null;
}
public void setTitle(String title) {
if(titleLabel != null) {
titleLabel.setText(title);
}
}
public String getSubTitle() {
return subTitleLabel != null ? subTitleLabel.getText() : null;
}
public void setSubTitle(String subTitle) {
if(subTitleLabel != null) {
subTitleLabel.setText(subTitle);
}
}
} }
@@ -13,7 +13,7 @@ import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements RaceSelectionChangeListener, RaceTimesInfoProviderListener { public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements RaceSelectionChangeListener, RaceTimesInfoProviderListener {
private RaceTimesInfoProvider raceTimesInfoProvider; private final RaceTimesInfoProvider raceTimesInfoProvider;
private RegattaAndRaceIdentifier selectedRace; private RegattaAndRaceIdentifier selectedRace;
private boolean autoAdjustPlayMode; private boolean autoAdjustPlayMode;
private RaceTimesInfoDTO lastRaceTimesInfo; private RaceTimesInfoDTO lastRaceTimesInfo;
@@ -84,9 +84,10 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
@Override @Override
public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) { public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) {
super.onTimeZoomChanged(zoomStartTimepoint, zoomEndTimepoint);
isTimeZoomed = true; isTimeZoomed = true;
timeSlider.setZoomed(true); timeSlider.setZoomed(true);
timer.setAutoAdvance(false); // timer.setAutoAdvance(false);
setMinMax(zoomStartTimepoint, zoomEndTimepoint, false); setMinMax(zoomStartTimepoint, zoomEndTimepoint, false);
timeSlider.clearMarkersAndLabelsAndTicks(); timeSlider.clearMarkersAndLabelsAndTicks();
redrawAllMarkers(lastRaceTimesInfo); redrawAllMarkers(lastRaceTimesInfo);
@@ -94,9 +95,10 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
@Override @Override
public void onTimeZoomReset() { public void onTimeZoomReset() {
super.onTimeZoomReset();
isTimeZoomed = false; isTimeZoomed = false;
timeSlider.setZoomed(false); timeSlider.setZoomed(false);
timer.setAutoAdvance(true); // timer.setAutoAdvance(true);
updateMinMax(this.lastRaceTimesInfo); updateMinMax(this.lastRaceTimesInfo);
timeSlider.clearMarkersAndLabelsAndTicks(); timeSlider.clearMarkersAndLabelsAndTicks();
redrawAllMarkers(lastRaceTimesInfo); redrawAllMarkers(lastRaceTimesInfo);
@@ -11,44 +11,46 @@ import java.util.Set;
import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
public class RaceTimesInfoProvider { public class RaceTimesInfoProvider {
private SailingServiceAsync sailingService; private final SailingServiceAsync sailingService;
private ErrorReporter errorReporter; private final ErrorReporter errorReporter;
private Set<RegattaAndRaceIdentifier> raceIdentifiers; private final Set<RegattaAndRaceIdentifier> raceIdentifiers;
private long requestInterval; private long requestIntervalInMillis;
private HashMap<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos; private final HashMap<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos;
private final Set<RaceTimesInfoProviderListener> listeners;
private Set<RaceTimesInfoProviderListener> listeners;
/** /**
* The <code>raceIdentifiers</code> has to be <code>not-null</code>, but can be empty. * The <code>raceIdentifiers</code> has to be non-<code>null</code>, but can be empty.
*/ */
public RaceTimesInfoProvider(SailingServiceAsync sailingService, ErrorReporter errorReporter, Collection<RegattaAndRaceIdentifier> raceIdentifiers, long requestInterval) { public RaceTimesInfoProvider(SailingServiceAsync sailingService, ErrorReporter errorReporter,
Collection<RegattaAndRaceIdentifier> raceIdentifiers, long requestIntervalInMillis) {
this.sailingService = sailingService; this.sailingService = sailingService;
this.errorReporter = errorReporter; this.errorReporter = errorReporter;
this.raceIdentifiers = new HashSet<RegattaAndRaceIdentifier>(raceIdentifiers); this.raceIdentifiers = new HashSet<RegattaAndRaceIdentifier>(raceIdentifiers);
this.requestInterval = requestInterval; this.requestIntervalInMillis = requestIntervalInMillis;
raceTimesInfos = new HashMap<RegattaAndRaceIdentifier, RaceTimesInfoDTO>(); raceTimesInfos = new HashMap<RegattaAndRaceIdentifier, RaceTimesInfoDTO>();
listeners = new HashSet<RaceTimesInfoProviderListener>(); listeners = new HashSet<RaceTimesInfoProviderListener>();
RepeatingCommand command = new RepeatingCommand() { RepeatingCommand command = new RepeatingCommand() {
@Override @Override
public boolean execute() { public boolean execute() {
readTimesInfos(); readTimesInfos();
Scheduler.get().scheduleFixedPeriod(this, (int) RaceTimesInfoProvider.this.requestInterval); Scheduler.get().scheduleFixedPeriod(this, (int) RaceTimesInfoProvider.this.requestIntervalInMillis);
// don't execute *this* particular scheduled repeating command again; the line above re-scheduled already
return false; return false;
} }
}; };
Scheduler.get().scheduleFixedPeriod(command, (int) this.requestInterval); command.execute();
forceTimesInfosUpdate();
} }
/** /**
@@ -113,7 +115,7 @@ public class RaceTimesInfoProvider {
} }
public long getRequestInterval() { public long getRequestInterval() {
return requestInterval; return requestIntervalInMillis;
} }
/** /**
@@ -121,7 +123,7 @@ public class RaceTimesInfoProvider {
* @param requestInterval The new request interval * @param requestInterval The new request interval
*/ */
public void setRequestInterval(long requestInterval){ public void setRequestInterval(long requestInterval){
this.requestInterval = requestInterval; this.requestIntervalInMillis = requestInterval;
} }
/** /**
@@ -176,5 +178,23 @@ public class RaceTimesInfoProvider {
}); });
} }
} }
public RegattaAndRaceIdentifier getFirstStartedAndUnfinishedRace(LeaderboardDTO leaderboard) {
RegattaAndRaceIdentifier firstStartedAndUnfinishedRace = null;
Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos = getRaceTimesInfos();
for (RaceColumnDTO race : leaderboard.getRaceList()) {
for (FleetDTO fleet : race.getFleets()) {
RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(fleet);
if (raceIdentifier != null) {
RaceTimesInfoDTO raceTimes = raceTimesInfos.get(raceIdentifier);
if (raceTimes != null && raceTimes.startOfTracking != null && raceTimes.endOfRace == null) {
firstStartedAndUnfinishedRace = raceIdentifier;
break;
}
}
}
}
return firstStartedAndUnfinishedRace;
}
} }
@@ -203,7 +203,8 @@ public interface SailingService extends RemoteService {
void removeLeaderboardGroup(String groupName); void removeLeaderboardGroup(String groupName);
LeaderboardGroupDTO createLeaderboardGroup(String groupName, String description, int[] overallLeaderboardDiscardThresholds, LeaderboardGroupDTO createLeaderboardGroup(String groupName, String description,
boolean displayGroupsInReverseOrder, int[] overallLeaderboardDiscardThresholds,
ScoringSchemeType overallLeaderboardScoringSchemeType); ScoringSchemeType overallLeaderboardScoringSchemeType);
void updateLeaderboardGroup(String oldName, String newName, String description, List<String> leaderboardNames, void updateLeaderboardGroup(String oldName, String newName, String description, List<String> leaderboardNames,
@@ -264,4 +265,6 @@ public interface SailingService extends RemoteService {
StrippedLeaderboardDTO getLeaderboard(String leaderboardName); StrippedLeaderboardDTO getLeaderboard(String leaderboardName);
void suppressCompetitorInLeaderboard(String leaderboardName, String competitorIdAsString, boolean suppressed); void suppressCompetitorInLeaderboard(String leaderboardName, String competitorIdAsString, boolean suppressed);
void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double newFactor);
} }
@@ -302,10 +302,11 @@ public interface SailingServiceAsync {
/** /**
* Creates a new group with the name <code>groupname</code>, the description <code>description</code> and an empty list of leaderboards.<br/> * Creates a new group with the name <code>groupname</code>, the description <code>description</code> and an empty list of leaderboards.<br/>
* @param displayGroupsInReverseOrder TODO
*/ */
void createLeaderboardGroup(String groupName, String description, void createLeaderboardGroup(String groupName, String description,
int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType, boolean displayGroupsInReverseOrder, int[] overallLeaderboardDiscardThresholds,
AsyncCallback<LeaderboardGroupDTO> callback); ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback<LeaderboardGroupDTO> callback);
/** /**
* Updates the data of the group with the name <code>oldName</code>. * Updates the data of the group with the name <code>oldName</code>.
@@ -421,4 +422,8 @@ public interface SailingServiceAsync {
void suppressCompetitorInLeaderboard(String leaderboardName, String competitorIdAsString, boolean suppressed, AsyncCallback<Void> asyncCallback); void suppressCompetitorInLeaderboard(String leaderboardName, String competitorIdAsString, boolean suppressed, AsyncCallback<Void> asyncCallback);
void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double newFactor,
AsyncCallback<Void> callback);
} }
@@ -389,6 +389,8 @@ public interface StringMessages extends Messages {
String racesWithNamePrefix(); String racesWithNamePrefix();
String maximumSpeedOverGroundInKnots(); String maximumSpeedOverGroundInKnots();
String totalTimeSailedDownwindInSeconds(); String totalTimeSailedDownwindInSeconds();
String totalTimeSailedUpwindInSeconds();
String totalTimeSailedReachingInSeconds();
String totalTimeSailedInSeconds(); String totalTimeSailedInSeconds();
String overallDetailsToShow(); String overallDetailsToShow();
String hhmmssUnit(); String hhmmssUnit();
@@ -405,4 +407,23 @@ public interface StringMessages extends Messages {
String unsuppress(); String unsuppress();
String suppressedCompetitors(); String suppressedCompetitors();
String pleaseSelectASeries(); String pleaseSelectASeries();
String pleaseSelectARace();
String displayGroupsInReverseOrder();
String actionConfigureUrl();
String configureLeaderboardUrlDescription();
String embedded();
String additionalUrlSettings();
String restrictToSingleSelectedRace();
String expandSelectedRace();
String showRaceDetails();
String numberOfRaces();
String numberOfRacesMustBeNonNegativeNumber();
String chooseTheWayYouSelectRaces();
String selectFromAllRaces();
String selectANumberOfRaces();
String numberOfLastNRaces();
String expandLastRace();
String factor();
String errorUpdatingIsMedalRace(String message);
String maneuverLoss();
} }
@@ -390,6 +390,8 @@ actionAddRaces=Add races
racesWithNamePrefix=races with name prefix racesWithNamePrefix=races with name prefix
maximumSpeedOverGroundInKnots=Max SOG maximumSpeedOverGroundInKnots=Max SOG
totalTimeSailedDownwindInSeconds=Total time downwind totalTimeSailedDownwindInSeconds=Total time downwind
totalTimeSailedUpwindInSeconds=Total time upwind
totalTimeSailedReachingInSeconds=Total time reaching
totalTimeSailedInSeconds=Total time totalTimeSailedInSeconds=Total time
hhmmssUnit=hh:mm:ss hhmmssUnit=hh:mm:ss
actionAddWindData=Add wind data actionAddWindData=Add wind data
@@ -405,5 +407,23 @@ isPublic=Is public
suppress=Suppress suppress=Suppress
unsuppress=Unsuppress unsuppress=Unsuppress
suppressedCompetitors=Suppressed Competitors suppressedCompetitors=Suppressed Competitors
pleaseSelectASeries=Please select a series. pleaseSelectASeries=Please select a series
pleaseSelectARace=Please select a race
displayGroupsInReverseOrder=Display groups in reverse order
actionConfigureUrl=Configure URL
configureLeaderboardUrlDescription=Configure a leaderboard and obtain the resulting URL from the link
embedded=Embedded
additionalUrlSettings=Additional URL Settings
restrictToSingleSelectedRace=Restrict to single selected race
expandSelectedRace=Expand selected race
showRaceDetails=Allow for race details
numberOfRaces=Number of races
numberOfRacesMustBeNonNegativeNumber=Please enter the number of last races to be displayed
chooseTheWayYouSelectRaces=Choose the way you select races
selectFromAllRaces=Select from all races
selectANumberOfRaces=Select a number of races
numberOfLastNRaces=Number of most current races to show
expandLastRace=Always expand last race shown
factor=Factor
errorUpdatingIsMedalRace=Error updating the medal race setting: {0}
maneuverLoss=Maneuver loss
@@ -389,8 +389,10 @@ overallStandings=Gesamtstand
type=Typ type=Typ
actionAddRaces=Rennen hinzuf�gen actionAddRaces=Rennen hinzuf�gen
racesWithNamePrefix=Rennen mit Namens-Pr�fix racesWithNamePrefix=Rennen mit Namens-Pr�fix
maximumSpeedOverGroundInKnots=Max F�G maximumSpeedOverGroundInKnots=Max FüG
totalTimeSailedDownwindInSeconds=Gesamtzeit Vorwind totalTimeSailedDownwindInSeconds=Gesamtzeit Vorwind
totalTimeSailedUpwindInSeconds=Gesamtzeit Kreuz
totalTimeSailedReachingInSeconds=Gesamtzeit Holeschlag
totalTimeSailedInSeconds=Gesamtzeit totalTimeSailedInSeconds=Gesamtzeit
hhmmssUnit=hh:mm:ss hhmmssUnit=hh:mm:ss
actionAddWindData=Winddaten hinzufügen actionAddWindData=Winddaten hinzufügen
@@ -401,9 +403,28 @@ latitude=Breite
longitude=Länge longitude=Länge
actionAddEvent=Veranstaltung hinzufügen actionAddEvent=Veranstaltung hinzufügen
publicationUrl=URL für die Veröffentlichung publicationUrl=URL für die Veröffentlichung
suppress=Entfernen suppress=Entfernen
isPublic=Öffentlich zugänglich isPublic=Öffentlich zugänglich
suppress=Entfernen suppress=Entfernen
unsuppress=Wieder hinzufügen unsuppress=Wieder hinzufügen
suppressedCompetitors=Unterdrückte Wettkampfteilnehmer suppressedCompetitors=Unterdrückte Wettkampfteilnehmer
pleaseSelectASeries=Bitte wählen Sie eine Serie aus. pleaseSelectASeries=Bitte wählen Sie eine Serie aus
pleaseSelectARace=Bitte wählen Sie ein Rennen aus
displayGroupsInReverseOrder=Zeige die Gruppen in umgekehrter Reihenfolge
actionConfigureUrl=URL konfigurieren
configureLeaderboardUrlDescription=Ergebnisansicht konfigurieren. Die fertige URL kann dem Link entnommen werden.
embedded=Eingebettet
additionalUrlSettings=Weitere URL-relevante Einstellungen
restrictToSingleSelectedRace=Nur einzelnes ausgewähltes Rennen zeigen
expandSelectedRace=Details des ausgewähltes Rennens zeigen
showRaceDetails=Details anbieten
numberOfRaces=Anzahl Rennen
numberOfRacesMustBeNonNegativeNumber=Bitte Anzahl der anzuzeigenden letzten Rennen eingeben
chooseTheWayYouSelectRaces=Wie wollen die anzuzeigenden Rennen ausgewählt werden?
selectFromAllRaces=Aus allen Rennen auswählen
selectANumberOfRaces=Eine Anzahl anzuzeigender Rennen auswählen
numberOfLastNRaces=Anzahl der zu zeigenden aktuellsten Rennen
expandLastRace=Immer letztes gezeigtes Rennen expandieren
factor=Faktor
errorUpdatingIsMedalRace=Fehler beim Ändern der Medaillenrennen-Einstellung: {0}
maneuverLoss=Manöververlust
@@ -0,0 +1,37 @@
package com.sap.sailing.gwt.ui.leaderboard;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
public abstract class AbstractRaceColumnSelection implements RaceColumnSelection {
@Override
public boolean isSelected(String raceColumnName) {
return Util.contains(getSelectedRaceColumnNames(), raceColumnName);
}
@Override
public Iterable<String> getSelectedRaceColumnNames() {
List<String> names = new ArrayList<String>();
for (RaceColumnDTO column : getSelectedRaceColumns()) {
names.add(column.name);
}
return names;
}
@Override
public Iterable<RaceColumnDTO> getSelectedRaceColumnsOrderedAsInLeaderboard(LeaderboardDTO leaderboard) {
Iterable<RaceColumnDTO> selectedColumns = getSelectedRaceColumns();
List<RaceColumnDTO> result = new ArrayList<RaceColumnDTO>();
for (RaceColumnDTO column : leaderboard.getRaceList()) {
if (Util.contains(selectedColumns, column)) {
result.add(column);
}
}
return result;
}
}
@@ -0,0 +1,31 @@
package com.sap.sailing.gwt.ui.leaderboard;
import com.google.gwt.i18n.client.NumberFormat;
import com.sap.sailing.domain.common.SortingOrder;
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
public class AbstractTimeInHoursMinutesSecondsColumn extends FormattedDoubleLegDetailColumn {
public AbstractTimeInHoursMinutesSecondsColumn(String title, String unit,
com.sap.sailing.gwt.ui.leaderboard.LegDetailColumn.LegDetailField<Double> field, int decimals, SortingOrder preferredSortingOrder,
String headerStyle, String columnStyle) {
super(title, unit, field, decimals, preferredSortingOrder, headerStyle, columnStyle);
}
@Override
public String getStringValueToRender(LeaderboardRowDTO object) {
Double timeInSeconds = getDoubleValue(object);
String result;
if (timeInSeconds == null) {
result = null;
} else {
int hh = (int) (timeInSeconds / 3600);
int mm = (int) ((timeInSeconds - 3600 * hh) / 60);
int ss = (int) (timeInSeconds - 3600 * hh - 60 * mm);
NumberFormat numberFormat = NumberFormatterFactory.getDecimalFormat(2, 0);
result = "" + numberFormat.format(hh) + ":" + numberFormat.format(mm) + ":" + numberFormat.format(ss);
}
return result;
}
}
@@ -1,27 +1,37 @@
package com.sap.sailing.gwt.ui.leaderboard; package com.sap.sailing.gwt.ui.leaderboard;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.gwt.dom.client.Style.Float; import com.google.gwt.dom.client.Style.Float;
import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
import com.sap.sailing.gwt.ui.client.RaceTimesCalculationUtil;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.TimeRangeChangeListener;
import com.sap.sailing.gwt.ui.client.TimeRangeWithZoomModel; import com.sap.sailing.gwt.ui.client.TimeRangeWithZoomModel;
import com.sap.sailing.gwt.ui.client.TimeRangeWithZoomProvider;
import com.sap.sailing.gwt.ui.client.Timer; import com.sap.sailing.gwt.ui.client.Timer;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sailing.gwt.ui.shared.charts.MultiChartPanel; import com.sap.sailing.gwt.ui.shared.charts.MultiChartPanel;
import com.sap.sailing.gwt.ui.shared.charts.MultiChartSettings; import com.sap.sailing.gwt.ui.shared.charts.MultiChartSettings;
import com.sap.sailing.gwt.ui.shared.components.ComponentToolbar; import com.sap.sailing.gwt.ui.shared.components.ComponentToolbar;
@@ -32,27 +42,36 @@ import com.sap.sailing.gwt.ui.shared.components.ComponentToolbar;
* @author Benjamin Ebling, Axel Uhl (d043530) * @author Benjamin Ebling, Axel Uhl (d043530)
* *
*/ */
public class CompareCompetitorsChartDialog extends DialogBoxExt { public class CompareCompetitorsChartDialog extends DialogBoxExt implements RaceTimesInfoProviderListener {
private final RaceSelectionProvider raceSelectionProvider; private final RaceSelectionProvider raceSelectionProvider;
private final SailingServiceAsync sailingService;
private final TimeRangeWithZoomProvider timeRangeWithZoomProvider;
private final Timer timer;
private final ErrorReporter errorReporter;
private final MultiChartPanel multiChartPanel; private final MultiChartPanel multiChartPanel;
final ListBox racesListBox; private final ListBox racesListBox;
public CompareCompetitorsChartDialog(SailingServiceAsync sailingService, public CompareCompetitorsChartDialog(SailingServiceAsync sailingService,
List<RegattaAndRaceIdentifier> races, final CompetitorSelectionProvider competitorSelectionProvider, Timer timer, List<RegattaAndRaceIdentifier> races, final RaceTimesInfoProvider raceTimesInfoProvider, final CompetitorSelectionProvider competitorSelectionProvider,
StringMessages stringConstants, ErrorReporter errorReporter) { Timer timer, StringMessages stringConstants, ErrorReporter errorReporter) {
super(new Label(stringConstants.close())); super(new Label(stringConstants.close()));
this.sailingService = sailingService;
this.timer = timer;
this.errorReporter = errorReporter;
this.timeRangeWithZoomProvider = new TimeRangeWithZoomModel();
this.setPopupPosition(15, 15); this.setPopupPosition(15, 15);
this.setHTML(stringConstants.compareCompetitors()); this.setHTML(stringConstants.compareCompetitors());
this.setWidth(Window.getClientWidth() - 250 + "px"); this.setWidth(Window.getClientWidth() - 250 + "px");
this.setAnimationEnabled(true); this.setAnimationEnabled(true);
raceTimesInfoProvider.addRaceTimesInfoProviderListener(this);
raceSelectionProvider = new RaceSelectionModel(); raceSelectionProvider = new RaceSelectionModel();
raceSelectionProvider.setAllRaces(races); raceSelectionProvider.setAllRaces(races);
multiChartPanel = new MultiChartPanel(sailingService, new AsyncActionsExecutor(), competitorSelectionProvider, raceSelectionProvider, multiChartPanel = new MultiChartPanel(sailingService, new AsyncActionsExecutor(), competitorSelectionProvider, raceSelectionProvider,
timer, new TimeRangeWithZoomModel(), stringConstants, errorReporter, false, false); timer, timeRangeWithZoomProvider, stringConstants, errorReporter, false, false);
multiChartPanel.setSize("100%", "100%"); multiChartPanel.setSize("100%", "100%");
VerticalPanel contentPanel = new VerticalPanel(); VerticalPanel contentPanel = new VerticalPanel();
@@ -66,7 +85,7 @@ public class CompareCompetitorsChartDialog extends DialogBoxExt {
HorizontalPanel raceSelectionPanel = new HorizontalPanel(); HorizontalPanel raceSelectionPanel = new HorizontalPanel();
raceSelectionPanel.setSpacing(3); raceSelectionPanel.setSpacing(3);
racesListBox = new ListBox(); racesListBox = new ListBox();
raceSelectionPanel.add(new Label("Select a race:")); raceSelectionPanel.add(new Label(stringConstants.pleaseSelectARace() + ":"));
raceSelectionPanel.add(racesListBox); raceSelectionPanel.add(racesListBox);
for (RegattaAndRaceIdentifier race : raceSelectionProvider.getAllRaces()) { for (RegattaAndRaceIdentifier race : raceSelectionProvider.getAllRaces()) {
racesListBox.addItem(race.toString()); racesListBox.addItem(race.toString());
@@ -101,7 +120,43 @@ public class CompareCompetitorsChartDialog extends DialogBoxExt {
} }
private void selectRace(final RegattaAndRaceIdentifier selectedRace) { private void selectRace(final RegattaAndRaceIdentifier selectedRace) {
raceSelectionProvider.setSelection(Collections.singletonList(selectedRace)); sailingService.getRaceTimesInfo(selectedRace, new AsyncCallback<RaceTimesInfoDTO>() {
@Override
public void onSuccess(RaceTimesInfoDTO raceTimesInfo) {
updateMinMax(raceTimesInfo);
raceSelectionProvider.setSelection(Collections.singletonList(selectedRace));
}
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error obtaining race times: " + caught.getMessage(), true /*silentMode */);
}
});
}
private void updateMinMax(RaceTimesInfoDTO newRaceTimesInfo) {
Pair<Date, Date> raceMinMax = RaceTimesCalculationUtil.caluclateRaceMinMax(timer, newRaceTimesInfo);
Date min = raceMinMax.getA();
Date max = raceMinMax.getB();
// never reduce max if it was already set
if (min != null && max != null && (timeRangeWithZoomProvider.getToTime() == null || timeRangeWithZoomProvider.getToTime().before(max))) {
timeRangeWithZoomProvider.setTimeRange(min, max, new TimeRangeChangeListener[0]);
}
}
private RegattaAndRaceIdentifier getSelectedRace() {
return raceSelectionProvider.getSelectedRaces().size() > 0 ? raceSelectionProvider.getSelectedRaces().get(0) : null;
}
@Override
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos) {
RegattaAndRaceIdentifier selectedRace = getSelectedRace();
if(selectedRace != null) {
updateMinMax(raceTimesInfos.get(selectedRace));
}
} }
} }
@@ -0,0 +1,92 @@
package com.sap.sailing.gwt.ui.leaderboard;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
/**
* An explicit selection of race columns. Requests to add to or clear the selection are respected. If an updated version
* of the leaderboard is announced, race columns added are implicitly also added to this selection.
*
* @author Axel Uhl (D043530)
*
*/
public class ExplicitRaceColumnSelection extends AbstractRaceColumnSelection implements RaceColumnSelection {
private final Map<String, RaceColumnDTO> selectedRaceColumns;
public ExplicitRaceColumnSelection() {
this.selectedRaceColumns = new HashMap<String, RaceColumnDTO>();
}
@Override
public Iterable<RaceColumnDTO> getSelectedRaceColumns() {
return selectedRaceColumns.values();
}
@Override
public void requestRaceColumnSelection(String raceColumnName, RaceColumnDTO column) {
selectedRaceColumns.put(raceColumnName, column);
}
@Override
public void requestClear() {
selectedRaceColumns.clear();
}
private List<RaceColumnDTO> getRacesAddedNew(LeaderboardDTO oldLeaderboard, LeaderboardDTO newLeaderboard) {
List<RaceColumnDTO> result = new ArrayList<RaceColumnDTO>();
for (RaceColumnDTO s : newLeaderboard.getRaceList()) {
if (oldLeaderboard == null || !leaderboardContainsColumnNamed(oldLeaderboard, s.getRaceColumnName())) {
result.add(s);
}
}
return result;
}
private boolean leaderboardContainsColumnNamed(LeaderboardDTO leaderboard, String raceColumnName) {
for (RaceColumnDTO column : leaderboard.getRaceList()) {
if (column.getRaceColumnName().equals(raceColumnName)) {
return true;
}
}
return false;
}
/**
* Considers the {@link #preSelectedRace} field as follows: if <code>null</code>, all race columns that
* <code>leaderboard</code> adds on top of the existing <code>oldLeaderboard</code> are returned.
* Otherwise, the column list obtained as described before is filtered such that only columns pass whose race
* identifier equals {@link #preSelectedRace}.
*
* @param newLeaderboard
* the new leaderboard, not yet {@link #setLeaderboard(LeaderboardDTO) set}, so as to allow for a
* comparison
*/
protected List<RaceColumnDTO> getRaceColumnsToAddImplicitly(LeaderboardDTO newLeaderboard, LeaderboardDTO oldLeaderboard) {
List<RaceColumnDTO> columnsToAddImplicitly = getRacesAddedNew(oldLeaderboard, newLeaderboard);
return columnsToAddImplicitly;
}
@Override
public void autoUpdateRaceColumnSelectionForUpdatedLeaderboard(LeaderboardDTO oldLeaderboard, LeaderboardDTO newLeaderboard) {
for (RaceColumnDTO selectedRaceColumn : getRaceColumnsToAddImplicitly(newLeaderboard, oldLeaderboard)) {
requestRaceColumnSelection(selectedRaceColumn.name, selectedRaceColumn);
}
}
@Override
public RaceColumnSelectionStrategies getType() {
return RaceColumnSelectionStrategies.EXPLICIT;
}
@Override
public Integer getNumberOfLastRaceColumnsToShow() {
return null;
}
}
@@ -0,0 +1,43 @@
package com.sap.sailing.gwt.ui.leaderboard;
import java.util.Iterator;
import java.util.List;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
/**
* A variant of {@link ExplicitRaceColumnSelection} which has a pre-selected race. As opposed to the base class,
* a new leaderboard column is added implicitly only if it is linked to the pre-selected race.
*
* @author Axel Uhl (D043530)
*
*/
public class ExplicitRaceColumnSelectionWithPreselectedRace extends ExplicitRaceColumnSelection {
private final RaceIdentifier preSelectedRace;
/**
* @param preSelectedRace must be non-<code>null</code>. Otherwise, you may use the superclass instead.
*/
public ExplicitRaceColumnSelectionWithPreselectedRace(RaceIdentifier preSelectedRace) {
super();
assert preSelectedRace != null;
this.preSelectedRace = preSelectedRace;
}
@Override
protected List<RaceColumnDTO> getRaceColumnsToAddImplicitly(LeaderboardDTO newLeaderboard,
LeaderboardDTO oldLeaderboard) {
List<RaceColumnDTO> columnsToAddImplicitly = super.getRaceColumnsToAddImplicitly(newLeaderboard, oldLeaderboard);
if (preSelectedRace != null) {
for (Iterator<RaceColumnDTO> i = columnsToAddImplicitly.iterator(); i.hasNext();) {
RaceColumnDTO next = i.next();
if (!next.containsRace(preSelectedRace)) {
i.remove();
}
}
}
return columnsToAddImplicitly;
}
}
@@ -1,8 +1,6 @@
package com.sap.sailing.gwt.ui.leaderboard; package com.sap.sailing.gwt.ui.leaderboard;
import com.google.gwt.i18n.client.NumberFormat;
import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
@@ -12,7 +10,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
* @author Axel Uhl (d043530) * @author Axel Uhl (d043530)
* *
*/ */
public class KingOfTheDownwindColumn extends FormattedDoubleLegDetailColumn { public class KingOfTheDownwindColumn extends AbstractTimeInHoursMinutesSecondsColumn {
private static class KingOfTheDownwindField implements LegDetailField<Double> { private static class KingOfTheDownwindField implements LegDetailField<Double> {
@Override @Override
@@ -29,20 +27,4 @@ public class KingOfTheDownwindColumn extends FormattedDoubleLegDetailColumn {
headerStyle, columnStyle); headerStyle, columnStyle);
} }
@Override
public String getStringValueToRender(LeaderboardRowDTO object) {
Double timeInSeconds = getDoubleValue(object);
String result;
if (timeInSeconds == null) {
result = null;
} else {
int hh = (int) (timeInSeconds / 3600);
int mm = (int) ((timeInSeconds - 3600 * hh) / 60);
int ss = (int) (timeInSeconds - 3600 * hh - 60 * mm);
NumberFormat numberFormat = NumberFormatterFactory.getDecimalFormat(2, 0);
result = "" + numberFormat.format(hh) + ":" + numberFormat.format(mm) + ":" + numberFormat.format(ss);
}
return result;
}
} }
Loaded 100 of 243 files, more files were not shown because too many files have changed in this diff. Show more