Merge branch 'master' into bug3992_new_concept

This commit is contained in:
Peter Siegmund committed 2017-06-01 08:55:36 +02:00
commit 28644c5b74
164 files changed
+4736 -2329

No files matched your search

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2017-04-12T15:29:20Z" original="/usr/sap/ljs/app/Converter_429408536936316109/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties" source-language="en" target-language="es">
<file datatype="javapropertyresourcebundle" date="2017-04-19T13:56:46Z" original="/usr/sap/ljs/app/Converter_429408536936316109/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties" source-language="en" target-language="es">
<header>
<skl>
<internal-file crc="9b947ad1ab0b80186f1c396a805fb4d1" form="base64">H4sIAAAAAAAAANy9a3PcSJIl+h2/At1tdyXZkqlHdffMVBusLkVKKs6IjyapUveuro2BmSCJUSaQA2SKYrXVf18/x90jAsgkqerp3XvtlpWSQLwQCER4ePjj+KJsyuvq1XlZz4ujeJ115bTabxeLerWqqr3lsjiThFxLdLkksMRPdXVbdZr3hdfZolzut82qa+e9NLjMp3aTLaVmcSo/2UqqXsi/N1+qZtUXF3ab633W39Z9f1Ev6ubaSpzHFC+ENj5Xs9fVVdtVxY91v2q7u7y9yi0jr7TcddVUXTkv3unfbC4l0d2+eC9XOS81sbouV6vS0+0uu62bWfFRfuTFmmr9peou7pYVhoo3zD+Vm/n78rKaFxc3dZ/L/6ubKketnHn57U09vcn7Nr8qO2RP28VyXq2q+V1eLZaru0nWVVdd1d8UZ/pX7hftl0pu8YdPOW/X3bRiX3K9zmblYlk1MiqHzarqvshrHnhK7kn4IPgQ/F78UrzqC311G67C/spQNPLHRwjXNkarelEV8g0qXl207XxVL5nAl8UbVatavkLey/yRam0jGfKu8+p6kr+VdBS7qjsZXUnSPA7FWno6y6+6diHjVDXj5pZl30s+UvtV2Untuqkm2XJe3p0vK+n6qVzlPS5j4o/VfFl86Kv8ty9/m8sUybvqej2XwUeJ3Ut5V62xk//2lRaYtevLebXLVJZioVIGWz5b27Dpo3ZWva/ls+AnpJxVuCr0T44UDtFBhVT+xnv2631Vfqn0w+erVrrGiuUqL7vLWj6ETGRU6P+UV/iIeV/JIpr1+dNqcj2RHv/xhXa5bapc1sR6VeVy91tLbST5GVbCDA/ML7lCZNDkkbNyVeZXGCi+5mHzb02L5cV3fvq5eZbhKxxU18VbfI2ns+r6Wda0nAB/rVYyuytpqpR/jXQbqfldtZpkq3Ylc+/T+sV3e99l07Lr7oq96XS9kBGXT5vFj1nsh8sktU+S+zT9vJpXU2mh+NuLX57/7eUvybToZVQ0cyI9PF9Pb2RUZ1V32ZbdrDhu816SZKKFtCy5FgJ1VV+vu3JVt02RVMwHOVlT3R60K/2/OK5uJ5NJVs3q1flUhrQv3shlftrWQmeQM23n60VzXMqXO2y862mvSCaZn9dN7iXypEhWzmbxiXuzGdrtqkbqxOQz3iPHFu0ZaXXTsLmL1htGMp5GAusLOQ8FMfVCH0LXSAAu2n2+CykAyulttm6QW3zgn3RA+Zh0HNnUtGymQhP3+UeWS1X21RvM6D2WP2WCzfEyx0sNCr1uy9X+XFb/uKQ8YpVPkZPN6l7mm9IrzNkf21shzXfnIBRGxk6a4kBL6YLBasDcVWoCUnlbr27ym/Y2FxJ/p8kyJJ1uDfGlPkox0Hd0fm/eScbdm6+yWfSDN2djJG54obzUgnnFkt7fixsQ+XY+64/W/ep1dbxeVF09DR1dhfx8IQVkEeeNFrmvhb1+WjUzeZuH2ii9kLeSlEeqjwhGUrp0WXUnV7oVnPtYkUyHsdIx0gk6mOlMGc9snUUXbVpS5rjNLsyztEIyFTDisoPICtSpzrm5Zfoo/atu9fvqeszaz8XJv2WLalbOUbs4wlWuGyG7yVTrMZNb2UK3rd8TSd++bLtSlsWZ/PDKN8dI1Z486XPkYN1jI+v06eQxzlhXr3NtI2Z8Q1NaOBM2QfibOVs70WttLcl4vLVe5hg2exD0s/v6JwM7L5d9Vexm1delbJHFf5d1cl28x7RaYbUfNkfC3QhhP7CErEQvrqvzdN/5tH716sUfcqZtKxCYDOmYZeuuneOd8utOWIdZPlt3mJjOa+x8aqblfKpbj69G2frqL3UvNJ0sIu+1Y+AXv1TgVy7vmIGdV+ZQNcPOgu+094/oGBr6B/es+fyurBvOuhxXISnt3ay+upI9G81Z88u2r7G56Uomr7XupMSKvA5LdNWXul0r55b15RIHAnmTvaac363qqVADTchDSkomU2qYpg/I5KDGebUCbRnSUU/Mes9NUrAE91ZSvF+dNEJ5rg+qFU4wujjBSoE4rEjwwWzOmJ3Zm/JTYo2844cKbNDJu4dLpAPrg7bx2YVD6OVL4RtfkFE+rr6uPpZ3S/AJ4AvIyxVvLva+qWD6zFBep8LqRt5zxCxjevTKVJZTnV+NNJvfWrs7+WXZO3Oe1gQh8HeSWddOa2FOF/I58uu2FTZZOtPX8nF0RmNyrqscxxKZYp2MucyoSXZdLp26xzd9J4dA6ZB+761F/B3f2Pvp8elpXCzPhOfsajnM6Iv7a1t3w4y2GU72gIMjC08OXJImU6G8kYfKEgzv7wv3p6N33DfSJjFnsBy4SJdVJ/kLrsnqP9foRavt2wO52+j7saV+fdlLQWuon3xq8PWuMQ7VfN7r9gn2tCq7eV114494267ns/wGh4Sbknya7N235L372Ek5O9b9JznrCfOPJ+OjgxYkXRx2zYZt2bX/oTuYD+q3jOdwHCeZz5EjafqdzBBfRDKa9+WlU3nbFNNDsh2eMbVuhQ48n1cV/tpCw/Cul8h8PmtvG05AjPGOTHgpIfNQWr7bye17lvO2ud4lm5w0wLWBicyvk/mjfK+6aN+1YQP7aJkJWW6lvw9W8hf9OH6H0EakvVsXYPgC9hWFafOPgZn5XxiE2INvGYfIJ7y1z7+Hrx8GR5b2r6/1+OjYqyYrwsYhSRnOx5Sv+t80PJ+a43a1nehupzVIkWkv9Fd45L7salmwWIlOQdnX+kqWuLDkUyEOWL3IrHk8XM4lX862OCnpwQxSgzcQGggrfFBdlev5aiBKoBRD0/VkKPwypWV6AMzlVqVimltezitl7i/81kU9uMfnsYoUfxmfyZObc4M8srFJJvNhSaIm6KkCW5yeIkjDNfHkig/AKUSzXH6Hw4hM+iT/jS0Bz7XueFf8vi+C5E4fO2w/VO9X7TLJa5cxa6oHZFl+kEuE6+xGZYwnV0liKnhM6mXyGeqmkgGPZYsDpuGkIt8hFk4P8X04quvBSg7Q5zIMZ9hMcM6Uk7Rw6DISnaV4Z/dQqLQ27JncwHD4tMaWbSdNnOI3gzTooJThe+9iIYxIV82YeM5LTRYeti8+nB322X/0bfOhmxf/en5ynH84e89G0BzbzNnSQWwp5lhzzEOdD12tz5VmrTCSrBgSVXabiDR0JvX5ftj6bKCym7ZfcY38aBcy9lNhTinA2b+ppp8v268yJB2YFBXJvpZtF+tM2OqDWJa8/nk6Z+yc6be5CRWyZbnuq731qhVepZ6awLY4RWpeenJuAt0MKWdD4W4sJeeyu4xyusgvUU6YP+2facbranVbVQ2etzf7AuLkRS41h63trpczck514+LC77W+CRja5hgLA8JLngvYgssFrmWarMiHyAT7uerabHrTtn31gY26JPnkKrLc2tlin8Vyfbg8WwtiNQxYcO1RVnVd2xkXjy8hdIxn7zdIz43PV6aS9BeHZNZ5W61IiVHpsCEzhk82rH9lhbSBOhYbNfbxpp5XF92dFIWsbPUxyNaH7d2inJCFOxLsFv3Lf8u2SVl622N+m1/Ny+usbpTUyvgbjW2LVSu/58I146oXrs8Z3BmJ6zn4wMjTozEIikGRVNfBKaknG9k1UVxFiJbkJ/thCT/7e6G5H468gJyWQqaKSI6koQ/L4sMyuT+QnbPAD+kGF3KyQoSC2+pgh3xl4B1Pmvmdy0gSsa6+bSuZQXCbSnNZdUs1fG2tig8qrOhX5bC3NYFXel8116ub4kIuZf7hmiupT0SiwtOR0VT5tfLad+06n9efydwJDwB5/A+Z1o8L833S3krWcX8hH0/IFi5zXGeNycxcO9QHPVG/mZeyw5rpjI9rmnpXpdSJKmWzocMGr7W9uXub6shdrIS0yaaD3+w/6ksZm3/Fb7as5GS/utuvu+lcEk/1Nrf7KEw77I/X87mK1MjPyOpfV2R7ZA62txT+p7L2oXw9VamlY6XUZ/9GZpWTmClugnzpwkQjQb6Ue8pGkaE0ZCxaCYw4+GpVL0We+1OTsJ7GmvMAakfpOrLkdd88eSKnpLqp5YhmkqODv68rUTj96/uiTOW4M+MD2XvheIuf/AiG1BzJqhWKYg8WU+UQEnNNTY/wLIED/oXLbjP2Ryb3+7acFaoc4fFfbrV9bTF8pygirBuhnpntXh+aelX0fncKGfCaG+aRTGPNfH58JDMRRwreL2S/k42s0rtP6xcvLl9k07lMnmIfv1QslV21P9I7QZ2VkpFZ1U+7emk8W7jOsIBUKE75ly2yjPrL7k6a4t+sab97LztZ1e230lLxoSFjLSNwVZt87fBkP/9ud85Cu1OUmkA6DlE4tmx5I2zw8odEkavAyCcXQc+5db8yYk8ngckXy9U2PcTk4TaEohh7/S2NRZqilSYZjj+yofwI5nO9zC6F4dq7Le8KcF55eQuFqJAc0h1eDeSpCwwkSSHI0na6BVpFUsWr7dVJz7ZXH5C3QN2mvB1mbm96Oahxz0MWZff5tOx77OdHck1FNnZ1ZDAla2U6dE72ZKp8bmTDDTQ7m7Xr63kpk38trG6na0n2ZCZKH5iq67/Pruq5TCfd397y2th+zXh9h4980kWdmnC//JZCVhJ9mpf27y+lXMGgWULq61Jb8+cskJRbe3qcCRJdPUC6CDcreeSB3pTK0+Q+XZXUq6ZLMhZLNa89ry0TupnXXXvby0BSTyMfw+6txNnAoiJpkioPbVKvvYKMX+hqToEAR4ysZCzyIVWKjjMHD00z33wFX/WXo/eFXuXySeUueZuDsr9J9E77LQS6IdE+RypSH36VVPIePk5px9Y9O6fWvdp7FId9rlfZnYzFX2UMmrY4buV4JRMRxH+P009OWdgI+FPq1DOxg/KTjSmfQTuhg/cbkpvD/t8wv4UNxd72gdyz8o/GUJPKKFeNTdWOgYWLine truncated
@@ -3829,7 +3829,7 @@
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="courseDesignerMode" resname="courseDesignerMode">
<source>Course designer</source>
<target state="signed-off">Diseñador de regatas</target>
<target state="translated">Diseñador de recorrido</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="racingProcedure" resname="racingProcedure">
<source>Racing procedure</source>
@@ -6521,11 +6521,11 @@
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="notificationRegattaConfigurationUpdatedUsingByMarks" resname="notificationRegattaConfigurationUpdatedUsingByMarks">
<source>Active regatta configuration was set, using the "By Marks" course designer.</source>
<target state="signed-off">Se ha fijado la configuración de regata activa con el diseñador de regatas "Por marcas".</target>
<target state="translated">Se ha fijado la configuración de regata activa con el diseñador de recorrido "Por marcas".</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="warningOverrideRegattaConfigurationCourseDesignerToByMarks" resname="warningOverrideRegattaConfigurationCourseDesignerToByMarks">
<source>Active regatta configuration already exists, but with the wrong course designer setting.\nWould you like to override course designer setting to "By Marks"?</source>
<target state="signed-off">Ya existe la configuración de regata activa pero con la opción de diseñador de regatas errónea.\¿Desea sustituir la opción de diseñador de regatas por "Por marcas"?</target>
<target state="translated">Ya existe la configuración de regata activa pero con la opción de diseñador de recorrido errónea.\¿Desea sustituir la opción de diseñador de recorrido por "Por marcas"?</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="allRacesHaveBeenDenoted" resname="allRacesHaveBeenDenoted">
<source>All races have been denoted for racelog tracking.</source>
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.common;
public enum LeaderboardType {
RegattaLeaderboard(false, true), RegattaMetaLeaderboard(true, true),
RegattaLeaderboardWithEliminations(false, true),
FlexibleLeaderboard(false, false), FlexibleMetaLeaderboard(true, false);
boolean isRegattaLeaderboard;
@@ -26,10 +27,10 @@ public enum LeaderboardType {
public String toString() {
String result = "";
if(isMetaLeaderboard) {
if (isMetaLeaderboard) {
result += "Meta, ";
}
result += isRegattaLeaderboard ? "Regatta" : "Flexible";
result += isRegattaLeaderboard ? "Regatta" + (this==RegattaLeaderboardWithEliminations?" w/ Elimination":"") : "Flexible";
return result;
}
}
@@ -28,6 +28,7 @@ import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
@@ -52,9 +53,7 @@ public interface DomainObjectFactory {
* @return the leaderboard loaded, if successful, or <code>null</code> if the leaderboard couldn't be loaded,
* e.g., because the regatta for a regatta leaderboard couldn't be found
*/
Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry);
Iterable<Leaderboard> getAllLeaderboards(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry);
Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry);
RaceIdentifier loadRaceIdentifier(DBObject dbObject);
@@ -192,4 +191,7 @@ public interface DomainObjectFactory {
*/
ConnectivityParametersLoadingResult loadConnectivityParametersForRacesToRestore(
Consumer<RaceTrackingConnectivityParameters> callback);
RegattaLeaderboardWithEliminations loadRegattaLeaderboardWithEliminations(DBObject dbLeaderboard,
String leaderboardName, String wrappedRegattaLeaderboardName, LeaderboardRegistry leaderboardRegistry);
}
@@ -181,10 +181,12 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.impl.DelayedLeaderboardCorrectionsImpl;
import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
@@ -368,14 +370,14 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry) {
public Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) {
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
Leaderboard result = null;
try {
BasicDBObject query = new BasicDBObject();
query.put(FieldNames.LEADERBOARD_NAME.name(), name);
for (DBObject o : leaderboardCollection.find(query)) {
result = loadLeaderboard(o, regattaRegistry, /* leaderboardRegistry */ null, /* groupForMetaLeaderboard */ null);
result = loadLeaderboard(o, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ null);
}
} catch (Exception e) {
// something went wrong during DB access; report, then use empty new wind track
@@ -386,27 +388,28 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public Iterable<Leaderboard> getAllLeaderboards(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) {
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
Set<Leaderboard> result = new HashSet<Leaderboard>();
try {
for (DBObject o : leaderboardCollection.find()) {
final Leaderboard loadedLeaderboard = loadLeaderboard(o, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ null);
if (loadedLeaderboard != null) {
result.add(loadedLeaderboard);
}
public RegattaLeaderboardWithEliminations loadRegattaLeaderboardWithEliminations(DBObject dbLeaderboard,
String leaderboardName, String wrappedRegattaLeaderboardName, LeaderboardRegistry leaderboardRegistry) {
final RegattaLeaderboardWithEliminations result;
BasicDBList eliminatedCompetitorIds = (BasicDBList) dbLeaderboard.get(FieldNames.ELMINATED_COMPETITORS.name());
result = new DelegatingRegattaLeaderboardWithCompetitorElimination(()->
(RegattaLeaderboard) leaderboardRegistry.getLeaderboardByName(wrappedRegattaLeaderboardName), leaderboardName);
for (Object eliminatedCompetitorId : eliminatedCompetitorIds) {
Competitor eliminatedCompetitor = baseDomainFactory.getCompetitorStore().getExistingCompetitorById((Serializable) eliminatedCompetitorId);
if (eliminatedCompetitor == null) {
logger.warning("Couldn't find eliminated competitor with ID "+eliminatedCompetitorId);
} else {
result.setEliminated(eliminatedCompetitor, true);
}
} catch (Exception e) {
// something went wrong during DB access; report, then use empty new wind track
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards.");
logger.log(Level.SEVERE, "getAllLeaderboards", e);
}
return result;
}
/**
* If the DBObject has a field {@link FieldNames#REGATTA_NAME} then the object represents a
* {@link RegattaLeaderboard}. Otherwise, a {@link FlexibleLeaderboard} will be loaded.
* If the DBObject has a field {@link FieldNames#WRAPPED_REGATTA_LEADERBOARD_NAME} then the object represents a
* {@link RegattaLeaderboardWithEliminations}. Otherwise, if the DBObject has a field
* {@link FieldNames#REGATTA_NAME} then the object represents a {@link RegattaLeaderboard}. Otherwise, a
* {@link FlexibleLeaderboard} will be loaded.
*
* @param leaderboardRegistry
* if not <code>null</code>, then before creating and loading the leaderboard it is looked up in this
@@ -430,24 +433,34 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
result = leaderboardRegistry.getLeaderboardByName(leaderboardName);
}
if (result == null) {
ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbLeaderboard, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS);
String regattaName = (String) dbLeaderboard.get(FieldNames.REGATTA_NAME.name());
if (groupForMetaLeaderboard != null) {
result = new LeaderboardGroupMetaLeaderboard(groupForMetaLeaderboard,
loadScoringScheme(dbLeaderboard), resultDiscardingRule);
groupForMetaLeaderboard.setOverallLeaderboard(result);
} else if (regattaName == null) {
result = loadFlexibleLeaderboard(dbLeaderboard, resultDiscardingRule);
String wrappedRegattaLeaderboardName = (String) dbLeaderboard.get(FieldNames.WRAPPED_REGATTA_LEADERBOARD_NAME.name());
if (wrappedRegattaLeaderboardName != null) {
result = loadRegattaLeaderboardWithEliminations(dbLeaderboard, leaderboardName, wrappedRegattaLeaderboardName, leaderboardRegistry);
} else {
result = loadRegattaLeaderboard(leaderboardName, regattaName, dbLeaderboard, resultDiscardingRule, regattaRegistry);
if (groupForMetaLeaderboard != null) {
result = new LeaderboardGroupMetaLeaderboard(groupForMetaLeaderboard,
loadScoringScheme(dbLeaderboard), loadResultDiscardingRule(dbLeaderboard, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS));
groupForMetaLeaderboard.setOverallLeaderboard(result);
} else {
String regattaName = (String) dbLeaderboard.get(FieldNames.REGATTA_NAME.name());
ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbLeaderboard, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS);
if (regattaName == null) {
result = loadFlexibleLeaderboard(dbLeaderboard, resultDiscardingRule);
} else {
result = loadRegattaLeaderboard(leaderboardName, regattaName, dbLeaderboard,
resultDiscardingRule, regattaRegistry);
}
}
if (result != null) {
DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result, baseDomainFactory);
loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, result.getScoreCorrection());
loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections);
loadColumnFactors(dbLeaderboard, result);
}
}
if (result != null) {
final Leaderboard finalResult = result;
finalResult.setDisplayName((String) dbLeaderboard.get(FieldNames.LEADERBOARD_DISPLAY_NAME.name()));
DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result, baseDomainFactory);
loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, result.getScoreCorrection());
loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections);
loadColumnFactors(dbLeaderboard, result);
// add the leaderboard to the registry
if (leaderboardRegistry != null) {
leaderboardRegistry.addLeaderboard(result);
@@ -80,7 +80,8 @@ public enum FieldNames {
LEADERBOARD_IS_MEDAL_RACE_COLUMN, LEADERBOARD_CARRIED_POINTS, LEADERBOARD_CARRIED_POINTS_BY_ID,
LEADERBOARD_SCORE_CORRECTIONS, LEADERBOARD_DISCARDING_THRESHOLDS,
LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON, LEADERBOARD_CORRECTED_SCORE, LEADERBOARD_SCORE_CORRECTION_TIMESTAMP,
LEADERBOARD_RANK, LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_COLUMN_FACTORS,
LEADERBOARD_RANK, LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_COLUMN_FACTORS, WRAPPED_REGATTA_LEADERBOARD_NAME,
ELMINATED_COMPETITORS,
// leaderboard group
LEADERBOARD_GROUP_UUID, LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION,
@@ -96,6 +96,7 @@ import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
@@ -294,26 +295,35 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
if (leaderboard.getDisplayName() != null) {
dbLeaderboard.put(FieldNames.LEADERBOARD_DISPLAY_NAME.name(), leaderboard.getDisplayName());
}
BasicDBList dbSuppressedCompetitorIds = new BasicDBList();
for (Competitor suppressedCompetitor : leaderboard.getSuppressedCompetitors()) {
dbSuppressedCompetitorIds.add(suppressedCompetitor.getId());
}
dbLeaderboard.put(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name(), dbSuppressedCompetitorIds);
if (leaderboard instanceof FlexibleLeaderboard) {
storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard);
} else if (leaderboard instanceof RegattaLeaderboard) {
storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard);
if (leaderboard instanceof RegattaLeaderboardWithEliminations) {
dbLeaderboard.put(FieldNames.WRAPPED_REGATTA_LEADERBOARD_NAME.name(), ((RegattaLeaderboardWithEliminations) leaderboard).getRegatta().getName());
BasicDBList eliminatedCompetitorIds = new BasicDBList();
for (final Competitor c : ((RegattaLeaderboardWithEliminations) leaderboard).getEliminatedCompetitors()) {
eliminatedCompetitorIds.add(c.getId());
}
dbLeaderboard.put(FieldNames.ELMINATED_COMPETITORS.name(), eliminatedCompetitorIds);
} else {
// at least store the scoring scheme
dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
BasicDBList dbSuppressedCompetitorIds = new BasicDBList();
for (Competitor suppressedCompetitor : leaderboard.getSuppressedCompetitors()) {
dbSuppressedCompetitorIds.add(suppressedCompetitor.getId());
}
dbLeaderboard.put(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name(), dbSuppressedCompetitorIds);
if (leaderboard instanceof FlexibleLeaderboard) {
storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard);
} else if (leaderboard instanceof RegattaLeaderboard) {
storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard);
} else {
// at least store the scoring scheme
dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
}
if (leaderboard.getDefaultCourseArea() != null) {
dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), leaderboard.getDefaultCourseArea().getId().toString());
} else {
dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), null);
}
storeColumnFactors(leaderboard, dbLeaderboard);
storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
}
if (leaderboard.getDefaultCourseArea() != null) {
dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), leaderboard.getDefaultCourseArea().getId().toString());
} else {
dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), null);
}
storeColumnFactors(leaderboard, dbLeaderboard);
storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
}
@@ -10,9 +10,6 @@ import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
@@ -54,20 +51,17 @@ import com.sap.sailing.server.gateway.deserialization.impl.DeviceAndSessionIdent
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class AbstractGPSFixStoreTest {
public class AbstractGPSFixStoreTest extends RaceLogTrackingTestHelper {
protected RacingEventService service;
protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
DeviceAndSessionIdentifierWithGPSFixesDeserializer deserializer =
new MockDeviceAndSessionIdentifierWithGPSFixesDeserializer();
protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
protected RaceLog raceLog;
protected RegattaLog regattaLog;
protected SensorFixStore store;
protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null);
protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark");
protected final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0);
protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) {
return new GPSFixMovingImpl(new DegreePosition(lat, lng),
new MillisecondsTimePoint(millis), new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
@@ -0,0 +1,34 @@
package com.sap.sailing.domain.racelogtracking.test;
import java.util.UUID;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sse.common.TimePoint;
public abstract class RaceLogTrackingTestHelper {
protected RaceLog raceLog;
protected AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0);
protected void setStartAndEndOfTrackingInRaceLog(TimePoint startOfTrackingInRacelog, TimePoint endOfTrackingInRacelog) {
raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTrackingInRacelog, author, 0));
raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTrackingInRacelog, author, 0));
}
protected void setStartAndEndOfRaceInRaceLog(TimePoint startTimeInRaceLog, TimePoint endTimeInRaceLog) {
raceLog.add(new RaceLogStartTimeEventImpl(startTimeInRaceLog, author, 0, startTimeInRaceLog));
raceLog.add(new RaceLogRaceStatusEventImpl(endTimeInRaceLog, endTimeInRaceLog, author, UUID.randomUUID(), 0, RaceLogRaceStatus.FINISHED));
}
protected void setManualTrackingTimesOnTrackedRace(DynamicTrackedRace trackedRace, TimePoint startTime, TimePoint endTime) {
trackedRace.setStartOfTrackingReceived(startTime);
trackedRace.setEndOfTrackingReceived(endTime);
}
}
@@ -7,18 +7,19 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.Timeout;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl;
@@ -50,6 +51,7 @@ import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RaceLogFixTrackerManager;
import com.sap.sailing.domain.racelogtracking.test.RaceLogTrackingTestHelper;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.Track;
@@ -60,7 +62,7 @@ import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class CreateAndTrackWithRaceLogTest {
public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper {
private RacingEventService service;
private final Fleet fleet = new FleetImpl("fleet");
@@ -68,7 +70,6 @@ public class CreateAndTrackWithRaceLogTest {
private RegattaLeaderboard leaderboard;
private RaceLogTrackingAdapter adapter;
private Regatta regatta;
private AbstractLogEventAuthor author;
private SensorFixStore sensorFixStore;
private long time = 0;
@@ -85,12 +86,17 @@ public class CreateAndTrackWithRaceLogTest {
Series series = new SeriesImpl("series", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(fleet), Collections.emptySet(),
service);
regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser",
/* startDate */null, /* endDate */null, UUID.randomUUID(), Collections.<Series> singletonList(series), false,
/* startDate */null, /* endDate */null, UUID.randomUUID(), Collections.<Series> singletonList(series), /* persistent */ true,
new HighPoint(), UUID.randomUUID(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new);
series.addRaceColumn(columnName, /* trackedRegattaRegistry */null);
leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {});
adapter = RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(DomainFactory.INSTANCE);
}
@After
public void tearDown() throws MalformedURLException, IOException, InterruptedException {
service.removeRegatta(regatta);
}
@Test
public void hasRaceLog() {
@@ -206,13 +212,51 @@ public class CreateAndTrackWithRaceLogTest {
raceLogFixTrackerManager.stop(false);
addFixes3(race, comp1, dev1);
}
/**
* See bug 4114: when the regatta is set to control the tracking times from start / finish times,
* we don't want this to override start/end tracking times set explicitly in the race log unless
* they have low priority. This could happen when the start time is adjusted manually or when
* the race log is detached / attached again with a start time event in the race log.
*/
@Test
public void testStartOfTrackingTimes() throws NotDenotedForRaceLogTrackingException, Exception {
regatta.setControlTrackingFromStartAndFinishTimes(true);
RaceColumn column = leaderboard.getRaceColumnByName(columnName);
raceLog = column.getRaceLog(fleet);
adapter.denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, "race");
// start tracking
adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false);
// now there is a trackedrace
TrackedRace race = column.getTrackedRace(fleet);
assertNotNull(race);
final TimePoint startTimeInRaceLog = new MillisecondsTimePoint(123456);
final TimePoint endTimeInRaceLog = new MillisecondsTimePoint(234567);
setStartAndEndOfRaceInRaceLog(startTimeInRaceLog, endTimeInRaceLog);
assertEquals(race.getStartOfRace().minus(TrackedRace.START_TRACKING_THIS_MUCH_BEFORE_RACE_START), race.getStartOfTracking());
final TimePoint explicitStartOfTracking = new MillisecondsTimePoint(123000); // just a bit before start of race
final TimePoint explicitEndOfTracking = new MillisecondsTimePoint(234600); // just a bit after end of race
setStartAndEndOfTrackingInRaceLog(explicitStartOfTracking, explicitEndOfTracking);
assertEquals(explicitStartOfTracking, race.getStartOfTracking());
assertEquals(explicitEndOfTracking, race.getEndOfTracking());
column.releaseTrackedRace(fleet);
column.setTrackedRace(fleet, race);
assertEquals(explicitStartOfTracking, race.getStartOfTracking());
assertEquals(explicitEndOfTracking, race.getEndOfTracking());
// now remove the tracked race and track again; again, the explicit tracking times in the race log must prevail
service.removeRace(regatta, race.getRace());
adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false);
TrackedRace race2 = column.getTrackedRace(fleet);
assertEquals(explicitStartOfTracking, race2.getStartOfTracking());
assertEquals(explicitEndOfTracking, race2.getEndOfTracking());
}
@Test
public void useEventsInRegattaLog() throws NotDenotedForRaceLogTrackingException, Exception {
RaceColumn column = leaderboard.getRaceColumnByName(columnName);
RegattaLog regattaLog = leaderboard.getRegattaLike().getRegattaLog();
RaceLog raceLog = column.getRaceLog(fleet);
raceLog = column.getRaceLog(fleet);
adapter.denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, "race");
// add a mapping and one fix in, one out of mapping
@@ -756,6 +756,8 @@ public class SensorFixStoreAndLoadTest {
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
// creating a new RaceLog instance to ensure that startOfTracking insn't set initially
raceLog = new RaceLogImpl("racelog");
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
@@ -25,6 +25,7 @@ import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
@@ -266,6 +267,38 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
});
}
/**
* Regression test for bug 4145: when fixes are mapped while startOfTracking is still null, fixes are expected
* to not make it into the race. However, when startOfTracking is then set to a value after the fix's time point,
* that fix is expected to be loaded if there is no better fix within the tracking interval, just like it already
* happens when starting the tracker for the race or adding pings while startOfTracking is already set to a
* non-null value.
*/
@Test
public void testFixesBeforeStartOfTrackingForMarkAreLoadedWhenSettingStartOfTracking() throws InterruptedException {
testFixesInternal(/* start of tracking */ null, /* end of tracking */ null, /* mappings and fixes */ () -> {
map(mark, device, 0, 200);
store.storeFix(device, createFix(50, 10, 20));
store.storeFix(device, createFix(150, 10, 20));
store.storeFix(device, createFix(250, 10, 20));
map(mark, device, 350, 500);
store.storeFix(device, createFix(400, 10, 20));
store.storeFix(device, createFix(450, 10, 20));
}, /* tests track is initially empty */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 0); // still no start of tracking set
}, /* now set start of tracking */ trackedRace -> trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(600)),
/* now check the last fix has arrived */ trackedRace -> {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1); // one fix
trackedRace.getOrCreateTrack(mark).lockForRead();
try {
final GPSFix fix = trackedRace.getOrCreateTrack(mark).getRawFixes().iterator().next();
assertEquals(new MillisecondsTimePoint(450), fix.getTimePoint());
} finally {
trackedRace.getOrCreateTrack(mark).unlockAfterRead();
}
});
}
/**
* Regression test for bug 4008 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4008 <br>
* Updated for bug 4009 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4009
@@ -456,13 +489,23 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
});
}
@SafeVarargs
private final void testFixes(long startOfTracking, long endOfTracking, Runnable beforeTrackingStarted,
Consumer<DynamicTrackedRace>... afterTrackingStartedConsumers) throws InterruptedException {
testFixesInternal(Long.valueOf(startOfTracking), Long.valueOf(endOfTracking), beforeTrackingStarted, afterTrackingStartedConsumers);
}
@SafeVarargs
private final void testFixesInternal(Long startOfTracking, Long endOfTracking, Runnable beforeTrackingStarted,
Consumer<DynamicTrackedRace>... afterTrackingStartedConsumers) throws InterruptedException {
defineMarksOnRegattaLog(mark, mark2);
raceLog.add(new RaceLogStartOfTrackingEventImpl(new MillisecondsTimePoint(startOfTracking), author, 0));
raceLog.add(new RaceLogEndOfTrackingEventImpl(new MillisecondsTimePoint(endOfTracking), author, 0));
if (startOfTracking != null) {
raceLog.add(new RaceLogStartOfTrackingEventImpl(new MillisecondsTimePoint(startOfTracking), author, 0));
}
if (endOfTracking != null) {
raceLog.add(new RaceLogEndOfTrackingEventImpl(new MillisecondsTimePoint(endOfTracking), author, 0));
}
beforeTrackingStarted.run();
DynamicTrackedRace trackedRace = createDynamicTrackedRace(boatClass, raceDefinition);
trackedRace.attachRaceLog(raceLog);
@@ -36,7 +36,6 @@ import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
@@ -77,42 +76,52 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
/* useMarkPassingCalculator */ false,
OneDesignRankingMetric::new, mock(RaceLogResolver.class));
setStartAndEndOfRaceInRaceLog(10000, 20000);
MillisecondsTimePoint startOfRaceInRaceLog = new MillisecondsTimePoint(10000);
MillisecondsTimePoint endOfRaceInRaceLog = new MillisecondsTimePoint(20000);
setStartAndEndOfRaceInRaceLog(startOfRaceInRaceLog, endOfRaceInRaceLog);
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
// test inference via race start/end time in racelog
MillisecondsTimePoint startOfRaceInRaceLog = new MillisecondsTimePoint(10000);
TimePoint expectedStartOfTracking = startOfRaceInRaceLog.minus(TrackedRace.START_TRACKING_THIS_MUCH_BEFORE_RACE_START);
MillisecondsTimePoint endOfRaceInRaceLog = new MillisecondsTimePoint(20000);
TimePoint expectedEndOfTracking = endOfRaceInRaceLog.plus(TrackedRace .STOP_TRACKING_THIS_MUCH_AFTER_RACE_FINISH);
TimePoint expectedEndOfTracking = endOfRaceInRaceLog.plus(TrackedRace.STOP_TRACKING_THIS_MUCH_AFTER_RACE_FINISH);
assertEquals(expectedStartOfTracking, trackedRace.getStartOfTracking());
assertEquals(expectedEndOfTracking, trackedRace.getEndOfTracking());
// test inference via manually set start/end of tracking
setManualTrackingTimesOnTrackedRace(trackedRace, 30000, 40000);
assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking());
assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking());
final MillisecondsTimePoint manualStartOfTracking = new MillisecondsTimePoint(30000);
final MillisecondsTimePoint manualEndOfTracking = new MillisecondsTimePoint(40000);
setManualTrackingTimesOnTrackedRace(trackedRace, manualStartOfTracking, manualEndOfTracking);
assertEquals(manualStartOfTracking, trackedRace.getStartOfTracking());
assertEquals(manualEndOfTracking, trackedRace.getEndOfTracking());
// shouldn't change when setting time in raceLog again
setStartAndEndOfRaceInRaceLog(100000, 200000);
assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking());
assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking());
setStartAndEndOfRaceInRaceLog(new MillisecondsTimePoint(100000), new MillisecondsTimePoint(200000));
assertEquals(manualStartOfTracking, trackedRace.getStartOfTracking());
assertEquals(manualEndOfTracking, trackedRace.getEndOfTracking());
// test inference via start/end of tracking in RaceLog
setStartAndEndOfTrackingInRaceLog(50000, 60000);
MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(50000);
MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(60000);
setStartAndEndOfTrackingInRaceLog(startOfTrackingInRacelog, endOfTrackingInRacelog);
assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking());
assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking());
// shouldn't change when setting time in raceLog again
setStartAndEndOfRaceInRaceLog(300000, 400000);
setStartAndEndOfRaceInRaceLog(new MillisecondsTimePoint(300000), new MillisecondsTimePoint(400000));
assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking());
assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking());
// shouldn't change when setting start/end of tracking
setManualTrackingTimesOnTrackedRace(trackedRace, 500000, 600000);
setManualTrackingTimesOnTrackedRace(trackedRace, new MillisecondsTimePoint(500000), new MillisecondsTimePoint(600000));
assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking());
assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking());
// bug 4114: now detach the race log and attach it again; this is expected to set the
// start of race from the race log which now is no longer expected to update the race log
// with new start/end of tracking times when there are already such explicit events:
trackedRace.detachRaceLog(raceLog.getId());
trackedRace.attachRaceLog(raceLog);
assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking());
assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking());
}
@@ -241,7 +250,8 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
trackedRace.updateMarkPassings(comp, Collections.singleton(new MarkPassingImpl(startMarkPassingTimePoint, trackedRace.getRace().getCourse().getFirstWaypoint(), comp)));
assertNotNull(newStartAndEndOfTrackingNotifiedByRace[0]);
assertTrue(trackedRace.getStartOfTracking().before(startMarkPassingTimePoint));
assertTrue(trackedRace.getStartOfTracking().after(new MillisecondsTimePoint(123456)));
final MillisecondsTimePoint startOfRaceInRaceLog = new MillisecondsTimePoint(123456);
assertTrue(trackedRace.getStartOfTracking().after(startOfRaceInRaceLog));
// test inference from finished time change by new blue flag down event
newStartAndEndOfTrackingNotifiedByRace[0] = null;
@@ -254,40 +264,45 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
// verify that setting a start and finished time through the race log adjusts the start/end of tracking times
newStartAndEndOfTrackingNotifiedByRace[0] = null;
newStartAndEndOfTrackingNotifiedByRace[1] = null;
setStartAndEndOfRaceInRaceLog(123456, 234567);
final MillisecondsTimePoint endOfRaceInRaceLog = new MillisecondsTimePoint(234567);
setStartAndEndOfRaceInRaceLog(startOfRaceInRaceLog, endOfRaceInRaceLog);
assertNotNull(trackedRace.getStartOfTracking());
assertNotNull(trackedRace.getEndOfTracking());
assertTrue(trackedRace.getStartOfTracking().before(new MillisecondsTimePoint(123456)));
assertTrue(trackedRace.getEndOfTracking().after(new MillisecondsTimePoint(234567)));
assertTrue(trackedRace.getStartOfTracking().before(startOfRaceInRaceLog));
assertTrue(trackedRace.getEndOfTracking().after(endOfRaceInRaceLog));
newStartAndEndOfTrackingNotifiedByRace[0] = null;
newStartAndEndOfTrackingNotifiedByRace[1] = null;
setManualTrackingTimesOnTrackedRace(trackedRace, 1111, 2222); // wrong values; race log should take precedence when available
final MillisecondsTimePoint manualStartOfTracking = new MillisecondsTimePoint(1111);
final MillisecondsTimePoint manualEndOfTracking = new MillisecondsTimePoint(2222);
setManualTrackingTimesOnTrackedRace(trackedRace, manualStartOfTracking, manualEndOfTracking); // wrong values; race log should take precedence when available
// assert that the event listener was triggered
assertEquals(new MillisecondsTimePoint(1111), newStartAndEndOfTrackingNotifiedByRace[0]);
assertEquals(new MillisecondsTimePoint(2222), newStartAndEndOfTrackingNotifiedByRace[1]);
assertEquals(manualStartOfTracking, newStartAndEndOfTrackingNotifiedByRace[0]);
assertEquals(manualEndOfTracking, newStartAndEndOfTrackingNotifiedByRace[1]);
// test values set immediately
assertEquals(new MillisecondsTimePoint(1111), trackedRace.getStartOfTracking());
assertEquals(new MillisecondsTimePoint(2222), trackedRace.getEndOfTracking());
assertEquals(manualStartOfTracking, trackedRace.getStartOfTracking());
assertEquals(manualEndOfTracking, trackedRace.getEndOfTracking());
newStartAndEndOfTrackingNotifiedByRace[0] = null;
newStartAndEndOfTrackingNotifiedByRace[1] = null;
setStartAndEndOfTrackingInRaceLog(10000, 20000); // correct values, taking precedence
final MillisecondsTimePoint newStartOfTrackingInRaceLog = new MillisecondsTimePoint(10000);
final MillisecondsTimePoint newEndOfTrackingInRaceLog = new MillisecondsTimePoint(20000);
setStartAndEndOfTrackingInRaceLog(newStartOfTrackingInRaceLog, newEndOfTrackingInRaceLog); // correct values, taking precedence
// assert that the event listener was triggered
assertEquals(new MillisecondsTimePoint(10000), newStartAndEndOfTrackingNotifiedByRace[0]);
assertEquals(new MillisecondsTimePoint(20000), newStartAndEndOfTrackingNotifiedByRace[1]);
assertEquals(newStartOfTrackingInRaceLog, newStartAndEndOfTrackingNotifiedByRace[0]);
assertEquals(newEndOfTrackingInRaceLog, newStartAndEndOfTrackingNotifiedByRace[1]);
// test inference via racelog
assertEquals(new MillisecondsTimePoint(10000), trackedRace.getStartOfTracking());
assertEquals(new MillisecondsTimePoint(20000), trackedRace.getEndOfTracking());
assertEquals(newStartOfTrackingInRaceLog, trackedRace.getStartOfTracking());
assertEquals(newEndOfTrackingInRaceLog, trackedRace.getEndOfTracking());
// shouldn't change anymore when setting explicitly because race log takes precedence
newStartAndEndOfTrackingNotifiedByRace[0] = null;
newStartAndEndOfTrackingNotifiedByRace[1] = null;
setManualTrackingTimesOnTrackedRace(trackedRace, 1111, 2222);
setManualTrackingTimesOnTrackedRace(trackedRace, manualStartOfTracking, manualEndOfTracking);
assertNull(newStartAndEndOfTrackingNotifiedByRace[0]);
assertNull(newStartAndEndOfTrackingNotifiedByRace[1]);
assertEquals(new MillisecondsTimePoint(10000), trackedRace.getStartOfTracking());
assertEquals(new MillisecondsTimePoint(20000), trackedRace.getEndOfTracking());
assertEquals(newStartOfTrackingInRaceLog, trackedRace.getStartOfTracking());
assertEquals(newEndOfTrackingInRaceLog, trackedRace.getEndOfTracking());
// test inference when setting null in RaceLog; RaceLog should still take precedence with its null values
newStartAndEndOfTrackingNotifiedByRace[0] = MillisecondsTimePoint.now();
@@ -337,29 +352,4 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
assertEquals(newStartOfRace, oldAndNewStartTimeNotifiedByRace[1]);
assertEquals(newStartOfRace, trackedRace.getStartOfRace());
}
public void setStartAndEndOfTrackingInRaceLog(int startTime, int endTime) {
MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(startTime);
MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(endTime);
raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTrackingInRacelog, author, 0));
raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTrackingInRacelog, author, 0));
}
public void setStartAndEndOfRaceInRaceLog(int startTime, int endTime) {
MillisecondsTimePoint startTimeInRaceLog2 = new MillisecondsTimePoint(startTime);
MillisecondsTimePoint endTimeInRaceLog2 = new MillisecondsTimePoint(endTime);
raceLog.add(new RaceLogStartTimeEventImpl(startTimeInRaceLog2, author, 0, startTimeInRaceLog2));
raceLog.add(new RaceLogRaceStatusEventImpl(endTimeInRaceLog2, endTimeInRaceLog2, author, UUID.randomUUID(), 0, RaceLogRaceStatus.FINISHED));
}
public void setManualTrackingTimesOnTrackedRace(DynamicTrackedRace trackedRace, int startTime, int endTime) {
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(startTime));
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(endTime));
}
}
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2017-03-03T16:45:41Z" original="/usr/sap/ljs/app/Converter_7335190261158680380/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="es">
<file datatype="javapropertyresourcebundle" date="2017-04-19T13:57:38Z" original="/usr/sap/ljs/app/Converter_429408536936316109/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="es">
<header>
<skl>
<internal-file crc="064477f4b22ea4cc8cbdaab36795b9a4" form="base64">H4sIAAAAAAAAAF1RQU7DQAy85xW+cakQ50o5FCSkSEBLk8J5m7iN1c16tXZbIsTf2U1SWnHzzjjjmYkGUx/I7Qt3IjVK7J455NWEwhWGHYdMauPe10/c4DJ8kJBugq14jXsSxbCQ/NUcEOQYEHo+QmtOCNoifD/8wMJ7ICdqrMXmHqoWHSS9YWHH1vI5XRzlgQPUlurDwFpyBzhKoqNsAOlMUN+yi+oMYToPRrIz2po7rDj/HKe4kNGy3AgGyUuN30F8JjOZcU1gam6pxQiNtI2aLmY/oe2HsFVL8hKt5MVuiGdizICmSb40ctAZssBjogZPVCOcSdvhneIbm9b7aw0zEOq87aesW4wlpEiS3Myz7ZH7Cl2DIU8j6DBnxvtSOWAxylT4pRdLDbs7vfaejvaoM/AWjeDl8DxJFCz5pHCtxPupgj/qtpJkYhXTxl/t/ZvpMC8XKyhj6lTBY7I40plErHAl7Vv9vwoTnv0Cs+RzW3sCAAA=</internal-file>
<internal-file crc="55a81e913c44ad945c6ad9615879c2ce" form="base64">H4sIAAAAAAAAAF1RQU7DQAy85xW+cakQ50o5FCSkSEBLk8J5m7iN1c16tXZbIsTf2U1SWnHzzjjjmYkGUx/I7Qt3IjVK7J455NWEwhWGHYdMauPe10/c4DJ8kJBugq14jXsSxbCQ/NUcEOQYEHo+QmtOCNoifD/8wMJ7ICdqrMXmHqoWHSS9YWHH1vI5XRzlgQPUlurDwFpyBzhKoqNsAOlMUN+yi+oMYToPRrIz2po7rDj/HKe4kNGy3AgGyUuN30F8JjOZcU1gam6pxQiNtI2aLmY/oe2HsFVL8hKt5MVuiGdizICmSb40ctAZssBjogZPVCOcSdvhneIbm9b7aw0zEOq87aesW4wlpEiS3Myz7ZH7Cl2DIU8j6DBnxvtSOWAxylT4pRdLDbu7O70Wn672qDPwFo3g5fI8aRQs+SRx7cT7qYM/6raT5GIV48Z/7f2b6TAvFysoY+zUwWPyONKZRKxwJe1b/b8KE579Ah7aV3t8AgAA</internal-file>
</skl>
<phase-group>
<phase company-name="SAP AG" date="2016-12-16T08:45:26Z" phase-name="file-creation" process-name="file-creation" tool-id="urn:x-sap:sls-mlt:java:properties-flexible-converter"/>
<phase company-name="SAP AG" date="2017-04-06T12:41:23Z" phase-name="file-creation" process-name="file-creation" tool-id="urn:x-sap:sls-mlt:java:properties-flexible-converter"/>
</phase-group>
<tool tool-company="SAP AG" tool-id="urn:x-sap:sls-mlt:java:properties-flexible-converter" tool-name="TEW: Javascript .properties Flexible File Converter" tool-version="1.1.0"/>
<tool tool-company="SAP AG" tool-id="urn:x-sap:sls-mlt:java:properties-flexible-converter" tool-name="TEW: Java properties Flexible File Converter" tool-version="1.1.0"/>
</header>
<body>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="trackingInvitationFor" resname="trackingInvitationFor">
@@ -40,7 +40,7 @@
<target state="signed-off">balizador</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="appStoreInstallText" resname="appStoreInstallText">
<source>If you don't have the App yet, please install:</source>
<source>If you don''t have the App yet, please install:</source>
<target state="signed-off">Si todavía no tiene instalada la aplicación, instálela:</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="appIos" resname="appIos">
@@ -1,54 +0,0 @@
package com.sap.sailing.domain.racelogtracking.impl;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* A listener for finished time changes that---when {@link Regatta#isControlTrackingFromStartAndFinishTimes() enabled},
* updates the end of tracking time for a race through the race log whenever the race's finished time has been changed,
* e.g., by a blue flag down event on the race log.
*
* @author Axel Uhl (d043530)
*
*/
public class EndOfTrackingController extends AbstractRaceChangeListener {
private final Regatta regatta;
private final TrackedRace trackedRace;
private final RaceLog raceLog;
private final AbstractLogEventAuthor raceLogEventAuthor;
/**
* Initially checks the {@code trackedRace} for a {@link TrackedRace#getFinishedTime() finished time}. If one is found and
* no {@link TrackedRace#getEndOfTracking() end of tracking} is defined yet, immediately invokes {@link #finishedTimeChanged(TimePoint, TimePoint)}
* which will set an end of tracking time in the race log.
*/
public EndOfTrackingController(TrackedRace trackedRace, RaceLog raceLog, AbstractLogEventAuthor raceLogEventAuthor) {
super();
this.regatta = trackedRace.getTrackedRegatta().getRegatta();
this.trackedRace = trackedRace;
this.raceLog = raceLog;
this.raceLogEventAuthor = raceLogEventAuthor;
final TimePoint finishedTime = trackedRace.getFinishedTime();
if (finishedTime != null) {
finishedTimeChanged(/* old finished time */ null, finishedTime);
}
}
@Override
public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) {
if (regatta.isControlTrackingFromStartAndFinishTimes()) {
final TimePoint newEndOfTracking = newFinishedTime.plus(TrackedRace.STOP_TRACKING_THIS_MUCH_AFTER_RACE_FINISH);
if (!Util.equalsWithNull(trackedRace.getEndOfTracking(), newEndOfTracking)) {
raceLog.add(new RaceLogEndOfTrackingEventImpl(newEndOfTracking,
raceLogEventAuthor, raceLog.getCurrentPassId()));
}
}
}
}
@@ -93,8 +93,6 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
private final RaceLogResolver raceLogResolver;
private volatile DynamicTrackedRace trackedRace;
private StartOfTrackingController startOfTrackingController;
private EndOfTrackingController endOfTrackingController;
public RaceLogRaceTracker(DynamicTrackedRegatta regatta, RaceLogConnectivityParams params, WindStore windStore,
RaceLogResolver raceLogResolver, RaceLogConnectivityParams connectivityParams) {
@@ -177,13 +175,6 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
for (Entry<AbstractLog<?, ?>, Object> visitor : visitors.entrySet()) {
visitor.getKey().removeListener(visitor.getValue());
}
if (startOfTrackingController != null && trackedRace != null) {
trackedRace.removeStartTimeChangedListener(startOfTrackingController);
}
if (endOfTrackingController != null && trackedRace != null) {
trackedRace.removeListener(endOfTrackingController);
}
logger.info(String.format("Stopped tracking race-log race %s %s %s", params.getLeaderboard(),
params.getRaceColumn(), params.getFleet()));
}
@@ -308,10 +299,6 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
trackedRace = trackedRegatta.createTrackedRace(raceDef, sidelines, windStore,
params.getDelayToLiveInMillis(), WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND,
boatClass.getApproximateManeuverDurationInMilliseconds(), null, /*useMarkPassingCalculator*/ true, raceLogResolver);
startOfTrackingController = new StartOfTrackingController(trackedRace, raceLog, raceLogEventAuthor);
trackedRace.addStartTimeChangedListener(startOfTrackingController);
endOfTrackingController = new EndOfTrackingController(trackedRace, raceLog, raceLogEventAuthor);
trackedRace.addListener(endOfTrackingController);
notifyRaceCreationListeners();
logger.info(String.format("Started tracking race-log race (%s)", raceLog));
// this wakes up all waiting race handles
@@ -1,52 +0,0 @@
package com.sap.sailing.domain.racelogtracking.impl;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* A listener for start time changes that---when {@link Regatta#isControlTrackingFromStartAndFinishTimes() enabled},
* updates the start of tracking time for a race through the race log whenever the race's start time has been changed.
*
* @author Axel Uhl (d043530)
*
*/
public class StartOfTrackingController implements StartTimeChangedListener {
private final Regatta regatta;
private final TrackedRace trackedRace;
private final RaceLog raceLog;
private final AbstractLogEventAuthor raceLogEventAuthor;
/**
* Initially checks the {@code trackedRace} for a {@link TrackedRace#getStartOfRace() start time}. If one is found and
* no {@link TrackedRace#getStartOfTracking() start of tracking} is defined yet, immediately invokes {@link #startTimeChanged(TimePoint)}
* which will set a start of tracking time in the race log.
*/
public StartOfTrackingController(TrackedRace trackedRace, RaceLog raceLog, AbstractLogEventAuthor raceLogEventAuthor) {
super();
this.regatta = trackedRace.getTrackedRegatta().getRegatta();
this.raceLog = raceLog;
this.trackedRace = trackedRace;
this.raceLogEventAuthor = raceLogEventAuthor;
final TimePoint startOfRace = trackedRace.getStartOfRace();
if (startOfRace != null) {
startTimeChanged(startOfRace);
}
}
@Override
public void startTimeChanged(TimePoint newTimePoint) {
if (regatta.isControlTrackingFromStartAndFinishTimes()) {
final TimePoint newStartOfTracking = newTimePoint.minus(TrackedRace.START_TRACKING_THIS_MUCH_BEFORE_RACE_START);
if (!Util.equalsWithNull(trackedRace.getStartOfTracking(), newStartOfTracking)) {
raceLog.add(new RaceLogStartOfTrackingEventImpl(newStartOfTracking,
raceLogEventAuthor, raceLog.getCurrentPassId()));
}
}
}
}
@@ -5,6 +5,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -103,6 +104,8 @@ import com.sap.sse.util.ThreadPoolUtil;
*/
public class FixLoaderAndTracker implements TrackingDataLoader {
private static final Logger logger = Logger.getLogger(FixLoaderAndTracker.class.getName());
private static final ScheduledExecutorService executor = ThreadPoolUtil.INSTANCE.createForegroundTaskThreadPoolExecutor(
FixLoaderAndTracker.class.getSimpleName());
protected final DynamicTrackedRace trackedRace;
private final SensorFixStore sensorFixStore;
private RegattaLogDeviceMappings<WithID> deviceMappings;
@@ -124,7 +127,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
if (newStartOfTracking != null) {
if (oldStartOfTracking == null) {
// Fixes wheren't loaded while startOfTracking was null. So we need to load all fixes in the tracking interval now.
loadFixesForExtendedTimeRange(getTrackingTimeRange());
loadFixesWhenStartOfTrackingIsReceived();
} else if (newStartOfTracking.before(oldStartOfTracking)) {
loadFixesForExtendedTimeRange(new TimeRangeImpl(newStartOfTracking, oldStartOfTracking));
}
@@ -403,7 +406,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(event.getMappedTo());
final GPSFix lastFixAtOrBeforeStartOfTracking = track.getLastFixAtOrBefore(trackingTimeRange.from());
// A better fix before start of tracking must be after the current best fix
final MultiTimeRange beforeRange = coveredTimeRanges
@@ -475,9 +477,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
}
sensorFixStore.removeListener(listener);
if (preemptiveStopRequested.get()) {
waitForLoadingToFinishRunning();
}
}
private void startTracking() {
@@ -487,22 +486,15 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
trackedRace.getRace().getName());
}
private void waitForLoadingToFinishRunning() {
synchronized (loadingJobs) {
try {
while (!loadingJobs.isEmpty()) {
loadingJobs.wait();
}
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Interrupted while waiting for Fixes to be loaded", e);
}
}
}
private void loadFixesForExtendedTimeRange(final TimeRange extendedTimeRange) {
deviceMappings.forEachItemAndCoveredTimeRanges((item, mappingsAndCoveredTimeRanges) -> addLoadingJob(
new LoadFixesInTrackingTimeRangeJob(mappingsAndCoveredTimeRanges, extendedTimeRange)));
}
private void loadFixesWhenStartOfTrackingIsReceived() {
deviceMappings.forEachItemAndCoveredTimeRanges((item, mappingsAndCoveredTimeRanges) -> addLoadingJob(
new LoadFixesForNewlyCoveredTimeRangesJob(item, mappingsAndCoveredTimeRanges)));
}
private void setStatusAndProgress(TrackedRaceStatusEnum status, double progress) {
trackedRace.onStatusChanged(this, new TrackedRaceStatusImpl(status, progress));
@@ -562,7 +554,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
loadingJobs.add(job);
updateStatusAndProgress();
}
ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().execute(job);
executor.execute(job);
}
/**
@@ -622,11 +614,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
/**
* Loads fixes for an item's mappings in a defined tracking {@link TimeRange}. This is used when:
* <ul>
* <li>Initially loading fixes into tracks</li>
* <li>The tracking {@link TimeRange} changes</li>
* </ul>
* Loads fixes for an item's mappings in a defined tracking {@link TimeRange}. This is used when the tracking
* {@link TimeRange} is extended. No better fixes for {@link Mark} are being loaded because if available, these must
* have either already been loaded or the best fix is inside of the extended {@link TimeRange} that is completely loaded by
* this job.
*/
private class LoadFixesInTrackingTimeRangeJob extends AbstractLoadingJob {
@@ -646,7 +637,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
/**
* This is used when device mappings for an item changed so that fixes in a new {@link TimeRange} are covered.
* This is used when device mappings for an item changed so that fixes in a new {@link TimeRange} are covered. This is also used when initially loading fixes due to startOfTracking being initially set. If
* the mapping is a {@link Mark}, best fixes outside of the tracking {@link TimeRange} are loaded if none is
* available in the tracking {@link TimeRange}.
*/
private class LoadFixesForNewlyCoveredTimeRangesJob extends AbstractLoadingJob {
private final WithID item;
@@ -181,12 +181,12 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
private Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> calculateCoveredTimeRanges(
final List<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForItem) {
final Map<RegattaLogDeviceMappingEvent<ItemT>, MultiTimeRange> coveredTimeRanges = new HashMap<>();
if(mappingsForItem != null) {
if (mappingsForItem != null) {
Map<Pair<DeviceIdentifier, Class<?>>, Iterable<DeviceMappingWithRegattaLogEvent<ItemT>>> groupedMappings = groupMappingsByDeviceIdAndMappingType(mappingsForItem);
groupedMappings.entrySet().forEach(entry -> {
Iterable<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForDeviceIdAndMappingType = entry.getValue();
final MultiTimeRange coveredTimeRange = getCoveredTimeRange(mappingsForDeviceIdAndMappingType);
if(!coveredTimeRange.isEmpty()) {
if (!coveredTimeRange.isEmpty()) {
coveredTimeRanges.put(Util.get(mappingsForDeviceIdAndMappingType, 0).getRegattaLogEvent(), coveredTimeRange);
}
});
@@ -25,7 +25,7 @@ public class DependentStartTimeResolver {
return internalResolve(event, dependingOnRaces);
}
StartTimeFinderResult internalResolve(RaceLogDependentStartTimeEvent event,Iterable<SimpleRaceLogIdentifier> dependingOnRaces) {
StartTimeFinderResult internalResolve(RaceLogDependentStartTimeEvent event, Iterable<SimpleRaceLogIdentifier> dependingOnRaces) {
SimpleRaceLogIdentifier identifier = event.getDependentOnRaceIdentifier();
Duration startTimeDifference = event.getStartTimeDifference();
RaceLog raceLog = raceLogResolver.resolve(identifier);
@@ -27,7 +27,7 @@ public class StartTimeFinderResult {
private final ResolutionFailed resolutionFailed;
private Duration startTimeDiff;
public StartTimeFinderResult(Iterable<SimpleRaceLogIdentifier> racesDependingOn, TimePoint startTime, Duration startTimeDiff) {
this(racesDependingOn, startTime, startTimeDiff, /* resolutionFailed */ null);
}
@@ -41,6 +41,7 @@ public class SwissTimingRaceConfigurationTest {
}
}
@Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19")
@Test
public void testRaceConfig_446483_no_detail_config() throws Exception {
URL configFileURL = new URL(MessageFormat.format(SwissTimingReplayServiceImpl.RACE_CONFIG_URL_TEMPLATE, "446483"));
@@ -58,6 +59,7 @@ public class SwissTimingRaceConfigurationTest {
}
@Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19")
@Test
public void testRaceConfig_6260_with_detail_config() throws Exception {
URL configFileURL = new URL(MessageFormat.format(SwissTimingReplayServiceImpl.RACE_CONFIG_URL_TEMPLATE, "6260"));
@@ -74,6 +74,7 @@ public class SwissTimingReplayAdapterServiceTest {
assertEquals(321920, replayCountListener.rankingMarkCount);
}
@Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19")
@Test
public void testRaceData_SAW005905_20120805_EqualsOnlineVersion() throws Exception {
byte[] localCopy = read(getClass().getResourceAsStream("/SAW005905.20120805.replay"));
@@ -113,6 +114,7 @@ public class SwissTimingReplayAdapterServiceTest {
assertEquals(9500, replayCountListener.rankingMarkCount);
}
@Ignore("According to Radek Masnica ('masnica.r@st-software.com') the OTA stuff may not be supported any longer by the SwissTiming Leipzip lab... 2017-05-19")
@Test
public void testRaceData_SAW005905_20120805_online() throws Exception {
SwissTimingReplayTestListener replayCountListener = new SwissTimingReplayTestListener();
@@ -0,0 +1,14 @@
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
import com.sap.sailing.domain.test.LeaderboardCourseChangeTest;
public class LeaderboardCourseChangeWithEliminationTest extends LeaderboardCourseChangeTest {
@Override
protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta,
ThresholdBasedResultDiscardingRule discardingRule) {
return new DelegatingRegattaLeaderboardWithCompetitorElimination(
()->super.createRegattaLeaderboard(regatta, discardingRule), "Test leaderboard with elimination");
}
}
@@ -42,6 +42,7 @@ import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
@@ -115,7 +116,7 @@ public class LeaderboardCourseChangeTest {
int[] ruleRaw = { 5, 3, 2 };
ThresholdBasedResultDiscardingRule rule = new ThresholdBasedResultDiscardingRuleImpl(ruleRaw);
Leaderboard leaderboard = new RegattaLeaderboardImpl(mockedRegatta, rule);
Leaderboard leaderboard = createRegattaLeaderboard(mockedRegatta, rule);
DomainFactory baseDomainFactory = new DomainFactoryImpl((srlid)->null);
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, raceColumnNames, false,
@@ -134,6 +135,11 @@ public class LeaderboardCourseChangeTest {
}
protected RegattaLeaderboard createRegattaLeaderboard(Regatta mockedRegatta,
ThresholdBasedResultDiscardingRule rule) {
return new RegattaLeaderboardImpl(mockedRegatta, rule);
}
private TrackedRace createSpyedTrackedRace(Regatta regatta, Course course, TimePoint timePoint, BoatClass boatClass) {
TrackedRegatta mockedTrackedRegatta = createMockedTrackedRegatta(regatta);
RaceDefinition mockedRace = createMockedRace(course, boatClass);
@@ -0,0 +1,72 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardRankingAndScoringWithEliminationTest extends LeaderboardScoringAndRankingTest {
@Override
protected RegattaLeaderboardWithEliminations createLeaderboard(Regatta regatta, int[] discardingThresholds) {
return new DelegatingRegattaLeaderboardWithCompetitorElimination(
()->super.createLeaderboard(regatta, discardingThresholds), "Test leaderboard with elimination");
}
/**
* See bug 4110: for partial leaderboards that are a "projection" of another leaderboard the suppressed
* competitors will receive their scores for all races as usual but won't be shown in the resulting
* leaderboard panel, except for score editing / import. Suppressed competitors, however, will not receive
* a regatta rank and shall not be part of the set of competitors shown in a leaderboard panel.<p>
*
* This test asserts that suppressing competitors will let worse competitors advance in the total ranking.
* @throws NoWindException
*/
@Test
public void testEliminationAdvancesWorseCompetitorsInRegattaRankButNotInRaces() throws NoWindException {
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] };
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */3, new String[] { "Default" },
/* medal */ false, /* medal */ 0, "testTotalTimeNotCountedForRacesStartedLaterThanTimePointReqeusted",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT));
RegattaLeaderboardWithEliminations 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 }, new TimePoint[] { earlier, now, later }, lastMarkPassingTimesForCompetitors);
leaderboard.setEliminated(c[1], true);
assertEquals(1.0, leaderboard.getTotalPoints(c[0], leaderboard.getRaceColumns().iterator().next(), later), 0.000001);
assertEquals(2.0, leaderboard.getTotalPoints(c[1], leaderboard.getRaceColumns().iterator().next(), later), 0.000001);
assertEquals(3.0, leaderboard.getTotalPoints(c[2], leaderboard.getRaceColumns().iterator().next(), later), 0.000001);
assertEquals(1, leaderboard.getTotalRankOfCompetitor(c[0], later));
assertEquals(0, leaderboard.getTotalRankOfCompetitor(c[1], later));
assertEquals(2, leaderboard.getTotalRankOfCompetitor(c[2], later));
}
}
@@ -56,6 +56,7 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
@@ -64,7 +65,6 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10Or8AndLastBre
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets12Or8AndLastBreaksTie2017;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
@@ -83,9 +83,9 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ArrayListNavigableSet;
public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRankingTestBase {
protected Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds);
return new RegattaLeaderboardImpl(regatta, discardingRules);
protected DelegatingRegattaLeaderboardWithCompetitorElimination createDelegatingRegattaLeaderboardWithCompetitorElimination(
Regatta regatta, String leaderboardName, int[] discardingThresholds) {
return new DelegatingRegattaLeaderboardWithCompetitorElimination(()->createLeaderboard(regatta, discardingThresholds), leaderboardName);
}
@Test
@@ -1818,7 +1818,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki
assertEquals(1. + .9, leaderboard1.getNetPoints(c1[0], afterEndOfR1), 0.00000001);
assertEquals(1, leaderboard1.getTotalRankOfCompetitor(c1[0], afterEndOfR1));
// now assert that the competitor c1[0] is best in r2 because of the score correction, although c1[0] doesn't have a fleet
assertEquals(0, leaderboard1.getCompetitorsFromBestToWorst(r2, afterEndOfR1).indexOf(c1[0]));
assertEquals(0, Util.indexOf(leaderboard1.getCompetitorsFromBestToWorst(r2, afterEndOfR1), c1[0]));
}
@Test
@@ -25,6 +25,7 @@ import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
@@ -42,7 +43,7 @@ import com.sap.sse.util.impl.ArrayListNavigableSet;
public class LeaderboardScoringAndRankingTestBase extends AbstractLeaderboardTest {
protected ArrayList<Series> series;
protected Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
protected RegattaLeaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds);
return new RegattaLeaderboardImpl(regatta, discardingRules);
}
@@ -159,7 +159,7 @@ public class RegattaLogEventNotificationForwardingTest extends AbstractSerializa
@Test
public void testAddCompetitorToRegattaAndEnsureCacheInvalidation() throws NoWindException, InterruptedException, ExecutionException {
Regatta regatta = createRegatta();
RegattaLeaderboard leaderboard = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(new int[0]));
RegattaLeaderboard leaderboard = createRegattaLeaderboard(regatta);
final TimePoint now = MillisecondsTimePoint.now();
leaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now);
LeaderboardDTO dto = leaderboard.getLeaderboardDTO(now.plus(10), Collections.emptySet(), /* addOverallDetails */ false,
@@ -190,7 +190,7 @@ public class RegattaLogEventNotificationForwardingTest extends AbstractSerializa
public void testAddCompetitorToRegattaAndEnsureCacheInvalidationOnDeserializedRegattaLeaderboard() throws NoWindException,
InterruptedException, ExecutionException, ClassNotFoundException, IOException {
Regatta regatta = createRegatta();
RegattaLeaderboard leaderboard = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(new int[0]));
RegattaLeaderboard leaderboard = createRegattaLeaderboard(regatta);
final TimePoint now = MillisecondsTimePoint.now();
leaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now);
RegattaLeaderboard deserializedLeaderboard = cloneBySerialization(leaderboard, DomainFactory.INSTANCE);
@@ -208,4 +208,8 @@ public class RegattaLogEventNotificationForwardingTest extends AbstractSerializa
assertFalse(dto3.competitors.isEmpty());
assertEquals(event.getCompetitor().getName(), dto3.competitors.get(0).getName());
}
protected RegattaLeaderboard createRegattaLeaderboard(Regatta regatta) {
return new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(new int[0]));
}
}
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.test;
import java.io.Serializable;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
/**
* Serializability is required because this instance forms the outer object for the serializable
* lambda required for the regatta leaderboard provider for the
* {@link DelegatingRegattaLeaderboardWithCompetitorElimination} constructor call.
*
* @author Axel Uhl (D043530)
*
*/
public class RegattaLogEventNotificationForwardingWithEliminationTest
extends RegattaLogEventNotificationForwardingTest implements Serializable {
private static final long serialVersionUID = -8184610575644911786L;
@Override
protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta) {
return new DelegatingRegattaLeaderboardWithCompetitorElimination(
()->super.createRegattaLeaderboard(regatta), "Test leaderboard with elimination");
}
}
@@ -8,8 +8,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Random;
import junit.framework.Assert;
import org.junit.AfterClass;
import org.junit.Test;
@@ -32,6 +30,8 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import junit.framework.Assert;
public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMarkPassingTest {
private static LinkedHashMap<String, Long> result = new LinkedHashMap<>();
@@ -75,7 +75,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
final long time = timeToAddCandidatesToChooser(500, 1, 25);
System.out.println(time);
result.put("ChooserPerformance", time);
assertTrue(time < 5000);
assertTrue(time < 15000);
}
private long timeToAddCandidatesToChooser(int numberOfTimesAdding, int numberToAddEachTime, int numberOfRepititions) {
@@ -66,7 +66,7 @@ public interface DomainFactory extends SharedDomainFactory {
PlacemarkDTO convertToPlacemarkDTO(Placemark placemark);
List<CompetitorDTO> getCompetitorDTOList(List<Competitor> competitors);
List<CompetitorDTO> getCompetitorDTOList(Iterable<Competitor> competitors);
TrackedRaceDTO createTrackedRaceDTO(TrackedRace trackedRace);
@@ -338,7 +338,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
}
@Override
public List<CompetitorDTO> getCompetitorDTOList(List<Competitor> competitors) {
public List<CompetitorDTO> getCompetitorDTOList(Iterable<Competitor> competitors) {
List<CompetitorDTO> result = new ArrayList<CompetitorDTO>();
for (Competitor competitor : competitors) {
result.add(convertToCompetitorDTO(competitor));
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
@@ -114,6 +115,8 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
/**
* Convenience method which returns the difference between {@link #getAllCompetitors()} and {@link #getCompetitors()}.
* The collection is a copy that can safely be held and modified, but it is not "live" in the sense that it does not
* reflect changes that occur to the set of suppressed competitors after calling this method, nor vice-versa.
*/
Iterable<Competitor> getSuppressedCompetitors();
@@ -204,19 +207,21 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
Map<Competitor, Double> getCompetitorsForWhichThereAreCarriedPoints();
/**
* Shorthand for {@link TrackedRace#getRank(Competitor, com.sap.sse.common.TimePoint)} with the
* additional logic that in case the <code>race</code> hasn't {@link TrackedRace#hasStarted(TimePoint) started} yet
* or no {@link TrackedRace} exists for <code>race</code>, 0 will be returned for all those competitors. The tracked
* race for the correct {@link Fleet} is determined using {@link RaceColumn#getTrackedRace(Competitor)}.<p>
* Shorthand for {@link TrackedRace#getRank(Competitor, com.sap.sse.common.TimePoint)} with the additional logic
* that in case the <code>race</code> hasn't {@link TrackedRace#hasStarted(TimePoint) started} yet or no
* {@link TrackedRace} exists for <code>race</code>, 0 will be returned for all those competitors. The tracked race
* for the correct {@link Fleet} is determined using {@link RaceColumn#getTrackedRace(Competitor)}.
* <p>
*
* For each competitor tracking-wise ranking better than <code>competitor</code> but with
* a {@link #getMaxPointsReason(Competitor, RaceColumn, TimePoint) disqualification reason} given, <code>competitor</code>'s
* rank is improved by one.
* For each competitor tracking-wise ranking better than <code>competitor</code> but with a
* {@link #getMaxPointsReason(Competitor, RaceColumn, TimePoint) disqualification reason} given,
* <code>competitor</code>'s rank is improved by one.
*
* @param competitor
* a competitor contained in the {@link #getCompetitors()} result
* @param race
* a race that is contained in the {@link #getRaceColumns()} result
* @return a 1-based rank, or 0 if no rank can be determined for the {@code competitor} in {@code race}
*/
int getTrackedRank(Competitor competitor, RaceColumn race, TimePoint timePoint);
@@ -293,7 +298,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
* {@link #getTotalPoints(Competitor, RaceColumn, TimePoint)} and not on
* {@link #getNetPoints(Competitor, RaceColumn, TimePoint)}.
*/
List<Competitor> getCompetitorsFromBestToWorst(RaceColumn raceColumn, TimePoint timePoint) throws NoWindException;
Iterable<Competitor> getCompetitorsFromBestToWorst(RaceColumn raceColumn, TimePoint timePoint) throws NoWindException;
/**
* Sorts the competitors according to the overall regatta standings, considering the sorting rules for
@@ -304,7 +309,8 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint);
/**
* Returns the total rank of the given competitor.
* Returns the total rank of the given competitor or {@code 0} if no rank can be determined for
* the {@code competitor} in this leaderboard.
*/
int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException;
@@ -355,6 +361,10 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
boolean hasCarriedPoints(Competitor competitor);
SettableScoreCorrection getScoreCorrection();
void addScoreCorrectionListener(ScoreCorrectionListener listener);
void removeScoreCorrectionListener(ScoreCorrectionListener listener);
Competitor getCompetitorByName(String competitorName);
@@ -494,7 +504,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
CourseArea getDefaultCourseArea();
/**
* Must be called when the leaderboard is removed from its server, becoming in accessible. This will give the leaderboard
* Must be called when the leaderboard is removed from its server, becoming inaccessible. This will give the leaderboard
* a chance to release all its resources that won't be collected or freed automatically. In particular, a leaderboard may
* hold on to listeners which in turn are registered with {@link TrackedRace}s and therefore won't be released to the garbage
* collector unless the tracked race becomes unreferenced. This may take long because the tracked race can generally
@@ -548,4 +558,6 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
* the boat class will be determined based on the most frequently occurring boat class when iterating across the competitors.
*/
BoatClass getBoatClass();
LeaderboardType getLeaderboardType();
}
@@ -291,7 +291,7 @@ public class LeaderboardCacheManager {
final CacheInvalidationUponCompetitorChangeListener competitorChangeListener = new CacheInvalidationUponCompetitorChangeListener(
leaderboard);
final NameChangeListener nameChangeListener = new NameChangeListener(leaderboard);
leaderboard.getScoreCorrection().addScoreCorrectionListener(scoreCorrectionListener);
leaderboard.addScoreCorrectionListener(scoreCorrectionListener);
leaderboard.addLeaderboardChangeListener(nameChangeListener);
scoreCorrectionListeners.put(leaderboard, scoreCorrectionListener);
competitorChangeListeners.put(leaderboard, competitorChangeListener);
@@ -0,0 +1,35 @@
package com.sap.sailing.domain.leaderboard;
import java.util.HashSet;
import java.util.Set;
import com.sap.sailing.domain.base.Competitor;
/**
* A regatta leaderboard that is derived from another regatta leaderboard by eliminating a subset of the competitors and
* that provides its own, unique name and optionally its own display name. This generally implements a "delegate"
* pattern for a {@link RegattaLeaderboard}. It therefore does not maintain its own score corrections or set of
* suppressed competitors. Note: "suppressed" is different from "eliminated" in that suppressed competitors do not show
* in any race and are not assigned any rank in any race, but eliminated competitors are; they only don't receive a
* regatta ("total") rank, and all competitors advance by as many ranks compared to the original leaderboard as there
* are eliminated competitors ranking better in the original leaderboard.
*
* @author Axel Uhl (d043530)
*/
public interface RegattaLeaderboardWithEliminations extends RegattaLeaderboard {
void setEliminated(Competitor competitor, boolean eliminated);
boolean isEliminated(Competitor competitor);
default Set<Competitor> getEliminatedCompetitors() {
final Set<Competitor> result = new HashSet<>();
for (final Competitor c : getAllCompetitors()) {
if (isEliminated(c)) {
result.add(c);
}
}
return result;
}
}
@@ -0,0 +1,457 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.ObscuringIterable;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
* A regatta leaderboard that is derived from another regatta leaderboard by eliminating a subset of the competitors and
* that provides its own, unique name and optionally its own display name. The class generally implements a "delegate"
* pattern for a {@link RegattaLeaderboard}. It therefore does not maintain its own score corrections or set of
* suppressed competitors. Note: "suppressed" is different from "eliminated" in that suppressed competitors do not show
* in any race and are not assigned any rank in any race, but eliminated competitors are; they only don't receive a
* regatta ("total") rank, and all competitors advance by as many ranks compared to the original leaderboard as there
* are eliminated competitors ranking better in the original leaderboard.
* <p>
*
* This behavior is achieved by overriding any method returning a collection of {@link Competitor}s, such as
* {@link #getCompetitors()}, such that the eliminated competitors are removed from the result which should let any
* leaderboard panel displaying the contents of this leaderboard list only the non-eliminated competitors. This includes
* {@link #getCompetitorsFromBestToWorst(TimePoint)} which also leads the implementation of
* {@link #getTotalRankOfCompetitor(Competitor, TimePoint)} to calculate the ranks based on the competitor list without
* those eliminated.
*
* @author Axel Uhl (d043530)
*/
public class DelegatingRegattaLeaderboardWithCompetitorElimination extends AbstractLeaderboardWithCache implements RegattaLeaderboardWithEliminations {
private static final long serialVersionUID = 8331154893189722924L;
private final String name;
private RegattaLeaderboard fullLeaderboard;
private transient final Supplier<RegattaLeaderboard> fullLeaderboardSupplier;
/**
* The particular use case for which this field is introduced is registering score correction
* listeners at a point in time when the full leaderboard hasn't been resolved yet. Instead of
* letting this listener registration attempt the resolution without success the request can
* be queued here, and each time the {@link #getFullLeaderboard()} successfully resolves a
* leaderboard, all consumers in this set will be triggered.
*/
private final ConcurrentHashMap<Consumer<RegattaLeaderboard>, Boolean> triggerWhenFullLeaderboardIsResolved;
/**
* Competitors eliminated from this leaderboard for regatta ranking; those competitors are not part of
* {@link #getCompetitors()} but appear in {@link #getAllCompetitors()}. They may have an overlap with
* {@link #getSuppressedCompetitors()}, but while suppressed competitors cannot receive a score in a single race,
* eliminated competitors can, and their scores are relevant for computing the regatta ranks, but ultimately, an
* eliminated competitor's regatta rank is defined as {@code 0} for this leaderboard, and competitors ranking worse
* in the {@link #fullLeaderboard original leaderboard} will advance by one rank per eliminated competitor ranking
* better.
*/
private final ConcurrentHashMap<Competitor, Boolean> eliminatedCompetitors;
/**
* The leaderboard wrapper starts out with an empty set of eliminated competitors
*/
public DelegatingRegattaLeaderboardWithCompetitorElimination(Supplier<RegattaLeaderboard> fullLeaderboardSupplier,
String name) {
this.name = name;
this.fullLeaderboardSupplier = fullLeaderboardSupplier;
this.eliminatedCompetitors = new ConcurrentHashMap<>();
this.triggerWhenFullLeaderboardIsResolved = new ConcurrentHashMap<>();
}
@Override
public String getName() {
return name;
}
public void setName(String newName) {
getFullLeaderboard().setName(newName);
}
@Override
public Iterable<Competitor> getCompetitors() {
return new ObscuringIterable<>(getFullLeaderboard().getCompetitors(), eliminatedCompetitors.keySet());
}
@Override
public void setEliminated(Competitor competitor, boolean eliminated) {
if (eliminated) {
eliminatedCompetitors.put(competitor, true);
} else {
eliminatedCompetitors.remove(competitor);
}
}
@Override
public boolean isEliminated(Competitor competitor) {
return eliminatedCompetitors.containsKey(competitor);
}
@Override
public Set<Competitor> getEliminatedCompetitors() {
return new HashSet<Competitor>(eliminatedCompetitors.keySet());
}
public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint)
throws NoWindException {
Map<RaceColumn, List<Competitor>> preResult = getFullLeaderboard().getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(timePoint);
for (final List<Competitor> e : preResult.values()) {
e.removeAll(eliminatedCompetitors.keySet());
}
return preResult;
}
public Map<Competitor, Double> getCompetitorsForWhichThereAreCarriedPoints() {
final Map<Competitor, Double> result = new HashMap<>();
for (final java.util.Map.Entry<Competitor, Double> e : getFullLeaderboard().getCompetitorsForWhichThereAreCarriedPoints().entrySet()) {
if (!isEliminated(e.getKey())) {
result.put(e.getKey(), e.getValue());
}
}
return result;
}
public Iterable<Competitor> getCompetitorsFromBestToWorst(RaceColumn raceColumn, TimePoint timePoint)
throws NoWindException {
return new ObscuringIterable<>(getFullLeaderboard().getCompetitorsFromBestToWorst(raceColumn, timePoint), eliminatedCompetitors.keySet());
}
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
final List<Competitor> result = new ArrayList<>();
for (final Competitor c : getFullLeaderboard().getCompetitorsFromBestToWorst(timePoint)) {
if (!isEliminated(c)) {
result.add(c);
}
}
return result;
}
public Map<Pair<Competitor, RaceColumn>, Entry> getContent(TimePoint timePoint) throws NoWindException {
final Map<Pair<Competitor, RaceColumn>, Entry> result = new HashMap<>();
for (final java.util.Map.Entry<Pair<Competitor, RaceColumn>, Entry> e : getFullLeaderboard().getContent(timePoint).entrySet()) {
if (!isEliminated(e.getKey().getA())) {
result.put(e.getKey(), e.getValue());
}
}
return result;
}
@Override
public LeaderboardType getLeaderboardType() {
return LeaderboardType.RegattaLeaderboardWithEliminations;
}
// --------------------- Delegate Pattern Implementation ----------------------
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
return getFullLeaderboard().getOrCreateCompetitorsProvider();
}
public Regatta getRegatta() {
return getFullLeaderboard().getRegatta();
}
public Iterable<Competitor> getCompetitorsRegisteredInRegattaLog() {
return getFullLeaderboard().getCompetitorsRegisteredInRegattaLog();
}
public IsRegattaLike getRegattaLike() {
return getFullLeaderboard().getRegattaLike();
}
public RaceLog getRacelog(String raceColumnName, String fleetName) {
return getFullLeaderboard().getRacelog(raceColumnName, fleetName);
}
public void registerCompetitor(Competitor competitor) {
getFullLeaderboard().registerCompetitor(competitor);
}
public void registerCompetitors(Iterable<Competitor> competitor) {
getFullLeaderboard().registerCompetitors(competitor);
}
public void deregisterCompetitor(Competitor competitor) {
getFullLeaderboard().deregisterCompetitor(competitor);
}
public void deregisterCompetitors(Iterable<Competitor> competitor) {
getFullLeaderboard().deregisterCompetitors(competitor);
}
public Iterable<Competitor> getAllCompetitors() {
return getFullLeaderboard().getAllCompetitors();
}
public Iterable<Competitor> getAllCompetitors(RaceColumn raceColumn, Fleet fleet) {
return getFullLeaderboard().getAllCompetitors(raceColumn, fleet);
}
public Iterable<Competitor> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
return getFullLeaderboard().getCompetitors(raceColumn, fleet);
}
public Iterable<Competitor> getSuppressedCompetitors() {
return getFullLeaderboard().getSuppressedCompetitors();
}
public boolean isSuppressed(Competitor competitor) {
return getFullLeaderboard().isSuppressed(competitor);
}
public void setSuppressed(Competitor competitor, boolean suppressed) {
getFullLeaderboard().setSuppressed(competitor, suppressed);
}
public Fleet getFleet(String fleetName) {
return getFullLeaderboard().getFleet(fleetName);
}
public Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
return getFullLeaderboard().getEntry(competitor, race, timePoint);
}
public Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint,
Set<RaceColumn> discardedRaceColumns) throws NoWindException {
return getFullLeaderboard().getEntry(competitor, race, timePoint, discardedRaceColumns);
}
public Map<RaceColumn, Map<Competitor, Double>> getNetPointsSumAfterRaceColumn(TimePoint timePoint)
throws NoWindException {
return getFullLeaderboard().getNetPointsSumAfterRaceColumn(timePoint);
}
public double getCarriedPoints(Competitor competitor) {
return getFullLeaderboard().getCarriedPoints(competitor);
}
public int getTrackedRank(Competitor competitor, RaceColumn race, TimePoint timePoint) {
return getFullLeaderboard().getTrackedRank(competitor, race, timePoint);
}
public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return getFullLeaderboard().getTotalPoints(competitor, raceColumn, timePoint);
}
public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn race, TimePoint timePoint) {
return getFullLeaderboard().getMaxPointsReason(competitor, race, timePoint);
}
public Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
return getFullLeaderboard().getNetPoints(competitor, race, timePoint);
}
public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return getFullLeaderboard().isDiscarded(competitor, raceColumn, timePoint);
}
public Double getNetPoints(Competitor competitor, TimePoint timePoint) {
return getFullLeaderboard().getNetPoints(competitor, timePoint);
}
public Double getNetPoints(Competitor competitor, Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint)
throws NoWindException {
return getFullLeaderboard().getNetPoints(competitor, raceColumnsToConsider, timePoint);
}
public Iterable<RaceColumn> getRaceColumns() {
return getFullLeaderboard().getRaceColumns();
}
public RaceColumn getRaceColumnByName(String name) {
return getFullLeaderboard().getRaceColumnByName(name);
}
public void setCarriedPoints(Competitor competitor, double carriedPoints) {
getFullLeaderboard().setCarriedPoints(competitor, carriedPoints);
}
public void unsetCarriedPoints(Competitor competitor) {
getFullLeaderboard().unsetCarriedPoints(competitor);
}
public boolean hasCarriedPoints() {
return getFullLeaderboard().hasCarriedPoints();
}
public boolean hasCarriedPoints(Competitor competitor) {
return getFullLeaderboard().hasCarriedPoints(competitor);
}
public SettableScoreCorrection getScoreCorrection() {
return getFullLeaderboard().getScoreCorrection();
}
@Override
public void addScoreCorrectionListener(ScoreCorrectionListener listener) {
if (fullLeaderboard != null) {
fullLeaderboard.addScoreCorrectionListener(listener);
} else {
triggerWhenFullLeaderboardIsResolved.put(leaderboard->leaderboard.addScoreCorrectionListener(listener), true);
}
}
@Override
public void removeScoreCorrectionListener(ScoreCorrectionListener listener) {
if (fullLeaderboard != null) {
fullLeaderboard.removeScoreCorrectionListener(listener);
} else {
triggerWhenFullLeaderboardIsResolved.put(leaderboard->leaderboard.removeScoreCorrectionListener(listener), true);
}
}
public Competitor getCompetitorByName(String competitorName) {
return getFullLeaderboard().getCompetitorByName(competitorName);
}
public void setDisplayName(Competitor competitor, String displayName) {
getFullLeaderboard().setDisplayName(competitor, displayName);
}
public String getDisplayName(Competitor competitor) {
return getFullLeaderboard().getDisplayName(competitor);
}
public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn,
TimePoint timePoint) {
return getFullLeaderboard().countRaceForComparisonWithDiscardingThresholds(competitor, raceColumn, timePoint);
}
public ResultDiscardingRule getResultDiscardingRule() {
return getFullLeaderboard().getResultDiscardingRule();
}
public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) {
getFullLeaderboard().setCrossLeaderboardResultDiscardingRule(discardingRule);
}
public Competitor getCompetitorByIdAsString(String idAsString) {
return getFullLeaderboard().getCompetitorByIdAsString(idAsString);
}
public void addRaceColumnListener(RaceColumnListener listener) {
getFullLeaderboard().addRaceColumnListener(listener);
}
public void removeRaceColumnListener(RaceColumnListener listener) {
getFullLeaderboard().removeRaceColumnListener(listener);
}
public Long getDelayToLiveInMillis() {
return getFullLeaderboard().getDelayToLiveInMillis();
}
public Iterable<TrackedRace> getTrackedRaces() {
return getFullLeaderboard().getTrackedRaces();
}
public ScoringScheme getScoringScheme() {
return getFullLeaderboard().getScoringScheme();
}
public TimePoint getTimePointOfLatestModification() {
return getFullLeaderboard().getTimePointOfLatestModification();
}
public Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) {
return getFullLeaderboard().getMaximumSpeedOverGround(competitor, timePoint);
}
public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
return getFullLeaderboard().getAverageSpeedOverGround(competitor, timePoint);
}
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, Iterable<RaceColumn> raceColumnsToConsider,
TimePoint timePoint) throws NoWindException {
return getFullLeaderboard().getNetPoints(competitor, raceColumn, raceColumnsToConsider, timePoint);
}
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
Set<RaceColumn> discardedRaceColumns) {
return getFullLeaderboard().getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
}
public TimePoint getNowMinusDelay() {
return getFullLeaderboard().getNowMinusDelay();
}
public CourseArea getDefaultCourseArea() {
return getFullLeaderboard().getDefaultCourseArea();
}
public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() {
return getFullLeaderboard().getNumberOfCompetitorsInLeaderboardFetcher();
}
public Pair<RaceColumn, Fleet> getRaceColumnAndFleet(TrackedRace trackedRace) {
return getFullLeaderboard().getRaceColumnAndFleet(trackedRace);
}
public BoatClass getBoatClass() {
return getFullLeaderboard().getBoatClass();
}
private RegattaLeaderboard getFullLeaderboard() {
if (fullLeaderboard == null) {
if (fullLeaderboardSupplier == null) {
throw new NullPointerException("Internal error: Regatta leaderboard supplier is null; this can only happen upon premature serialization");
}
fullLeaderboard = fullLeaderboardSupplier.get();
if (fullLeaderboard != null) {
for (Iterator<Consumer<RegattaLeaderboard>> i=triggerWhenFullLeaderboardIsResolved.keySet().iterator(); i.hasNext(); ) {
final Consumer<RegattaLeaderboard> toTrigger = i.next();
toTrigger.accept(fullLeaderboard);
i.remove();
}
}
}
return fullLeaderboard;
}
/**
* Before being serialized, ensure that the leaderboard supplier has been used to tru
* to resolve the leaderboard.
* @throws IOException
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
getFullLeaderboard();
oos.defaultWriteObject();
}
}
@@ -363,7 +363,7 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
}
@Override
protected LeaderboardType getLeaderboardType() {
public LeaderboardType getLeaderboardType() {
return LeaderboardType.FlexibleLeaderboard;
}
@@ -122,7 +122,7 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R
}
@Override
protected LeaderboardType getLeaderboardType() {
public LeaderboardType getLeaderboardType() {
return LeaderboardType.RegattaLeaderboard;
}
@@ -194,7 +194,7 @@ public abstract class AbstractMetaLeaderboard extends AbstractSimpleLeaderboardI
leaderboard.addRaceColumnListener(this);
final ScoreCorrectionChangeForwarder listener = new ScoreCorrectionChangeForwarder();
scoreCorrectionChangeForwardersByLeaderboard.put(leaderboard, listener);
leaderboard.getScoreCorrection().addScoreCorrectionListener(listener);
leaderboard.addScoreCorrectionListener(listener);
}
protected void unregisterScoreCorrectionChangeForwarder(Leaderboard leaderboard) {
@@ -64,7 +64,7 @@ public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
}
@Override
protected LeaderboardType getLeaderboardType() {
public LeaderboardType getLeaderboardType() {
return LeaderboardType.FlexibleMetaLeaderboard;
}
}
@@ -92,7 +92,7 @@ public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard imp
}
@Override
protected LeaderboardType getLeaderboardType() {
public LeaderboardType getLeaderboardType() {
return LeaderboardType.RegattaMetaLeaderboard;
}
}
@@ -14,6 +14,7 @@ import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Competitor;
@@ -68,7 +69,7 @@ public class CandidateChooserImpl implements CandidateChooser {
/**
* Distance ratios of actual distance traveled and leg length above this threshold will receive
* penalties on their probability. Ratios below 1.0 receive the ratio as the penalty.
* See {@link #getDistanceEstimationBasedProbability(Competitor, Candidate, Candidate)}.
* See {@link #getDistanceEstimationBasedProbability(Competitor, Candidate, Candidate, Distance)}.
*/
private static final double MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH = 2.0;
@@ -321,8 +322,11 @@ public class CandidateChooserImpl implements CandidateChooser {
continue; // don't create edge from/to same waypoint
}
final Supplier<Double> estimatedDistanceProbabilitySupplier;
final double estimatedDistanceProbability;
final double startTimingProbability;
// when null, don't create an edge; when a valid distance, use this as the totalGreatCircleDistance value
final Distance ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping;
if (early == start) {
// An edge starting at the start proxy node. If the late candidate is for a start mark passing,
// determine a probability not based on distance traveled but based on the
@@ -332,7 +336,9 @@ public class CandidateChooserImpl implements CandidateChooser {
// the race's start time.
if (isGateStart == Boolean.TRUE || start.getTimePoint() == null) { // TODO for gate start read gate timing and scale probability accordingly
startTimingProbability = 1; // no start time point known; all candidate time points equally likely
estimatedDistanceProbability = 1.0; // can't tell distance sailed either because we don't know the start time
estimatedDistanceProbability = 1.0;
ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping = Distance.NULL;
estimatedDistanceProbabilitySupplier = null; // can't tell distance sailed either because we don't know the start time
} else {
// no gate start and we know the race start time
if (late.getWaypoint() != null && late.getWaypoint() == race.getRace().getCourse().getFirstWaypoint()) {
@@ -347,10 +353,20 @@ public class CandidateChooserImpl implements CandidateChooser {
DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES.plus(
timeGapBetweenStartOfRaceAndCandidateTimePoint));
estimatedDistanceProbability = 1.0;
ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping = Distance.NULL;
estimatedDistanceProbabilitySupplier = null;
} else {
startTimingProbability = 1; // can't really tell how well the start time was matched when
// we don't have a start candidate
estimatedDistanceProbability = late == end ? 1.0 : getDistanceEstimationBasedProbability(c, early, late);
// we don't have a start candidate
if (late == end) {
estimatedDistanceProbability = 1.0;
ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping = Distance.NULL;
estimatedDistanceProbabilitySupplier = null;
} else {
estimatedDistanceProbability = 0.0;
ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping = getIgnoreDueToTimingInducedEstimatedSpeeds(c, early, late);
estimatedDistanceProbabilitySupplier = ()->getDistanceEstimationBasedProbability(c, early, late, ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping);
}
}
}
} else {
@@ -358,8 +374,12 @@ public class CandidateChooserImpl implements CandidateChooser {
if (late == end) {
// final edge; we don't know anything about distances for the end proxy node
estimatedDistanceProbability = 1.0;
ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping = Distance.NULL;
estimatedDistanceProbabilitySupplier = null;
} else {
estimatedDistanceProbability = getDistanceEstimationBasedProbability(c, early, late);
estimatedDistanceProbability = 0.0;
ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping = getIgnoreDueToTimingInducedEstimatedSpeeds(c, early, late);
estimatedDistanceProbabilitySupplier = ()->getDistanceEstimationBasedProbability(c, early, late, ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping);
}
}
// If one of the candidates is fixed, the edge is always created unless they travel backwards in time.
@@ -368,9 +388,16 @@ public class CandidateChooserImpl implements CandidateChooser {
final NavigableSet<Candidate> fixed = fixedPassings.get(c);
// TODO this comparison does not exactly implement the condition "if distance is more likely than skipping"
if (travelingForwardInTimeOrUnknown(early, late) &&
(fixed.contains(early) || fixed.contains(late) || estimatedDistanceProbability > MINIMUM_PROBABILITY)) {
addEdge(edges, new Edge(early, late,
startTimingProbability * estimatedDistanceProbability, race.getRace().getCourse().getNumberOfWaypoints()));
(fixed.contains(early) || fixed.contains(late) || ignoreEdgeDueToProbabilityLowerThanMinimumForSkipping != null)) {
final Edge edge;
if (estimatedDistanceProbabilitySupplier != null) {
edge = new Edge(early, late,
()->startTimingProbability * estimatedDistanceProbabilitySupplier.get(), race.getRace().getCourse().getNumberOfWaypoints());
} else {
edge = new Edge(early, late,
startTimingProbability * estimatedDistanceProbability, race.getRace().getCourse().getNumberOfWaypoints());
}
addEdge(edges, edge);
}
}
}
@@ -428,7 +455,7 @@ public class CandidateChooserImpl implements CandidateChooser {
boolean endFound = false;
currentEdgesMoreLikelyFirst.add(new Util.Pair<Edge, Double>(new Edge(
new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
1, race.getRace().getCourse().getNumberOfWaypoints()), 1.0));
()->1.0, race.getRace().getCourse().getNumberOfWaypoints()), 1.0));
while (!endFound) {
Util.Pair<Edge, Double> mostLikelyEdgeWithProbability = currentEdgesMoreLikelyFirst.pollFirst();
if (mostLikelyEdgeWithProbability == null) {
@@ -503,24 +530,72 @@ public class CandidateChooserImpl implements CandidateChooser {
}
}
/**
* The timing of the candidates is put in relation to the distance between the waypoints they connect. This implies
* a rough speed estimate. If this is completely "out of whack" (way too low or way too high), we can assume that if
* ultimately carrying out the potentially expensive (for long-distance races) calculation of the distance sailed,
* we would only find that the distance-based probability would be below the {@link #MINIMUM_PROBABILITY} threshold
* such that that edge would be ignored.
*
* @return {@code null} if the edge between {@code c1} and {@code c2} shall be ignored; the
* {@link #getMinimumTotalGreatCircleDistanceBetweenWaypoints(Waypoint, Waypoint, TimePoint) straight-line
* distance between the corresponding waypoints} otherwise. This distance can be used for a subsequent call
* to {@link #getDistanceEstimationBasedProbability(Competitor, Candidate, Candidate, Distance)} so it doesn't have
* to be re-calculated there.
*/
private Distance getIgnoreDueToTimingInducedEstimatedSpeeds(Competitor c, Candidate c1, Candidate c2) {
final boolean ignore;
assert c1.getOneBasedIndexOfWaypoint() < c2.getOneBasedIndexOfWaypoint();
assert c2 != end;
final TimePoint middleOfc1Andc2 = new MillisecondsTimePoint(c1.getTimePoint().plus(c2.getTimePoint().asMillis()).asMillis() / 2);
Waypoint first = getFirstWaypoint(c1);
final Waypoint second = c2.getWaypoint();
final Distance totalGreatCircleDistance = getMinimumTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
if (totalGreatCircleDistance == null) {
ignore = true; // no distance known; cannot tell, so ignore the edge
} else {
// Computing the distance traveled can be quite expensive, especially for candidates very far apart.
// As a quick approximation let's look at how long the time between the candidates was and relate that to the minimum distance
// between the waypoints. This leads to a speed estimation; if we take the minimum distance times two, we
// get an upper bound for a reasonable distance sailed between the waypoints and therefore an estimation
// for the maximum speed at which the competitor would have had to sail:
Speed estimatedMaxSpeed = totalGreatCircleDistance.scale(2).inTime(c1.getTimePoint().until(c2.getTimePoint()));
final double estimatedMinSpeedBasedProbability = Math.max(0, estimatedMaxSpeed.divide(MINIMUM_REASONABLE_SPEED));
final double estimatedMaxSpeedBasedProbability = Math.max(0, MAXIMUM_REASONABLE_SPEED.divide(estimatedMaxSpeed));
final double estimatedSpeedBasedProbabilityMinimum = Math.min(estimatedMaxSpeedBasedProbability, estimatedMinSpeedBasedProbability);
ignore = estimatedSpeedBasedProbabilityMinimum < MINIMUM_PROBABILITY;
}
return ignore ? null : totalGreatCircleDistance;
}
/**
* If the candidate has no waypoint associated, return the course's first waypoint; otherwise, return the candidates
* {@link Candidate#getWaypoint() waypoint}.
*/
private Waypoint getFirstWaypoint(Candidate candidate) {
Waypoint first;
if (candidate.getOneBasedIndexOfWaypoint() == 0) {
first = race.getRace().getCourse().getFirstWaypoint();
} else {
first = candidate.getWaypoint();
}
return first;
}
/**
* The distance between waypoints is used to estimate the distance that should be covered between these two
* candidates. This estimation is then compared to the distance actually sailed. A distance smaller than the
* estimation is (aside from a small tolerance) impossible, a distance larger get increasingly unlikely.
*
* @param totalGreatCircleDistance
* the result of a previous
* {@link #getMinimumTotalGreatCircleDistanceBetweenWaypoints(Waypoint, Waypoint, TimePoint)} call for
* the two waypoints of {@code c1} and {@code c2}
*/
private double getDistanceEstimationBasedProbability(Competitor c, Candidate c1, Candidate c2) {
private double getDistanceEstimationBasedProbability(Competitor c, Candidate c1, Candidate c2, Distance totalGreatCircleDistance) {
final double result;
assert c1.getOneBasedIndexOfWaypoint() < c2.getOneBasedIndexOfWaypoint();
assert c2 != end;
Waypoint first;
final TimePoint middleOfc1Andc2 = new MillisecondsTimePoint(c1.getTimePoint().plus(c2.getTimePoint().asMillis()).asMillis() / 2);
if (c1.getOneBasedIndexOfWaypoint() == 0) {
first = race.getRace().getCourse().getFirstWaypoint();
} else {
first = c1.getWaypoint();
}
final Waypoint second = c2.getWaypoint();
final Distance totalGreatCircleDistance = getMinimumTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
if (totalGreatCircleDistance == null) {
result = 0.0; // no distance known; cannot tell
} else {
@@ -529,19 +604,11 @@ public class CandidateChooserImpl implements CandidateChooser {
// between the waypoints. This leads to a speed estimation; if we take the minimum distance times two, we
// get an upper bound for a reasonable distance sailed between the waypoints and therefore an estimation
// for the maximum speed at which the competitor would have had to sail:
Speed estimatedMaxSpeed = totalGreatCircleDistance.scale(2).inTime(c1.getTimePoint().until(c2.getTimePoint()));
final double estimatedMinSpeedBasedProbability = Math.max(0, estimatedMaxSpeed.divide(MINIMUM_REASONABLE_SPEED));
final double estimatedMaxSpeedBasedProbability = Math.max(0, estimatedMaxSpeed.divide(MAXIMUM_REASONABLE_SPEED));
final double estimatedSpeedBasedProbabilityMinimum = Math.min(estimatedMaxSpeedBasedProbability, estimatedMinSpeedBasedProbability);
if (estimatedSpeedBasedProbabilityMinimum < MINIMUM_PROBABILITY) {
result = estimatedSpeedBasedProbabilityMinimum;
} else {
final Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
final double probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength =
c2.getWaypoint() == race.getRace().getCourse().getLastWaypoint() ? PENALTY_FOR_LATEST_FINISH_PASSING : 1.0;
result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled,
probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength);
}
final Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
final double probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength =
c2.getWaypoint() == race.getRace().getCourse().getLastWaypoint() ? PENALTY_FOR_LATEST_FINISH_PASSING : 1.0;
result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled,
probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength);
}
return result;
}
@@ -602,10 +602,8 @@ public class CandidateFinderImpl implements CandidateFinder {
List<Distance> waypointDistances = fixDistances.get(w);
List<Distance> waypointDistancesBefore = fixDistancesBefore.get(w);
List<Distance> waypointDistancesAfter = fixDistancesAfter.get(w);
// due to course changes, waypoints that exist in the waypoints collection may not have a
// corresponding
// key in passingInstructions' key set which is the basis for the waypoints for which
// getDistances(...)
// due to course changes, waypoints that exist in the waypoints collection may not have a corresponding
// key in passingInstructions' key set which is the basis for the waypoints for which getDistances(...)
// computes results; so we have to check for null here:
if (waypointDistances != null && waypointDistancesBefore != null && waypointDistancesAfter != null) {
Iterator<Distance> disIter = waypointDistances.iterator();
@@ -871,7 +869,8 @@ public class CandidateFinderImpl implements CandidateFinder {
final Distance d = portMark ? distances.get(0) : distances.get(1);
final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, t);
final double sidePenalty = getSidePenalty(w, p, t, portMark, markPositionCache);
double probability = getDistanceBasedProbability(w, t, d, markPositionCache) * sidePenalty;
final Double distanceBasedProbability = getDistanceBasedProbability(w, t, d, markPositionCache);
double probability = distanceBasedProbability == null ? sidePenalty : distanceBasedProbability * sidePenalty;
final Double passesInTheRightDirectionProbability = passesInTheRightDirection(w, xte1, xte2, portMark);
// null would mean "unknown"; no penalty for those cases
probability = passesInTheRightDirectionProbability == null ? probability : probability * passesInTheRightDirectionProbability;
@@ -1202,20 +1201,31 @@ public class CandidateFinderImpl implements CandidateFinder {
}
/**
* @return a probability based on the distance to <code>w</code> and the average leg lengths before and after.
* @return a probability based on the distance to <code>w</code>; for single marks the average leg lengths before
* and after the waypoint {@code w} is also taken into account; for two-mark waypoints such as gates and
* lines it seems fair to assume that the length of the adjacent legs should not play a role in how accurate
* the competitor needs to pass the waypoint.
*/
private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
assert distance.getMeters() >= 0;
final Double result;
Distance legLength = getAverageLengthOfAdjacentLegs(t, w, markPositionCache);
if (legLength != null) {
if (Util.size(w.getControlPoint().getMarks())>1) {
result = 1 / (STRICTNESS_OF_DISTANCE_BASED_PROBABILITY/* Raising this will make it stricter */
// reduce distance by 2x the typical HDOP, accounting for the possibility that some distance from the mark
// may have been caused by inaccurate GPS tracking
* Math.abs(Math.max(0.0, distance.add(GPSFix.TYPICAL_HDOP.scale(-2)).divide(legLength))) + 1);
// for a two-mark control point such as a gate or a line only consider the relation of
// the distance to 150x the typical HDOP error;
* Math.abs(Math.max(0.0, distance.divide(GPSFix.TYPICAL_HDOP.scale(150)))) + 1);
} else {
result = null;
Distance legLength = getAverageLengthOfAdjacentLegs(t, w, markPositionCache);
if (legLength != null) {
result = 1 / (STRICTNESS_OF_DISTANCE_BASED_PROBABILITY/* Raising this will make it stricter */
// reduce distance by 2x the typical HDOP, accounting for the possibility that some distance from the mark
// may have been caused by inaccurate GPS tracking
* Math.abs(Math.max(0.0, distance.add(GPSFix.TYPICAL_HDOP.scale(-2)).divide(legLength))) + 1);
} else {
result = null;
}
}
return result;
}
@@ -1328,12 +1338,11 @@ public class CandidateFinderImpl implements CandidateFinder {
PassingInstruction instruction = getPassingInstructions(w);
if (instruction == PassingInstruction.Line) {
Util.Pair<Mark, Mark> marks = getPortAndStarboardMarks(t, w, markPositionCache);
Position portPosition = null;
Bearing b = null;
Mark portMark = marks.getA();
Mark starBoardMark = marks.getB();
if (portMark != null && starBoardMark != null) {
portPosition = markPositionCache.getEstimatedPosition(portMark);
Position portPosition = markPositionCache.getEstimatedPosition(portMark);
Position starboardPosition = markPositionCache.getEstimatedPosition(starBoardMark);
if (portPosition != null && starboardPosition != null) {
b = portPosition.getBearingGreatCircle(starboardPosition);
@@ -1414,7 +1423,7 @@ public class CandidateFinderImpl implements CandidateFinder {
}
markPositions.add(estimatedMarkPosition);
}
if (markPositions.size() != 2){
if (markPositions.size() != 2) {
return new Util.Pair<Mark, Mark>(null, null);
}
final List<Leg> legs = race.getRace().getCourse().getLegs();
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import java.util.function.Supplier;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.markpassingcalculation.Candidate;
@@ -19,12 +21,21 @@ public class Edge implements Comparable<Edge> {
/**
* The probability-reducing factor for skipping a waypoint is {@link #PENALTY_FOR_SKIPPED} but is reduced to
* {@link #PENALTY_FOR_SKIPPED_TO_END} when skipping to the end. The reason for preferring skips to the end proxy node
* is that in live situations where the course hasn't been completed yet it is required to skip to the end. The main
* probability comes from the probability of the product of the start node, end node and distance-based
* {@link #PENALTY_FOR_SKIPPED_TO_END} when skipping to the end. The reason for preferring skips to the end proxy
* node is that in live situations where the course hasn't been completed yet it is required to skip to the end. The
* main probability comes from the probability of the product of the start node, end node and distance-based
* probabilities.
* <p>
*
* Once used, the result is stored in {@link #estimatedDistanceAndStartTimingProbability}, and this field is set to
* {@code null}.
*/
private final double estimatedDistanceAndStartTimingProbability;
private Supplier<Double> estimatedDistanceAndStartTimingProbabilitySupplier;
/**
* Either provided at construction time or calculated by the {@link #estimatedDistanceAndStartTimingProbabilitySupplier}.
*/
private double estimatedDistanceAndStartTimingProbability;
/**
* The penalty for an edge's probability in case the edge skips one or more waypoints and does not
@@ -48,6 +59,13 @@ public class Edge implements Comparable<Edge> {
private final int numberOfWaypoints;
public Edge(Candidate start, Candidate end, Supplier<Double> estimatedDistanceAndStartTimingProbabilitySupplier, int numberOfWaypoints) {
this.start = start;
this.end = end;
this.estimatedDistanceAndStartTimingProbabilitySupplier = estimatedDistanceAndStartTimingProbabilitySupplier;
this.numberOfWaypoints = numberOfWaypoints;
}
public Edge(Candidate start, Candidate end, double estimatedDistanceAndStartTimingProbability, int numberOfWaypoints) {
this.start = start;
this.end = end;
@@ -70,10 +88,17 @@ public class Edge implements Comparable<Edge> {
final double penalty = getEnd().getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? PENALTY_FOR_SKIPPED_TO_END : getPenaltyForSkipping();
// See bug 3241 comment #38: only use the edge's end candidate's probability; the start candidate's probability is the
// previous edge's end probability. The only probability we'll miss this way is that of the start proxy node and that is always 1.
return getEnd().getProbability() * estimatedDistanceAndStartTimingProbability *
return getEnd().getProbability() * getEstimatedDistanceAndStartTimingProbability() *
Math.pow(penalty, (getEnd().getOneBasedIndexOfWaypoint() - getStart().getOneBasedIndexOfWaypoint() - 1));
}
private double getEstimatedDistanceAndStartTimingProbability() {
if (estimatedDistanceAndStartTimingProbabilitySupplier != null) {
estimatedDistanceAndStartTimingProbability = estimatedDistanceAndStartTimingProbabilitySupplier.get();
estimatedDistanceAndStartTimingProbabilitySupplier = null;
}
return estimatedDistanceAndStartTimingProbability;
}
public Candidate getStart() {
return start;
@@ -627,6 +627,11 @@ public abstract class AbstractRankingMetric implements RankingMetric {
final Position estimatedPosition = getTrackedRace().getTrack(competitor).getEstimatedPosition(timePoint, /* extrapolate */ true);
if (estimatedPosition != null) {
final Distance windwardDistanceFromLegStart = trackedLeg.getWindwardDistanceFromLegStart(estimatedPosition, cache);
if (windwardDistanceFromLegStart == null) {
// probably the leg start position is not known; therefore, distance cannot be determined; return null:
d = null;
break;
}
final Distance legWindwardDistance = trackedLeg.getWindwardDistance(cache);
if (legWindwardDistance != null && legWindwardDistance.compareTo(windwardDistanceFromLegStart) < 0) {
d = d.add(legWindwardDistance);
@@ -548,6 +548,7 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
if (fromPos == null) {
result = Distance.NULL;
} else {
// TODO bug 4122: idea: use the subset to determine fromPos and toPos in one go, eliminating two expensive getEstimatedPosition calls
NavigableSet<GPSFix> subset = getGPSFixes().subSet(new DummyGPSFix(from),
/* fromInclusive */false, new DummyGPSFix(to),
/* toInclusive */false);
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="66"
height="66"
viewBox="0 0 17.462499 17.4625"
version="1.1"
id="svg3788"
sodipodi:docname="liveraces.svg"
style="enable-background:new"
inkscape:version="0.92.1 r15371">
<defs
id="defs3782" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="10"
inkscape:cx="44.577827"
inkscape:cy="33.336717"
inkscape:document-units="mm"
inkscape:current-layer="layer5"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="1072"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata3785">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Ebene 3"
style="display:inline"
transform="translate(0.2381256,0.29108406)">
<path
style="fill:none;stroke:#ffffff;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 0.05291608,0.05291787 15.857454,2.1960429 V 6.4029173 L 0.05291608,8.281459 Z"
id="path3848"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.26458332"
d="M 3.4925001,0.58208334 6.4822917,1.031875 v 6.4558328 l -2.9897916,0.3175 z"
id="rect3852"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.26458332"
d="M 9.8424996,1.42875 12.699999,1.825625 V 6.6674998 L 9.8424996,6.9320831 Z"
id="rect3855"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.25730553"
width="8.7120695"
height="5.3254027"
x="5.5261526"
y="9.4949026"
id="rect3870" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 2.5399991,8.0168736 v 8.8899584 0 0 H 16.95979 V 7.3554154 H 7.831666"
id="path3872"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -249,7 +249,7 @@ public class DesktopStartView extends Composite implements StartView {
EventDTO result = null;
int selectedIndex = eventSelectionBox.getSelectedIndex();
if (events != null && selectedIndex > 0) {
String selectedItemText = eventSelectionBox.getItemText(selectedIndex);
String selectedItemText = eventSelectionBox.getValue(selectedIndex);
for (EventDTO event : events) {
if (event.getName().equals(selectedItemText)) {
result = event;
@@ -9,8 +9,8 @@ import com.sap.sailing.gwt.home.server.EventActionUtil;
import com.sap.sailing.gwt.home.server.RaceListDataCalculator;
import com.sap.sse.common.Duration;
import com.sap.sse.gwt.dispatch.shared.caching.IsClientCacheable;
import com.sap.sse.gwt.dispatch.shared.commands.ListResult;
import com.sap.sse.gwt.dispatch.shared.commands.ResultWithTTL;
import com.sap.sse.gwt.dispatch.shared.commands.SortedSetResult;
/**
* <p>
@@ -24,7 +24,7 @@ import com.sap.sse.gwt.dispatch.shared.commands.SortedSetResult;
* state} using a duration of <i>5 minutes</i> for currently running events.
* </p>
*/
public class GetFinishedRacesAction implements SailingAction<ResultWithTTL<SortedSetResult<RaceListRaceDTO>>>,
public class GetFinishedRacesAction implements SailingAction<ResultWithTTL<ListResult<RaceListRaceDTO>>>,
IsClientCacheable {
private UUID eventId;
@@ -47,12 +47,12 @@ public class GetFinishedRacesAction implements SailingAction<ResultWithTTL<Sorte
@Override
@GwtIncompatible
public ResultWithTTL<SortedSetResult<RaceListRaceDTO>> execute(SailingDispatchContext context) {
public ResultWithTTL<ListResult<RaceListRaceDTO>> execute(SailingDispatchContext context) {
RaceListDataCalculator raceListDataCalculator = new RaceListDataCalculator();
EventActionUtil.forRacesOfRegatta(context, eventId, regattaId, raceListDataCalculator);
return new ResultWithTTL<>(EventActionUtil.getEventStateDependentTTL(context, eventId,
Duration.ONE_MINUTE.times(5)), new SortedSetResult<>(raceListDataCalculator.getResult()));
Duration.ONE_MINUTE.times(5)), new ListResult<>(raceListDataCalculator.getResult()));
}
@Override
@@ -2,6 +2,7 @@ package com.sap.sailing.gwt.home.communication.event;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
@@ -14,11 +15,9 @@ import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO;
import com.sap.sailing.gwt.home.server.EventActionUtil;
import com.sap.sailing.gwt.home.server.EventActionUtil.LeaderboardCallback;
import com.sap.sailing.gwt.home.server.EventActionUtil.RaceCallback;
import com.sap.sailing.gwt.home.server.LeaderboardContext;
import com.sap.sailing.gwt.home.server.RaceContext;
import com.sap.sailing.gwt.server.HomeServiceUtil;
import com.sap.sse.common.Duration;
import com.sap.sailing.gwt.home.server.RaceRefreshCalculator;
import com.sap.sse.gwt.dispatch.shared.caching.IsClientCacheable;
import com.sap.sse.gwt.dispatch.shared.commands.ResultWithTTL;
@@ -53,39 +52,27 @@ public class GetRegattasAndLiveRacesForEventAction implements SailingAction<Resu
@Override
@GwtIncompatible
public ResultWithTTL<RegattasAndLiveRacesDTO> execute(SailingDispatchContext context) {
Event event = context.getRacingEventService().getEvent(eventId);
final Map<String, RegattaMetadataDTO> regattas = mapRegattas(context, event);
final Map<String, RegattaMetadataDTO> regattas = mapRegattas(context);
final TreeMap<RegattaMetadataDTO, TreeSet<LiveRaceDTO>> regattasWithRaces = new TreeMap<>();
EventState eventState = HomeServiceUtil.calculateEventState(event);
final Duration ttl;
if (eventState == EventState.RUNNING || eventState == EventState.UPCOMING) {
EventActionUtil.forRacesOfEvent(context, eventId, new RaceCallback() {
@Override
public void doForRace(RaceContext context) {
LiveRaceDTO liveRace = context.getLiveRaceOrNull();
if(liveRace != null) {
ensureRegatta(context, regattas, regattasWithRaces).add(liveRace);
}
}
});
ttl = Duration.ONE_MINUTE.times(2);
} else {
ttl = EventActionUtil.calculateTtlForNonLiveEvent(event, eventState);
}
final TreeSet<RegattaMetadataDTO> regattasWithoutRaces = new TreeSet<>();
for (RegattaMetadataDTO regatta : regattas.values()) {
if (!regattasWithRaces.containsKey(regatta)) {
regattasWithoutRaces.add(regatta);
}
}
return new ResultWithTTL<RegattasAndLiveRacesDTO>(ttl, new RegattasAndLiveRacesDTO(regattasWithRaces, regattasWithoutRaces));
final RaceRefreshCalculator refreshCalculator = new RaceRefreshCalculator();
EventActionUtil.forRacesOfEvent(context, eventId, raceContent -> {
Optional<LiveRaceDTO> liveRace = Optional.ofNullable(raceContent.getLiveRaceOrNull());
liveRace.ifPresent(r -> ensureRegatta(raceContent, regattas, regattasWithRaces).add(r));
refreshCalculator.doForRace(raceContent);
});
regattas.values().stream().filter(regatta -> !regattasWithRaces.containsKey(regatta))
.forEach(regattasWithoutRaces::add);
RegattasAndLiveRacesDTO dto = new RegattasAndLiveRacesDTO(regattasWithRaces, regattasWithoutRaces);
ResultWithTTL<RegattasAndLiveRacesDTO> result = new ResultWithTTL<>(refreshCalculator.getTTL(), dto);
return EventActionUtil.withLiveRaceOrDefaultSchedule(context, eventId, event -> result, dto);
}
@GwtIncompatible
private Map<String, RegattaMetadataDTO> mapRegattas(SailingDispatchContext context, Event event) {
private Map<String, RegattaMetadataDTO> mapRegattas(SailingDispatchContext context) {
final Map<String, RegattaMetadataDTO> result = new HashMap<>();
EventActionUtil.forLeaderboardsOfEvent(context, event, new LeaderboardCallback() {
EventActionUtil.forLeaderboardsOfEvent(context, eventId, new LeaderboardCallback() {
@Override
public void doForLeaderboard(LeaderboardContext context) {
result.put(context.getLeaderboardName(), context.asRegattaMetadataDTO());
@@ -95,7 +82,8 @@ public class GetRegattasAndLiveRacesForEventAction implements SailingAction<Resu
}
@GwtIncompatible
protected Set<LiveRaceDTO> ensureRegatta(RaceContext context, Map<String, RegattaMetadataDTO> regattas, Map<RegattaMetadataDTO, TreeSet<LiveRaceDTO>> regattasWithRaces) {
protected Set<LiveRaceDTO> ensureRegatta(RaceContext context, Map<String, RegattaMetadataDTO> regattas,
Map<RegattaMetadataDTO, TreeSet<LiveRaceDTO>> regattasWithRaces) {
String regattaName = context.getRegattaName();
RegattaMetadataDTO regatta = regattas.get(regattaName);
TreeSet<LiveRaceDTO> races = regattasWithRaces.get(regatta);
@@ -1,7 +1,7 @@
package com.sap.sailing.gwt.home.communication.event;
import java.util.Collection;
import java.util.TreeSet;
import java.util.LinkedHashSet;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.gwt.home.communication.race.FleetMetadataDTO;
@@ -11,7 +11,7 @@ public class RaceCompetitionFormatFleetDTO implements IsSerializable {
private FleetMetadataDTO fleet;
private int competitorCount = 0;
private TreeSet<SimpleRaceMetadataDTO> races = new TreeSet<>();
private LinkedHashSet<SimpleRaceMetadataDTO> races = new LinkedHashSet<>();
protected RaceCompetitionFormatFleetDTO() {
}
@@ -49,6 +49,11 @@ public class LiveRacesList extends Composite {
this.regattaNameColumn.setShowDetails(showRegattaDetails);
}
@Override
protected SortableRaceListColumn<LiveRaceDTO, ?> getDefaultSortColumn() {
return startTimeColumn;
}
@Override
protected void setTableData(Collection<LiveRaceDTO> data) {
boolean hasFleets = RaceListDataUtil.hasFleets(data);
@@ -13,16 +13,17 @@ import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicatorResources;
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
public abstract class AbstractLeaderboardFullscreenViewer<T extends Widget> extends FullscreenContainer<T> implements
LeaderboardDelegate<T> {
public abstract class AbstractLeaderboardFullscreenViewer extends FullscreenContainer<Widget> implements
LeaderboardDelegate {
private final Image autoRefreshControl = new Image("images/home/reload.svg");
private final Image settingsControl = new Image("images/home/settings.svg");
protected final Image autoRefreshControl = new Image("images/home/reload.svg");
protected final Image settingsControl = new Image("images/home/settings.svg");
private final static BusyIndicatorResources RESOURCES = GWT.create(BusyIndicatorResources.class);
private final Label lastScoringUpdateTime = new Label();
private final Label lastScoringUpdateText = new Label();
protected final Widget lastScoringUpdatePanel;
protected final Label lastScoringComment = new Label();
protected final Label scoringScheme = new Label();
protected final BusyIndicator busyIndicator = new SimpleBusyIndicator(false, 0.9f, RESOURCES.busyIndicatorCircleInverted());
@@ -31,7 +32,7 @@ public abstract class AbstractLeaderboardFullscreenViewer<T extends Widget> exte
showLogo();
showBorder();
addToolbarBusyIndicator(busyIndicator);
addToolbarInfo(createPanel(lastScoringUpdateText, lastScoringUpdateTime));
addToolbarInfo(lastScoringUpdatePanel = createPanel(lastScoringUpdateText, lastScoringUpdateTime));
addToolbarAction(autoRefreshControl);
addToolbarAction(settingsControl);
}
@@ -56,13 +57,14 @@ public abstract class AbstractLeaderboardFullscreenViewer<T extends Widget> exte
protected Widget createPanel(Widget... contentWidgets) {
FlowPanel panel = new FlowPanel();
for (Widget widget : contentWidgets)
for (Widget widget : contentWidgets) {
panel.add(widget);
}
return panel;
}
@Override
public void setLeaderboardPanel(T leaderboardPanel) {
public void setLeaderboardPanel(Widget leaderboardPanel) {
showContent(leaderboardPanel);
}
@@ -83,7 +83,9 @@
.regattaleaderboard_meta_update {
display: inline-block;
text-align: right;
vertical-align: top;
}
.regattaleaderboard_meta_liveraces_button,
.regattaleaderboard_meta_reload,
.regattaleaderboard_meta_settings_button,
.regattaleaderboard_meta_fullscreen_button {
@@ -109,6 +111,7 @@
.regattaleaderboard_meta_busyindicator {
display: inline-block;
margin: 0 0.666666666666667em;
vertical-align: text-bottom;
}
.regattaleaderboard_meta_liverace {
@@ -127,6 +130,11 @@
.regattaleaderboard_meta_settings {
display: inline-block;
text-align: right;
font-size: 0;
}
.regattaleaderboard_meta_settings * {
font-size: 1rem;
}
.regattaleaderboard_meta_settings_button {
background-color: #9f9f9f;
@@ -228,9 +236,6 @@
width: 100%;
table-layout: fixed;
}
.marginFix {
margin-top:0.5rem;
}
.letItScroll {
overflow-x: auto;
margin: 10px 0px;
@@ -239,3 +244,7 @@
background-color: #9f9f9f;
background-image: url("images/home/fullscreen.svg");
}
.regattaleaderboard_meta_liveraces_button {
background-color: #9f9f9f;
background-image: url("images/home/liveraces.svg");
}
@@ -64,9 +64,9 @@ public interface EventRegattaLeaderboardResources extends ClientBundle {
String leaderboardcompetitor_item_table();
String leaderboardcompetitor_itemcenter_table();
String leaderboardcompetitorshort();
String marginFix();
String letItScroll();
String regattaleaderboard_meta_fullscreen_button();
String regattaleaderboard_meta_liveraces_button();
}
}
@@ -6,9 +6,9 @@ import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
public interface LeaderboardDelegate<T extends Widget> {
public interface LeaderboardDelegate {
void setLeaderboardPanel(T leaderboardPanel);
void setLeaderboardPanel(Widget leaderboardPanel);
void setBusyState(boolean isBusy);
@@ -20,6 +20,7 @@ import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
@@ -49,9 +50,10 @@ public class OldLeaderboard extends Composite implements BusyStateChangeListener
}
@UiField HTMLPanel oldLeaderboardPanel;
@UiField Anchor settingsAnchor;
@UiField Anchor autoRefreshAnchor;
@UiField Anchor showLiveRacesAnchor;
@UiField Anchor fullscreenAnchor;
@UiField DivElement lastScoringUpdateTimeDiv;
@UiField DivElement lastScoringUpdateTextDiv;
@@ -65,16 +67,13 @@ public class OldLeaderboard extends Composite implements BusyStateChangeListener
private Timer autoRefreshTimer;
private final OldLeaderboardDelegate delegate;
public OldLeaderboard() {
this(null);
}
public OldLeaderboard(final OldLeaderboardDelegate delegate) {
this.leaderboardPanel = null;
EventRegattaLeaderboardResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
settingsAnchor.setTitle(StringMessages.INSTANCE.settings());
autoRefreshAnchor.setTitle(StringMessages.INSTANCE.refresh());
showLiveRacesAnchor.setTitle(StringMessages.INSTANCE.showLiveNow());
fullscreenAnchor.setTitle(StringMessages.INSTANCE.openFullscreenView());
this.delegate = delegate;
this.setupFullscreenDelegate();
@@ -206,6 +205,24 @@ public class OldLeaderboard extends Composite implements BusyStateChangeListener
}
}
/**
* Provides access to the {@link OldLeaderboard leaderboard}'s control to show live races.
*
* @return the {@link FocusWidget control} for showing live races.
*/
public FocusWidget getShowLiveRacesControl() {
return showLiveRacesAnchor;
}
/**
* Provides access to the {@link OldLeaderboard leaderboard}'s control for fullscreen mode.
*
* @return the {@link FocusWidget control} for fullscreen mode.
*/
public FocusWidget getFullscreenControl() {
return fullscreenAnchor;
}
public void setLeaderboard(LeaderboardPanel leaderboardPanel, final Timer timer) {
this.autoRefreshTimer = timer;
this.leaderboardPanel = leaderboardPanel;
@@ -274,7 +291,7 @@ public class OldLeaderboard extends Composite implements BusyStateChangeListener
}
}
public interface OldLeaderboardDelegate extends LeaderboardDelegate<LeaderboardPanel> {
public interface OldLeaderboardDelegate extends LeaderboardDelegate {
Element getHasLiveRaceElement();
}
@@ -4,7 +4,7 @@
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages"/>
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.old.EventRegattaLeaderboardResources" />
<g:HTMLPanel styleName=" {local_res.css.marginFix}">
<g:HTMLPanel styleName="{res.mainCss.spacermargintopsmall}">
<div class="{res.mediaCss.grid}">
<div class="{res.mediaCss.small6} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_scoring}">
<div ui:field="lastScoringCommentDiv" class="{local_res.css.regattaleaderboard_meta_scoring_comment}"></div>
@@ -17,9 +17,10 @@
<div ui:field="lastScoringUpdateTextDiv" class="{local_res.css.regattaleaderboard_meta_update_text}"></div>
<div ui:field="lastScoringUpdateTimeDiv" class="{local_res.css.regattaleaderboard_meta_update_timestamp}"></div>
</div>
<g:Anchor ui:field="autoRefreshAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_reload}"></g:Anchor>
<g:Anchor ui:field="settingsAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_settings_button}"></g:Anchor>
<g:Anchor ui:field="fullscreenAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_fullscreen_button}"></g:Anchor>
<g:Anchor ui:field="autoRefreshAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_reload}" />
<g:Anchor ui:field="settingsAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_settings_button}" />
<g:Anchor ui:field="showLiveRacesAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_liveraces_button}" />
<g:Anchor ui:field="fullscreenAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_fullscreen_button}" />
</div>
</div>
@@ -1,23 +1,106 @@
package com.sap.sailing.gwt.home.desktop.partials.old.leaderboard;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.desktop.partials.old.AbstractLeaderboardFullscreenViewer;
import com.sap.sailing.gwt.home.desktop.partials.old.leaderboard.OldLeaderboard.OldLeaderboardDelegate;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
public class OldLeaderboardDelegateFullscreenViewer extends AbstractLeaderboardFullscreenViewer<LeaderboardPanel>
public class OldLeaderboardDelegateFullscreenViewer extends AbstractLeaderboardFullscreenViewer
implements OldLeaderboardDelegate {
interface OldLeaderboardDelegateFullscreenViewerUiBinder
extends UiBinder<Widget, OldLeaderboardDelegateFullscreenViewer.ContentLayout> {
}
private static OldLeaderboardDelegateFullscreenViewerUiBinder uiBinder = GWT
.create(OldLeaderboardDelegateFullscreenViewerUiBinder.class);
private final Image showLiveRacesControl = new Image("images/home/liveraces.svg");
private final Label hasLiveRace = new Label();
private final Widget headerWidget;
private final ContentLayout contentLayout = new ContentLayout();
private final Widget content = uiBinder.createAndBindUi(this.contentLayout);
class ContentLayout {
@UiField SimplePanel liveRacesContainerUi, leaderboardContainerUi;
@UiField FlowPanel subHeaderUi, subHeaderContentUi, subHeaderToolbarUi;
}
/**
* Create a new {@link OldLeaderboardDelegateFullscreenViewer} instance including live races information.
*
* @see #OldLeaderboardDelegateFullscreenViewer(boolean)
*/
public OldLeaderboardDelegateFullscreenViewer() {
setHeaderWidget(createPanel(lastScoringComment, hasLiveRace, scoringScheme));
this(true);
}
/**
* Create a new {@link OldLeaderboardDelegateFullscreenViewer} instance optionally including live races information
* which expand when they are selected and available.
*
* @param includeLiveRaces
* <code>true</code> to include live races information, <code>false</code> to not include
*/
public OldLeaderboardDelegateFullscreenViewer(boolean includeLiveRaces) {
setHeaderWidget(headerWidget = createPanel(lastScoringComment, hasLiveRace, scoringScheme));
if (includeLiveRaces) {
addToolbarAction(showLiveRacesControl);
} else {
contentLayout.liveRacesContainerUi.removeFromParent();
contentLayout.subHeaderUi.removeFromParent();
}
}
@Override
protected void onShow() {
super.onShow();
getContentWidget().getElement().getFirstChildElement().getStyle().clearMarginTop();
}
public void setLiveRacesPanel(Widget liveRaces) {
contentLayout.liveRacesContainerUi.setWidget(liveRaces);
}
public Image getShowLiveRacesControl() {
return showLiveRacesControl;
}
public void setShowLiveRaces(boolean show) {
if (show) {
contentLayout.subHeaderContentUi.add(headerWidget);
contentLayout.subHeaderToolbarUi.add(busyIndicator);
contentLayout.subHeaderToolbarUi.add(lastScoringUpdatePanel);
contentLayout.subHeaderToolbarUi.add(autoRefreshControl);
contentLayout.subHeaderToolbarUi.add(settingsControl);
} else {
setHeaderWidget(headerWidget);
addToolbarBusyIndicator(busyIndicator);
addToolbarInfo(lastScoringUpdatePanel);
addToolbarAction(autoRefreshControl);
addToolbarAction(settingsControl);
addToolbarAction(showLiveRacesControl);
}
contentLayout.liveRacesContainerUi.setVisible(show);
contentLayout.subHeaderUi.setVisible(show);
}
@Override
public void setLeaderboardPanel(Widget leaderboardPanel) {
contentLayout.leaderboardContainerUi.setWidget(leaderboardPanel);
showContent(content);
}
@Override
public Element getHasLiveRaceElement() {
return hasLiveRace.getElement();
}
}
@@ -0,0 +1,30 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
.liveRacesContainer {
margin-top: .5em;
}
.subHeader {
background-color: rgba(0, 0, 0, 0.9);
color: #ffffff;
}
.subHeaderContent {
padding: .5em .1em;
}
.subHeaderToolbar {
float: right;
text-align: right;
}
.leaderboardContainer {
margin-top: -10px;
}
</ui:style>
<g:FlowPanel>
<g:SimplePanel ui:field="liveRacesContainerUi" addStyleNames="{style.liveRacesContainer}"/>
<g:FlowPanel ui:field="subHeaderUi" addStyleNames="{style.subHeader}">
<g:FlowPanel ui:field="subHeaderToolbarUi" addStyleNames="{style.subHeaderToolbar}"/>
<g:FlowPanel ui:field="subHeaderContentUi" addStyleNames="{style.subHeaderContent}"/>
</g:FlowPanel>
<g:SimplePanel ui:field="leaderboardContainerUi" addStyleNames="{style.leaderboardContainer}"/>
</g:FlowPanel>
</ui:UiBinder>
@@ -240,6 +240,6 @@ public class OldMultiLeaderboard extends Composite implements SelectedLeaderboar
lastSelectedLeaderboardPanel = selectedLeaderboard;
}
public interface OldMultiLeaderboardDelegate extends LeaderboardDelegate<MultiLeaderboardProxyPanel>{
public interface OldMultiLeaderboardDelegate extends LeaderboardDelegate {
}
}
@@ -4,7 +4,7 @@
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages"/>
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.old.EventRegattaLeaderboardResources" />
<g:HTMLPanel styleName=" {local_res.css.marginFix}">
<g:HTMLPanel styleName="{res.mainCss.spacermargintopsmall}">
<div class="{res.mediaCss.grid}">
<div class="{res.mediaCss.small6} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_scoring}">
<div ui:field="lastScoringCommentDiv" class="{local_res.css.regattaleaderboard_meta_scoring_comment}"></div>
@@ -2,10 +2,9 @@ package com.sap.sailing.gwt.home.desktop.partials.old.multileaderboard;
import com.sap.sailing.gwt.home.desktop.partials.old.AbstractLeaderboardFullscreenViewer;
import com.sap.sailing.gwt.home.desktop.partials.old.multileaderboard.OldMultiLeaderboard.OldMultiLeaderboardDelegate;
import com.sap.sailing.gwt.ui.leaderboard.MultiLeaderboardProxyPanel;
public class OldMultiLeaderboardDelegateFullscreenViewer extends
AbstractLeaderboardFullscreenViewer<MultiLeaderboardProxyPanel> implements OldMultiLeaderboardDelegate {
AbstractLeaderboardFullscreenViewer implements OldMultiLeaderboardDelegate {
public OldMultiLeaderboardDelegateFullscreenViewer() {
setHeaderWidget(createPanel(lastScoringComment, scoringScheme));
@@ -57,9 +57,11 @@ public abstract class AbstractRaceList<T extends RaceMetadataDTO<? extends Abstr
protected void setTableData(Collection<T> data) {
this.ensureInitTableColumns();
this.columnSet.updateColumnVisibilities();
List<T> dataList = new ArrayList<>(data);
for (int i = 0; i < this.cellTable.getColumnCount(); i++) {
SortableRaceListColumn<T, ?> column = (SortableRaceListColumn<T, ?>) this.cellTable.getColumn(i);
column.updateHeaderAndCellStyles();
column.setRacesInNaturalOrder(dataList);
}
this.cellTable.setPageSize(data.size());
this.cellTable.setList(data);
@@ -90,8 +92,8 @@ public abstract class AbstractRaceList<T extends RaceMetadataDTO<? extends Abstr
ColumnSortList sortList = this.cellTable.getColumnSortList();
List<ColumnSortInfo> oldSortInfos;
if (sortList.size() == 0) {
boolean ascending = startTimeColumn.getPreferredSortingOrder().isAscending();
oldSortInfos = Collections.singletonList(new ColumnSortInfo(startTimeColumn, ascending));
boolean ascending = getDefaultSortColumn().getPreferredSortingOrder().isAscending();
oldSortInfos = Collections.singletonList(new ColumnSortInfo(getDefaultSortColumn(), ascending));
} else {
oldSortInfos = new ArrayList<ColumnSortList.ColumnSortInfo>(sortList.size());
for (int i = sortList.size() - 1; i >= 0; i--) {
@@ -104,6 +106,10 @@ public abstract class AbstractRaceList<T extends RaceMetadataDTO<? extends Abstr
}
}
protected SortableRaceListColumn<T, ?> getDefaultSortColumn() {
return raceNameColumn;
}
protected void add(SortableRaceListColumn<T, ?> column) {
Header<?> header = column.getHeader();
boolean ascending = column.getPreferredSortingOrder().isAscending();
@@ -133,7 +139,7 @@ public abstract class AbstractRaceList<T extends RaceMetadataDTO<? extends Abstr
}
return filterableValues;
}
public abstract boolean hasWind();
public abstract boolean hasVideos();
@@ -0,0 +1,19 @@
package com.sap.sailing.gwt.home.desktop.partials.racelist;
import com.sap.sailing.gwt.home.communication.race.RaceMetadataDTO;
public class DefaultRaceListColumnComparator<T extends RaceMetadataDTO<?>> extends RaceListColumnComparator<T, String> {
@Override
public int compare(T o1, T o2) {
return compareByNaturalOrder(o1, o2);
}
/**
* This method is unused by the overridden {@link #compare(RaceMetadataDTO, RaceMetadataDTO)} implementation; still
* it needs an implementation because the super class declares it as abstract.
*/
@Override
public String getValue(T object) {
return null;
}
}
@@ -0,0 +1,93 @@
package com.sap.sailing.gwt.home.desktop.partials.racelist;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter;
import com.sap.sailing.gwt.home.communication.race.RaceMetadataDTO;
import com.sap.sse.common.Util;
/**
* Special Comparator with following precedences:
* <ol>
* <li>The value of the column</li>
* <li>Natural order of races</li>
* <li>Race names</li>
* </ol>
*
* Following constrains apply to the sorting order:
* <ul>
* <li>If a sorting value is null/empty, it gets always sorted below the non-empty values</li>
* <li>The presendences 2) und 3) for sorting between empty values still apply</li>
* <li>Ascending and descending order is also considered for empty values</li>
* </ul>
*
* @author Vladislav Chumak
*
* @param <R>
* DTO type
* @param <S>
* type of the value to be sorted
*/
public abstract class RaceListColumnComparator<R extends RaceMetadataDTO<?>, S extends Comparable<S>>
extends InvertibleComparatorAdapter<R> {
private List<R> racesInNaturalOrder = null;
private final Map<R, Integer> positionInNaturalOrder = new HashMap<>();
@Override
public int compare(R o1, R o2) {
int compareResult;
if (getValue(o1) == getValue(o2)) {
compareResult = 0;
} else if (getValue(o1) == null) {
return isAscending() ? 1 : -1;
} else if (getValue(o2) == null) {
return isAscending() ? -1 : 1;
} else {
compareResult = getValue(o1).compareTo(getValue(o2));
}
if (compareResult == 0) {
compareResult = compareByNaturalOrder(o1, o2);
}
if (compareResult == 0) {
// RaceListRaceDTO implements Comparable which considers race names for its comparison
compareResult = o1.compareTo(o2);
}
return compareResult;
}
protected int compareByNaturalOrder(R o1, R o2) {
final int compareResult;
if (racesInNaturalOrder != null) {
getAndCacheIndexInNaturalOrder(o1);
compareResult = Util.compareToWithNull(getAndCacheIndexInNaturalOrder(o1), getAndCacheIndexInNaturalOrder(o2), /* nullIsLess */ !isAscending());
} else {
compareResult = 0;
}
return compareResult;
}
private Integer getAndCacheIndexInNaturalOrder(R o) {
Integer i = positionInNaturalOrder.get(o);
if (i == null) {
i = racesInNaturalOrder.indexOf(o);
positionInNaturalOrder.put(o, i);
}
return i;
}
public abstract S getValue(R object);
/**
* Sets the comparator to use the order of the passed {@link List} as second comparison criteria.
*
* @param racesInNaturalOrder
* The list with items which order should be considered as second comparison criteria
*/
public void setRacesInNaturalOrder(List<R> racesInNaturalOrder) {
this.racesInNaturalOrder = racesInNaturalOrder;
positionInNaturalOrder.clear();
}
}
@@ -1,6 +1,5 @@
package com.sap.sailing.gwt.home.desktop.partials.racelist;
import static com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState.TRACKED_VALID_DATA;
import static com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil.shortTimeFormatter;
import java.util.Date;
@@ -21,9 +20,7 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.cellview.client.SafeHtmlHeader;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.SortingOrder;
import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter;
import com.sap.sailing.gwt.home.communication.event.LiveRaceDTO;
import com.sap.sailing.gwt.home.communication.event.RaceListRaceDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
@@ -36,17 +33,15 @@ import com.sap.sailing.gwt.home.communication.race.wind.AbstractWindDTO;
import com.sap.sailing.gwt.home.communication.race.wind.WindStatisticsDTO;
import com.sap.sailing.gwt.home.desktop.partials.racelist.RaceListResources.LocalCss;
import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPadCell;
import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPadController;
import com.sap.sailing.gwt.home.desktop.places.event.EventView;
import com.sap.sailing.gwt.home.shared.utils.HomeSailingFlagsBuilder;
import com.sap.sailing.gwt.regattaoverview.client.FlagsMeaningExplanator;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.shared.util.NullSafeComparableComparator;
import com.sap.sailing.gwt.ui.shared.util.NullSafeComparatorWrapper;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.util.NaturalComparator;
public class RaceListColumnFactory {
@@ -81,8 +76,7 @@ public class RaceListColumnFactory {
}
}
};
InvertibleComparator<T> comparator = null;
return new SortableRaceListColumn<T, FleetMetadataDTO>("", cell, comparator) {
return new SortableRaceListColumn<T, FleetMetadataDTO>("", cell, null) {
@Override
public String getColumnStyle() {
return CSS.race_fleetcorner();
@@ -97,9 +91,9 @@ public class RaceListColumnFactory {
public static <T extends RaceMetadataDTO<?>> SortableRaceListColumn<T, String> getRegattaNameColumn() {
Cell<String> cell = new TextCell();
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, String>(new NaturalComparator(false)) {
RaceListColumnComparator<T, String> comparator = new RaceListColumnComparator<T, String>() {
@Override
protected String getComparisonValue(T object) {
public String getValue(T object) {
return object.getRegattaDisplayName();
}
};
@@ -123,13 +117,8 @@ public class RaceListColumnFactory {
public static <T extends RaceMetadataDTO<?>> SortableRaceListColumn<T, String> getRaceNameColumn() {
Cell<String> cell = new TextCell();
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, String>(new NaturalComparator(false)) {
@Override
protected String getComparisonValue(T object) {
return object.getRaceName();
}
};
return new SortableRaceListColumn<T, String>(I18N.race(), cell, comparator) {
DefaultRaceListColumnComparator<T> comparator = new DefaultRaceListColumnComparator<>();
return new SortableRaceListColumn<T, String>(I18N.race(), cell, comparator, SortingOrder.DESCENDING) {
@Override
public String getHeaderStyle() {
return CSS.raceslist_head_item();
@@ -149,9 +138,9 @@ public class RaceListColumnFactory {
public static <T extends RaceMetadataDTO<?>> SortableRaceListColumn<T, String> getFleetNameColumn() {
Cell<String> cell = new TextCell();
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, String>(new NaturalComparator(false)) {
RaceListColumnComparator<T, String> comparator = new RaceListColumnComparator<T, String>() {
@Override
protected String getComparisonValue(T object) {
public String getValue(T object) {
return object.getFleet() != null ? object.getFleet().getFleetName() : null;
}
};
@@ -174,9 +163,10 @@ public class RaceListColumnFactory {
}
public static <T extends RaceMetadataDTO<?>> SortableRaceListStartTimeColumn<T> getStartTimeColumn() {
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Date>(new NullSafeComparableComparator<Date>(true)) {
RaceListColumnComparator<T, Date> comparator = new RaceListColumnComparator<T, Date>() {
@Override
protected Date getComparisonValue(T object) {
public Date getValue(T object) {
return object.getStart();
}
};
@@ -185,9 +175,10 @@ public class RaceListColumnFactory {
public static <T extends RaceListRaceDTO> SortableRaceListColumn<T, String> getDurationColumn() {
Cell<String> cell = new TextCell();
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Duration>(new NullSafeComparableComparator<Duration>(false)) {
RaceListColumnComparator<T, Duration> comparator = new RaceListColumnComparator<T, Duration>() {
@Override
protected Duration getComparisonValue(T object) {
public Duration getValue(T object) {
return object.getDuration();
}
};
@@ -219,8 +210,7 @@ public class RaceListColumnFactory {
}
}
};
InvertibleComparator<T> comparator = null;
return new SortableRaceListColumn<T, FlagStateDTO>(I18N.flags(), cell, comparator) {
return new SortableRaceListColumn<T, FlagStateDTO>(I18N.flags(), cell, null) {
@Override
public String getHeaderStyle() {
return CSS.raceslist_head_item();
@@ -239,9 +229,9 @@ public class RaceListColumnFactory {
}
public static <T extends LiveRaceDTO> SortableRaceListColumn<T, String> getWindSpeedColumn() {
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Double>( new NullSafeComparableComparator<Double>()) {
RaceListColumnComparator<T, Double> comparator = new RaceListColumnComparator<T, Double>() {
@Override
protected Double getComparisonValue(T object) {
public Double getValue(T object) {
return object.getWind() != null ? object.getWind().getTrueWindSpeedInKnots() : null;
}
};
@@ -254,9 +244,9 @@ public class RaceListColumnFactory {
}
public static <T extends RaceListRaceDTO> SortableRaceListColumn<T, String> getWindRangeColumn() {
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Double>(new NullSafeComparableComparator<Double>()) {
RaceListColumnComparator<T, Double> comparator = new RaceListColumnComparator<T, Double>() {
@Override
protected Double getComparisonValue(T object) {
public Double getValue(T object) {
WindStatisticsDTO wind = object.getWind();
return wind != null ? (wind.getTrueUpperboundWindInKnots() + wind .getTrueLowerboundWindInKnots()) : null;
}
@@ -288,9 +278,9 @@ public class RaceListColumnFactory {
}
}
};
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Double>( new NullSafeComparableComparator<Double>()) {
RaceListColumnComparator<T, Double> comparator = new RaceListColumnComparator<T, Double>() {
@Override
protected Double getComparisonValue(T object) {
public Double getValue(T object) {
return object.getWind() != null ? object.getWind().getTrueWindFromDeg() : null;
}
};
@@ -314,9 +304,9 @@ public class RaceListColumnFactory {
public static <T extends RaceMetadataDTO<?>> SortableRaceListColumn<T, String> getCourseAreaColumn() {
Cell<String> cell = new TextCell();
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, String>(new NaturalComparator(false)) {
RaceListColumnComparator<T, String> comparator = new RaceListColumnComparator<T, String>() {
@Override
protected String getComparisonValue(T object) {
public String getValue(T object) {
return object.getCourseArea();
}
};
@@ -340,9 +330,9 @@ public class RaceListColumnFactory {
public static <T extends RaceMetadataDTO<?>> SortableRaceListColumn<T, String> getCourseColumn() {
Cell<String> cell = new TextCell();
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, String>(new NaturalComparator(false)) {
RaceListColumnComparator<T, String> comparator = new RaceListColumnComparator<T, String>() {
@Override
protected String getComparisonValue(T object) {
public String getValue(T object) {
return object.getCourse();
}
};
@@ -365,9 +355,9 @@ public class RaceListColumnFactory {
}
public static <T extends RaceListRaceDTO> SortableRaceListColumn<T, Number> getWindSourcesCountColumn() {
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Integer>(new NullSafeComparableComparator<Integer>(false)) {
RaceListColumnComparator<T, Integer> comparator = new RaceListColumnComparator<T, Integer>() {
@Override
protected Integer getComparisonValue(T object) {
public Integer getValue(T object) {
return object.getWindSourcesCount();
}
};
@@ -380,9 +370,9 @@ public class RaceListColumnFactory {
}
public static <T extends RaceListRaceDTO> SortableRaceListColumn<T, Number> getVideoCountColumn() {
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Integer>(new NullSafeComparableComparator<Integer>(false)) {
RaceListColumnComparator<T, Integer> comparator = new RaceListColumnComparator<T, Integer>() {
@Override
protected Integer getComparisonValue(T object) {
public Integer getValue(T object) {
return object.getVideoCount();
}
};
@@ -395,9 +385,9 @@ public class RaceListColumnFactory {
}
public static <T extends RaceListRaceDTO> SortableRaceListColumn<T, Number> getAudioCountColumn() {
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, Integer>(new NullSafeComparableComparator<Integer>(false)) {
RaceListColumnComparator<T, Integer> comparator = new RaceListColumnComparator<T, Integer>() {
@Override
protected Integer getComparisonValue(T object) {
public Integer getValue(T object) {
return object.getAudioCount();
}
};
@@ -428,9 +418,9 @@ public class RaceListColumnFactory {
}
}
};
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, RaceViewState>(new NullSafeComparableComparator<RaceViewState>()) {
RaceListColumnComparator<T, RaceViewState> comparator = new RaceListColumnComparator<T, RaceViewState>() {
@Override
protected RaceViewState getComparisonValue(T object) {
public RaceViewState getValue(T object) {
return object.getViewState();
}
};
@@ -463,10 +453,9 @@ public class RaceListColumnFactory {
}
}
};
InvertibleComparator<T> comparator = new InvertibleComparatorWrapper<T, String>(
new NullSafeComparatorWrapper<String>(new NaturalComparator(false), false)) {
RaceListColumnComparator<T, String> comparator = new RaceListColumnComparator<T, String>() {
@Override
protected String getComparisonValue(T object) {
public String getValue(T object) {
return object.getWinner() != null ? object.getWinner().getName() : null;
}
};
@@ -490,21 +479,19 @@ public class RaceListColumnFactory {
public static <T extends RaceMetadataDTO<?>> SortableRaceListColumn<T, T> getRaceViewerButtonColumn(
final EventView.Presenter presenter, final boolean showNotTracked) {
InvertibleComparator<T> comparator = new InvertibleComparatorAdapter<T>() {
final RaceviewerLaunchPadController lpPresenter = new RaceviewerLaunchPadController(presenter::getRaceViewerURL);
final RaceviewerLaunchPadCell<T> lpadCell = new RaceviewerLaunchPadCell<T>(lpPresenter, showNotTracked);
DefaultRaceListColumnComparator<T> comparator = new DefaultRaceListColumnComparator<T>() {
@Override
public int compare(T o1, T o2) {
if (o1.getTrackingState() == TRACKED_VALID_DATA && o2.getTrackingState() == TRACKED_VALID_DATA) {
if (o1.getViewState() == RaceViewState.FINISHED && o2.getViewState() != RaceViewState.FINISHED) {
return 1;
}
if (o1.getViewState() != RaceViewState.FINISHED && o2.getViewState() == RaceViewState.FINISHED) {
return -1;
}
int compareResult = lpPresenter.getRenderingStyle(o1).compareTo(lpPresenter.getRenderingStyle(o2));
if (compareResult == 0) {
compareResult = super.compare(o1, o2);
}
return -o1.getTrackingState().compareTo(o2.getTrackingState());
return compareResult;
}
};
return new SortableRaceListColumn<T, T>("", new RaceviewerLaunchPadCell<T>(presenter, showNotTracked), comparator) {
return new SortableRaceListColumn<T, T>("", lpadCell, comparator) {
@Override
public String getHeaderStyle() {
return getStyleNamesString(CSS.raceslist_head_item(), CSS.raceslist_head_itembutton());
@@ -523,7 +510,7 @@ public class RaceListColumnFactory {
}
public static class SortableRaceListStartTimeColumn<T extends RaceMetadataDTO<?>> extends SortableRaceListColumn<T, Date> {
protected SortableRaceListStartTimeColumn(InvertibleComparator<T> comparator) {
protected SortableRaceListStartTimeColumn(RaceListColumnComparator<T, ?> comparator) {
super(I18N.start(), new StartTimeCell(), comparator, SortingOrder.DESCENDING);
}
@@ -560,7 +547,7 @@ public class RaceListColumnFactory {
private static abstract class WindSpeedOrRangeColumn<T extends RaceMetadataDTO<?>> extends SortableRaceListColumn<T, String> {
protected WindSpeedOrRangeColumn(InvertibleComparator<T> comparator) {
protected WindSpeedOrRangeColumn(RaceListColumnComparator<T, ?> comparator) {
super(I18N.wind(), new TextCell(), comparator);
}
@@ -580,7 +567,7 @@ public class RaceListColumnFactory {
private static final SafeUri ICON_VIDEO = UriUtils.fromTrustedString("images/home/icon-video.png");
private static final SafeUri ICON_AUDIO = UriUtils.fromTrustedString("images/home/icon-audio.png");
private DataCountColumn(final SafeUri imageUri, InvertibleComparator<T> comparator) {
private DataCountColumn(final SafeUri imageUri, RaceListColumnComparator<T, ?> comparator) {
super(new SafeHtmlHeader(TEMPLATE.imageHeader(CSS.raceslist_head_itemflag(), imageUri)), new AbstractCell<Number>() {
@Override
public void render(Context context, Number value, SafeHtmlBuilder sb) {
@@ -1,5 +1,7 @@
package com.sap.sailing.gwt.home.desktop.partials.racelist;
import java.util.List;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.Header;
@@ -7,10 +9,11 @@ import com.google.gwt.user.cellview.client.SafeHtmlHeader;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.SortingOrder;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.home.communication.race.RaceMetadataDTO;
import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn;
import com.sap.sse.common.Util;
public abstract class SortableRaceListColumn<T, C> extends SortableColumn<T, C> {
public abstract class SortableRaceListColumn<T extends RaceMetadataDTO<?>, C> extends SortableColumn<T, C> {
enum ColumnVisibility {
ALWAYS {
@@ -33,28 +36,34 @@ public abstract class SortableRaceListColumn<T, C> extends SortableColumn<T, C>
}
private final Header<?> header;
private final InvertibleComparator<T> comparator;
private final RaceListColumnComparator<T, ?> comparator;
private boolean showDetails = true;
private ColumnVisibility columnVisibility = ColumnVisibility.ALWAYS;
protected SortableRaceListColumn(String headerText, Cell<C> cell, InvertibleComparator<T> comparator) {
protected SortableRaceListColumn(String headerText, Cell<C> cell, RaceListColumnComparator<T, ?> comparator) {
this(new WrappedTextHeader(headerText), cell, comparator);
}
protected SortableRaceListColumn(String headerText, Cell<C> cell, InvertibleComparator<T> comparator, SortingOrder preferredSortingOrder) {
protected SortableRaceListColumn(String headerText, Cell<C> cell, RaceListColumnComparator<T, ?> comparator, SortingOrder preferredSortingOrder) {
this(new WrappedTextHeader(headerText), cell, comparator, preferredSortingOrder);
}
private SortableRaceListColumn(Header<?> header, Cell<C> cell, InvertibleComparator<T> comparator) {
private SortableRaceListColumn(Header<?> header, Cell<C> cell, RaceListColumnComparator<T, ?> comparator) {
this(header, cell, comparator, SortingOrder.ASCENDING);
}
protected SortableRaceListColumn(Header<?> header, Cell<C> cell, InvertibleComparator<T> comparator, SortingOrder preferredSortingOrder) {
protected SortableRaceListColumn(Header<?> header, Cell<C> cell, RaceListColumnComparator<T, ?> comparator, SortingOrder preferredSortingOrder) {
super(cell, preferredSortingOrder);
this.header = header;
this.comparator = comparator;
}
public void setRacesInNaturalOrder(List<T> racesInNaturalOrder) {
if (this.comparator != null) {
this.comparator.setRacesInNaturalOrder(racesInNaturalOrder);
}
}
@Override
public final InvertibleComparator<T> getComparator() {
return comparator;
@@ -11,15 +11,8 @@ import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.sap.sailing.gwt.home.communication.race.RaceMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.desktop.places.event.EventView;
import com.sap.sailing.gwt.home.desktop.places.event.EventView.Presenter;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes;
import com.sap.sse.common.Util;
public class RaceviewerLaunchPadCell<T extends RaceMetadataDTO<?>> extends AbstractCell<T> {
@@ -45,33 +38,23 @@ public class RaceviewerLaunchPadCell<T extends RaceMetadataDTO<?>> extends Abstr
private final String liveStyleNames = Util.join(" ", analyzeStyleNames, local_res.css().raceviewerlaunchpadlive());
private final String iconStyleNames = local_res.css().raceviewerlaunchpad_icon();
private final EventView.Presenter presenter;
private final PopupPanel panel = new PopupPanel(true, false);
private final RaceviewerLaunchPadController launchPadController;
private final boolean showNotTracked;
public RaceviewerLaunchPadCell(Presenter presenter, boolean showNotTracked) {
public RaceviewerLaunchPadCell(RaceviewerLaunchPadController launchPadPresenter, boolean showNotTracked) {
super(BrowserEvents.CLICK);
this.showNotTracked = showNotTracked;
local_res.css().ensureInjected();
this.presenter = presenter;
this.launchPadController = launchPadPresenter;
}
@Override
public void onBrowserEvent(Context context, final Element parent, T data, NativeEvent event,
ValueUpdater<T> valueUpdater) {
if (!renderAsDirectLinkButton(data)) {
if (!launchPadController.renderAsDirectLink(data)) {
if (data.hasValidTrackingData() && BrowserEvents.CLICK.equals(event.getType())
&& parent.getFirstChildElement().isOrHasChild(Element.as(event.getEventTarget()))) {
panel.setWidget(createLaunchPad(parent.getFirstChildElement(), data));
panel.setPopupPositionAndShow(new PositionCallback() {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
int alignBottom = parent.getAbsoluteTop() + parent.getOffsetHeight() - offsetHeight;
int top = (alignBottom - Window.getScrollTop() < 0 ? parent.getAbsoluteTop() - 1 : alignBottom + 1);
panel.setPopupPosition(parent.getAbsoluteRight() + 1 - offsetWidth, top);
panel.getElement().scrollIntoView();
}
});
launchPadController.showRaceviewerLaunchPad(data, parent);
}
return;
}
@@ -80,38 +63,21 @@ public class RaceviewerLaunchPadCell<T extends RaceMetadataDTO<?>> extends Abstr
@Override
public void render(Context context, T data, SafeHtmlBuilder sb) {
if (data.hasValidTrackingData()) {
if (renderAsDirectLinkButton(data)) {
switch (launchPadController.getRenderingStyle(data)) {
case WATCH_LIVE_ONLY:
sb.append(TEMPLATE.standaloneButton(plannedStyleNames, iconStyleNames, I18N.watchLive(),
"launch-play",
UriUtils.fromString(presenter.getRaceViewerURL(data, RaceBoardModes.PLAYER.name()))));
} else {
String styleNames = data.isFinished() ? analyzeStyleNames : liveStyleNames;
sb.append(TEMPLATE.raceviewerLaunchPad(styleNames, iconStyleNames, I18N.raceDetailsToShow()));
}
} else if (showNotTracked) {
sb.append(TEMPLATE.raceNotTracked(notTrackedStyleNames, I18N.eventRegattaRaceNotTracked()));
} else {
sb.append(TEMPLATE.raceNotTracked(notTrackedStyleNames, ""));
"launch-play", UriUtils.fromString(launchPadController.getDirectLinkUrl(data))));
break;
case WATCH_LIVE_OR_ANALYZE:
sb.append(TEMPLATE.raceviewerLaunchPad(liveStyleNames, iconStyleNames, I18N.raceDetailsToShow()));
break;
case ANALYZE:
sb.append(TEMPLATE.raceviewerLaunchPad(analyzeStyleNames, iconStyleNames, I18N.raceDetailsToShow()));
break;
case NOT_TRACKED:
sb.append(TEMPLATE.raceNotTracked(notTrackedStyleNames, showNotTracked ? I18N.eventRegattaRaceNotTracked() : ""));
break;
}
}
/**
* Creates a {@link RaceviewerLaunchPad} for the given data object.
*/
private RaceviewerLaunchPad createLaunchPad(Element button, T data) {
RaceviewerLaunchPad launchPad = new RaceviewerLaunchPad(data, panel) {
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String raceBoardMode) {
return presenter.getRaceViewerURL(data, raceBoardMode);
}
};
return launchPad;
}
/** Determine whether a direct link button should be/is rendered instead of a menu popup. */
private boolean renderAsDirectLinkButton(T data) {
return !data.isFinished() && !data.isRunning();
}
}
@@ -0,0 +1,126 @@
package com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad;
import java.util.function.BiFunction;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes;
public class RaceviewerLaunchPadController {
public static enum RenderingStyle {
WATCH_LIVE_ONLY, WATCH_LIVE_OR_ANALYZE, ANALYZE, NOT_TRACKED
}
private final PopupPanel panel = new PopupPanel(true, false);
private final BiFunction<SimpleRaceMetadataDTO, String, String> raceboardUrlFactory;
/**
* Creates a new {@link RaceviewerLaunchPadController} instance with the given URL factory.
*
* @param raceviewerUrlFactory
* {@link BiFunction Factory method} to get the raceboard URL for given {@link SimpleRaceMetadataDTO
* race} and {@link RaceBoardModes raceboard mode} information
*/
public RaceviewerLaunchPadController(BiFunction<SimpleRaceMetadataDTO, String, String> raceboardUrlFactory) {
this.raceboardUrlFactory = raceboardUrlFactory;
}
/**
* Provides the direct link URL for the given {@link SimpleRaceMetadataDTO race}.
*
* @param data
* the {@link SimpleRaceMetadataDTO race} to get the link URL for
* @return the direct link URL (as {@link String})
*/
public <T extends SimpleRaceMetadataDTO> String getDirectLinkUrl(T data) {
return raceboardUrlFactory.apply(data, RaceBoardModes.PLAYER.name());
}
/**
* Creates a {@link RaceviewerLaunchPad} for the given {@link SimpleRaceMetadataDTO race} and {@link PopupPanel}
* instance.
*
* @param data
* the {@link SimpleRaceMetadataDTO race} to create a launch pad for
* @param parent
* the {@link PopupPanel} which will contain the launch pad
* @return the newly created {@link RaceviewerLaunchPad} instance
*/
public <T extends SimpleRaceMetadataDTO> RaceviewerLaunchPad getRaceviewerLaunchPad(T data, PopupPanel parent) {
return new RaceviewerLaunchPad(data, parent) {
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String raceBoardMode) {
return raceboardUrlFactory.apply(data, raceBoardMode);
}
};
}
/**
* Shows a {@link RaceviewerLaunchPad} for the given {@link SimpleRaceMetadataDTO race} relative to the also
* provided element.
*
* @param data
* the {@link SimpleRaceMetadataDTO race} to create a launch pad for
* @param relTo
* the {@link Element element} to show the launch pad relative to
*/
public <T extends SimpleRaceMetadataDTO> void showRaceviewerLaunchPad(T data, Element relTo) {
relTo.scrollIntoView();
panel.setWidget(new RaceviewerLaunchPad(data, panel) {
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String raceBoardMode) {
return raceboardUrlFactory.apply(data, raceBoardMode);
}
});
panel.setVisible(false);
panel.show();
Scheduler.get().scheduleDeferred(() -> {
Widget panelContent = panel.getWidget();
int alignRight = relTo.getAbsoluteLeft() + relTo.getOffsetWidth() - panelContent.getOffsetWidth();
int left = (alignRight - Window.getScrollLeft() < 0 ? relTo.getAbsoluteLeft() - 1 : alignRight + 1);
int alignBottom = relTo.getAbsoluteTop() + relTo.getOffsetHeight() - panelContent.getOffsetHeight();
int top = (alignBottom - Window.getScrollTop() < 0 ? relTo.getAbsoluteTop() - 1 : alignBottom + 1);
panel.setPopupPosition(left, top);
panel.setVisible(true);
});
}
/**
* Determine whether a direct link should be/is rendered for the given {@link SimpleRaceMetadataDTO race} instead of
* a menu popup.
*
* @param data
* the {@link SimpleRaceMetadataDTO race} which should be/is rendered
* @return <code>true</code> if a direct link should be/is rendered, <code>false</code> otherwise
*/
public <T extends SimpleRaceMetadataDTO> boolean renderAsDirectLink(T data) {
return !data.isRunning() && !data.isFinished();
}
/**
* Calculates the {@link RenderingStyle} to use for the given {@link SimpleRaceMetadataDTO race}.
*
* @param data
* the {@link SimpleRaceMetadataDTO race} to get the {@link RenderingStyle} for
* @return the calculated {@link RenderingStyle}
*/
public <T extends SimpleRaceMetadataDTO> RenderingStyle getRenderingStyle(T data) {
final RenderingStyle result;
if (data.hasValidTrackingData()) {
if (renderAsDirectLink(data)) {
result = RenderingStyle.WATCH_LIVE_ONLY;
} else {
result = data.isFinished() ? RenderingStyle.ANALYZE : RenderingStyle.WATCH_LIVE_OR_ANALYZE;
}
} else {
result = RenderingStyle.NOT_TRACKED;
}
return result;
}
}
@@ -95,7 +95,10 @@
<li>
<ui:text from='{i18n.contentSapInSailing12}' />
</li>
</ul>
<li>
<ui:text from='{i18n.contentSapInSailing13}' />
</li>
</ul>
</div>
</div>
</div>
@@ -89,8 +89,7 @@ public class StandingsList extends Widget implements RefreshableWidget<GetMiniLe
}
if (data.getLastScoreUpdate() != null) {
DivElement lastUpdateElement = Document.get().createDivElement();
String lastUpdate = DateAndTimeFormatterUtil.longDateFormatter.render(data.getLastScoreUpdate()) + " "
+ DateAndTimeFormatterUtil.formatElapsedTime(data.getLastScoreUpdate().getTime());
String lastUpdate = DateAndTimeFormatterUtil.formatLongDateAndTimeGMT(data.getLastScoreUpdate());
lastUpdateElement.setInnerText(StringMessages.INSTANCE.lastScoreUpdate() + ": " + lastUpdate);
scoreInformationUi.appendChild(lastUpdateElement);
}
@@ -1,16 +1,23 @@
package com.sap.sailing.gwt.home.desktop.places.event.regatta.leaderboardtab;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.common.client.controls.tabbar.TabView;
import com.sap.sailing.gwt.home.communication.event.EventState;
import com.sap.sailing.gwt.home.communication.event.GetLiveRacesForRegattaAction;
import com.sap.sailing.gwt.home.communication.event.LiveRaceDTO;
import com.sap.sailing.gwt.home.desktop.partials.liveraces.LiveRacesList;
import com.sap.sailing.gwt.home.desktop.partials.old.leaderboard.OldLeaderboard;
import com.sap.sailing.gwt.home.desktop.partials.old.leaderboard.OldLeaderboardDelegateFullscreenViewer;
import com.sap.sailing.gwt.home.desktop.places.Consumer;
@@ -19,32 +26,40 @@ import com.sap.sailing.gwt.home.desktop.places.event.regatta.EventRegattaView.Pr
import com.sap.sailing.gwt.home.desktop.places.event.regatta.RegattaAnalyticsDataManager;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.SharedLeaderboardRegattaTabView;
import com.sap.sailing.gwt.home.shared.partials.placeholder.InfoPlaceholder;
import com.sap.sailing.gwt.home.shared.refresh.RefreshManager;
import com.sap.sailing.gwt.home.shared.refresh.RefreshManagerWithErrorAndBusy;
import com.sap.sailing.gwt.home.shared.refresh.RefreshableWidget;
import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateProvider;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sse.gwt.client.shared.perspective.DefaultOnSettingsLoadedCallback;
import com.sap.sse.gwt.dispatch.shared.commands.CollectionResult;
/**
* Created by pgtaboada on 25.11.14.
*/
public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView<RegattaLeaderboardPlace> {
interface MyBinder extends UiBinder<HTMLPanel, RegattaLeaderboardTabView> {
interface MyBinder extends UiBinder<FlowPanel, RegattaLeaderboardTabView> {
}
interface Style extends CssResource {
String showLiveRacesActive();
}
private static MyBinder ourUiBinder = GWT.create(MyBinder.class);
private Presenter currentPresenter;
@UiField(provided = true)
protected OldLeaderboard leaderboard;
@UiField Style style;
@UiField FlowPanel buttonContainer;
@UiField FlowPanel liveRacesContainer;
@UiField(provided = true) LiveRacesList liveRaces;
@UiField(provided = true) protected OldLeaderboard leaderboard;
private LeaderboardUpdateProvider leaderboardUpdateProvider = null;
private OldLeaderboardAndLiveRacesPresenter leaderboardAndLiveRacesPresenter;
public RegattaLeaderboardTabView() {
leaderboard = new OldLeaderboard(new OldLeaderboardDelegateFullscreenViewer());
}
@Override
public Class<RegattaLeaderboardPlace> getPlaceClassForActivation() {
return RegattaLeaderboardPlace.class;
@@ -73,37 +88,53 @@ public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView<R
if (regattaId != null && !regattaId.isEmpty()) {
String leaderboardName = regattaId;
RegattaAnalyticsDataManager regattaAnalyticsManager = currentPresenter.getCtx().getRegattaAnalyticsManager();
LeaderboardPanel leaderboardPanel = regattaAnalyticsManager.getLeaderboardPanel();
LeaderboardPanel leaderboardPanel = regattaAnalyticsManager.getLeaderboardPanel();
final Consumer<LeaderboardPanel> leaderboardConsumer = new Consumer<LeaderboardPanel>() {
@Override
public void consume(LeaderboardPanel leaderboardPanel) {
leaderboardUpdateProvider = leaderboardPanel;
leaderboardUpdateProvider.addLeaderboardUpdateListener(RegattaLeaderboardTabView.this);
OldLeaderboardDelegateFullscreenViewer leaderboardDelegate = new OldLeaderboardDelegateFullscreenViewer();
leaderboard = new OldLeaderboard(leaderboardDelegate);
liveRaces = new LiveRacesList(currentPresenter, false);
initWidget(ourUiBinder.createAndBindUi(RegattaLeaderboardTabView.this));
style.ensureInjected();
leaderboardAndLiveRacesPresenter = new OldLeaderboardAndLiveRacesPresenter(leaderboard, liveRaces,
buttonContainer, liveRacesContainer, leaderboardDelegate);
RefreshManager refreshManager = new RefreshManagerWithErrorAndBusy(RegattaLeaderboardTabView.this,
contentArea, currentPresenter.getDispatch(),
currentPresenter.getErrorAndBusyClientFactory());
refreshManager.add(leaderboardAndLiveRacesPresenter.getLiveRacesRefreshableWrapper(),
new GetLiveRacesForRegattaAction(currentPresenter.getEventDTO().getId(), regattaId));
leaderboard.setLeaderboard(leaderboardPanel, currentPresenter.getAutoRefreshTimer());
if (currentPresenter.getEventDTO().getState() == EventState.RUNNING) {
// TODO: start autorefresh?
}
regattaAnalyticsManager.hideCompetitorChart();
contentArea.setWidget(RegattaLeaderboardTabView.this);
if(leaderboardPanel.getLeaderboard() != null) {
if (leaderboardPanel.getLeaderboard() != null) {
leaderboard.updatedLeaderboard(leaderboardPanel.getLeaderboard());
}
}
};
if(leaderboardPanel == null) {
createSharedLeaderboardPanel(leaderboardName, regattaAnalyticsManager, currentPresenter.getUserService(), /*FIXME placeToken */ null, leaderboardConsumer);
} else if( /*FIXME placeToken not empty */ false) {
createLeaderboardComponentContext(leaderboardName, currentPresenter.getUserService(), /*FIXME placeToken */ null).initInitialSettings(new DefaultOnSettingsLoadedCallback<LeaderboardSettings>() {
@Override
public void onSuccess(LeaderboardSettings settings) {
leaderboardPanel.updateSettings(settings);
leaderboardConsumer.consume(leaderboardPanel);
}
});
if (leaderboardPanel == null) {
createSharedLeaderboardPanel(leaderboardName, regattaAnalyticsManager,
currentPresenter.getUserService(), /* FIXME placeToken */ null, leaderboardConsumer);
} else if ( /* FIXME placeToken not empty */ false) {
createLeaderboardComponentContext(leaderboardName, currentPresenter.getUserService(),
/* FIXME placeToken */ null)
.initInitialSettings(new DefaultOnSettingsLoadedCallback<LeaderboardSettings>() {
@Override
public void onSuccess(LeaderboardSettings settings) {
leaderboardPanel.updateSettings(settings);
leaderboardConsumer.consume(leaderboardPanel);
}
});
} else {
leaderboardConsumer.consume(leaderboardPanel);
}
} else {
contentArea.setWidget(new Label("No leaderboard specified, cannot proceed to leaderboardpage"));
new com.google.gwt.user.client.Timer() {
@@ -140,4 +171,73 @@ public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView<R
leaderboardUpdateProvider.removeLeaderboardUpdateListener(this);
}
}
@UiHandler("collapseLiveRacesButton")
void onCollapseLiveRacesButtonClicked(ClickEvent event) {
leaderboardAndLiveRacesPresenter.setShowLiveRaces(false);
}
@UiHandler("fullscreenButton")
void onFullscreenButtonClicked(ClickEvent event) {
leaderboard.getFullscreenControl().fireEvent(event);
}
private class OldLeaderboardAndLiveRacesPresenter {
private final OldLeaderboard leaderboard;
private final LiveRacesList liveRaces;
private final FlowPanel buttonContainer, liveRacesContainer;
private final OldLeaderboardDelegateFullscreenViewer leaderboardDelegate;
boolean show = false, available = false;
private OldLeaderboardAndLiveRacesPresenter(OldLeaderboard leaderboard, LiveRacesList liveRaces,
FlowPanel buttonContainer, FlowPanel liveRacesContainer,
OldLeaderboardDelegateFullscreenViewer leaderboardDelegate) {
this.leaderboard = leaderboard;
this.liveRaces = liveRaces;
this.buttonContainer = buttonContainer;
this.liveRacesContainer = liveRacesContainer;
this.leaderboardDelegate = leaderboardDelegate;
ClickHandler showLivesRacesClickHandler = event -> setShowLiveRaces(!show);
this.leaderboard.getShowLiveRacesControl().addClickHandler(showLivesRacesClickHandler);
this.leaderboardDelegate.getShowLiveRacesControl().addClickHandler(showLivesRacesClickHandler);
this.leaderboardDelegate.getShowLiveRacesControl().setTitle(StringMessages.INSTANCE.showLiveNow());
this.leaderboardDelegate.addCloseHandler(event -> {
liveRaces.removeFromParent();
liveRacesContainer.add(liveRaces);
});
this.leaderboard.getFullscreenControl().addClickHandler(event -> {
liveRaces.removeFromParent();
leaderboardDelegate.setLiveRacesPanel(liveRaces);
});
this.setShowLiveRaces(false);
}
private RefreshableWidget<CollectionResult<LiveRaceDTO>> getLiveRacesRefreshableWrapper() {
return new RefreshableWidget<CollectionResult<LiveRaceDTO>>() {
@Override
public void setData(CollectionResult<LiveRaceDTO> data) {
available = data != null && !data.getValues().isEmpty();
OldLeaderboardAndLiveRacesPresenter.this.updateLiveRaces();
liveRaces.getRefreshable().setData(data);
}
};
}
private void setShowLiveRaces(boolean show) {
this.show = show;
leaderboard.getShowLiveRacesControl().setStyleName(style.showLiveRacesActive(), show);
leaderboardDelegate.getShowLiveRacesControl().setStyleName(style.showLiveRacesActive(), show);
this.updateLiveRaces();
}
private void updateLiveRaces() {
leaderboard.getShowLiveRacesControl().setVisible(!available || !show);
leaderboard.getFullscreenControl().setVisible(!available || !show);
buttonContainer.setVisible(available && show);
liveRacesContainer.setVisible(available && show);
leaderboardDelegate.setShowLiveRaces(available && show);
}
}
}
@@ -1,11 +1,32 @@
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:dp="urn:import:com.sap.sailing.gwt.home.desktop.partials">
<ui:style>
.outerBox {
padding-top: 10px;
}
xmlns:dp="urn:import:com.sap.sailing.gwt.home.desktop.partials">
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages"/>
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="lb_res" type="com.sap.sailing.gwt.home.desktop.partials.old.EventRegattaLeaderboardResources" />
<ui:style type="com.sap.sailing.gwt.home.desktop.places.event.regatta.leaderboardtab.RegattaLeaderboardTabView.Style">
.mainPanel {
position: relative;
}
.buttons {
text-align: right;
position: absolute;
}
.showLiveRacesActive {
background-color: red;
}
</ui:style>
<g:HTMLPanel styleName="{style.outerBox}">
<dp:old.leaderboard.OldLeaderboard ui:field='leaderboard'/>
</g:HTMLPanel>
<g:FlowPanel addStyleNames="{res.mediaCss.grid} {style.mainPanel}">
<g:FlowPanel ui:field="liveRacesContainer" addStyleNames="{res.mediaCss.column} {res.mediaCss.small12} {res.mainCss.spacermargintopmedium}">
<dp:liveraces.LiveRacesList ui:field="liveRaces" />
</g:FlowPanel>
<g:FlowPanel ui:field="buttonContainer" addStyleNames="{res.mediaCss.column} {res.mediaCss.small12} {res.mainCss.spacermargintopsmall} {style.buttons}">
<g:Anchor href="javascript:;" ui:field="collapseLiveRacesButton" title="{i18n.showLiveNow}"
addStyleNames="{res.mainCss.button} {lb_res.css.regattaleaderboard_meta_liveraces_button} {style.showLiveRacesActive}" />
<g:Anchor href="javascript:;" ui:field="fullscreenButton" title="{i18n.openFullscreenView}"
addStyleNames="{res.mainCss.button} {lb_res.css.regattaleaderboard_meta_fullscreen_button}" />
</g:FlowPanel>
<g:FlowPanel styleName="{res.mediaCss.column} {res.mediaCss.small12}">
<dp:old.leaderboard.OldLeaderboard ui:field='leaderboard'/>
</g:FlowPanel>
</g:FlowPanel>
</ui:UiBinder>
@@ -36,7 +36,8 @@ public class EventSeriesOverallLeaderboardTabView extends SharedLeaderboardEvent
protected OldLeaderboard leaderboard;
public EventSeriesOverallLeaderboardTabView() {
leaderboard = new OldLeaderboard(new OldLeaderboardDelegateFullscreenViewer());
leaderboard = new OldLeaderboard(new OldLeaderboardDelegateFullscreenViewer(false));
leaderboard.getShowLiveRacesControl().removeFromParent();
}
@Override
@@ -105,8 +105,7 @@ public class MinileaderboardBox extends Composite implements RefreshableWidget<G
scoreInformation.add(new Label(data.getScoreCorrectionText()));
}
if (data.getLastScoreUpdate() != null) {
String lastUpdate = DateAndTimeFormatterUtil.longDateFormatter.render(data.getLastScoreUpdate()) + " "
+ DateAndTimeFormatterUtil.formatElapsedTime(data.getLastScoreUpdate().getTime());
String lastUpdate = DateAndTimeFormatterUtil.formatLongDateAndTimeGMT(data.getLastScoreUpdate());
scoreInformation.add(new Label(StringMessages.INSTANCE.lastScoreUpdate() + ": " + lastUpdate));
}
scoreInformation.getElement().getStyle().setBackgroundColor("#f2f2f2");
@@ -67,7 +67,10 @@
<li>
<ui:text from='{i18n.contentSapInSailing12}' />
</li>
</ul>
<li>
<ui:text from='{i18n.contentSapInSailing13}' />
</li>
</ul>
</div>
</div>
<div ui:field="sapSailingAnalyticsUi"
@@ -108,11 +108,17 @@ public final class EventActionUtil {
return calculateTtlForNonLiveEvent(event, eventState);
}
public static <T extends DTO> ResultWithTTL<T> withLiveRaceOrDefaultSchedule(SailingDispatchContext context, UUID eventId, CalculationWithEvent<T> callback) {
public static <T extends DTO> ResultWithTTL<T> withLiveRaceOrDefaultSchedule(SailingDispatchContext context,
UUID eventId, CalculationWithEvent<T> callback) {
return withLiveRaceOrDefaultSchedule(context, eventId, callback, null);
}
public static <T extends DTO> ResultWithTTL<T> withLiveRaceOrDefaultSchedule(SailingDispatchContext context,
UUID eventId, CalculationWithEvent<T> callback, T defaultResult) {
Event event = context.getRacingEventService().getEvent(eventId);
EventState eventState = HomeServiceUtil.calculateEventState(event);
if (eventState != EventState.RUNNING) {
return new ResultWithTTL<T>(calculateTtlForNonLiveEvent(event, eventState), null);
if (eventState == EventState.FINISHED) {
return new ResultWithTTL<T>(calculateTtlForNonLiveEvent(event, eventState), defaultResult);
}
return callback.calculateWithEvent(event);
}
@@ -74,7 +74,7 @@ public class RaceCompetitionFormatDataCalculator implements RaceCallback {
}
private class SeriesFleetData {
private final Set<SimpleRaceMetadataDTO> races = new HashSet<>();
private final Set<SimpleRaceMetadataDTO> races = new LinkedHashSet<>();
private final Set<SimpleCompetitorDTO> competitors = new HashSet<>();
}
@@ -48,6 +48,7 @@ import com.sap.sailing.domain.leaderboard.ScoreCorrection;
import com.sap.sailing.domain.tracking.RaceLogWindFixDeclinationHelper;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.gwt.home.communication.event.EventState;
import com.sap.sailing.gwt.home.communication.event.LiveRaceDTO;
import com.sap.sailing.gwt.home.communication.event.RaceListRaceDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
@@ -475,12 +476,12 @@ public class RaceContext {
// TODO do not calculate the winner if the blue flag is currently shown.
try {
TimePoint finishTime = getLiveTimePoint();
List<Competitor> competitors = leaderboard.getCompetitorsFromBestToWorst(raceColumn, finishTime);
if (competitors == null || competitors.isEmpty()) {
Iterable<Competitor> competitors = leaderboard.getCompetitorsFromBestToWorst(raceColumn, finishTime);
if (competitors == null || Util.isEmpty(competitors)) {
return null;
}
if (Util.size(raceColumn.getFleets()) == 1) {
return new SimpleCompetitorDTO(competitors.get(0));
return new SimpleCompetitorDTO(competitors.iterator().next());
}
for (Competitor competitor : competitors) {
if (isCompetitorInFleet(competitor)) {
@@ -522,6 +523,9 @@ public class RaceContext {
}
public boolean isLiveOrOfPublicInterest() {
if (HomeServiceUtil.calculateEventState(event) == EventState.FINISHED) {
return false;
}
boolean isLive = false;
boolean isOfPublicInterest = false;
if (trackedRace != null) {
@@ -1,7 +1,8 @@
package com.sap.sailing.gwt.home.server;
import java.util.Collection;
import java.util.TreeSet;
import java.util.LinkedHashSet;
import java.util.Set;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.gwt.home.communication.event.RaceListRaceDTO;
@@ -13,12 +14,12 @@ import com.sap.sailing.gwt.home.server.EventActionUtil.RaceCallback;
*/
@GwtIncompatible
public class RaceListDataCalculator implements RaceCallback {
private final TreeSet<RaceListRaceDTO> races = new TreeSet<>();
private final Set<RaceListRaceDTO> races = new LinkedHashSet<>();
@Override
public void doForRace(RaceContext context) {
RaceListRaceDTO finishedRace = context.getFinishedRaceOrNull();
if(finishedRace != null) {
if (finishedRace != null) {
races.add(finishedRace);
}
}
@@ -14,7 +14,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
* TTL} can be used for as refresh interval of "Live races" sections.
*/
@GwtIncompatible
final class RaceRefreshCalculator implements RaceCallback {
public final class RaceRefreshCalculator implements RaceCallback {
private long ttl = Duration.ONE_MINUTE.times(3).asMillis();
@Override
@@ -6,34 +6,28 @@ import static com.sap.sse.common.impl.MillisecondsTimePoint.now;
import java.util.Date;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.text.client.DateTimeFormatRenderer;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.client.DateUtil;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState;
import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPad;
import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPadController;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionRaceView;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes;
public abstract class AbstractRegattaCompetitionFleetRace extends Widget implements RegattaCompetitionRaceView {
private static final StringMessages I18N = StringMessages.INSTANCE;
private final RaceviewerLaunchPadController launchPadController;
protected final Element mainElement;
private final SimpleRaceMetadataDTO race;
private final RegattaCompetitionPresenter presenter;
private final PopupPanel panel = new PopupPanel(true, false);
protected AbstractRegattaCompetitionFleetRace(final SimpleRaceMetadataDTO race,
RegattaCompetitionPresenter presenter) {
this.race = race;
this.presenter = presenter;
this.launchPadController = new RaceviewerLaunchPadController(presenter::getRaceViewerURL);
this.mainElement = getMainUiElement();
setupRaceState(race);
getRaceNameUiElement().setInnerText(race.getRaceName());
@@ -52,37 +46,16 @@ public abstract class AbstractRegattaCompetitionFleetRace extends Widget impleme
}
// If rendered as direct link button, open link in new tab directly instead of showing the menu popup
if (!race.isFinished() && !race.isRunning()) {
Window.open(presenter.getRaceViewerURL(race, RaceBoardModes.PLAYER.name()), "_blank", "");
if (launchPadController.renderAsDirectLink(race)) {
Window.open(launchPadController.getDirectLinkUrl(race), "_blank", "");
return;
}
// Show menu popup relative to button by ensuring it stays with the viewport
this.getElement().scrollIntoView();
panel.setWidget(new RaceviewerLaunchPad(race, panel) {
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String mode) {
return presenter.getRaceViewerURL(data, mode);
}
});
panel.setVisible(false);
panel.show();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
Widget button = AbstractRegattaCompetitionFleetRace.this, panelContent = panel.getWidget();
int alignRight = button.getAbsoluteLeft() + button.getOffsetWidth() - panelContent.getOffsetWidth();
int left = (alignRight - Window.getScrollLeft() < 0 ? button.getAbsoluteLeft() - 1 : alignRight + 1);
int alignBottom = button.getAbsoluteTop() + button.getOffsetHeight() - panelContent.getOffsetHeight();
int top = (alignBottom - Window.getScrollTop() < 0 ? button.getAbsoluteTop() - 1 : alignBottom + 1);
panel.setPopupPosition(left, top);
panel.setVisible(true);
}
});
launchPadController.showRaceviewerLaunchPad(race, this.getElement());
}
private void setupRaceState(SimpleRaceMetadataDTO race) {
boolean isUntrackedRace = race.getTrackingState() != RaceTrackingState.TRACKED_VALID_DATA;
final boolean isUntrackedRace = !race.hasValidTrackingData();
if (race.isRunning()) {
mainElement.addClassName(getRaceLiveStyleName());
getRaceStateUiElement().setInnerText(isUntrackedRace ? I18N.live() : I18N.actionWatch());
@@ -62,7 +62,8 @@ public class LeaderboardPanelLifecycle
currentLeaderboardSettings.isShowAddedScores(),
currentLeaderboardSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
currentLeaderboardSettings.isShowCompetitorSailIdColumn(),
currentLeaderboardSettings.isShowCompetitorFullNameColumn());
currentLeaderboardSettings.isShowCompetitorFullNameColumn(),
currentLeaderboardSettings.isShowCompetitorNationality());
globalLeaderboardSettings = LeaderboardSettingsFactory.getInstance().keepDefaults(currentLeaderboardSettings,
globalLeaderboardSettings);
return globalLeaderboardSettings;
@@ -54,6 +54,7 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
private boolean autoExpandPreSelectedRace = false;
private LongSetting delayBetweenAutoAdvancesInMilliseconds;
private BooleanSetting updateUponPlayStateChange;
private BooleanSetting isShowCompetitorNationality;
/**
* There are two ways to select race columns.
@@ -85,6 +86,7 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
@Override
protected void addChildSettings() {
isShowCompetitorNationality = new BooleanSetting("showCompetitorNationality", this, false);
namesOfRaceColumnsToShow = new StringListSetting("namesOfRaceColumnsToShow", this);
namesOfRacesToShow = new StringListSetting("namesOfRacesToShow", this, null);
numberOfLastRacesToShow = new IntegerSetting("numberOfLastRacesToShow", this, null);
@@ -155,7 +157,8 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
boolean autoExpandPreSelectedRace, Long delayBetweenAutoAdvancesInMilliseconds, String nameOfRaceToSort,
boolean sortAscending, boolean updateUponPlayStateChange, RaceColumnSelectionStrategies activeRaceColumnSelectionStrategy,
boolean showAddedScores, boolean showOverallColumnWithNumberOfRacesCompletedPerCompetitor,
boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn) {
boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn,
boolean isCompetitorNationalityColumnVisible) {
if (namesOfRacesToShow != null && namesOfRaceColumnsToShow != null) {
throw new IllegalArgumentException("You can identify races either only by their race or by their column names, not both");
}
@@ -176,6 +179,7 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
this.showCompetitorSailIdColumn.setValue(showCompetitorSailIdColumn);
this.showCompetitorFullNameColumn.setValue(showCompetitorFullNameColumn);
this.showOverallColumnWithNumberOfRacesCompletedPerCompetitor.setValue(showOverallColumnWithNumberOfRacesCompletedPerCompetitor);
this.isShowCompetitorNationality.setValue(isCompetitorNationalityColumnVisible);
}
/**
@@ -280,6 +284,10 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
return showCompetitorFullNameColumn.getValue();
}
public boolean isShowCompetitorNationality() {
return isShowCompetitorNationality.getValue();
}
void overrideDefaultValues(LeaderboardSettings newDefaults) {
autoExpandPreSelectedRace = newDefaults.autoExpandPreSelectedRace;
boolean namesOfRaceColumnsToShowWasDefault = Util.equals(namesOfRaceColumnsToShow.getValues(), namesOfRaceColumnsToShow.getDefaultValues());
@@ -306,6 +314,7 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
showCompetitorSailIdColumn.setDefaultValue(newDefaults.showCompetitorSailIdColumn.getValue());
showCompetitorFullNameColumn.setDefaultValue(newDefaults.showCompetitorFullNameColumn.getValue());
showOverallColumnWithNumberOfRacesCompletedPerCompetitor.setDefaultValue(newDefaults.showOverallColumnWithNumberOfRacesCompletedPerCompetitor.getValue());
isShowCompetitorNationality.setDefaultValue(newDefaults.isShowCompetitorNationality.getValue());
}
void setValues(LeaderboardSettings settingsWithCustomValues) {
@@ -325,6 +334,7 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
this.showAddedScores.setValue(settingsWithCustomValues.isShowAddedScores());
this.showCompetitorSailIdColumn.setValue(settingsWithCustomValues.isShowCompetitorSailIdColumn());
this.showCompetitorFullNameColumn.setValue(settingsWithCustomValues.isShowCompetitorFullNameColumn());
this.isShowCompetitorNationality.setValue(settingsWithCustomValues.isShowCompetitorNationality());
this.showOverallColumnWithNumberOfRacesCompletedPerCompetitor.setValue(settingsWithCustomValues.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor());
}
@@ -347,6 +357,7 @@ public class LeaderboardSettings extends AbstractGenericSerializableSettings {
leaderboardSettings.showCompetitorSailIdColumn.setDefaultValue(showCompetitorSailIdColumn.getDefaultValue());
leaderboardSettings.showCompetitorFullNameColumn.setDefaultValue(showCompetitorFullNameColumn.getDefaultValue());
leaderboardSettings.showOverallColumnWithNumberOfRacesCompletedPerCompetitor.setDefaultValue(showOverallColumnWithNumberOfRacesCompletedPerCompetitor.getDefaultValue());
leaderboardSettings.isShowCompetitorNationality.setDefaultValue(isShowCompetitorNationality.getDefaultValue());
return leaderboardSettings;
}
@@ -52,6 +52,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone
private CheckBox showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox;
private CheckBox showCompetitorSailIdColumnheckBox;
private CheckBox showCompetitorFullNameColumnCheckBox;
private CheckBox isCompetitorNationalityColumnVisible;
private LeaderboardSettings initialSettings;
public LeaderboardSettingsDialogComponent(LeaderboardSettings initialSettings, List<String> allRaceColumnNames, StringMessages stringMessages) {
@@ -193,13 +194,23 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone
dialog.addTooltip(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox, stringMessages.showNumberOfRacesScored());
showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.setValue(initialSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor());
overallDetailDialogContent.add(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox);
FlowPanel overallDetailDialogContentSecondLine = new FlowPanel();
overallDetailDialogContentSecondLine.addStyleName("dialogInnerContent");
showCompetitorSailIdColumnheckBox = dialog.createCheckbox(stringMessages.showCompetitorSailIdColumn());
showCompetitorSailIdColumnheckBox.setTitle(stringMessages.showCompetitorSailIdColumnTooltip(stringMessages.alwaysShowCompetitorNationalityColumn()));
showCompetitorSailIdColumnheckBox.setValue(initialSettings.isShowCompetitorSailIdColumn());
overallDetailDialogContent.add(showCompetitorSailIdColumnheckBox);
overallDetailDialogContentSecondLine.add(showCompetitorSailIdColumnheckBox);
isCompetitorNationalityColumnVisible = dialog.createCheckbox(stringMessages.alwaysShowCompetitorNationalityColumn());
isCompetitorNationalityColumnVisible.setTitle(stringMessages.alwaysShowCompetitorNationalityColumnTooltip());
isCompetitorNationalityColumnVisible.setValue(initialSettings.isShowCompetitorNationality());
overallDetailDialogContentSecondLine.add(isCompetitorNationalityColumnVisible);
showCompetitorFullNameColumnCheckBox = dialog.createCheckbox(stringMessages.showCompetitorFullNameColumn());
showCompetitorFullNameColumnCheckBox.setValue(initialSettings.isShowCompetitorFullNameColumn());
overallDetailDialogContent.add(showCompetitorFullNameColumnCheckBox);
overallDetailDialogContentSecondLine.add(showCompetitorFullNameColumnCheckBox);
overallDetailDialog.add(overallDetailDialogContent);
overallDetailDialog.add(overallDetailDialogContentSecondLine);
return overallDetailDialog;
}
@@ -389,7 +400,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone
true, /* updateUponPlayStateChange */ true, activeRaceColumnSelectionStrategy,
/*showAddedScores*/ showAddedScoresCheckBox.getValue().booleanValue(),
/*showOverallColumnWithNumberOfRacesSailedPerCompetitor*/ showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.getValue().booleanValue(),
showCompetitorSailIdColumnheckBox.getValue(), showCompetitorFullNameColumnCheckBox.getValue());
showCompetitorSailIdColumnheckBox.getValue(), showCompetitorFullNameColumnCheckBox.getValue(),
isCompetitorNationalityColumnVisible.getValue());
return LeaderboardSettingsFactory.getInstance().keepDefaults(initialSettings, newSettings);
}
@@ -83,7 +83,7 @@ public class LeaderboardSettingsFactory {
/* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort,
/* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelectionType,
/*showAddedScores*/ false, /*showOverallRacesCompleted*/ false,
showCompetitorSailIdColumn, showCompetitorNameColumn);
showCompetitorSailIdColumn, showCompetitorNameColumn, false);
break;
case Replay:
settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */
@@ -172,7 +172,8 @@ public class LeaderboardSettingsFactory {
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
existingSettings.isShowCompetitorFullNameColumn(),
existingSettings.isShowCompetitorNationality());
final LeaderboardSettings existingDefaultSettings = existingSettings.getDefaultSettings();
@@ -190,7 +191,8 @@ public class LeaderboardSettingsFactory {
existingDefaultSettings.isShowAddedScores(),
existingDefaultSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingDefaultSettings.isShowCompetitorSailIdColumn(),
existingDefaultSettings.isShowCompetitorFullNameColumn());
existingDefaultSettings.isShowCompetitorFullNameColumn(),
existingSettings.isShowCompetitorNationality());
newDefaultSettings = LeaderboardSettingsFactory.getInstance().createNewSettingsWithCustomDefaults(newDefaultSettings);
@@ -225,7 +227,8 @@ public class LeaderboardSettingsFactory {
return new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetailsToShow,
namesOfRaceColumnsToShow, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalInMs,
nameOfRaceToSort, sortAscending, updateUponPlayStateChange, strategy, /*showAddedScores*/ false,
/*showOverallRacesCompleted*/ false, showCompetitorSailIdColumn, showCompetitorFullNameColumns);
/* showOverallRacesCompleted */ false, showCompetitorSailIdColumn, showCompetitorFullNameColumns,
settingsWithRaceSelection.isShowCompetitorNationality());
}
private List<DetailType> copyDetailTypes(List<DetailType> detailTypes) {
@@ -0,0 +1,49 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import java.util.Set;
import java.util.function.Consumer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public abstract class AbstractCompetitorRegistrationDialog extends DataEntryDialog<Set<CompetitorDTO>> {
protected final ErrorReporter errorReporter;
protected final SailingServiceAsync sailingService;
protected final StringMessages stringMessages;
protected final String leaderboardName;
protected final CompetitorRegistrationsPanel competitorRegistrationsPanel;
public AbstractCompetitorRegistrationDialog(SailingServiceAsync sailingService, StringMessages stringMessages,
ErrorReporter errorReporter, boolean editable,
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<Set<CompetitorDTO>> callback,
String leaderboardName, String boatClass, Validator<Set<CompetitorDTO>> validator) {
super(stringMessages.registerCompetitors(), /* messsage */null, stringMessages.save(), stringMessages.cancel(),
validator, callback);
this.errorReporter = errorReporter;
this.stringMessages = stringMessages;
this.sailingService = sailingService;
this.leaderboardName = leaderboardName;
this.competitorRegistrationsPanel = new CompetitorRegistrationsPanel(sailingService, stringMessages, errorReporter, editable, leaderboardName, boatClass,
()->validateAndUpdate(), getRegisteredCompetitorsRetriever(), /* restrictPoolToLeaderboard */ false, getAdditionalWidgetsToInsertAboveCompetitorTables(stringMessages));
}
@Override
protected Widget getAdditionalWidget() {
return competitorRegistrationsPanel;
}
@Override
protected Set<CompetitorDTO> getResult() {
return competitorRegistrationsPanel.getResult();
}
protected abstract Consumer<AsyncCallback<Collection<CompetitorDTO>>> getRegisteredCompetitorsRetriever();
protected abstract Widget[] getAdditionalWidgetsToInsertAboveCompetitorTables(StringMessages stringMessages);
}
@@ -8,24 +8,24 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public abstract class AbstractLeaderboardDialog extends DataEntryDialog<LeaderboardDescriptor> {
public abstract class AbstractLeaderboardDialog<LD extends LeaderboardDescriptor> extends DataEntryDialog<LD> {
protected final StringMessages stringMessages;
protected TextBox nameTextBox;
protected TextBox displayNameTextBox;
protected LeaderboardDescriptor leaderboardDescriptor;
protected LD leaderboardDescriptor;
protected DiscardThresholdBoxes discardThresholdBoxes;
protected static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 4;
public AbstractLeaderboardDialog(String title, LeaderboardDescriptor leaderboardDescriptor, StringMessages stringMessages,
Validator<LeaderboardDescriptor> validator, DialogCallback<LeaderboardDescriptor> callback) {
public AbstractLeaderboardDialog(String title, LD leaderboardDescriptor, StringMessages stringMessages,
Validator<LD> validator, DialogCallback<LD> callback) {
super(title, null, stringMessages.ok(), stringMessages.cancel(), validator, callback);
this.stringMessages = stringMessages;
this.leaderboardDescriptor = leaderboardDescriptor;
}
@Override
protected LeaderboardDescriptor getResult() {
protected LD getResult() {
leaderboardDescriptor.setName(nameTextBox.getValue().trim()); // avoid trailing blank issues; leaderboard names may appear in URLs
leaderboardDescriptor.setDisplayName(displayNameTextBox.getValue().trim().isEmpty() ? null : displayNameTextBox.getValue());
leaderboardDescriptor.setDiscardThresholds(discardThresholdBoxes==null?null:discardThresholdBoxes.getDiscardThresholds());
@@ -1,7 +1,10 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import com.google.gwt.core.client.Callback;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -19,6 +22,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.CompetitorDescriptor;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTOImpl;
import com.sap.sailing.domain.common.dto.CompetitorWithToolTipDTO;
import com.sap.sailing.gwt.ui.adminconsole.CompetitorImportProviderSelectionDialog.MatchImportedCompetitorsDialogFactory;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
@@ -28,51 +32,65 @@ import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel;
import com.sap.sse.gwt.client.controls.busyindicator.BusyDisplay;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDialog<Set<CompetitorDTO>> implements BusyDisplay {
/**
* Shows two competitor tables next to each other; the table on the left is that of the "registered" competitors, the table
* on the right is the "pool" of all competitors with those already "registered" removed. Between the tables are buttons
* to move competitors left and right, thereby assigning them to the "registered" set or moving them back to the pool.
* The {@link #getResult result} consists of all {@link CompetitorDTO competitors} in the table of "registered" competitors.<p>
*
* @author Axel Uhl (D043530)
*
*/
public class CompetitorRegistrationsPanel extends FlowPanel implements BusyDisplay {
protected CompetitorTableWrapper<RefreshableMultiSelectionModel<CompetitorDTO>> allCompetitorsTable;
protected CompetitorTableWrapper<RefreshableMultiSelectionModel<CompetitorDTO>> registeredCompetitorsTable;
protected final boolean filterByLeaderBoardInitially = false;
final StringMessages stringMessages;
protected final SailingServiceAsync sailingService;
final ErrorReporter errorReporter;
private boolean editable;
private Button registerBtn;
private Button unregisterBtn;
private Button addCompetitorButton;
private CheckBox showOnlyCompetitorsOfLogCheckBox;
private String boatClass;
protected String leaderboardName;
private final BusyIndicator busyIndicator;
private final ImportCompetitorCallback importCompetitorCallback;
private final Runnable validator;
private final Consumer<AsyncCallback<Collection<CompetitorDTO>>> registeredCompetitorsRetriever;
private final boolean restrictPoolToLeaderboard;
/**
* @param boatClass
* The <code>boatClass</code> parameter describes the default shown boat class for new competitors. The
* <code>boatClass</code> parameter is <code>null</code>, if you want to edit a competitor or there is no
* boat class for the new competitor.
* @param validator
* when this panel is used to modify the resulting competitor set, this callback's {@link Runnable#run}
* method is invoked if a non-{@code null} value is passed here.
* @param registeredCompetitorsRetriever
* although declared as a consumer, this is a provider of the set of competitors to be shown in the
* "registered" competitors table. Technically, it "consumes" a callback to which to pass the competitors
* retrieved as the "registered" ones.
* @param restrictPoolToLeaderboard
* whether the pool of "all" competitors is to be restricted to those obtained from the leaderboard, or
* to all competitors in the server's competitor store
* @param additionalWidgetsBeforeTables widgets to be inserted above / before the competitor tables; may be {@code null} or empty
*/
protected AbstractCompetitorRegistrationsDialog(final SailingServiceAsync sailingService,
protected CompetitorRegistrationsPanel(final SailingServiceAsync sailingService,
final StringMessages stringMessages, final ErrorReporter errorReporter, boolean editable,
DialogCallback<Set<CompetitorDTO>> callback, String leaderboardName, String boatClass,
com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator<Set<CompetitorDTO>> validator) {
super(stringMessages.registerCompetitors(), /* messsage */null, stringMessages.save(), stringMessages.cancel(), validator, callback);
String leaderboardName, final String boatClass, Runnable validator,
Consumer<AsyncCallback<Collection<CompetitorDTO>>> registeredCompetitorsRetriever,
boolean restrictPoolToLeaderboard, Widget... additionalWidgetsBeforeTables) {
this.stringMessages = stringMessages;
this.sailingService = sailingService;
this.errorReporter = errorReporter;
this.editable = editable;
this.restrictPoolToLeaderboard = restrictPoolToLeaderboard;
this.validator = validator;
this.leaderboardName = leaderboardName;
this.boatClass = boatClass;
this.busyIndicator = new SimpleBusyIndicator();
this.registeredCompetitorsRetriever = registeredCompetitorsRetriever;
this.importCompetitorCallback = new RaceOrRegattaImportCompetitorCallback(this, sailingService, errorReporter, stringMessages);
}
@Override
protected Widget getAdditionalWidget() {
final FlowPanel mainPanel = new FlowPanel();
final HorizontalPanel buttonPanel = new HorizontalPanel();
addCompetitorButton = new Button(stringMessages.add(stringMessages.competitor()));
addCompetitorButton.addClickHandler(new ClickHandler() {
@@ -102,7 +120,7 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
MatchImportedCompetitorsDialogFactory matchCompetitorsDialogFactory = getMatchCompetitorsDialogFactory(
sailingService, stringMessages, errorReporter);
CompetitorImportProviderSelectionDialog dialog = new CompetitorImportProviderSelectionDialog(
matchCompetitorsDialogFactory, AbstractCompetitorRegistrationsDialog.this, providerNames, sailingService,
matchCompetitorsDialogFactory, CompetitorRegistrationsPanel.this, providerNames, sailingService,
stringMessages, errorReporter);
dialog.show();
}
@@ -151,7 +169,7 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
buttonPanel.add(inviteCompetitorsButton);
buttonPanel.add(competitorImportButton);
buttonPanel.add(busyIndicator);
mainPanel.add(buttonPanel);
this.add(buttonPanel);
showOnlyCompetitorsOfLogCheckBox = new CheckBox(stringMessages.showOnlyCompetitorsOfLog());
showOnlyCompetitorsOfLogCheckBox.setValue(false);
showOnlyCompetitorsOfLogCheckBox.addClickHandler(new ClickHandler() {
@@ -164,11 +182,14 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
}
}
});
mainPanel.add(showOnlyCompetitorsOfLogCheckBox);
addAdditionalWidgets(mainPanel);
mainPanel.add(competitorRegistrationPanel);
this.add(showOnlyCompetitorsOfLogCheckBox);
if (additionalWidgetsBeforeTables != null) {
for (final Widget additionalWidget : additionalWidgetsBeforeTables) {
add(additionalWidget);
}
}
this.add(competitorRegistrationPanel);
refreshCompetitors();
return mainPanel;
}
private MatchImportedCompetitorsDialogFactory getMatchCompetitorsDialogFactory(
@@ -190,8 +211,6 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
busyIndicator.setBusy(isBusy);
}
public abstract void addAdditionalWidgets(FlowPanel mainPanel);
protected void move(CompetitorTableWrapper<?> from, CompetitorTableWrapper<?> to, Iterable<CompetitorDTO> toMove) {
from.getFilterField().removeAll(toMove);
to.getFilterField().addAll(toMove);
@@ -218,17 +237,27 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
setRegisterableCompetitorsAndRegisteredCompetitors();
}
protected abstract void setRegisteredCompetitors();
private void setRegisterableCompetitorsAndRegisteredCompetitors() {
allCompetitorsTable.refreshCompetitorList(null, new Callback<Iterable<CompetitorDTO>, Throwable>() {
allCompetitorsTable.refreshCompetitorList(this.restrictPoolToLeaderboard ? leaderboardName : null, new Callback<Iterable<CompetitorDTO>, Throwable>() {
@Override
public void onSuccess(Iterable<CompetitorDTO> result) {
setRegisteredCompetitors();
registeredCompetitorsRetriever.accept(new AsyncCallback<Collection<CompetitorDTO>>() {
@Override
public void onSuccess(Collection<CompetitorDTO> registeredCompetitors) {
moveFromPoolToRegistered(registeredCompetitors);
validateAndUpdate();
}
@Override
public void onFailure(Throwable reason) {
errorReporter.reportError("Could not load already registered competitors: " + reason.getMessage());
}
});
}
@Override
public void onFailure(Throwable reason) {
errorReporter.reportError("Could not load pool of competitors: " + reason.getMessage());
}
});
}
@@ -243,6 +272,12 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
validateAndUpdate();
}
private void validateAndUpdate() {
if (validator != null) {
validator.run();
}
}
public void activateRegistrationButtons(){
registerBtn.setEnabled(true);
unregisterBtn.setEnabled(true);
@@ -253,10 +288,21 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
validateAndUpdate();
}
@Override
protected Set<CompetitorDTO> getResult() {
public Set<CompetitorDTO> getResult() {
final Set<CompetitorDTO> registeredCompetitors = new HashSet<>();
Util.addAll(registeredCompetitorsTable.getAllCompetitors(), registeredCompetitors);
return registeredCompetitors;
}
public void grayOutCompetitorsFromRegistered(List<CompetitorWithToolTipDTO> competitors) {
registeredCompetitorsTable.grayOutCompetitors(competitors);
}
public void grayOutCompetitorsFromPool(List<CompetitorWithToolTipDTO> competitors) {
allCompetitorsTable.grayOutCompetitors(competitors);
}
public void moveFromPoolToRegistered(Collection<CompetitorDTO> registeredCompetitors) {
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
}
}
@@ -34,6 +34,18 @@ import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
/**
* A filterable competitor table. The data model is managed by the {@link #getFilterField() filter field}. In
* order to set an initial set of competitors to display by this table, use {@link #refreshCompetitorList(Iterable)}.
* The selected competitors can be obtained from the {@link #getSelectionModel() selection model}. The competitor
* set can also be updated to that of a leaderboard by using {@link #refreshCompetitorList(String)}, providing the
* leaderboard name as parameter. The competitors currently in the table (regardless of the current filter settings)
* are returned by {@link #getAllCompetitors()}.
*
* @author Axel Uhl (D043530)
*
* @param <S>
*/
public class CompetitorTableWrapper<S extends RefreshableSelectionModel<CompetitorDTO>> extends TableWrapper<CompetitorDTO, S> {
private final LabeledAbstractFilterablePanel<CompetitorDTO> filterField;
@@ -285,43 +297,28 @@ public class CompetitorTableWrapper<S extends RefreshableSelectionModel<Competit
*/
public void refreshCompetitorList(String leaderboardName, final Callback<Iterable<CompetitorDTO>,
Throwable> callback) {
final AsyncCallback<Iterable<CompetitorDTO>> myCallback = new AsyncCallback<Iterable<CompetitorDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage());
if (callback != null) {
callback.onFailure(caught);
}
}
@Override
public void onSuccess(Iterable<CompetitorDTO> result) {
getFilteredCompetitors(result);
refreshCompetitorList(result);
if (callback != null) {
callback.onSuccess(result);
}
}
};
if (leaderboardName != null) {
sailingService.getCompetitorsOfLeaderboard(leaderboardName, new AsyncCallback<Iterable<CompetitorDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage());
if (callback != null) {
callback.onFailure(caught);
}
}
@Override
public void onSuccess(Iterable<CompetitorDTO> result) {
refreshCompetitorList(result);
if (callback != null) {
callback.onSuccess(result);
}
}
});
sailingService.getCompetitorsOfLeaderboard(leaderboardName, myCallback);
} else {
sailingService.getCompetitors(new AsyncCallback<Iterable<CompetitorDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage());
if (callback != null) {
callback.onFailure(caught);
}
}
@Override
public void onSuccess(Iterable<CompetitorDTO> result) {
getFilteredCompetitors(result);
refreshCompetitorList(result);
if (callback != null) {
callback.onSuccess(result);
}
}
});
sailingService.getCompetitors(myCallback);
}
}
@@ -16,7 +16,7 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.ErrorReporter;
public abstract class FlexibleLeaderboardDialog extends AbstractLeaderboardDialog {
public abstract class FlexibleLeaderboardDialog extends AbstractLeaderboardDialog<LeaderboardDescriptor> {
protected ListBox scoringSchemeListBox;
protected ListBox sailingEventsListBox;
protected Collection<EventDTO> existingEvents;
@@ -33,6 +33,7 @@ import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
@@ -123,6 +124,16 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
}
});
Button createRegattaLeaderboardWithEliminationsBtn = new Button(stringMessages.createRegattaLeaderboardWithEliminations() + "...");
createRegattaLeaderboardWithEliminationsBtn.ensureDebugId("CreateRegattaLeaderboardWithEliminationsButton");
controlsPanel.add(createRegattaLeaderboardWithEliminationsBtn);
createRegattaLeaderboardWithEliminationsBtn.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent clickEvent) {
createRegattaLeaderboardWithEliminations();
}
});
leaderboardRemoveButton = new Button(stringMessages.remove());
leaderboardRemoveButton.ensureDebugId("LeaderboardsRemoveButton");
leaderboardRemoveButton.setEnabled(false);
@@ -227,11 +238,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
TextColumn<StrippedLeaderboardDTO> leaderboardTypeColumn = new TextColumn<StrippedLeaderboardDTO>() {
@Override
public String getValue(StrippedLeaderboardDTO leaderboard) {
String result = leaderboard.type.isRegattaLeaderboard() ? "Regatta" : "Flexible";
if (leaderboard.type.isMetaLeaderboard()) {
result += " , Meta";
}
return result;
return leaderboard.type.toString();
}
};
leaderboardTypeColumn.setSortable(true);
@@ -285,37 +292,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
removeLeaderboard(leaderboardDTO);
}
} else if (LeaderboardConfigImagesBarCell.ACTION_EDIT.equals(value)) {
final String oldLeaderboardName = leaderboardDTO.name;
List<StrippedLeaderboardDTO> otherExistingLeaderboard = new ArrayList<StrippedLeaderboardDTO>();
otherExistingLeaderboard.addAll(availableLeaderboardList);
otherExistingLeaderboard.remove(leaderboardDTO);
if (leaderboardDTO.type.isMetaLeaderboard()) {
Window.alert(stringMessages.metaLeaderboardCannotBeChanged());
} else {
if (leaderboardDTO.type.isRegattaLeaderboard()) {
LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name,
leaderboardDTO.displayName, /* scoring scheme provided by regatta */ null,
leaderboardDTO.discardThresholds, leaderboardDTO.regattaName,
leaderboardDTO.defaultCourseAreaId);
AbstractLeaderboardDialog dialog = new RegattaLeaderboardEditDialog(Collections
.unmodifiableCollection(otherExistingLeaderboard), Collections.unmodifiableCollection(allRegattas),
descriptor, stringMessages, errorReporter,
new DialogCallback<LeaderboardDescriptor>() {
@Override
public void cancel() {
}
@Override
public void ok(LeaderboardDescriptor result) {
updateLeaderboard(oldLeaderboardName, result);
}
});
dialog.show();
} else {
LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name, leaderboardDTO.displayName, leaderboardDTO.scoringScheme, leaderboardDTO.discardThresholds, leaderboardDTO.defaultCourseAreaId);
openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, leaderboardDTO.name, descriptor);
}
}
editLeaderboard(leaderboardDTO);
} else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_SCORES.equals(value)) {
String leaderboardEditingUrl = EntryPointWithSettingsLinkFactory.createLeaderboardEditingLink(leaderboardDTO.name);
Window.open(leaderboardEditingUrl, "_blank", null);
@@ -357,6 +334,81 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
leaderboardTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager());
}
private void editLeaderboard(StrippedLeaderboardDTO leaderboardDTO) {
final String oldLeaderboardName = leaderboardDTO.name;
List<StrippedLeaderboardDTO> otherExistingLeaderboard = new ArrayList<StrippedLeaderboardDTO>();
otherExistingLeaderboard.addAll(availableLeaderboardList);
otherExistingLeaderboard.remove(leaderboardDTO);
if (leaderboardDTO.type.isMetaLeaderboard()) {
Window.alert(stringMessages.metaLeaderboardCannotBeChanged());
} else {
AbstractLeaderboardDialog<?> dialog;
switch (leaderboardDTO.type) {
case RegattaLeaderboard:
dialog = new RegattaLeaderboardEditDialog(Collections
.unmodifiableCollection(otherExistingLeaderboard), Collections.unmodifiableCollection(allRegattas),
createLeaderboardDescriptor(leaderboardDTO, /* scoring scheme is provided by regatta, not leaderboard */ null),
stringMessages, errorReporter,
new DialogCallback<LeaderboardDescriptor>() {
@Override
public void cancel() {
}
@Override
public void ok(LeaderboardDescriptor result) {
updateLeaderboard(oldLeaderboardName, result);
}
});
dialog.show();
break;
case RegattaLeaderboardWithEliminations:
dialog = new RegattaLeaderboardWithEliminationsEditDialog(sailingService, Collections
.unmodifiableCollection(otherExistingLeaderboard),
Collections.unmodifiableCollection(allRegattas),
new LeaderboardDescriptorWithEliminations(
createLeaderboardDescriptor(leaderboardDTO, /* scoring scheme is provided by regatta, not leaderboard */ null),
/* eliminated competitors */ null), stringMessages,
errorReporter, new DialogCallback<LeaderboardDescriptorWithEliminations>() {
@Override
public void cancel() {
}
@Override
public void ok(LeaderboardDescriptorWithEliminations result) {
updateLeaderboard(oldLeaderboardName, result);
sailingService.setEliminatedCompetitors(oldLeaderboardName, result.getEliminatedCompetitors(),
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to update eliminated competitors for leaderboard "
+ oldLeaderboardName + ": " + caught.getMessage());
}
@Override
public void onSuccess(Void v) {
// nothing to do for now; maybe if the table once will show the number of eliminated competitors or similar, update it
}
});
}
});
dialog.show();
break;
case FlexibleLeaderboard:
openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, leaderboardDTO.name, createLeaderboardDescriptor(leaderboardDTO,
leaderboardDTO.scoringScheme));
break;
default:
Window.alert(stringMessages.unknownLeaderboardType(leaderboardDTO.type.name()));
}
}
}
private LeaderboardDescriptor createLeaderboardDescriptor(StrippedLeaderboardDTO leaderboardDTO, ScoringSchemeType scoringScheme) {
return new LeaderboardDescriptor(leaderboardDTO.name,
leaderboardDTO.displayName, scoringScheme,
leaderboardDTO.discardThresholds, leaderboardDTO.regattaName,
leaderboardDTO.defaultCourseAreaId);
}
@Override
protected void addColumnsToRacesTable(CellTable<RaceColumnDTOAndFleetDTOWithNameBasedEquality> racesTable) {
TextColumn<RaceColumnDTOAndFleetDTOWithNameBasedEquality> explicitFactorColumn = new TextColumn<RaceColumnDTOAndFleetDTOWithNameBasedEquality>() {
@@ -742,8 +794,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
}
private void createFlexibleLeaderboard(List<EventDTO> existingEvents) {
AbstractLeaderboardDialog dialog = new FlexibleLeaderboardCreateDialog(Collections.unmodifiableCollection(availableLeaderboardList),
final FlexibleLeaderboardCreateDialog dialog = new FlexibleLeaderboardCreateDialog(Collections.unmodifiableCollection(availableLeaderboardList),
stringMessages, Collections.unmodifiableCollection(existingEvents), errorReporter, new DialogCallback<LeaderboardDescriptor>() {
@Override
public void cancel() {
@@ -801,6 +852,48 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
dialog.show();
}
private void createRegattaLeaderboardWithEliminations() {
RegattaLeaderboardWithEliminationsCreateDialog dialog = new RegattaLeaderboardWithEliminationsCreateDialog(
sailingService, Collections.unmodifiableCollection(availableLeaderboardList),
Collections.unmodifiableCollection(allRegattas), stringMessages, errorReporter,
new DialogCallback<LeaderboardDescriptorWithEliminations>() {
@Override
public void cancel() {
}
@Override
public void ok(final LeaderboardDescriptorWithEliminations newLeaderboard) {
sailingService.createRegattaLeaderboardWithEliminations(newLeaderboard.getName(), newLeaderboard.getDisplayName(),
newLeaderboard.getRegattaName(), new AsyncCallback<StrippedLeaderboardDTO>() {
@Override
public void onFailure(Throwable t) {
errorReporter.reportError("Error trying to create the new regatta leaderboard " + newLeaderboard.getName()
+ ": " + t.getMessage());
}
@Override
public void onSuccess(StrippedLeaderboardDTO result) {
addLeaderboard(result);
sailingService.setEliminatedCompetitors(newLeaderboard.getName(), newLeaderboard.getEliminatedCompetitors(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to set the eliminated competitors for leaderboard "+newLeaderboard.getName()+
": "+caught.getMessage());
}
@Override
public void onSuccess(Void result) {
// nothing to do for now until any elimination properties are shown in the leaderboard table...
}
});
}
});
}
});
dialog.ensureDebugId("RegattaLeaderboardCreateDialog");
dialog.show();
}
private void addLeaderboard(StrippedLeaderboardDTO result) {
filteredLeaderboardList.getList().add(result);
availableLeaderboardList.add(result);
@@ -0,0 +1,18 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Set;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class LeaderboardDescriptorWithEliminations extends LeaderboardDescriptor {
private final Set<CompetitorDTO> eliminatedCompetitors;
public LeaderboardDescriptorWithEliminations(LeaderboardDescriptor base, Set<CompetitorDTO> eliminatedCompetitors) {
super(base.getName(), base.getDisplayName(), base.getScoringScheme(), base.getDiscardThresholds(), base.getRegattaName(), base.getCourseAreaId());
this.eliminatedCompetitors = eliminatedCompetitors;
}
public Set<CompetitorDTO> getEliminatedCompetitors() {
return eliminatedCompetitors;
}
}
@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.controls.ImagesBarCell;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
@@ -34,7 +33,7 @@ public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell {
result.add(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())));
result.add(new ImageSpec(ACTION_UNLINK, stringMessages.actionRaceUnlink(), makeImagePrototype(resources.unlinkIcon())));
StrippedLeaderboardDTO selectedLeaderboard = selectedLeaderboardProvider.getSelectedLeaderboard();
if (selectedLeaderboard != null && selectedLeaderboard.type != LeaderboardType.RegattaLeaderboard && selectedLeaderboard.type != LeaderboardType.RegattaMetaLeaderboard) {
if (selectedLeaderboard != null && !selectedLeaderboard.type.isRegattaLeaderboard()) {
// race columns cannot be removed from a regatta leaderboard; they need to be removed from the regatta instead
result.add(new ImageSpec(ACTION_REMOVE, stringMessages.actionRaceRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon())));
}
@@ -10,12 +10,13 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.CompetitorWithToolTipDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
@@ -26,12 +27,15 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegistrationsDialog {
public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegistrationDialog {
private String fleetName;
private String raceColumnName;
private CheckBox competitorRegistrationInRaceLogCheckBox;
private final Map<String, Set<CompetitorDTO>> fleetNameWithCompetitors;
private final ErrorReporter errorReporter;
private final SailingServiceAsync sailingService;
private final StringMessages stringMessages;
private static class Validator implements com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator<Set<CompetitorDTO>> {
private CheckBox competitorRegistrationInRaceLogCheckBox;
@@ -102,14 +106,33 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<Set<CompetitorDTO>> callback,
String leaderboardName, String boatClass, String raceColumnName, String fleetName, List<FleetDTO> fleets, Validator validator) {
super(sailingService, stringMessages, errorReporter, editable, callback, leaderboardName, boatClass, validator);
this.errorReporter = errorReporter;
this.stringMessages = stringMessages;
this.sailingService = sailingService;
this.raceColumnName = raceColumnName;
this.fleetName = fleetName;
fleetNameWithCompetitors = findCompetitorsFromTheSameRaceColumn(fleets);
competitorRegistrationInRaceLogCheckBox = createCheckbox(stringMessages.registerCompetitorsOnRace());
validator.setCompetitorRegistrationInRaceLogCheckBox(competitorRegistrationInRaceLogCheckBox);
validator.setCompetitorRegistrationInRaceLogCheckBox(getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages));
validator.setFleetWithCompetitors(fleetNameWithCompetitors);
setupCompetitorRegistationsOnRaceCheckbox();
}
@Override
protected Widget[] getAdditionalWidgetsToInsertAboveCompetitorTables(StringMessages stringMessages) {
return new Widget[] { getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages) };
}
private CheckBox getOrCreateCompetitorRegistrationInRaceLogCheckBox(StringMessages stringMessages) {
if (competitorRegistrationInRaceLogCheckBox == null) {
competitorRegistrationInRaceLogCheckBox = createCheckbox(stringMessages.registerCompetitorsOnRace());
}
return competitorRegistrationInRaceLogCheckBox;
}
@Override
protected Consumer<AsyncCallback<Collection<CompetitorDTO>>> getRegisteredCompetitorsRetriever() {
return (callback)->getRegisteredCompetitors(callback);
}
/**
* For all {@code fleets} passed and not equal to {@link #fleetName} retrieves the competitor-per-race registrations
@@ -165,60 +188,28 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
}
}
}
allCompetitorsTable.grayOutCompetitors(competitors);
registeredCompetitorsTable.grayOutCompetitors(competitors);
competitorRegistrationsPanel.grayOutCompetitorsFromPool(competitors);
competitorRegistrationsPanel.grayOutCompetitorsFromRegistered(competitors);
}
@Override
protected void setRegisteredCompetitors() {
if (showOnlyCompetitorsOfLog()) {
sailingService.getCompetitorRegistrationsInRaceLog(leaderboardName, raceColumnName, fleetName,
new AsyncCallback<Collection<CompetitorDTO>>() {
@Override
public void onSuccess(Collection<CompetitorDTO> registeredCompetitors) {
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
validateAndUpdate();
}
@Override
public void onFailure(Throwable reason) {
errorReporter.reportError("Could not load already registered competitors: "
+ reason.getMessage());
}
});
private void getRegisteredCompetitors(AsyncCallback<Collection<CompetitorDTO>> callback) {
if (competitorRegistrationsPanel.showOnlyCompetitorsOfLog()) {
sailingService.getCompetitorRegistrationsInRaceLog(leaderboardName, raceColumnName, fleetName, callback);
} else {
sailingService.getCompetitorRegistrationsForRace(leaderboardName, raceColumnName, fleetName,
new AsyncCallback<Collection<CompetitorDTO>>() {
@Override
public void onSuccess(Collection<CompetitorDTO> registeredCompetitors) {
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
validateAndUpdate();
}
@Override
public void onFailure(Throwable reason) {
errorReporter.reportError("Could not load already registered competitors: "
+ reason.getMessage());
}
});
sailingService.getCompetitorRegistrationsForRace(leaderboardName, raceColumnName, fleetName, callback);
}
}
@Override
public void addAdditionalWidgets(FlowPanel mainPanel) {
mainPanel.add(competitorRegistrationInRaceLogCheckBox);
}
private void setupCompetitorRegistationsOnRaceCheckbox() {
sailingService.areCompetitorRegistrationsEnabledForRace(leaderboardName, raceColumnName, fleetName,
new AsyncCallback<Boolean>() {
@Override
public void onSuccess(Boolean isEnabled) {
competitorRegistrationInRaceLogCheckBox.setValue(isEnabled);
getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).setValue(isEnabled);
if (isEnabled) {
activateRegistrationButtons();
competitorRegistrationsPanel.activateRegistrationButtons();
} else {
deactivateRegistrationButtons(stringMessages.competitorRegistrationsOnRaceDisabled());
competitorRegistrationsPanel.deactivateRegistrationButtons(stringMessages.competitorRegistrationsOnRaceDisabled());
}
}
@@ -228,12 +219,12 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
+ reason.getMessage());
}
});
competitorRegistrationInRaceLogCheckBox.addClickHandler(new ClickHandler() {
getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
final String title;
final String message;
if (competitorRegistrationInRaceLogCheckBox.getValue()) {
if (getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).getValue()) {
title = stringMessages.doYouWantToRegisterCompetitorsDirectlyOnTheRace();
message = stringMessages.warningDirectCompetitorRegistration();
} else {
@@ -244,14 +235,14 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
new DialogCallback<Void>() {
@Override
public void ok(Void editedObject) {
if (competitorRegistrationInRaceLogCheckBox.getValue()) {
if (getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).getValue()) {
sailingService.enableCompetitorRegistrationsForRace(leaderboardName,
raceColumnName, fleetName, new AsyncCallback<Void>() {
@Override
public void onSuccess(Void isEnabled) {
competitorRegistrationInRaceLogCheckBox.setValue(true);
activateRegistrationButtons();
refreshCompetitors();
getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).setValue(true);
competitorRegistrationsPanel.activateRegistrationButtons();
competitorRegistrationsPanel.refreshCompetitors();
}
@Override
@@ -267,10 +258,10 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
raceColumnName, fleetName, new AsyncCallback<Void>() {
@Override
public void onSuccess(Void isEnabled) {
competitorRegistrationInRaceLogCheckBox.setValue(false);
deactivateRegistrationButtons(stringMessages
getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).setValue(false);
competitorRegistrationsPanel.deactivateRegistrationButtons(stringMessages
.competitorRegistrationsOnRaceDisabled());
refreshCompetitors();
competitorRegistrationsPanel.refreshCompetitors();
}
@Override
@@ -285,7 +276,7 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
@Override
public void cancel() {
competitorRegistrationInRaceLogCheckBox.setValue(!competitorRegistrationInRaceLogCheckBox.getValue());
getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).setValue(!getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).getValue());
}
}) {
@Override
@@ -299,7 +290,7 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
@Override
protected Set<CompetitorDTO> getResult() {
if (competitorRegistrationInRaceLogCheckBox.getValue()) {
if (getOrCreateCompetitorRegistrationInRaceLogCheckBox(stringMessages).getValue()) {
return super.getResult();
} else {
return Collections.emptySet();
@@ -8,9 +8,9 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.ErrorReporter;
public class RaceOrRegattaImportCompetitorCallback extends ImportCompetitorCallback {
private final AbstractCompetitorRegistrationsDialog registrationsDialog;
private final CompetitorRegistrationsPanel registrationsDialog;
public RaceOrRegattaImportCompetitorCallback(AbstractCompetitorRegistrationsDialog registrationsDialog, SailingServiceAsync sailingService, ErrorReporter errorReporter,
public RaceOrRegattaImportCompetitorCallback(CompetitorRegistrationsPanel registrationsDialog, SailingServiceAsync sailingService, ErrorReporter errorReporter,
StringMessages stringMessages) {
super(sailingService, errorReporter, stringMessages);
this.registrationsDialog = registrationsDialog;
@@ -11,10 +11,12 @@ import com.sap.sse.gwt.client.ErrorReporter;
public class RegattaLeaderboardCreateDialog extends RegattaLeaderboardDialog {
public RegattaLeaderboardCreateDialog(Collection<StrippedLeaderboardDTO> existingLeaderboards, Collection<RegattaDTO> existingRegattas, StringMessages stringMessages,
ErrorReporter errorReporter, DialogCallback<LeaderboardDescriptor> callback) {
super(stringMessages.createRegattaLeaderboard(), new LeaderboardDescriptor(), existingRegattas, stringMessages, errorReporter, new RegattaLeaderboardDialog.LeaderboardParameterValidator(stringMessages, existingLeaderboards), callback);
public RegattaLeaderboardCreateDialog(Collection<StrippedLeaderboardDTO> existingLeaderboards,
Collection<RegattaDTO> existingRegattas, StringMessages stringMessages, ErrorReporter errorReporter,
DialogCallback<LeaderboardDescriptor> callback) {
super(stringMessages.createRegattaLeaderboard(), new LeaderboardDescriptor(), existingRegattas, stringMessages,
errorReporter, new RegattaLeaderboardDialog.LeaderboardParameterValidator(stringMessages, existingLeaderboards),
callback);
nameTextBox = createTextBox(null);
nameTextBox.ensureDebugId("NameTextBox");
nameTextBox.setVisibleLength(50);
Loaded 100 of 164 files, more files were not shown because too many files have changed in this diff. Show more